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:

Š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:
- createconfig.py — vytvára súbor settings.ini s nastaveniami komponentov pre rôzne prostredia pre následné nasadenie (Preproducția, Produkcia, Testovanie, …)
- install_venv.sh — vytvára virtuálne prostredie pre py-komponenty v určitej adresári a kopíruje ho na vzdialené servery
- prepare_init.d.py — pripravuje skripty na štart a zastavenie komponentov na základe šablóny
- 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

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

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 .
Zdroj: habr.com
