Распоредете апликации користејќи Docker Swarm

Онлајн системот за препораки за видео содржини на кој работиме е затворен комерцијален развој и технички е повеќекомпонентен кластер на сопственички и компоненти со отворен код. Целта на пишувањето на овој напис е да се опише имплементацијата на системот за кластерирање на докерски рој за платформа за стадиуми, без да се наруши воспоставениот работен тек на нашите процеси под ограничени временски услови. Наративот што ви е претставен е поделен на два дела. Првиот дел го опишува CI/CD пред да се користи докерскиот рој, а вториот дел го опишува процесот на неговото спроведување. Оние кои не се заинтересирани да го читаат првиот дел, можат безбедно да преминат на вториот.

Дел I

Некогаш, имаше потреба да се постави процес на CI/CD што е можно побрзо. Еден од условите беше да не се користи Docker за распоредување компонентите се развиваат поради неколку причини:

  • за посигурна и стабилна работа на компонентите во производството (т.е., во суштина, барањето да не се користи виртуелизација)
  • водечките програмери не сакаа да работат со Docker (чудно, но така беше)
  • од идеолошки причини за управување со истражување и развој

Инфраструктурата, оџакот и приближните првични барања за MVP беа како што следува:

  • 4 Intel® X5650 сервери со Debian (уште една моќна машина целосно за развој)
  • Развојот на вашите сопствени сопствени компоненти се врши во C++, Python3
  • Главни користени алатки од трета страна: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql,…
  • Цевководи за градење и тестирање на компоненти одделно за дебагирање и ослободување

Едно од првите прашања што треба да се реши во почетната фаза е како сопствените компоненти ќе бидат распоредени во која било средина (CI/CD).

Решивме системски да инсталираме компоненти од трети страни и да ги ажурираме системски. Прилагодените апликации развиени во C++ или Python може да се распоредат на неколку начини. Меѓу нив, на пример: создавање системски пакети, нивно испраќање до складиштето на собрани слики и нивна последователна инсталација на серверите. Од веќе непозната причина, избран е друг метод, имено: со помош на CI, се компајлираат извршните датотеки на апликацијата, се создава виртуелна проектна средина, се инсталираат py модули од барања.txt и сите овие артефакти се испраќаат заедно со конфигурации, скрипти и придружното опкружување на апликацијата до серверите. Следно, апликациите се стартуваат од виртуелен корисник без администраторски права.

Gitlab-CI беше избран како CI/CD систем. Добиениот гасовод изгледаше отприлика вака:

Распоредете апликации користејќи 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

Вреди да се напомене дека склопувањето и тестирањето се вршат на сопствена слика, каде што се веќе инсталирани сите потребни системски пакети и се направени други поставки.

Иако секое од овие скрипти на работните места е интересно на свој начин, сигурно нема да зборувам за нив; за опишување на секоја од нив ќе биде потребно значително време и тоа не е целта на статијата. Дозволете ми само да ви го привлечам вниманието на фактот дека фазата на распоредување се состои од низа скрипти за повикување:

  1. createconfig.py — создава датотека settings.ini со поставки за компоненти во различни средини за последователно распоредување (препродукција, производство, тестирање, ...)
  2. install_venv.sh — создава виртуелна средина за py компоненти во одреден директориум и ја копира на оддалечени сервери
  3. подготви_init.d.py — подготвува скрипти за старт-стоп компоненти врз основа на шаблонот
  4. распореди.py — распоредува и рестартира нови компоненти

Времето помина. Сценската фаза беше заменета со претпродукција и продукција. Поддршката за производот е додадена на уште една дистрибуција (CentOS). Додадени се уште 5 моќни физички сервери и десетина виртуелни. И на програмерите и тестерите им стана сè потешко да ги тестираат своите задачи во средина повеќе или помалку блиска до работната состојба. Во тоа време стана јасно дека е невозможно да се направи без него ...

II дел

Распоредете апликации користејќи Docker Swarm

Значи, нашиот кластер е спектакуларен систем од неколку десетици индивидуални компоненти кои не се опишани од Dockerfiles. Можете да го конфигурирате за распоредување во одредена средина само општо. Наша задача е да го распоредиме кластерот во опкружување за поставување за да го тестираме пред тестирањето пред објавувањето.

Теоретски, може да има неколку кластери кои работат истовремено: онолку колку што има задачи во завршена состојба или блиску до завршување. Моќта на серверите со кои располагаме ни овозможува да работиме неколку кластери на секој сервер. Секој кластер за постановување мора да биде изолиран (не треба да има преклопување во пристаништата, директориумите итн.).

Нашиот највреден ресурс е нашето време, а ние немавме многу од него.

За побрз почеток, го избравме 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 на серверот за управување. Оваа Член ни заштеди многу време.

Следно, додадовме работни места за создавање и уништување на стекот во .gitlab-ci .yml.

Додадени се уште неколку работни места во .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

Од горниот фрагмент од кодот е јасно дека две копчиња се додадени на Pipelines (deploy_staging, stop_staging) кои бараат рачно дејство.

Распоредете апликации користејќи Docker Swarm
Името на магацинот се совпаѓа со името на гранката и оваа единственост треба да биде доволна. Услугите во оџакот добиваат единствени IP адреси и порти, директориуми итн. ќе биде изолиран, но исто од стек до стек (бидејќи конфигурациската датотека е иста за сите стекови) - тоа е она што го сакавме. Ние го распоредуваме стекот (кластерот) користејќи docker-compose.yml, кој го опишува нашиот кластер.

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

Овде можете да видите дека компонентите се поврзани со една мрежа (nw_swarm) и се достапни меѓу себе.

Системските компоненти (врз основа на redis, mysql) се одвоени од општиот базен на сопствени компоненти (во плановите, сопствените компоненти се исто така поделени како услуги). Фазата на распоредување на нашиот кластер изгледа како пренос на CMD на нашата една голема конфигурирана слика и, генерално, практично не се разликува од распоредувањето опишано во Дел I. Ќе ги нагласам разликите:

  • гит клон... — ги добиваме датотеките неопходни за извршување на распоредувањето (createconfig.py, install_venv.sh, итн.)
  • навивам... && отпакувај... — преземете и отпакувајте ги артефактите за изградба (компајлирани алатки)

Има само еден сè уште неопишан проблем: компонентите што имаат веб-интерфејс не се достапни од прелистувачите на програмерите. Овој проблем го решаваме користејќи обратен прокси, на тој начин:

Во .gitlab-ci.yml, по распоредувањето на стекот на кластерот, додајте линија за распоредување на балансерот (кој, кога е извршен, само ја ажурира неговата конфигурација (создава нови конфигурациски датотеки nginx според шаблонот: /etc/nginx/conf.d /${CI_COMMIT_REF_NAME}.conf) - видете код 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

Значи, распоредувањето на изолирани кластери за поставување е имплементирано и програмерите сега можат да ги лансираат во која било количина доволна за тестирање на нивните задачи.

Идни планови:

  • Одделете ги нашите компоненти како услуги
  • Направете Dockerfile за секој
  • Автоматски откривајте помалку оптоварени јазли во оџакот
  • Наведете јазли користејќи шаблон за име (наместо да користите id како во статијата)
  • Додадете проверка дека стекот е уништен
  • ...

Посебна благодарност за статью.

Извор: www.habr.com

Додадете коментар