Deploy applications using Docker Swarm

Online doporučovací systém video obsahu, na ktorom pracujeme, je uzavretý komerčný projekt a technicky predstavuje viackomponentný klaster zo súkromných a open source komponentov. Cieľom napísania tohto článku je popísať implementáciu systému klusteringu Docker Swarm pre staging prostredie, bez narušenia existujúceho workflow našich procesov v podmienkach obmedzeného času. Predložené rozprávanie je rozdelené na dve časti. Prvá časť popisuje CI/CD pred použitím Docker Swarm, a druhá - proces jeho implementácie. Kto nemá záujem čítať prvú časť, môže prejsť priamo na druhú.

Časť I

V dávnom roku bolo potrebné čo najrýchlejšie nastaviť proces CI/CD. Jedným z podmienok bolo nepripustiť používanie Docker pre nasadenie vyvíjaných komponentov z niekoľkých dôvodov:

  • pre spoľahlivejšiu a stabilnejšiu prevádzku komponentov v produkcii (t.j. v podstate požiadavka na nepoužitie virtualizácie)
  • vedúci vývojári nechceli pracovať s Dockerom (čudné, ale presne tak to bolo)
  • na základe ideologických úvah vedenia R&D

Infrastruktúra, stack a približné počiatočné požiadavky pre MVP sa javili nasledovne:

  • 4 servery Intel® X5650 s Debianom (jedna výkonnejšia mašina úplne pre vývoj)
  • Vývoj vlastných prispôsobených komponentov prebieha v C++, Python3
  • Hlavné 3rd party používané nástroje: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
  • Pipelines pre zostavenie a testovanie komponentov zvlášť pre debug a release

Jednou z prvých otázok, ktorú je potrebné vyriešiť v počiatočnej fáze, je, akým spôsobom sa bude vykonávať nasadzovanie prispôsobených komponentov v nejakom prostredí (CI/CD).

Externé komponenty sa rozhodli inštalovať a aktualizovať systémovo. Vlastné aplikácie vyvíjané v C++ alebo Pythone sa môžu nasadzovať rôznymi spôsobmi. Medzi nimi, napríklad: vytvorenie systémových balíkov, ich odoslanie do repozitára zostavených obrazov a následná inštalácia na serveroch. Z neznámeho dôvodu bol zvolený iný prístup, a to: pomocou CI sa kompilujú spustiteľné súbory aplikácií, vytvára sa virtuálne prostredie projektu, inštalujú sa py-moduly z requirements.txt a všetky tieto artefakty sa odosielajú spolu s konfiguráciami, skriptami a sprievodným prostredím aplikácií na servery. Následne sa aplikácie spúšťajú od virtuálneho používateľa bez administrátorských práv.

Ako systém CI/CD bol zvolený Gitlab-CI. Vytvorený pipeline vyzeral približne takto:

Deploy applications using Docker Swarm
Štrukturálne gitlab-ci.yml vyzeral nasledovne

---
variables:
  # minimálna verzia CPU na serveroch, kde sa nasadzuje klaster
  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 potrebné poznamenať, že zostavenie a testovanie sa uskutočňuje na vlastnom obraze, kde sú už nainštalované všetky potrebné systémové balíky a vykonané iné nastavenia.

Hoci každý z týchto skriptov v joboch je zaujímavý svojím spôsobom, nebudem o nich rozprávať, pretože popis každého z nich by zaberalo značný čas a nie je to účelom tohto článku. Len upozorním, že fáza nasadenia pozostáva z postupnosti volania skriptov:

  1. createconfig.py — vytvára súbor settings.ini s nastaveniami komponentov pre rôzne prostredia pre následné nasadenie (Preproducția, Produkcia, Testovanie, …)
  2. install_venv.sh — vytvára virtuálne prostredie pre py-komponenty v určitej adresári a kopíruje ho na vzdialené servery
  3. prepare_init.d.py — pripravuje skripty na štart a zastavenie komponentov na základe šablóny
  4. deploy.py — rozkladá a reštartuje nové komponenty

Utekal čas. Fázu staging nahradili preprodukcia a produkcia. Pridala sa podpora produktu na ďalšom distribúcii (CentOS). Pribudlo ďalších 5 výkonných fyzických serverov a desiatky virtuálnych. A predeveloperom a testerom bolo čoraz ťažšie realizovať svoje úlohy v prostredí, ktoré bolo minimálne blízko prevádzkovému stavu. V tejto dobe sa stalo jasným, že sa bez neho nedá obiť...

Časť II

Deploy applications using Docker Swarm

Takže náš klaster predstavuje ešte zaujímavú sústavu z pár desiatok samostatných komponentov, ktoré nie sú popísané Dockerfile-ami. Konfigurácia pre nasadenie do určitého prostredia sa dá urobiť iba celkovo. Našou úlohou je nasadiť klaster do stagingového prostredia na vyskúšanie pred pred-release testingom.

Teoreticky môže byť súčasne fungujúcich klastrov niekoľko: toľko, koľko úloh je v dokončenom stave alebo blízko dokončenia. Výkon našich serverov umožňuje spúšťať niekoľko klastrov na každom serveri. Každý stagingový klaster by mal byť izolovaný (nesmie byť prekrytie portov, adresárov atď.).

Najcennejší zdroj je náš čas, a toho sme mali málo.

Pre rýchlejší štart sme zvolili Docker Swarm kvôli jeho jednoduchej a flexibilnej architektúre. Prvá vec, ktorú sme urobili, bolo vytvoriť na vzdialených serveroch manažéra a niekoľko nód:

$ 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

Ďalej, vytvorili sme sieť:


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

Potom sme prepojili Gitlab-CI a uzly Swarm ohľadom vzdialeného ovládania uzlov z CI: inštalácia certifikátov, nastavenie tajných premenných a konfigurácia Docker služby na riadiacom serveri. Toto článok nám výrazne ušetrilo čas.

Následne sme pridali joby na vytváranie a ničenie stacku do .gitlab-ci .yml.

V .gitlab-ci .yml pribudlo niekoľko ďalších jobov

## 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 vyššie uvedeného fragmentu kódu je vidno, že do Pipelines pribudli dve tlačidlá (deploy_staging, stop_staging), ktoré vyžadujú manuálny zásah.

Deploy applications using Docker Swarm
Názov stacku zodpovedá názvu vetvy a táto jedinečnosť by mala byť dostatočná. Služby v stacku získavajú jedinečné ip adresy, a porty, adresáre atď. budú izolované, ale rovnaké medzi stackmi (keďže konfiguračný súbor je rovnaký pre všetky stacky) — čo bolo naším cieľom. Stack (klaster) rozbiehame pomocou docker-compose.yml, v ktorom je opísaný náš klaster.

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 vidieť, že komponenty sú spojené jednou sieťou (nw_swarm) a sú si navzájom dostupné.

Systémové komponenty (založené na redis, mysql) sú oddelené od hlavného súboru prispôsobených komponentov (v plánoch je aj prispôsobenie ako služby). Fáza nasadenia nášho klastera vyzerá ako odovzdanie CMD do našej jednej veľkej nakonfigurovanej image a vo všeobecnosti sa prakticky nelíši od nasadenia opísaného v Časti I. Upozorňujem na rozdiely:

  • git clone … — získavame súbory potrebné na vykonanie nasadenia (createconfig.py, install_venv.sh atď.)
  • curl… && unzip … — sťahujeme a rozbaľujeme artefakty zostavy (skompilované utilitky)

Zostala len jedna doteraz nepopísaná probléma: komponenty, ktoré majú webové rozhranie, nie sú dostupné z prehliadačov vývojárov. Túto problém riešime pomocou reverse proxy, a to takto:

V .gitlab-ci.yml po nasadení zásobníka klastru pridáme riadok nasadenia vyváženia (ktorý pri commitoch len aktualizuje svoju konfiguráciu (vytvára nové konfiguračné súbory nginx podľa šablóny: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME}.conf) — pozri 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čoch vývojárov aktualizujeme /etc/hosts; zapíšeme url k nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Takže nasadenie izolovaných staging klastrov je realizované a vývojári ich môžu spúšťať v dostatočnom množstve na overenie svojich úloh.

Ďalšie plány:

  • Rozdelíme naše komponenty ako služby
  • Vytvoríme pre každý Dockerfile
  • Automaticky budeme určovať menej zaťažené nody v zásobníku
  • Nastavíme nody podľa šablóny mena (a nebudeme používať id ako v článku)
  • Pridáme kontrolu, že zásobník bol zničený
  • …

Osobitná vďaka za článok.

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster