Nasazení aplikací pomocí Docker Swarm

Online doporučovací systém video obsahu, na kterém pracujeme, je uzavřený komerční vývoj a technicky představuje vícekomponentní cluster složený z vlastních a open source komponentů. Cílem tohoto článku je popis implementace systému clusteringu docker swarm pro stagingové prostředí, aniž by se narušil stávající workflow našich procesů v omezeném čase. Předložený text je rozdělen na dvě části. První část popisuje CI/CD před použitím docker swarm, a druhá část se věnuje procesu jeho zavádění. Kdo nemá zájem o čtení první části, může přejít rovnou na druhou.

Část I

V dávném roce bylo třeba co nejrychleji nastavit proces CI/CD. Jednou z podmínek bylo nepoužívat Docker pro nasazení vyvíjených komponentů z několika důvodů:

  • pro spolehlivější a stabilnější práci komponentů v produkci (tj. v podstatě požadavek na nepoužívání virtualizace)
  • vedoucí vývojáři nechtěli pracovat s Dockerem (podivné, ale bylo to tak)
  • z ideologických důvodů vedení R&D

Infrastruktura, technologie a přibližné požadavky na MVP měly následující podobu:

  • 4 servery Intel® X5650 s Debianem (jedna mocnější mašina plně pro vývoj)
  • Vývoj vlastních přizpůsobených komponent se provádí v C++, Python3
  • Hlavní používané 3rdparty nástroje: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
  • Pipeline pro sestavení a testování komponent zvlášť pro debug a release

Jednou z prvních otázkách, které je třeba vyřešit v rané fázi, je, jakým způsobem bude probíhat nasazení přizpůsobených komponent v jakémkoli prostředí (CI/CD).

Externí komponenty byly rozhodnuty instalovat a aktualizovat systémově. Na druhou stranu lze vlastní aplikace vyvinuté v C++ nebo Pythonu nasazovat několika způsoby. Mezi nimi je například: vytváření systémových balíčků, jejich odeslání do repozitáře sestavených obrazů a následná instalace na serverech. Z neznámého důvodu byla zvolena jiná metoda, a to: pomocí CI se kompilují spustitelné soubory aplikací, vytváří se virtuální prostředí projektu, instalují se py-moduly z requirements.txt a všechny tyto artefakty se odesílají spolu s konfiguracemi, skripty a souvisejícím prostředím aplikací na servery. Následně se aplikace spouští od virtuálního uživatele bez administrátorských práv.

Jako CI/CD systém byl vybrán Gitlab-CI. Výsledný pipeline vypadá přibližně takto:

Nasazení aplikací pomocí Docker Swarm
Strukturálně gitlab-ci.yml vypadá následovně

---
variables:
  # minimální verze CPU na serverech, kde se nasazuje cluster
  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

Je třeba poznamenat, že sestavení a testování probíhá na vlastním obraze, kde jsou již nainstalovány všechny potřebné systémové balíčky a provedena další nastavení.

Ačkoli je každý z těchto skriptů v jobech zajímavý po svém, nebudu o nich samozřejmě vyprávět, protože popis každého z nich by zabral značné množství času a to není cílem článku. Pouze upozorním, že fáze nasazení se skládá z sekvence volání skriptů:

  1. createconfig.py — vytváří soubor settings.ini s nastaveními komponent v různých prostředích pro následné nasazení (Preproduction, Production, Testing, …)
  2. install_venv.sh — vytváří virtuální prostředí pro py-komponenty ve specifikovaném adresáři a kopíruje je na vzdálené servery
  3. prepare_init.d.py — připravuje skripty pro start-stop komponent na základě šablony
  4. deploy.py — rozmisťuje a restartuje nové komponenty

Čas plynul. Fáze staging byla nahrazena preproduction a produkcí. Přidala se podpora produktu na dalším distribuci (CentOS). Přibylo dalších 5 výkonných fyzických serverů a desítky virtuálních. A pro developery a testery bylo stále obtížnější testovat své úkoly v prostředí, které bylo alespoň trochu blízké pracovnímu stavu. V tuto chvíli bylo jasné, že se bez něj nelze obejít…

Část II

Nasazení aplikací pomocí Docker Swarm

Náš cluster je doopravdy podivuhodný systém složený z několika desítek samostatných komponent, které nejsou popsány Dockerfile. Konfigurovat jej pro nasazení do konkrétního prostředí je možné pouze jako celek. Naším úkolem je nasadit cluster do stagingového prostředí, aby se otestoval před předběžným testováním.

Teoreticky může existovat několik současně fungujících clusterů: tolik, kolik úkolů je ve stavu dokončení nebo blízko dokončení. Výkon serverů, které máme k dispozici, nám umožňuje spouštět několik clusterů na každém serveru. Každý stagingový cluster musí být izolován (nesmí být překryvy portů, adresářů atd.).

Nejcennějším zdrojem je náš čas, a toho jsme měli málo.

Pro rychlejší start jsme zvolili Docker Swarm kvůli jeho jednoduchosti a flexibilitě architektury. První, co jsme udělali, bylo vytvořené manažera na vzdálených serverech a několika nodí:

$ 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

Dále jsme vytvořili síť:


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

Dále jsme propojili Gitlab-CI a uzly Swarm pro vzdálenou správu uzlů z CI: instalace certifikátů, nastavení tajných proměnných a také konfigurace Docker služby na řídícím serveru. Tohle neaktuální statistika nám výrazně ušetřilo čas.

Dále jsme přidali úkoly pro vytvoření a zničení zásobníku do .gitlab-ci.yml.

V .gitlab-ci.yml bylo přidáno několik dalších úkolů

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

Z výše uvedeného úryvku kódu je patrné, že do Pipelines byly přidány dvě tlačítka (deploy_staging, stop_staging), která vyžadují manuální zásah.

Nasazení aplikací pomocí Docker Swarm
Název zásobníku odpovídá názvu větve a tato jedinečnost by měla být dostatečná. Služby v zásobníku získávají jedinečné IP adresy, zatímco porty, adresáře atd. zůstanou izolované, ale stejné napříč zásobníky (protože konfigurační soubor je stejný pro všechny zásobníky) — což je to, čeho jsme se snažili dosáhnout. Zásobník (klastr) rozbíháme pomocí docker-compose.yml, ve kterém je popsán náš klastr.

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

Je jasné, že komponenty jsou spojeny jednou sítí (nw_swarm) a jsou si navzájem dostupné.

Systémové komponenty (na bázi redis, mysql) jsou odděleny od společného poolu vlastních komponentů (v plánech také vlastní rozdělit jako služby). Fáze nasazení našeho clusteru vypadá jako předání CMD do našeho velkého nakonfigurovaného obrazu a v podstatě se téměř neliší od nasazení popsaného v Části I. Zdůrazním rozdíly:

  • git clone … — získáme soubory potřebné k provedení nasazení (createconfig.py, install_venv.sh atd.)
  • curl… && unzip … — stáhneme a rozbalíme artefakty sestavení (zkompilované nástroje)

Zůstává pouze jeden dosud nepopisovaný problém: komponenty, které mají webové rozhraní, nejsou dostupné z prohlížečů vývojářů. Tento problém řešíme pomocí reverse proxy takto:

V .gitlab-ci.yml po nasazení stacku klastru přidáme řádek pro nasazení load balanceru (který při commitech pouze aktualizuje svou konfiguraci (vytváří nové konfigurační soubory nginx podle šablony: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME}.conf) — viz kód 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

Na počítačích vývojářů aktualizujeme /etc/hosts; napíšeme url až do nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Takže nasazení izolovaných staging klastrů je realizováno a vývojáři je nyní mohou spouštět v dostatečném počtu pro ověření svých úloh.

Další plány:

  • Rozdělit naše komponenty na služby
  • Zavést pro každý Dockerfile
  • Automaticky určit méně zatížené uzly ve stacku
  • Určit uzly podle šablony názvu (a nepoužívat id jako ve článku)
  • Přidat kontrolu, že stack byl zničen
  • …

Zvláštní poděkování za článek.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster