Deploy прыкладанняў з дапамогай Docker Swarm

Анлайн рэкамендацыйная сістэма відэа-кантэнту, над якой мы працуем, з'яўляецца закрытай камерцыйнай распрацоўкай і тэхнічна ўяўляе сабой шматкампанентны кластар з уласных і open source кампанентаў. Мэтай напісання дадзенага артыкула з'яўляецца апісанне ўкаранення сістэмы кластарызацыі docker swarm пад staging-пляцоўку, не парушаючы які склаўся workflow нашых працэсаў ва ўмовах абмежаванага часу. Прадстаўленае вашай увазе апавяданне падзелена на дзве часткі. Першая частка апісвае CI/CD да выкарыстання docker swarm, а другая - працэс яго ўкаранення. Хто не зацікаўлены ў чытанні першай часткі, можа смела пераходзіць да другой.

частка I

У далёкім-далёкім годзе патрабавалася як мага хутчэй наладзіць працэс CI/CD. Адной з умоў было не выкарыстоўваць Docker для дэплою распрацоўваных кампанент па некалькіх прычынах:

  • для больш надзейнай і стабільнай працы кампанент у Production (г.зн. па сутнасці патрабаванне не выкарыстоўваць віртуалізацыю)
  • вядучыя распрацоўшчыкі не хацелі працаваць з Docker (дзіўна, але было менавіта так)
  • па ідэйных меркаваннях кіраўніцтва R&D

Інфраструктура, стэк і прыкладныя зыходныя патрабаванні для MVP прадстаўляліся наступнымі:

  • 4 сервера Intel® X5650 з Debian (адна больш магутная машына цалкам пад распрацоўку)
  • Распрацоўка ўласных кастамных кампанент вядзецца на C++, Python3
  • Асноўныя 3rdparty-выкарыстоўваныя сродкі: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
  • Pipelines зборкі і тэсціравання кампанент асобна для debug і release

Адным з першых пытанняў, які патрабуецца вырашыць на пачатковай стадыі, гэта якім чынам будзе рабіцца разгортванне кастамных кампанент у якім-небудзь асяроддзі (CI/CD).

Іншыя кампаненты вырашылі ставіць сістэмна і абнаўляць іх сістэмна. Кастомныя ж прыкладанні, якія распрацоўваюцца на C++ ці Python, разгортваць можна некалькімі спосабамі. Сярод іх, напрыклад: стварэнне сістэмных пакетаў, адпраўка іх у рэпазітар сабраных выяў і іх наступная ўсталёўка на серверах. Па невядомым ужо чынніку быў абраны іншы спосаб, а менавіта: з дапамогай CI кампілююцца выкананыя файлы прыкладанняў, ствараецца віртуальнае асяроддзе праекту, усталёўваюцца py-модулі з requirements.txt і ўсе гэтыя артэфакты адпраўляюцца разам з канфігамі, скрыптамі і спадарожным асяроддзем прыкладанняў на серверы. Далей ажыццяўляецца запуск прыкладанняў ад віртуальнага карыстальніка без правоў адміністратара.

У якасці сістэмы CI/CD быў абраны Gitlab-CI. Атрыманы pipeline выглядаў прыкладна так:

Deploy прыкладанняў з дапамогай Docker Swarm
Структурна gitlab-ci.yml выглядаў наступным чынам

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

Варта заўважыць, што зборка і тэставанне вырабляецца на сваёй уласнай выяве, дзе ўжо ўсталяваныя ўсе неабходныя сістэмныя пакеты і выкананы іншыя налады.

Хоць кожны з гэтых скрыптоў у job-ах цікавы па-свойму, але расказваць я пра іх вядома ж не буду апісанне кожнага з іх зойме значны час і не ў гэтым мэта артыкула. Звярну толькі ўвагу, што стадыя дэплою складаецца з паслядоўнасці выкліку скрыптоў:

  1. createconfig.py - Стварае файл settings.ini з наладамі кампанент у розным асяроддзі для наступнага дэплою (Preproduction, Production, Testing, …)
  2. install_venv.sh - стварае віртуальнае асяроддзе для py-кампанент у вызначанай дырэкторыі і капіюе яго на выдаленыя серверы
  3. prepare_init.d.py - Падрыхтоўвае тэрміны старту-ступня кампанент на падставе шаблону
  4. deploy.py - раскладвае і перазапускае новыя кампаненты

Ішоў час. Стадыю staging замянілі preproduction і production. Дадалася падтрымка прадукта яшчэ на адным дытрыбутыве (CentOS). Дадалося яшчэ 5 магутных фізічных сервераў і дзясятак віртуальных. А распрацоўшчыкам і тэсціроўшчыкам станавілася ўсё больш складана абкатваць свае задачы на ​​асяроддзі больш-менш набліжаным да рабочага стану. У гэты час стала зразумела, што немагчыма абысціся без яго…

частка II

Deploy прыкладанняў з дапамогай Docker Swarm

Такім чынам, наш кластар уяўляе з сябе тое яшчэ відовішча сістэму з пары дзясяткаў асобных кампанент, не апісаных Dockerfile-амі. Сканфігураваць яго для дэплою на вызначанае асяроддзе можна толькі ў цэлым. Наша задача складаецца ў тым, каб дэплоіць кластар у staging-асяроддзе для абкаткі яго перад предрелизным тэставаннем.

Тэарэтычна, адначасова якія працуюць кластараў можа быць некалькі: столькі колькі задач у завершаным стане або блізкім да завяршэння. Магутнасці, якія ёсць у нашым распараджэнні сервераў, дазваляюць запускаць некалькі кластараў на кожным серверы. Кожны staging-кластар павінен быць ізаляваны (не павінна быць скрыжавання па партах, дырэкторыям і да т.п.).

Самы каштоўны рэсурс гэта наш час, а яго ў нас было няшмат.

Для хутчэйшага старту абралі Docker Swarm у сілу яго прастаты і гнуткасці архітэктуры. Першае што мы зрабілі гэта стварылі на выдаленых серверах мэнэджара і некалькі нод:

$ 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

Далей, стварылі сетку:


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

Далей, звязалі Gitlab-CI і ноды Swarm у частцы выдаленага кіравання нодамі з CI: усталёўка сертыфікатаў, налада сакрэтных зменных, а таксама налада сэрвісу Docker на кіравальным серверы. Вось гэтая артыкул нам моцна зэканоміла час.

Далей, дадалі job'ы стварэння і знішчэнні стэка ў .gitlab-ci .yml.

У .gitlab-ci .yml дадалося яшчэ некалькі job

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

З вышэйпрыведзенага фрагмента кода відаць, што ў Pipelines дадаліся дзве кнопкі (deploy_staging, stop_staging), якія патрабуюць ручнога ўздзеяння.

Deploy прыкладанняў з дапамогай Docker Swarm
Імя стэка адпавядае імя галінкі і гэтай унікальнасці павінна быць дастаткова. Сэрвісы ў стэку атрымліваюць унікальныя ip-адрасы, а парты, дырэкторыі і да т.п. будуць ізаляванымі, але аднолькавымі ад стэка да стэка (бо канфігурацыйны файл аднолькавы для ўсіх стэкаў) — тое, чаго мы і дамагаліся. Стэк (кластар) мы разгортваем з дапамогай докер-compose.yml, у якім апісаны наш кластар.

докер-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

Тут бачна, што кампаненты аб'яднаны адной сеткай (nw_swarm) і адзін аднаму даступныя.

Сістэмныя кампаненты (на аснове redis, mysql) падзеленыя ад агульнага пула кастамных кампанентаў (у планах і кастамныя падзяліць як сэрвісы). Стадыя дэплою нашага кластара выглядае як перадача CMD у наш адзін вялікі сканфігураваны image і ў цэлым практычна не адрозніваецца ад дэплою, апісанага ў Частцы I. Падкрэслю адрозненні:

  • git clone … - атрымліваем файлы, неабходныя, каб вырабіць дэплой (createconfig.py, install_venv.sh і да т.п.)
  • curl… && unzip … - спампоўваем і разархівуем артэфакты зборкі (скампляваныя ўтыліты)

Засталася толькі адна пакуль неапісаная праблема: кампаненты, якія маюць вэб-інтэрфейс не даступныя з браўзэраў распрацоўшчыкаў. Мы гэтую праблему вырашаем з дапамогай reverse proxy, такім чынам:

У .gitlab-ci.yml пасля дэплою стэка кластара дадаем радок дэплоя балансавальніка (які пры комітах, толькі абнаўляе сваю канфігурацыю (стварае новыя канфігурацыйныя файлы nginx па шаблоне: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME). гл. код 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

На кампутарах распрацоўнікаў абнаўляем /etc/hosts; прапісваем url да nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Такім чынам, дэплой ізаляваных staging-кластэраў рэалізаваны і распрацоўшчыкі зараз могуць запускаць іх у любым дастатковай для праверкі сваіх задач колькасці.

Далейшыя планы:

  • Падзяліць нашы кампаненты як сэрвісы
  • Завесці для кожнага Dockerfile
  • Аўтаматычна вызначаць менш загружаныя ноды ў стэк
  • Задаваць ноды па шаблоне імя (а не выкарыстоўваць id як у артыкуле)
  • Дадаць праверку, што стэк знішчаны
  • ...

Асобная падзяка за артыкул.

Крыніца: habr.com

Дадаць каментар