Онлајн системот за препораки за видео содржини на кој работиме е затворен комерцијален развој и технички е повеќекомпонентен кластер на сопственички и компоненти со отворен код. Целта на пишувањето на овој напис е да се опише имплементацијата на системот за кластерирање на докерски рој за платформа за стадиуми, без да се наруши воспоставениот работен тек на нашите процеси под ограничени временски услови. Наративот што ви е претставен е поделен на два дела. Првиот дел го опишува 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 систем. Добиениот гасовод изгледаше отприлика вака:
Структурно, 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 — подготвува скрипти за старт-стоп компоненти врз основа на шаблонот
- распореди.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
Од горниот фрагмент од кодот е јасно дека две копчиња се додадени на Pipelines (deploy_staging, stop_staging) кои бараат рачно дејство.
Името на магацинот се совпаѓа со името на гранката и оваа единственост треба да биде доволна. Услугите во оџакот добиваат единствени 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