Toepassingen implementeren met Docker Swarm

De online aanbevelingssysteem voor videocontent waaraan we werken, is een gesloten commerciële ontwikkeling en technisch gezien een multi-componentencluster bestaande uit eigen en open source componenten. Het doel van dit artikel is het beschrijven van de implementatie van het Docker Swarm-cluster voor de staging-omgeving, zonder de bestaande workflow van onze processen te verstoren binnen de tijdsbeperkingen. Het verhaal dat aan u wordt gepresenteerd, is verdeeld in twee delen. Het eerste deel beschrijft de CI/CD vóór het gebruik van Docker Swarm, en het tweede deel beschrijft het implementatieproces. Wie geen interesse heeft in het lezen van het eerste deel, kan gerust doorgaan naar het tweede.

Deel I

In een verre, verre tijd was er behoefte om het CI/CD-proces zo snel mogelijk in te stellen. Een van de voorwaarden was om Docker niet te gebruiken voor de deployment van de ontwikkelde componenten om verschillende redenen:

  • voor een betrouwbaardere en stabielere werking van componenten in productie (met andere woorden, de eis om virtualisatie niet te gebruiken)
  • de belangrijkste ontwikkelaars wilden niet met Docker werken (vreemd, maar dat was precies hoe het was)
  • om ideologische redenen van het management van R&D

De infrastructuur, techstack en de algemene initiƫle vereisten voor de MVP waren als volgt:

  • 4 servers IntelĀ® X5650 met Debian (ƩƩn krachtiger machine volledig voor ontwikkeling)
  • De ontwikkeling van eigen aangepaste componenten vindt plaats in C++, Python3
  • De belangrijkste gebruikte 3rd-party tools: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
  • Pipelines voor het bouwen en testen van componenten zijn apart voor debug en release

Een van de eerste vragen die op de beginfase moeten worden beantwoord, is op welke manier de implementatie van aangepaste componenten in een bepaalde omgeving (CI/CD) zal plaatsvinden.

Der centrale componenten worden systemisch geĆÆnstalleerd en systemisch bijgewerkt. Aangepaste applicaties, ontwikkeld in C++ of Python, kunnen op verschillende manieren worden uitgerold. Onder andere door: systeem pakketten te maken, deze naar een repository van samengestelde afbeeldingen te sturen en ze vervolgens op servers te installeren. Om onbekende redenen is een andere methode gekozen, namelijk: met behulp van CI worden uitvoerbare bestanden van applicaties gecompileerd, wordt er een virtuele omgeving voor het project aangemaakt, worden py-modules uit requirements.txt geĆÆnstalleerd en worden al deze artefacten samen met configuraties, scripts en bijbehorende omgevingen van applicaties naar de servers gestuurd. Daarna worden de applicaties gestart vanaf een virtuele gebruiker zonder administratorrechten.

Gitlab-CI is gekozen als CI/CD-systeem. De resulterende pipeline zag er ongeveer zo uit:

Toepassingen implementeren met Docker Swarm
De structuur van gitlab-ci.yml zag er als volgt uit

---
variables:
  # minimale CPU-versie op de servers waar het cluster wordt uitgerold
  CMAKE_CPUTYPE: "westmere"

  DEBIAN: "MYREGISTRY:5000/debian:latest"

before_script:
  - eval $(ssh-agent -s)
  - ssh-add  ~/.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 vermeldenswaardig dat de bouw en het testen plaatsvinden op hun eigen afbeelding, waarin al de nodige systeempakketten zijn geĆÆnstalleerd en andere instellingen zijn uitgevoerd.

Hoewel elk van deze scripts in jobs op zijn eigen manier interessant is, zal ik ze natuurlijk niet apart beschrijven; een uitgebreide uitleg van elk zou veel tijd vergen en dat is niet het doel van dit artikel. Ik wil alleen opmerken dat de deploy-fase bestaat uit een reeks scriptaanroepen:

  1. createconfig.py — creĆ«ert het settings.ini-bestand met configuraties van componenten in verschillende omgevingen voor de daaropvolgende deploy (Preproductie, Productie, Testen, …)
  2. install_venv.sh — creĆ«ert een virtuele omgeving voor py-componenten in een bepaalde map en kopieert deze naar de externe servers
  3. prepare_init.d.py — bereidt start-stop scripts van componenten voor op basis van een sjabloon
  4. deploy.py — verspreidt en herstart nieuwe componenten

De tijd verstreek. De staging-fase werd vervangen door preproductie en productie. Ondersteuning voor het product op een andere distributie (CentOS) werd toegevoegd. Vijf krachtige fysieke servers en een dozijn virtuele servers werden toegevoegd. En het werd steeds moeilijker voor ontwikkelaars en testers om hun taken te testen in een omgeving die meer of minder leek op een operationele staat. Op dat moment werd het duidelijk dat we niet zonder konden …

Deel II

Toepassingen implementeren met Docker Swarm

Dus, onze cluster is een kleurrijke schakeling van een paar tientallen afzonderlijke componenten die niet door Dockerfile's zijn beschreven. Het configureren voor deploy naar een specifieke omgeving kan slechts globaal worden gedaan. Onze taak is om de cluster in een staging-omgeving te deployen ter voorbereiding op pre-release testen.

Theoretisch kunnen er meerdere actieve clusters tegelijk zijn: zoveel als er taken in een voltooid of bijna voltooid stadium zijn. De capaciteiten van onze servers maken het mogelijk om meerdere clusters op elke server te draaien. Elke staging-cluster moet geĆÆsoleerd zijn (er mag geen overlap zijn in poorten, mappen, enz.).

De meest waardevolle hulpbron is onze tijd, en die hadden we maar weinig.

Voor een snellere start hebben we Docker Swarm gekozen vanwege zijn eenvoud en flexibiliteit van de architectuur. Het eerste wat we deden was het creƫren van een manager en meerdere nodes op de externe servers:

$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
kilqc94pi2upzvabttikrfr5d     nop-test-1     Klaar               Actief                                  19.03.2
jilwe56pl2zvabupryuosdj78     nop-test-2     Klaar               Actief                                  19.03.2
j5a4yz1kr2xke6b1ohoqlnbq5 *   nop-test-3     Klaar               Actief              Leider              19.03.2

Daarna hebben we een netwerk aangemaakt:


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

Vervolgens hebben we Gitlab-CI en de Swarm-nodes gekoppeld voor het op afstand beheren van de nodes vanuit CI: certificaten installeren, geheimen configureren, en ook de Docker-service op de beheerserver instellen. Dit heeft ons enorm veel tijd bespaard. artikel De volgende stap was het toevoegen van jobs voor het creƫren en vernietigen van de stack in .gitlab-ci.yml.

In .gitlab-ci.yml zijn nog enkele jobs toegevoegd.

Uit het bovenstaande codefragment blijkt dat er twee knoppen (deploy_staging, stop_staging) aan de Pipelines zijn toegevoegd, die handmatige interventie vereisen.

## 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

De naam van de stack komt overeen met de naam van de branch en deze uniciteit zou voldoende moeten zijn. Diensten in de stack krijgen unieke IP-adressen, en poorten, directories, enzovoort, zullen geĆÆsoleerd zijn maar identiek van stack tot stack (aangezien het configuratiebestand voor alle stacks identiek is) — dat is precies wat we wilden bereiken. We implementeren de stack (cluster) met behulp van

Toepassingen implementeren met Docker Swarm
, waarin ons cluster is beschreven. docker-compose.ymlwaarin ons cluster wordt beschreven.

docker-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 zien we dat de componenten verbonden zijn door ƩƩn netwerk (nw_swarm) en voor elkaar toegankelijk zijn.

De systeemelementen (ondersteund door redis, mysql) zijn gescheiden van de algemene pool van aangepaste componenten (de plannen zijn om ook de aangepaste componenten als diensten te scheiden). De implementatiefase van onze cluster ziet eruit als het doorgeven van CMD naar onze grote, geconfigureerde image en verschilt in principe niet van de implementatie die in Deel I is beschreven. Ik zal de verschillen benadrukken:

  • git clone … — we verkrijgen de bestanden die nodig zijn om de implementatie uit te voeren (createconfig.py, install_venv.sh, enz.).
  • curl… && unzip … — we downloaden en unzippen de build-artifacten (gecompileerde utilities).

Er is nog maar ƩƩn niet-beschreven probleem: de componenten met een webinterface zijn niet toegankelijk vanuit de browsers van de ontwikkelaars. We lossen dit probleem op met behulp van een reverse proxy, op de volgende manier:

In .gitlab-ci.yml voegen we na de implementatie van de clusterstack een regel toe voor de implementatie van de load balancer (die bij commits alleen zijn configuratie bijwerkt (nieuwe configuratiebestanden voor nginx aanmaakt op basis van sjabloon: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME}.conf) — zie de code docker-compose-nginx.yml)

    - 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 computers van de ontwikkelaars updaten we /etc/hosts; we schrijven de URL naar nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Dus de implementatie van geĆÆsoleerde staging-clusters is gerealiseerd en ontwikkelaars kunnen deze nu in een voldoende aantal voor het testen van hun taken opstarten.

Toekomstige plannen:

  • Onze componenten als diensten splitsen
  • Een Dockerfile voor elk aanmaken
  • Automatisch minder drukke nodes in de stack bepalen
  • Nodes op naam sjabloon definiĆ«ren (en niet id gebruiken zoals in het artikel)
  • Controleren of de stack is vernietigd
  • …

Bijzonder dank voor artikel.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers šŸ”„ Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster