Deploy Uwendungen mat Docker Swarm

Den Online Videoinhalt Empfehlungssystem un deem mir schaffen ass eng zougemaach kommerziell Entwécklung an ass technesch e Multi-Komponentecluster vu propriétaire an Open Source Komponenten. Den Zweck fir dësen Artikel ze schreiwen ass d'Ëmsetzung vum Docker Schwarm Clustering System fir eng Inszenéierungsplattform ze beschreiwen, ouni den etabléierten Workflow vun eise Prozesser ënner limitéierter Zäitbedéngungen ze stéieren. D'narrativ, déi Iech opmierksam gemaach gëtt, ass an zwee Deeler opgedeelt. Den éischten Deel beschreift CI / CD ier Dir Docker-Schwarm benotzt, an den zweeten Deel beschreift de Prozess vun der Ëmsetzung. Déi, déi net interesséiert sinn den éischten Deel ze liesen, kënne sécher op den zweeten weidergoen.

Deel I

Et war emol e Besoin fir sou séier wéi méiglech e CI/CD Prozess op d'Been ze stellen. Ee vun de Konditioune war den Docker net ze benotzen fir Asaz Komponente ginn aus verschiddene Grënn entwéckelt:

  • fir méi zouverlässeg a stabil Operatioun vu Komponenten an der Produktioun (dh am Wesentlechen d'Ufuerderung net Virtualiséierung ze benotzen)
  • féierend Entwéckler wollten net mat Docker schaffen (komesch, awer dat ass wéi et war)
  • aus ideologesche Grënn vun der R&D Gestioun

D'Infrastruktur, Stack an ongeféier initial Ufuerderunge fir de MVP waren wéi follegt:

  • 4 Intel® X5650 Server mat Debian (eng méi mächteg Maschinn komplett fir Entwécklung)
  • Entwécklung vun Ären eegene personaliséierte Komponenten gëtt am C++, Python3 duerchgefouert
  • Main Drëtt Partei Tools benotzt: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, ...
  • Pipelines fir Komponenten ze bauen an ze testen separat fir Debug a Verëffentlechung

Eng vun den éischte Froen déi an der éischter Etapp geléist musse ginn ass wéi personaliséiert Komponenten an all Ëmfeld (CI / CD) ofgesat ginn.

Mir hunn décidéiert Drëtt Partei Komponenten systemesch z'installéieren an se systemesch ze aktualiséieren. Benotzerdefinéiert Uwendungen entwéckelt am C ++ oder Python kënnen op verschidde Manéieren ofgesat ginn. Ënnert hinnen, zum Beispill: System Packagen erstellen, se an de Repository vu gesammelten Biller schécken an hir spéider Installatioun op Serveren. Aus e schonn onbekannte Grond gouf eng aner Method gewielt, nämlech: mat CI, Applikatioun ausführbar Dateie ginn kompiléiert, e virtuellt Projetëmfeld gëtt erstallt, py Moduler aus requirement.txt installéiert, an all dës Artefakte ginn zesumme mat Konfiguratiounen, Scripten an déi begleedend Applikatiounsëmfeld op d'Serveren. Als nächst ginn Uwendungen vun engem virtuelle Benotzer ouni Administratorrechter gestart.

Gitlab-CI gouf als CI / CD System gewielt. Déi resultéierend Pipeline huet sou ausgesinn:

Deploy Uwendungen mat Docker Swarm
Strukturell huet gitlab-ci.yml esou ausgesinn:

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

Et ass derwäert ze notéieren datt d'Versammlung an d'Tester op hirem eegene Bild duerchgefouert ginn, wou all déi néideg Systempakete scho installéiert sinn an aner Astellunge gemaach ginn.

Och wa jidderee vun dësen Scripten an Aarbechtsplazen op seng eege Manéier interessant ass, wäert ech sécher net iwwer si schwätzen; jidderee vun hinnen ze beschreiwen wäert erheblech Zäit huelen an dëst ass net den Zweck vum Artikel. Loosst mech just Är Opmierksamkeet op d'Tatsaach zéien datt d'Deploymentstadium aus enger Sequenz vun Uruff Scripten besteet:

  1. createconfig.py - erstellt eng Settings.ini Datei mat Astellunge fir Komponenten a verschiddenen Ëmfeld fir spéider Deployment (Preproduction, Production, Testing, ...)
  2. install_venv.sh - erstellt e virtuellt Ëmfeld fir py Komponenten an engem spezifesche Verzeechnes a kopéiert et op Fernserveren
  3. prepare_init.d.py - preparéiert Scripte fir Start-Stop Komponenten op Basis vun der Schabloun
  4. deploy.py - deployéiert an nei Komponenten nei op

Zäit ass vergaang. D'Bühn gouf duerch Preproduktioun a Produktioun ersat. Ënnerstëtzung fir de Produit gouf op eng weider Verdeelung (CentOS) bäigefüügt. Aner 5 mächteg kierperlech Serveren an eng Dose virtuelle goufen dobäi. An et gouf ëmmer méi schwéier fir Entwéckler an Tester hir Aufgaben an engem Ëmfeld méi oder manner no beim Aarbechtszoustand ze testen. Zu dëser Zäit gouf et kloer datt et onméiglech ass ouni hien ...

Deel II

Deploy Uwendungen mat Docker Swarm

Also, eise Stärekoup ass e spektakuläre System vun e puer Dosen eenzel Komponenten déi net vun Dockerfiles beschriwwe ginn. Dir kënnt et konfiguréieren fir den Ofbau an e spezifescht Ëmfeld nëmmen am Allgemengen. Eis Aufgab ass de Cluster an e Staging Ëmfeld z'installéieren fir et ze testen ier Pre-Release Tester.

Theoretesch kënnen et e puer Stärekéip gläichzäiteg funktionnéieren: sou vill wéi et Aufgaben an engem ofgeschlossen Zoustand sinn oder no der Fäerdegstellung. D'Kraaft vun de Serveren déi eis zur Verfügung stinn erlaabt eis e puer Cluster op all Server ze lafen. All Staging Cluster muss isoléiert sinn (et sollt keng Iwwerlappung an Häfen, Verzeichnisser, etc.).

Eis wäertvollst Ressource ass eis Zäit, a mir haten net vill dovun.

Fir e méi séier Start hu mir Docker Swarm gewielt wéinst senger Einfachheet a flexibeler Architektur. Déi éischt Saach, déi mir gemaach hunn, war e Manager a verschidde Noden op Remote Serveren erstellen:

$ 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

Als nächst hu mir en Netzwierk erstallt:


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

Als nächst hu mir Gitlab-CI a Swarm Node verbonne wat d'Fernverwaltung vun den Noden vum CI ugeet: Zertifikaten z'installéieren, geheime Variablen astellen, an och den Docker Service um Management Server opzestellen. Dësen en Artikel huet eis vill Zäit gespuert.

Als nächst hu mir Aarbechtsplaze bäigefüügt fir de Stack an .gitlab-ci .yml ze kreéieren an ze zerstéieren.

Puer méi Aarbechtsplaze goufen dobäi .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

Aus dem uewe genannte Codefragment ass et kloer datt zwee Knäppercher op Pipelines bäigefüügt goufen (deploy_staging, stop_staging) déi manuell Handlung erfuerderen.

Deploy Uwendungen mat Docker Swarm
De Stacknumm entsprécht dem Branchenumm an dës Eenzegaartegkeet sollt genuch sinn. Servicer am Stack kréien eenzegaarteg IP Adressen, an Häfen, Verzeechnes, etc. wäert isoléiert sinn, awer d'selwecht vu Stack zu Stack (well d'Konfiguratiounsdatei fir all Stack d'selwecht ass) - dat ass wat mir wollten. Mir installéieren de Stack (Cluster) benotzt docker-compose.yml, deen eise Cluster beschreift.

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

Hei kënnt Dir gesinn datt d'Komponente vun engem Netz verbonne sinn (nw_swarm) an zougänglech fir all aner sinn.

Systemkomponenten (baséiert op Redis, mysql) gi vum allgemenge Pool vu personaliséierte Komponenten getrennt (a Pläng ginn personaliséiert Komponenten och als Servicer opgedeelt). D'Deploymentstadium vun eisem Cluster gesäit aus wéi CMD op eist grousst konfiguréiert Bild ze transferéieren an am Allgemengen ass praktesch net anescht wéi d'Deployment beschriwwen am Deel I. Ech wäert d'Ënnerscheeder ënnersträichen:

  • git clone... - Mir kréien déi néideg Dateien fir den Deployment auszeféieren (createconfig.py, install_venv.sh, etc.)
  • curl... && unzip... - Luet an unzip d'Build Artefakte (kompiléiert Utilities)

Et gëtt nëmmen een nach onbeschriwwene Problem: Komponenten déi e Webinterface hunn sinn net zougänglech vun den Entwéckler Browser. Mir léisen dëse Problem mam Reverse Proxy, also:

Am .gitlab-ci.yml, nodeems de Cluster Stack ofgebaut gouf, füügt eng Zeil fir de Balancer z'installéieren (deen, wann engagéiert, nëmmen seng Konfiguratioun aktualiséiert (erstellt nei nginx Konfiguratiounsdateien no der Schabloun: /etc/nginx/conf.d /${CI_COMMIT_REF_NAME}.conf) - kuckt 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 Entwéckler Computeren, update /etc/hosts; Setzt d'URL op nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Also, den Détachement vun isoléierten Inszenéierungscluster gouf implementéiert an d'Entwéckler kënnen se elo an all Quantitéit starten fir hir Aufgaben ze testen.

Zukunft Pläng:

  • Trennt eis Komponenten als Servicer
  • Erstellt eng Dockerfile fir all
  • Automatesch detektéieren manner gelueden Wirbelen am Stack
  • Spezifizéiert Noden mat engem Numm Schabloun (anstatt ID ze benotzen wéi am Artikel)
  • Füügt e Scheck datt de Stack zerstéiert gouf
  • ...

Besonnesche Merci fir Artikel.

Source: will.com

Setzt e Commentaire