Системата за препоръчване на онлайн видеосъдържание, върху която работим, е затворена комерсиална разработка и технически е многокомпонентен клъстер от частни компоненти и компоненти с отворен код. Целта на написването на тази статия е да се опише внедряването на системата за клъстериране на docker swarm за етапен сайт, без да се нарушава установеният работен поток на нашите процеси за ограничено време. Представеният на вашето внимание разказ е разделен на две части. Първата част описва CI / CD преди използването на docker swarm, а втората описва процеса на неговото внедряване. Тези, които не се интересуват от четене на първата част, могат спокойно да преминат към втората.
Част 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 модулите се инсталират от requirements.txt и всички тези артефакти се изпращат заедно с конфигурации, скриптове и съпътстваща приложна среда към сървърите. След това приложенията се стартират като виртуален потребител без администраторски права.
Gitlab-CI беше избран като CI/CD система. Полученият тръбопровод изглеждаше по следния начин:
Структурно 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
Струва си да се отбележи, че сглобяването и тестването се извършват на собствен образ, където вече са инсталирани всички необходими системни пакети и са направени други настройки.
Въпреки че всеки от тези скриптове в работни места е интересен по свой начин, разбира се, няма да говоря за тях, описанието на всеки от тях ще отнеме много време и това не е целта на статията. Само ще обърна внимание на факта, че етапът на внедряване се състои от последователност от извикващи скриптове:
- createconfig.py - създава файл settings.ini с настройки на компоненти в различни среди за последващо внедряване (предварителна продукция, производство, тестване, ...)
- install_venv.sh - създава виртуална среда за py компоненти в определена директория и я копира на отдалечени сървъри
- подготви_init.d.py — подготвя старт-стоп скриптове за компонента въз основа на шаблона
- deploy.py - разлага и рестартира нови компоненти
Мина време. Етапът на постановката беше заменен от предпродукция и продукция. Добавена е поддръжка за продукта в още една дистрибуция (CentOS). Добавени са още 5 мощни физически сървъра и дузина виртуални. И ставаше все по-трудно за разработчиците и тестерите да тестват своите задачи в среда, повече или по-малко близка до работното състояние. По това време стана ясно, че е невъзможно без него ...
Част II
И така, нашият клъстер е грандиозна система от няколко дузини отделни компоненти, които не са описани от 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
От горния кодов фрагмент можете да видите, че два бутона (deploy_staging, stop_staging) са добавени към Pipelines, изискващи ръчно действие.
Името на стека съвпада с името на клона и тази уникалност трябва да е достатъчна. Услугите в стека получават уникални 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 в нашето едно голямо конфигурирано изображение и като цяло практически не се различава от внедряването, описано в част I. Ще подчертая разликите:
- git клонинг... - вземете файловете, необходими за внедряване (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
- Автоматично откриване на по-малко натоварени възли в стека
- Посочете възли по модел на име (вместо да използвате идентификатор, както е в статията)
- Добавете проверка, че стекът е унищожен
- ...
Специални благодарности за
Източник: www.habr.com