Desplegueu aplicacions amb Docker Swarm

El sistema de recomanació de contingut de vídeo en línia en el qual estem treballant és un desenvolupament comercial tancat i tècnicament és un clúster multicomponent de components propietaris i de codi obert. L'objectiu d'escriure aquest article és descriure la implementació del sistema d'agrupament de docker swarm per a un lloc de prova sense interrompre el flux de treball establert dels nostres processos en un temps limitat. La narració presentada a la vostra atenció està dividida en dues parts. La primera part descriu CI / CD abans d'utilitzar Docker Swarm, i la segona descriu el procés de la seva implementació. Aquells que no estiguin interessats en llegir la primera part poden passar amb seguretat a la segona.

Primera part

A l'any llunyà i llunyà, va ser necessari configurar el procés CI / CD el més ràpidament possible. Una de les condicions era no utilitzar Docker per al desplegament components desenvolupats per diverses raons:

  • per a un funcionament més fiable i estable dels components en Producció (és a dir, de fet, el requisit de no utilitzar la virtualització)
  • els principals desenvolupadors no volien treballar amb Docker (estrany, però així era)
  • segons les consideracions ideològiques de la gestió de l'R+D

La infraestructura, la pila i els requisits inicials aproximats per a MVP es van presentar de la següent manera:

  • 4 servidors Intel® X5650 amb Debian (una màquina més potent està totalment desenvolupada)
  • El desenvolupament de components personalitzats propis es realitza en C++, Python3
  • Eines principals de tercers utilitzades: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
  • Conduccions per construir i provar components per separat per a la depuració i el llançament

Una de les primeres preguntes que cal abordar en l'etapa inicial és com es desplegaran els components personalitzats en qualsevol entorn (CI/CD).

Vam decidir instal·lar components de tercers de manera sistèmica i actualitzar-los sistemàticament. Les aplicacions personalitzades desenvolupades en C++ o Python es poden desplegar de diverses maneres. Entre ells, per exemple: crear paquets del sistema, enviar-los al repositori d'imatges construïdes i després instal·lar-los en servidors. Per una raó desconeguda, es va triar un altre mètode, és a dir: mitjançant CI, es compilen els fitxers executables de l'aplicació, es crea un entorn de projecte virtual, s'instal·len mòduls py des de requirements.txt i tots aquests artefactes s'envien juntament amb configuracions, scripts i el entorn d'aplicació que l'acompanya als servidors. A continuació, s'inicien les aplicacions com a usuari virtual sense drets d'administrador.

Es va triar Gitlab-CI com a sistema CI/CD. El pipeline resultant semblava a això:

Desplegueu aplicacions amb Docker Swarm
Estructuralment, gitlab-ci.yml tenia aquest aspecte

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

Val la pena assenyalar que el muntatge i les proves es realitzen a la seva pròpia imatge, on ja s'han instal·lat tots els paquets de sistema necessaris i s'han fet altres configuracions.

Tot i que cadascun d'aquests guions a les feines és interessant a la seva manera, però per descomptat no en parlaré.La descripció de cadascun d'ells trigarà molt de temps i aquest no és l'objectiu de l'article. Només cridaré la vostra atenció sobre el fet que l'etapa de desplegament consisteix en una seqüència d'scripts de trucada:

  1. createconfig.py - crea un fitxer settings.ini amb la configuració dels components en diversos entorns per al desplegament posterior (preproducció, producció, proves, ...)
  2. install_venv.sh - crea un entorn virtual per als components py en un directori específic i el copia a servidors remots
  3. prepare_init.d.py — prepara scripts d'inici i parada per al component basant-se en la plantilla
  4. deploy.py - descompon i reinicia nous components

El temps va passar. L'etapa de muntatge va ser substituïda per la preproducció i la producció. S'ha afegit suport per al producte en una distribució més (CentOS). S'han afegit 5 ​​servidors físics més potents i una dotzena de virtuals. I cada vegada era més difícil per als desenvolupadors i provadors provar les seves tasques en un entorn més o menys proper a l'estat de treball. En aquest moment, va quedar clar que era impossible prescindir d'ell...

Part II

Desplegueu aplicacions amb Docker Swarm

Per tant, el nostre clúster és un sistema espectacular d'un parell de dotzenes de components separats que Dockerfiles no descriu. Només podeu configurar-lo per al desplegament en un entorn específic en general. La nostra tasca és desplegar el clúster en un entorn de prova per provar-lo abans de la prova prèvia al llançament.

Teòricament, hi pot haver diversos clústers que s'executen simultàniament: tants com tasques hi hagi en estat completat o properes a la finalització. Les capacitats dels servidors de què disposem ens permeten executar diversos clústers a cada servidor. Cada clúster de prova ha d'estar aïllat (no hi ha d'haver intersecció en ports, directoris, etc.).

El nostre recurs més valuós és el nostre temps, i no en teníem gaire.

Per començar més ràpid, vam triar Docker Swarm per la seva senzillesa i flexibilitat arquitectònica. El primer que vam fer va ser crear un gestor i diversos nodes als servidors remots:

$ 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

A continuació, creeu una xarxa:


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

A continuació, vam connectar els nodes Gitlab-CI i Swarm pel que fa al control remot dels nodes des de CI: instal·lació de certificats, configuració de variables secretes i configuració del servei Docker al servidor de control. Aquest article ens va estalviar molt de temps.

A continuació, hem afegit feines de creació i destrucció de piles a .gitlab-ci .yml.

S'han afegit algunes feines més a .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

Des del fragment de codi anterior, podeu veure que s'han afegit dos botons (deploy_staging, stop_staging) a Pipelines, que requereixen una acció manual.

Desplegueu aplicacions amb Docker Swarm
El nom de la pila coincideix amb el nom de la branca i aquesta singularitat hauria de ser suficient. Els serveis de la pila reben adreces IP úniques i ports, directoris, etc. estarà aïllat, però igual de pila en pila (perquè el fitxer de configuració és el mateix per a totes les piles), el que volíem. Despleguem la pila (clúster) utilitzant docker-compose.yml, que descriu el nostre clúster.

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

Aquí podeu veure que els components estan connectats per una xarxa (nw_swarm) i estan disponibles entre ells.

Els components del sistema (basats en redis, mysql) es separen del conjunt general de components personalitzats (en els plans i els personalitzats es divideixen com a serveis). L'etapa de desplegament del nostre clúster sembla passar CMD a la nostra gran imatge configurada i, en general, pràcticament no difereix del desplegament descrit a la part I. Destacaré les diferències:

  • git clon... - obtenir els fitxers necessaris per implementar (createconfig.py, install_venv.sh, etc.)
  • arrissol... && descomprimir... - descarregar i descomprimir artefactes de construcció (utilitats compilades)

Només hi ha un problema encara no descrit: els components que tenen una interfície web no són accessibles des dels navegadors dels desenvolupadors. Resolem aquest problema utilitzant el proxy invers, així:

A .gitlab-ci.yml, després de desplegar la pila de clúster, afegim la línia de desplegament de l'equilibrador (que, quan es compromet, només actualitza la seva configuració (crea nous fitxers de configuració nginx segons la plantilla: /etc/nginx/conf. d/${CI_COMMIT_REF_NAME}.conf) - vegeu el codi 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

Als ordinadors de desenvolupament, actualitzeu /etc/hosts; prescriu l'URL a nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Per tant, s'ha implementat el desplegament de clústers de prova aïllats i els desenvolupadors ara els poden executar en qualsevol nombre suficient per comprovar les seves tasques.

Plans futurs:

  • Separeu els nostres components com a serveis
  • Teniu per a cada Dockerfile
  • Detecta automàticament els nodes menys carregats a la pila
  • Especifiqueu els nodes pel patró de nom (en lloc d'utilitzar id com a l'article)
  • Afegiu una comprovació que la pila està destruïda
  • ...

Gràcies especials per un article.

Font: www.habr.com

Afegeix comentari