Sistemi online i rekomandimeve pĂ«r pĂ«rmbajtjen video, me tĂ« cilin po punojmĂ«, Ă«shtĂ« njĂ« zhvillim komercial i mbyllur dhe nĂ« mĂ«nyrĂ« teknike paraqet njĂ« klaster shumĂ«komponentĂ«sh nga komponentĂ« tĂ« vetĂ«caktuar dhe open source. QĂ«llimi i shkrimit tĂ« kĂ«tij artikulli Ă«shtĂ« pĂ«rshkrimi i zbatimit tĂ« sistemit tĂ« klasterizimit docker swarm pĂ«r njĂ« ambient staging, pa e dĂ«mtuar workflow-n tonĂ« nĂ« kushte kohore tĂ« kufizuara. Narrativa qĂ« ju prezantohet Ă«shtĂ« e ndarĂ« nĂ« dy pjesĂ«. Pjesa e parĂ« pĂ«rshkruan CI/CD para pĂ«rdorimit tĂ« docker swarm, ndĂ«rsa e dyta â procesin e implementimit tĂ« tij. Ata qĂ« nuk janĂ« tĂ« interesuar tĂ« lexojnĂ« pjesĂ«n e parĂ«, mund tĂ« kalojnĂ« me besim nĂ« tĂ« dytĂ«n.
Pjesa I
Në një vit të largët, kërkohej të vendosej sa më shpejt procesi CI/CD. Një nga kushtet ishte që të mos përdorej Docker për deploy të komponenteve që po zhvilloheshin për disa arsye:
- për një funksionim më të besueshëm dhe stabil të komponentëve në Production (pra në thelb një kërkesë për të mos përdorur virtualizimin)
- programuesit kryesorë nuk donin të punonin me Docker (çuditërisht, por ishte pikërisht kështu)
- për arsye ideologjike të drejtuesve të R&D
Infrastruktura, steku dhe kërkesat përfundimtare për MVP paraqiteshin si më poshtë:
- 4 serverë IntelŸ X5650 me Debian (një makinë më e fuqishme plotësisht për zhvillim)
- Zhvillimi i komponentëve të personalizuar bëhet në C++, Python3
- Mjetet kryesore të 3rdparty që përdoren: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, ...
- Pipeline për ndërtimin dhe testimin e komponentëve veçmas për debug dhe release
Një nga pyetjet e para që duhet të zgjidhet në fazën fillestare është se si do të bëhet zhvillimi i komponentëve të personalizuar në ndonjë ambient (CI/CD).
Komponentët e jashtëm u vendos të instalohen dhe azhurnohen sistematikisht. Aplikacionet e personalizuara, të zhvilluara në C++ ose Python, mund të implementohen në disa mënyra. Mes tyre, për shembull: krijimi i paketave sistemore, dërgimi i tyre në një repository të imazheve të ndërtuara dhe instalimi i tyre në servera. Për një arsye tashmë të panjohur, u zgjodh një mënyrë tjetër, konkretisht: me ndihmën e CI, kompilohen skedarët ekzekutivë të aplikacioneve, krijohet një ambient virtual të projektit, instalohen modulët py nga requirements.txt dhe të gjithë këto artefakte dërgohen së bashku me konfigurimet, skriptet dhe ambientin e shoqërueshëm të aplikacioneve në servera. Më pas, aplikacionet fillojnë nga një përdorues virtual pa të drejta administratori.
Si sistem CI/CD u zgjodh Gitlab-CI. Pipeline i arritur dukej përafërsisht kështu:

Struktura e gitlab-ci.yml dukej si më poshtë
---
variables:
# versioni minimal i CPU në serverat ku implementohet klasteri
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
ĂshtĂ« e rĂ«ndĂ«sishme tĂ« theksohet se ndĂ«rtimi dhe testimi bĂ«het nĂ« imazhin e tij, ku janĂ« instaluar tĂ« gjitha paketat sistemore tĂ« nevojshme dhe janĂ« kryer konfigurime tĂ« tjera.
Megjithëse secili nga këto skripte në punët është interesant në mënyrën e vet, nuk do të flas për to; përshkrimi i secilit do të zinte një kohë të konsiderueshme dhe kjo nuk është qëllimi i artikullit. Do të vë në dukje vetëm se faza e depoy-t përbëhet nga një renditje e thirrjeve të skripteve:
- createconfig.py â krijon skedarin settings.ini me konfigurimet e komponenteve nĂ« ambjente tĂ« ndryshme pĂ«r depoy-imin e mĂ«vonshĂ«m (Preproduction, Production, Testing, ...)
- install_venv.sh â krijon njĂ« ambjent virtual pĂ«r komponentet py nĂ« njĂ« drejtor tĂ« caktuar dhe e kopjon atĂ« nĂ« serverĂ«t e largĂ«t
- prepare_init.d.py â pĂ«rgatit skriptet pĂ«r nisje-ndalje tĂ« komponenteve bazuar nĂ« njĂ« model
- deploy.py â vendos dhe rilanson komponentet e reja
Kaloi koha. Faza e staging u zëvendësua nga preproduction dhe production. U shtua mbështetje për produktin në një distribucion tjetër (CentOS). U shtuan edhe 5 servera fizikë të fuqishëm dhe një dhjetë virtualë. Dhe zhvilluesve dhe testuesve po u bëhej gjithnjë e më e vështirë të testonin detyrat e tyre në një ambient që ishte më afër gjendjes operuese. Në atë kohë u bë e qartë se nuk mund të shpëtohej pa të...
Pjesa II

Pra, klusteri ynë është një sistem mjaft interesante me disa dhjetra komponente të veçanta, të papërshkruara nga Dockerfile. Të konfigurosh atë për depoy në një ambjent të caktuar mund të bëhet vetëm në përmbledhje. Detyra jonë është të depoy-ojmë klusterin në ambientin staging për të testuar atë para testimit para-lëshimit.
Teoretikisht, ka mundĂ«si tĂ« kenĂ« disa klustera qĂ« punojnĂ« njĂ«kohĂ«sisht: aq sa janĂ« detyrat nĂ« gjendje tĂ« pĂ«rfunduar ose tĂ« afĂ«rta me pĂ«rfundimin. Kapacitetet qĂ« kemi nĂ« dispozicionin e serverĂ«ve tanĂ« lejojnĂ« tĂ« startohet disa klustera nĂ« çdo server. Ădo kluster staging duhet tĂ« jetĂ« i izoluar (nuk duhet tĂ« ketĂ« ndĂ«rtime nĂ« porte, drejtorira etj.).
Burimi më i çmuar është koha jonë, dhe ajo ishte e kufizuar.
Për të filluar më shpejt, zgjodhëm Docker Swarm për shkak të thjeshtësisë dhe fleksibilitetit të arkitekturës. E para që bëmë ishte krijimi i një menaxheri dhe disa nodesh në serverat e largët:
$ 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
MĂ« pas, krijuam rrjetin:
$ docker network create --driver overlay --subnet 10.10.10.0/24 nw_swarm
Më pas, lidhëm Gitlab-CI dhe nodet Swarm për menaxhimin e largët të nodave nga CI: instalimi i certifikatave, konfigurimi i variablave të fshehta dhe konfigurimi i shërbimit Docker në serverin menaxhues. Ky na kurseu shumë kohë.
Më pas, shtuam job-at për krijimin dhe shkatërrimin e stekëve në .gitlab-ci.yml.
Në .gitlab-ci.yml janë shtuar edhe disa job
## 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
Nga fragmenti më sipër i kodit, shihet se në Pipelines janë shtuar dy butona (deploy_staging, stop_staging), që kërkojnë ndërhyrje manuale.

Emri i stek-ut pĂ«rkon me emrin e degĂ«s dhe kjo unikĂ«si duhet tĂ« jetĂ« e mjaftueshme. ShĂ«rbimet nĂ« stek marrin adresa unike IP, dhe portet, direktoret etj., do tĂ« jenĂ« tĂ« izoluara, por tĂ« njĂ«jta nga steku nĂ« stek (pasi skedari i konfigurimit Ă«shtĂ« identik pĂ«r tĂ« gjitha stekĂ«t) â kjo Ă«shtĂ« pikĂ«risht ajo qĂ« ne kĂ«rkonim. Ne e zhvillojmĂ« stekun (klusterin) me ndihmĂ«n e docker-compose.yml, nĂ« tĂ« cilin pĂ«rshkruhet klusteri ynĂ«.
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
Këtu shihet se komponentët janë të bashkuar në një rrjet të vetëm (nw_swarm) dhe janë të arritshëm nga njëri-tjetri.
Komponentët sistemorë (të bazuar në redis, mysql) janë të ndarë nga grupi i përgjithshëm i komponentëve personalë (në planet, po ashtu janë planifikuar të ndahen si shërbime). Faza e dërgimit të klasterit tonë duket si transferimi i CMD në imazhin tonë të madh të konfiguruar dhe në përgjithësi nuk ndryshon pothuajse nga dërgimi i përshkruar në Pjesën I. theksoj dallimet:
- git clone ⊠â merrni skedarĂ«t e nevojshĂ«m pĂ«r tĂ« kryer dĂ«rgimin (createconfig.py, install_venv.sh etj.)
- curl⊠&& unzip ⊠â shkarkoni dhe shkarkoni artifaktet e ndĂ«rtimit (njĂ«sitĂ« e kompilura)
Tani gjendjet vetëm një problem i pa përshkruar deri tani: komponentët me ndërfaqe web nuk janë të arritshëm nga shfletuesit e zhvilluesve. Ne po e zgjidhim këtë problem me anë të reverse proxy, në këtë mënyrë:
NĂ« .gitlab-ci.yml pas deploy-it tĂ« stack-ut tĂ« klasterit, shtojmĂ« linjĂ«n e deploy-it tĂ« balancuesit (i cili nĂ« commit-e, thjesht pĂ«rditĂ«son konfiguarcioni i tij (krijon skedarĂ« konfiguacioni tĂ« rinj nginx sipas modelit: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME}.conf) â shih kodin 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
Në kompjuterët e zhvilluesve përditësojmë /etc/hosts; shkruajmë url-në deri te nginx:
10.50.173.106 staging_BRANCH-1831_cluster.dev
Pra, deploy-i i klasterëve staging të izoluara është realizuar dhe zhvilluesit tani mund t'i operojnë ata në çdo numër të mjaftueshëm për të verifikuar detyrat e tyre.
Planet e mëtejshme:
- Të ndahen komponentët tanë si shërbime
- Të krijohet një Dockerfile për secilin
- Të zbulohen automatikisht nodet më pak të ngarkuara në stack
- Të caktohen nodet sipas modelit të emrit (dhe jo të përdoren id si në artikull)
- Të shtohet kontrolli që stack-u të ndodhet shkatërruar
- âŠ
Një falënderim i veçantë për .
Burimi: habr.com
