Implementeer applicaties met Docker Swarm

Het online videocontentaanbevelingssysteem waaraan we werken is een gesloten commerciële ontwikkeling en is technisch gezien een uit meerdere componenten bestaand cluster van eigen en open source componenten. Het doel van het schrijven van dit artikel is om de implementatie van het docker-zwermclustersysteem voor een stagingsite te beschrijven zonder de gevestigde workflow van onze processen in een beperkte tijd te verstoren. Het verhaal dat onder uw aandacht wordt gebracht, is verdeeld in twee delen. Het eerste deel beschrijft CI / CD voordat docker-zwerm wordt gebruikt, en het tweede beschrijft het implementatieproces ervan. Degenen die niet geïnteresseerd zijn in het lezen van het eerste deel, kunnen veilig doorgaan naar het tweede.

Deel I

Terug in het verre, verre jaar was het nodig om het CI/CD-proces zo snel mogelijk op te zetten. Eén van de voorwaarden was om Docker niet te gebruiken voor inzet ontwikkelde componenten om verschillende redenen:

  • voor een betrouwbaardere en stabielere werking van componenten in productie (dat is in feite de vereiste om geen virtualisatie te gebruiken)
  • toonaangevende ontwikkelaars wilden niet met Docker werken (raar, maar zo was het)
  • volgens de ideologische overwegingen van het R&D-management

Infrastructuur, stack en geschatte initiële vereisten voor MVP werden als volgt gepresenteerd:

  • 4 Intel® X5650-servers met Debian (een krachtigere machine is volledig ontwikkeld)
  • Ontwikkeling van eigen maatwerkcomponenten wordt uitgevoerd in C++, Python3
  • Belangrijkste gebruikte tools van derden: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, ...
  • Pijplijnen voor het afzonderlijk bouwen en testen van componenten voor foutopsporing en release

Een van de eerste vragen die in de beginfase moet worden beantwoord, is hoe aangepaste componenten in elke omgeving (CI/CD) zullen worden ingezet.

We hebben besloten om componenten van derden systemisch te installeren en systematisch bij te werken. Maatwerkapplicaties ontwikkeld in C++ of Python kunnen op verschillende manieren worden ingezet. Onder hen bijvoorbeeld: systeempakketten maken, deze naar de repository met ingebouwde afbeeldingen sturen en ze vervolgens op servers installeren. Om onbekende reden is er voor een andere methode gekozen, namelijk: met behulp van CI worden uitvoerbare bestanden van applicaties gecompileerd, wordt een virtuele projectomgeving gecreëerd, worden py-modules geïnstalleerd vanuit eisen.txt, en al deze artefacten worden meegestuurd met configuraties, scripts en de begeleidende applicatieomgeving naar servers. Vervolgens worden applicaties gestart als virtuele gebruiker zonder beheerdersrechten.

Gitlab-CI werd gekozen als het CI/CD-systeem. De resulterende pijplijn zag er ongeveer zo uit:

Implementeer applicaties met Docker Swarm
Structureel zag gitlab-ci.yml er zo uit

---
variables:
  # минимальная версия ЦПУ на серверах, где разворачивается кластер
  CMAKE_CPUTYPE: "westmere"

  DEBIAN: "MYREGISTRY:5000/debian:latest"

before_script:
  - eval $(ssh-agent -s)
  - ssh-add <(echo "$SSH_PRIVATE_KEY")
  - mkdir -p ~/.ssh && echo -e "Host *ntStrictHostKeyChecking nonn" > ~/.ssh/config

stages:
  - build
  - testing
  - deploy

debug.debian:
  stage: build
  image: $DEBIAN
  script:
    - cd builds/release && ./build.sh
    paths:
      - bin/
      - builds/release/bin/
    when: always
release.debian:
  stage: build
  image: $DEBIAN
  script:
    - cd builds/release && ./build.sh
    paths:
      - bin/
      - builds/release/bin/
    when: always

## testing stage
tests.codestyle:
  stage: testing
  image: $DEBIAN
  dependencies:
    - release.debian
  script:
    - /bin/bash run_tests.sh -t codestyle -b "${CI_COMMIT_REF_NAME}_codestyle"
tests.debug.debian:
  stage: testing
  image: $DEBIAN
  dependencies:
    - debug.debian
  script:
    - /bin/bash run_tests.sh -e codestyle/test_pylint.py -b "${CI_COMMIT_REF_NAME}_debian_debug"
  artifacts:
    paths:
      - run_tests/username/
    when: always
    expire_in: 1 week
tests.release.debian:
  stage: testing
  image: $DEBIAN
  dependencies:
    - release.debian
  script:
    - /bin/bash run_tests.sh -e codestyle/test_pylint.py -b "${CI_COMMIT_REF_NAME}_debian_release"
  artifacts:
    paths:
      - run_tests/username/
    when: always
    expire_in: 1 week

## staging stage
deploy_staging:
  stage: deploy
  environment: staging
  image: $DEBIAN
  dependencies:
    - release.debian
  script:
    - cd scripts/deploy/ &&
        python3 createconfig.py -s $CI_ENVIRONMENT_NAME &&
        /bin/bash install_venv.sh -d -r ../../requirements.txt &&
        python3 prepare_init.d.py &&
        python3 deploy.py -s $CI_ENVIRONMENT_NAME
  when: manual

Het is vermeldenswaard dat de assemblage en het testen worden uitgevoerd op een eigen image, waarbij alle benodigde systeempakketten al zijn geïnstalleerd en andere instellingen zijn gemaakt.

Hoewel elk van deze scripts in banen op zijn eigen manier interessant is, maar ik zal er natuurlijk niet over praten. De beschrijving van elk van hen zal veel tijd vergen en dit is niet het doel van het artikel. Ik vestig alleen uw aandacht op het feit dat de implementatiefase bestaat uit een reeks aanroepscripts:

  1. createconfig.py - creëert een settings.ini-bestand met componentinstellingen in verschillende omgevingen voor latere implementatie (preproductie, productie, testen, ...)
  2. install_venv.sh - creëert een virtuele omgeving voor py-componenten in een specifieke map en kopieert deze naar externe servers
  3. prepare_init.d.py — bereidt start-stop-scripts voor de component voor op basis van de sjabloon
  4. implementeren.py - ontleedt en start nieuwe componenten opnieuw

Verstreken tijd. De enscenering werd vervangen door preproductie en productie. Ondersteuning toegevoegd voor het product op nog een distributie (CentOS). Er zijn vijf krachtigere fysieke servers en een tiental virtuele servers toegevoegd. En het werd voor ontwikkelaars en testers steeds moeilijker om hun taken te testen in een omgeving die min of meer dicht bij de werkende staat lag. Op dit moment werd duidelijk dat het onmogelijk was om zonder hem te doen ...

Deel II

Implementeer applicaties met Docker Swarm

Ons cluster is dus een spectaculair systeem van een paar dozijn afzonderlijke componenten die niet door Dockerfiles worden beschreven. U kunt het alleen configureren voor implementatie in een specifieke omgeving in het algemeen. Onze taak is om het cluster in een faseringsomgeving te implementeren om het te testen voordat het pre-releasetesten uitvoert.

Theoretisch kunnen er meerdere clusters tegelijkertijd actief zijn: net zoveel als er taken zijn die voltooid zijn of bijna voltooid zijn. Dankzij de capaciteiten van de servers die wij tot onze beschikking hebben, kunnen wij op elke server meerdere clusters draaien. Elk faseringscluster moet geïsoleerd zijn (er mag geen kruispunt zijn in poorten, mappen, enz.).

Onze meest waardevolle hulpbron is onze tijd, en daar hadden we niet veel van.

Voor een snellere start hebben we voor Docker Swarm gekozen vanwege de eenvoud en architectuurflexibiliteit. Het eerste dat we deden was een manager en verschillende knooppunten op de externe servers maken:

$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
kilqc94pi2upzvabttikrfr5d     nop-test-1     Ready               Active                                  19.03.2
jilwe56pl2zvabupryuosdj78     nop-test-2     Ready               Active                                  19.03.2
j5a4yz1kr2xke6b1ohoqlnbq5 *   nop-test-3     Ready               Active              Leader              19.03.2

Maak vervolgens een netwerk:


$ docker network create --driver overlay --subnet 10.10.10.0/24 nw_swarm

Vervolgens hebben we Gitlab-CI- en Swarm-nodes met elkaar verbonden in termen van afstandsbediening van knooppunten vanuit CI: het installeren van certificaten, het instellen van geheime variabelen en het opzetten van de Docker-service op de controleserver. Deze artikel heeft ons veel tijd bespaard.

Vervolgens hebben we taken voor het maken en vernietigen van stapels toegevoegd aan .gitlab-ci .yml.

Er zijn nog een paar taken toegevoegd aan .gitlab-ci .yml

## staging stage
deploy_staging:
  stage: testing
  before_script:
    - echo "override global 'before_script'"
  image: "REGISTRY:5000/docker:latest"
  environment: staging
  dependencies: []
  variables:
    DOCKER_CERT_PATH: "/certs"
    DOCKER_HOST: tcp://10.50.173.107:2376
    DOCKER_TLS_VERIFY: 1
    CI_BIN_DEPENDENCIES_JOB: "release.centos.7"
  script:
    - mkdir -p $DOCKER_CERT_PATH
    - echo "$TLSCACERT" > $DOCKER_CERT_PATH/ca.pem
    - echo "$TLSCERT" > $DOCKER_CERT_PATH/cert.pem
    - echo "$TLSKEY" > $DOCKER_CERT_PATH/key.pem
    - docker stack deploy -c docker-compose.yml ${CI_ENVIRONMENT_NAME}_${CI_COMMIT_REF_NAME} --with-registry-auth
    - rm -rf $DOCKER_CERT_PATH
  when: manual

## stop staging stage
stop_staging:
  stage: testing
  before_script:
    - echo "override global 'before_script'"
  image: "REGISTRY:5000/docker:latest"
  environment: staging
  dependencies: []
  variables:
    DOCKER_CERT_PATH: "/certs"
    DOCKER_HOST: tcp://10.50.173.107:2376
    DOCKER_TLS_VERIFY: 1
  script:
    - mkdir -p $DOCKER_CERT_PATH
    - echo "$TLSCACERT" > $DOCKER_CERT_PATH/ca.pem
    - echo "$TLSCERT" > $DOCKER_CERT_PATH/cert.pem
    - echo "$TLSKEY" > $DOCKER_CERT_PATH/key.pem
    - docker stack rm ${CI_ENVIRONMENT_NAME}_${CI_COMMIT_REF_NAME}
    # TODO: need check that stopped
  when: manual

Uit het bovenstaande codefragment kunt u zien dat er twee knoppen (deploy_staging, stop_staging) zijn toegevoegd aan Pipelines, waarvoor handmatige actie vereist is.

Implementeer applicaties met Docker Swarm
De stapelnaam komt overeen met de vertakkingsnaam en dit unieke karakter zou voldoende moeten zijn. Services in de stapel ontvangen unieke IP-adressen en poorten, mappen, enz. zal geïsoleerd zijn, maar hetzelfde van stapel tot stapel (omdat het configuratiebestand voor alle stapels hetzelfde is) - wat we wilden. We implementeren de stapel (cluster) met behulp van havenarbeider-compose.yml, die ons cluster beschrijft.

havenarbeider-compose.yml

---
version: '3'

services:
  userprop:
    image: redis:alpine
    deploy:
      replicas: 1
      placement:
        constraints: [node.id == kilqc94pi2upzvabttikrfr5d]
      restart_policy:
        condition: none
    networks:
      nw_swarm:
  celery_bcd:
    image: redis:alpine
    deploy:
      replicas: 1
      placement:
        constraints: [node.id == kilqc94pi2upzvabttikrfr5d]
      restart_policy:
        condition: none
    networks:
      nw_swarm:

  schedulerdb:
    image: mariadb:latest
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
      MYSQL_DATABASE: schedulerdb
      MYSQL_USER: ****
      MYSQL_PASSWORD: ****
    command: ['--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci', '--explicit_defaults_for_timestamp=1']
    deploy:
      replicas: 1
      placement:
        constraints: [node.id == kilqc94pi2upzvabttikrfr5d]
      restart_policy:
        condition: none
    networks:
      nw_swarm:

  celerydb:
    image: mariadb:latest
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
      MYSQL_DATABASE: celerydb
      MYSQL_USER: ****
      MYSQL_PASSWORD: ****
    deploy:
      replicas: 1
      placement:
        constraints: [node.id == kilqc94pi2upzvabttikrfr5d]
      restart_policy:
        condition: none
    networks:
      nw_swarm:

  cluster:
    image: $CENTOS7
    environment:
      - CENTOS
      - CI_ENVIRONMENT_NAME
      - CI_API_V4_URL
      - CI_REPOSITORY_URL
      - CI_PROJECT_ID
      - CI_PROJECT_URL
      - CI_PROJECT_PATH
      - CI_PROJECT_NAME
      - CI_COMMIT_REF_NAME
      - CI_BIN_DEPENDENCIES_JOB
    command: >
      sudo -u myusername -H /bin/bash -c ". /etc/profile &&
        mkdir -p /storage1/$CI_COMMIT_REF_NAME/$CI_PROJECT_NAME &&
        cd /storage1/$CI_COMMIT_REF_NAME/$CI_PROJECT_NAME &&
            git clone -b $CI_COMMIT_REF_NAME $CI_REPOSITORY_URL . &&
            curl $CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/artifacts/$CI_COMMIT_REF_NAME/download?job=$CI_BIN_DEPENDENCIES_JOB -o artifacts.zip &&
            unzip artifacts.zip ;
        cd /storage1/$CI_COMMIT_REF_NAME/$CI_PROJECT_NAME/scripts/deploy/ &&
            python3 createconfig.py -s $CI_ENVIRONMENT_NAME &&
            /bin/bash install_venv.sh -d -r ../../requirements.txt &&
            python3 prepare_init.d.py &&
            python3 deploy.py -s $CI_ENVIRONMENT_NAME"
    deploy:
      replicas: 1
      placement:
        constraints: [node.id == kilqc94pi2upzvabttikrfr5d]
      restart_policy:
        condition: none
    tty: true
    stdin_open: true
    networks:
      nw_swarm:

networks:
  nw_swarm:
    external: true

Hier kun je zien dat de componenten via één netwerk (nw_swarm) met elkaar verbonden zijn en voor elkaar beschikbaar zijn.

Systeemcomponenten (gebaseerd op redis, mysql) zijn gescheiden van de algemene pool van aangepaste componenten (in plannen en aangepaste componenten zijn verdeeld als services). De implementatiefase van ons cluster lijkt op het doorgeven van CMD aan ons ene grote geconfigureerde image en verschilt in het algemeen praktisch niet van de implementatie die in Deel I wordt beschreven. Ik zal de verschillen benadrukken:

  • git kloon... - haal de bestanden op die nodig zijn om te implementeren (createconfig.py, install_venv.sh, etc.)
  • krullen... && uitpakken... - download en pak build-artefacten uit (gecompileerde hulpprogramma's)

Er is slechts één nog onbeschreven probleem: componenten met een webinterface zijn niet toegankelijk vanuit de browsers van ontwikkelaars. We lossen dit probleem op met behulp van reverse proxy, dus:

In .gitlab-ci.yml voegen we, na het implementeren van de clusterstack, de regel toe voor het implementeren van de balancer (die, wanneer hij een commit uitvoert, alleen de configuratie bijwerkt (nieuwe nginx-configuratiebestanden aanmaakt volgens de sjabloon: /etc/nginx/conf. d/${CI_COMMIT_REF_NAME}.conf) - zie docker-compose-nginx.yml-code)

    - docker stack deploy -c docker-compose-nginx.yml ${CI_ENVIRONMENT_NAME} --with-registry-auth

docker-compose-nginx.yml

---
version: '3'

services:
  nginx:
    image: nginx:latest
    environment:
      CI_COMMIT_REF_NAME: ${CI_COMMIT_REF_NAME}
      NGINX_CONFIG: |-
            server {
                listen 8080;
                server_name staging_${CI_COMMIT_REF_NAME}_cluster.dev;

                location / {
                    proxy_pass http://staging_${CI_COMMIT_REF_NAME}_cluster:8080;
                }
            }
            server {
                listen 5555;
                server_name staging_${CI_COMMIT_REF_NAME}_cluster.dev;

                location / {
                    proxy_pass http://staging_${CI_COMMIT_REF_NAME}_cluster:5555;
                }
            }
    volumes:
      - /tmp/staging/nginx:/etc/nginx/conf.d
    command:
      /bin/bash -c "echo -e "$$NGINX_CONFIG" > /etc/nginx/conf.d/${CI_COMMIT_REF_NAME}.conf;
        nginx -g "daemon off;";
        /etc/init.d/nginx reload"
    ports:
      - 8080:8080
      - 5555:5555
      - 3000:3000
      - 443:443
      - 80:80
    deploy:
      replicas: 1
      placement:
        constraints: [node.id == kilqc94pi2upzvabttikrfr5d]
      restart_policy:
        condition: none
    networks:
      nw_swarm:

networks:
  nw_swarm:
    external: true

Op de ontwikkelcomputers update je /etc/hosts; schrijf url voor naar nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

De inzet van geïsoleerde staging-clusters is dus geïmplementeerd en ontwikkelaars kunnen ze nu in een groot aantal uitvoeren dat voldoende is om hun taken te controleren.

Plannen voor de toekomst:

  • Scheid onze componenten als services
  • Heb voor elk Dockerfile
  • Detecteer automatisch minder geladen knooppunten in de stapel
  • Specificeer knooppunten op naampatroon (in plaats van id te gebruiken zoals in het artikel)
  • Voeg een controle toe dat de stapel is vernietigd
  • ...

Speciale dank voor статью.

Bron: www.habr.com

Voeg een reactie