Az online videótartalom-ajánló rendszer, amelyen dolgozunk, egy zárt kereskedelmi fejlesztés, és technikailag egy saját és nyílt forráskódú összetevőkből álló többkomponensű klasztert képvisel. A cikk megírásának célja a Docker Swarm klaszterizációs rendszer bevezetésének leírása egy staging környezetben, anélkül, hogy megsértenénk a folyamataink megszokott munkafolyamatát a korlátozott idő alatt. Az Ön elé tárt beszámoló két részre van osztva. Az első rész bemutatja a CI/CD folyamatot a Docker Swarm használata előtt, a második a bevezetés folyamatát írja le. Aki nem kíváncsi az első részre, átugorhat a másodikra.
I. rész
Réges-régen szükség volt a CI/CD folyamat gyors beállítására. Az egyik feltétel az volt, hogy ne használjunk Dockert a telepítéshez kifejlesztett komponensekhez több okból:
- a komponensek megbízhatóbb és stabilabb működéséhez a Production-ban (tehát gyakorlatilag a virtualizáció használatának elkerülése)
- a vezető fejlesztők nem akartak dolgozni a Dockerrel (ez furcsa, de így történt)
- az R&D vezetésének elvi okokból
Az infrastruktúra, a technológiai stack és a MVP-re vonatkozó általános követelmények a következőképpen néztek ki:
- 4 Intel® X5650 szerver Debian rendszerrel (egy erősebb gép teljesen a fejlesztéshez)
- A saját egyedi komponensek fejlesztése C++ és Python3 nyelveken történik
- A fő harmadik féltől származó eszközök: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
- A komponensek építése és tesztelése különböző csatornákra debug és release
Az egyik első kérdés, amit a kezdeti szakaszban meg kell oldani, hogy hogyan valósul meg a testreszabott komponensek telepítése bármilyen környezetben (CI/CD).
A külső komponenseket rendszerszinten telepítik és frissítik. A C++ vagy Python nyelven fejlesztett egyedi alkalmazásokat többféleképpen is telepíthetjük. Például: rendszerszintű csomagok létrehozása, azok küldése a becsomagolt képek tárolójába és azok későbbi telepítése a szervereken. Ismeretlen ok miatt egy másik módszer lett kiválasztva, nevezetesen: CI segítségével végzik el az alkalmazások végrehajtható fájljainak fordítását, létrejön a projekt virtuális környezete, a requirements.txt-ből telepítődnek a py-modulok, és ezeket az artefaktumokat a konfigurációkkal, szkriptekkel és a kapcsolódó alkalmazás környezettel együtt küldik a szerverekre. Ezt követően az alkalmazásokat virtuális felhasználóként indítják, rendszergazdai jogok nélkül.
A CI/CD rendszerként a Gitlab-CI-t választották. Az elkészült pipeline körülbelül így nézett ki:

A gitlab-ci.yml struktúrája a következőképpen alakult:
---
variables:
# minimális CPU verzió a klaszterek telepítéséhez használt szervereken
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
Érdemes megjegyezni, hogy a build és tesztelés saját képen történik, ahol már telepítve vannak az összes szükséges rendszerkomponens és egyéb beállítások.
Bár ezek a szkriptek a job-okban mindegyike érdekes a maga módján, de természetesen nem fogok mindegyikről beszélni, mivel a leírásuk jelentős időt venne igénybe, és nem ez a cikk célja. Csak arra szeretnék figyelmet fordítani, hogy a telepítési szakasz a szkriptek sorozatának meghívásából áll:
- createconfig.py — létrehozza a settings.ini fájlt a különböző környezetekhez tartozó komponensbeállításokkal a további telepítéshez (Preproduction, Production, Testing stb.)
- install_venv.sh — létrehozza a virtuális környezetet a py-komponensek számára egy adott könyvtárban, és átmásolja azt a távoli szerverekre
- prepare_init.d.py — előkészíti a komponens indításához és leállításához szükséges szkripteket egy sablon alapján
- deploy.py — elhelyezi és újraindítja az új komponenseket
Eltelt az idő. A staging szakaszt preproduction és production váltotta fel. Támogatást kaptunk egy új disztribúcióra (CentOS) is. Öt új, erőteljes fizikai szerver és egy tucat virtuális szerver került bevezetésre. A fejlesztőknek és tesztelőknek egyre nehezebb volt a feladataikat egy munkához közelítő környezetben tesztelni. Ekkor vált világossá, hogy nélküle nem lehet boldogulni…
II. rész

Tehát, a klaszterünk tényleg lenyűgöző látványt nyújt, egy pár tucat különálló komponens rendszere, amelyek nincsenek leírva Dockerfile-okban. A telepítéshez a megfelelő környezetbe csak általánosan lehet konfigurálni. A feladatunk az, hogy a klasztert staging környezetbe telepítsük, hogy tesztelhessük a megjelenés előtti tesztelés előtt.
Elméletileg több párhuzamosan működő klaszter is lehet: annyi, ahány feladat van befejezett állapotban vagy közel a befejezéshez. A rendelkezésünkre álló szerverek teljesítménye lehetővé teszi, hogy minden szerveren több klasztert indítsunk. Minden staging klaszternek izoláltnak kell lennie (nem lehet átfedés portokon, könyvtárakon stb.).
A legértékesebb erőforrásunk az időnk, és abból nem volt sok.
A gyorsabb indítás érdekében a Docker Swarm-ot választottuk, mivel egyszerűsége és architektúrájának rugalmassága miatt. Az első, amit tettünk, az volt, hogy létrehoztunk egy menedzsert és néhány csomópontot a távoli szervereken:
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
kilqc94pi2upzvabttikrfr5d nop-test-1 Kész Aktív 19.03.2
jilwe56pl2zvabupryuosdj78 nop-test-2 Kész Aktív 19.03.2
j5a4yz1kr2xke6b1ohoqlnbq5 * nop-test-3 Kész Aktív Vezető 19.03.2
Ezután létrehoztuk a hálózatot:
$ docker network create --driver overlay --subnet 10.10.10.0/24 nw_swarm
Ezután összekapcsoltuk a Gitlab-CI-t és a Swarm node-okat a távoli node-ok CI-ból való irányítása érdekében: tanúsítványok telepítése, titkos változók beállítása, valamint a Docker szolgáltatás konfigurálása a vezérlő szerveren. Ez sokat spórolt nekünk az időnkből.
Ezután hozzátettük a stack létrehozására és megsemmisítésére vonatkozó jobokat a .gitlab-ci.yml fájlhoz.
A .gitlab-ci.yml fájlhoz még néhány job lett hozzáadva
## 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
A fenti kódrészletből látható, hogy a Pipelines-hoz két gomb (deploy_staging, stop_staging) került, amelyek kézi beavatkozást igényelnek.

A stack neve megfelel az ág nevének, és ennek az egyediségnek elegendőnek kell lennie. A stackben lévő szolgáltatások egyedi IP-címeket kapnak, míg a portok, könyvtárak stb. izoláltak, de azonosak maradnak az egyes stackek között (mivel a konfigurációs fájl azonos minden stack esetén) — ezt akartuk elérni. A stack-et (klasztert) a következőkkel kezeljük: docker-compose.yml, amely leírja klaszterünket.
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
Itt látható, hogy a komponensek egy hálózatba (nw_swarm) vannak összekapcsolva, és elérhetők egymás számára.
A rendszerkomponensek (redis, mysql alapú) elkülönülnek az általános egyedi komponens-olvasztótól (továbbiakban az egyedi komponenseket is szolgáltatásként osztjuk meg). A klaszterünk telepítési fázisa úgy tűnik, hogy CMD átadása a nagy konfigurált image-ünknek, és egyáltalán nem különbözik a I. Részben leírt telepítéstől. Kiemelném a különbségeket:
- git clone … — beszerzzük a telepítéshez szükséges fájlokat (createconfig.py, install_venv.sh stb.)
- curl… && unzip … — letölthetjük és kitömöríthetjük a build artefaktumokat (kompilált segédeszközök)
Csak egyetlen, még nem dokumentált probléma maradt: az olyan komponensek, amelyek webes interfésszel rendelkeznek, nem érhetők el a fejlesztők böngészőiből. Ezt a problémát reverse proxy segítségével oldjuk meg így:
A .gitlab-ci.yml fájlban a klaszter stack telepítése után hozzáadjuk a terheléselosztó telepítési sorát (amely a commitok során csak a konfigurációját frissíti, új nginx konfigurációs fájlokat hoz létre a következő sablon szerint: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME}.conf) — lásd a docker-compose-nginx.yml kódot
- 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
A fejlesztők számítógépein frissítjük az /etc/hosts fájlt; beírjuk az nginx-hez vezető URL-t:
10.50.173.106 staging_BRANCH-1831_cluster.dev
Tehát az elszigetelt staging klaszterek telepítése megvalósult, és a fejlesztők most már bármilyen megfelelő mennyiségben elindíthatják őket a feladataik ellenőrzésére.
További tervek:
- A komponenseinket szolgáltatásokra bontani
- Mindegyik Dockerfile számára létrehozni
- Automatikusan meghatározni a terhelésmentesebb node-okat a stackben
- A node-okat név sablon szerint megadni (nem pedig az azonosítókat használni, mint a cikkben)
- Ellenőrzést hozzáadni, hogy a stack megsemmisült-e
- …
Külön köszönet a .
Forrás: habr.com
