Déployer des applications à l'aide de Docker Swarm

Le système de recommandation de contenu vidéo en ligne sur lequel nous travaillons est un développement commercial fermé et est techniquement un cluster multi-composants de ses propres composants open source. Le but de cet article est de décrire l'introduction d'un système de clustering Docker Swarm pour une plate-forme intermédiaire, sans perturber le flux de travail existant de nos processus dans un temps limité. Le récit présenté à votre attention est divisé en deux parties. La première partie décrit le CI / CD avant d'utiliser l'essaim de docker et la seconde décrit le processus de mise en œuvre. Ceux qui ne sont pas intéressés par la lecture de la première partie peuvent passer en toute sécurité à la seconde.

Partie I


Au cours d'une année lointaine et lointaine, il a fallu configurer le processus CI / CD le plus rapidement possible. L'une des conditions était de ne pas utiliser Docker pour déployer les composants développés pour plusieurs raisons:

  • pour un fonctionnement plus fiable et stable des composants en Production (c'est-à-dire, en fait, l'exigence de ne pas utiliser la virtualisation)
  • Les principaux développeurs ne voulaient pas travailler avec Docker (étrange, mais c'était juste ça)
  • pour des raisons idéologiques gestion R&D

L'infrastructure, la pile et les exemples d'exigences initiales pour MVP étaient les suivants:

  • 4 serveurs Intel® X5650 avec Debian (une machine plus puissante complètement pour le développement)
  • Le développement de composants personnalisés s'effectue en C ++, Python3
  • Les principaux outils tiers: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, ...
  • Assemblage des pipelines et test des composants séparément pour le débogage et la publication

L'un des premiers problèmes à résoudre à l'étape initiale est de savoir comment déployer des composants personnalisés dans n'importe quel environnement (CI / CD).

Les composants tiers ont décidé de procéder à une installation systémique et de les mettre à jour de manière systémique. Les applications personnalisées développées en C ++ ou Python peuvent être déployées de plusieurs manières. Parmi eux, par exemple: la création de packages système, leur envoi vers le référentiel des images collectées et leur installation ultérieure sur les serveurs. Pour une raison inconnue, une autre méthode a été choisie, à savoir, à l'aide de CI, les fichiers d'application exécutables sont compilés, un environnement de projet virtuel est créé, des modules py à partir de requirements.txt sont installés et tous ces artefacts sont envoyés avec les configurations, les scripts et l'environnement d'application qui l'accompagne aux serveurs. Ensuite, les applications sont lancées à partir d'un utilisateur virtuel sans droits d'administrateur.

Gitlab-CI a été choisi comme système CI / CD. Le pipeline résultant ressemblait à ceci:

image
Structurellement, gitlab-ci.yml ressemblait à ceci
--- 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 *\n\tStrictHostKeyChecking no\n\n" > ~/.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 


Il convient de noter que l'assemblage et les tests sont effectués sur sa propre image, où tous les packages système nécessaires sont déjà installés et d'autres paramètres sont définis.

Bien que chacun de ces scripts en cours d'emploi soit intéressant à sa manière, je n'en parlerai certainement pas , mais la description de chacun d'entre eux prendra beaucoup de temps et ce n'est pas le but de l'article. Je ferai seulement attention au fait que l'étape de déploiement consiste en une séquence d'appels de script:

  1. createconfig.py - crée le fichier settings.ini avec les paramètres des composants dans un environnement différent pour le déploiement ultérieur (préproduction, production, test, ...)
  2. install_venv.sh - crée un environnement virtuel pour les composants py dans un répertoire spécifique et le copie sur des serveurs distants
  3. prepare_init.d.py - prépare les scripts de démarrage et d'arrêt des composants sur la base d'un modèle
  4. deploy.py - décompresse et redémarre de nouveaux composants

Le temps a passé. La phase de mise en scène a été remplacée par la préproduction et la production. Le support produit a été ajouté sur un autre kit de distribution (CentOS). 5 serveurs physiques plus puissants et une douzaine de serveurs virtuels ont été ajoutés. Et il devenait de plus en plus difficile pour les développeurs et les testeurs d'exécuter leurs tâches dans un environnement plus ou moins proche de l'état de fonctionnement. A cette époque, il est devenu clair qu'il est impossible de se passer de lui ...

Partie II


image

Ainsi, notre cluster est toujours le spectacle d'un système de quelques dizaines de composants distincts qui ne sont pas décrits par Dockerfiles. Vous pouvez le configurer pour le déploiement dans un environnement spécifique uniquement dans son ensemble. Notre tâche consiste à déployer le cluster dans un environnement intermédiaire pour l'exécuter avant les tests préliminaires.

Théoriquement, il peut y avoir plusieurs clusters fonctionnant simultanément: autant de tâches sont terminées ou presque terminées. Les capacités disponibles sur nos serveurs nous permettent d'exécuter plusieurs clusters sur chaque serveur. Chaque cluster intermédiaire doit être isolé (il ne doit pas y avoir d'intersection sur les ports, les répertoires, etc.).

La ressource la plus précieuse est notre temps, et nous n'avions pas grand-chose.

Pour un démarrage plus rapide, ils ont choisi Docker Swarm en raison de sa simplicité et de sa flexibilité d'architecture. La première chose que nous avons faite a été de créer sur les serveurs de gestion à distance et plusieurs nœuds:

 $ 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 

Ensuite, nous avons créé un réseau:

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

Ensuite, nous avons connecté les nœuds Gitlab-CI et Swarm en termes de gestion à distance des nœuds CI: installation de certificats, configuration de variables secrètes, ainsi que configuration du service Docker sur le serveur de gestion. Cet article nous a fait gagner beaucoup de temps.

Ensuite, nous avons ajouté des tâches pour créer et détruire la pile dans .gitlab-ci .yml.

Quelques travaux supplémentaires ont été ajoutés à .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 


À partir de l'extrait de code ci-dessus, il est clair que deux boutons (deploy_staging, stop_staging) ont été ajoutés aux pipelines qui nécessitent une intervention manuelle.

image
Le nom de la pile correspond au nom de la branche et cette unicité devrait suffire. Les services de la pile reçoivent des adresses IP uniques, des ports, des répertoires, etc. sera isolé, mais le même d'une pile à l'autre (car le fichier de configuration est le même pour toutes les piles) - c'est ce que nous avons réalisé. Nous déployons la pile (cluster) à l'aide de docker-compose.yml , qui décrit notre cluster.

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 


Ici, vous pouvez voir que les composants sont connectés par un réseau (nw_swarm) et sont accessibles les uns aux autres.

Les composants système (basés sur redis, mysql) sont séparés du pool commun de composants personnalisés (dans les plans et personnalisés sont divisés en services). L'étape de déploiement de notre cluster ressemble au transfert CMD vers notre grande image configurée et, dans l'ensemble, ne diffère pratiquement pas du déploiement décrit dans la partie I. Je souligne les différences:

  • git clone ... - nous obtenons les fichiers nécessaires pour faire un déploiement (createconfig.py, install_venv.sh, etc.)
  • curl ... && unzip ... - télécharger et décompresser les artefacts d'assemblage (utilitaires compilés)

Il n'y a qu'un seul problème qui n'a pas encore été décrit: les composants qui ont une interface Web ne sont pas accessibles depuis les navigateurs des développeurs. Nous résolvons ce problème en utilisant un proxy inverse, donc:

Dans .gitlab-ci.yml, après le déploiement de la pile de cluster, ajoutez la ligne de déploiement de l'équilibreur (qui, lors de la validation, ne met à jour que sa configuration (crée de nouveaux fichiers de configuration nginx en utilisant le modèle: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME►.conf) - voir le 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 


Sur les ordinateurs de développement, mettez à jour / etc / hosts; enregistrer l'url sur nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Ainsi, le déploiement de clusters de staging isolés a été implémenté et les développeurs peuvent désormais les lancer en quantité suffisante pour tester leurs tâches.

Plans futurs:

  • Séparez nos composants en tant que services
  • Faites pour chaque Dockerfile
  • Détecter automatiquement les nœuds moins chargés dans la pile
  • Définir les nœuds par modèle de nom (plutôt que d'utiliser id comme dans l'article)
  • Ajouter vérifier que la pile est détruite
  • ...

Un merci spécial pour l' article .

Source: https://habr.com/ru/post/fr471528/


All Articles