Sistemi rekomandues online pĂ«r pĂ«rmbajtjen e videos, mbi tĂ« cilin po punojmĂ«, Ă«shtĂ« njĂ« zhvillim komercial i mbyllur dhe pĂ«rbĂ«n teknikisht njĂ« grumbull multikomponentesh nga komponentĂ«t tonĂ« dhe ato open source. QĂ«llimi i kĂ«tij artikulli Ă«shtĂ« tĂ« pĂ«rshkruajĂ« zbatimin e sistemit tĂ« grumbullimit docker swarm pĂ«r njĂ« platformĂ« staging, pa e prishur rrjedhĂ«n e procesit tonĂ« nĂ« kushte kohe tĂ« kufizuar. Narrativa e paraqitur pĂ«r ju Ă«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 lehtĂ«si nĂ« tĂ« dytĂ«n.
Pjesa I
Në vitin shumë të largët, nevojitej të konfigurohej sa më shpejt procesi CI/CD. Një nga kushtet ishte që të mos përdorej Docker për deployment komponentëve të zhvilluar për disa arsye:
- për një funksionim më të besueshëm dhe të qëndrueshëm të komponentëve në Production (dmth, në thelb kërkesa për të mos përdorur virtualizimin)
- udhëheqësit e zhvilluesve nuk donin të punonin me Docker (çuditërisht, por ishte ashtu)
- për arsye ideologjike të drejtuesve të R&D
Infrastruktura, staku dhe kërkesat e para për MVP u paraqitën si të tillë:
- 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 tĂ« pĂ«rdorura: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, âŠ
- Pipeline të ndara për ndërtimin dhe testimin e komponentëve 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 vendosja e komponentëve të personalizuar në ndonjë ambient (CI/CD).
Komponentët e jashtëm u vendosën të instalohen dhe përditësohen në mënyrë sistematike. Aplikacionet e personalizuara, të zhvilluara në C++ ose Python, mund të implementohen në disa mënyra. Ndër to, për shembull: krijimi i paketave sistematike, dërgimi i tyre në një depo të imazheve të ndërtuara dhe instalimi i tyre të mëvonshëm në servera. Për një arsye të panjohur, u zgjodh një mënyrë tjetër, konkretisht: me ndihmën e CI, compile-ohen skedarët ekzekutivë të aplikacioneve, krijohet një ambient virtual për projektin, instalohet modulet py nga requirements.txt dhe të gjithë këto artefakte dërgohen së bashku me konfigurimet, skriptet dhe ambientin përkatës të aplikacioneve në servera. Më pas, aplikacionet fillojnë të punojnë nga një përdorues virtual pa të drejta administratori.
Si sistem CI/CD u zgjodh Gitlab-CI. Pipeline i krijuar dukej kështu:

Strukturore, gitlab-ci.yml kishte pamjen e mëposhtme
---
variables:
# versioni minimale të CPU-së në serverët ku zhvillohet klasteri
CMAKE_CPUTYPE: "westmere"
DEBIAN: "MYREGISTRY:5000/debian:latest"
before_script:
- eval $(ssh-agent -s)
- ssh-add ~/.ssh/config
stages:
- ndërtim
- testim
- shpërndarje
debug.debian:
stage: ndërtim
image: $DEBIAN
script:
- cd builds/release && ./build.sh
paths:
- bin/
- builds/release/bin/
when: always
release.debian:
stage: ndërtim
image: $DEBIAN
script:
- cd builds/release && ./build.sh
paths:
- bin/
- builds/release/bin/
when: always
## faza e testimit
tests.codestyle:
stage: testim
image: $DEBIAN
dependencies:
- release.debian
script:
- /bin/bash run_tests.sh -t codestyle -b "${CI_COMMIT_REF_NAME}_codestyle"
tests.debug.debian:
stage: testim
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: testim
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
## faza e angazhimit
deploy_staging:
stage: shpërndarje
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
Vlen të theksohet se ndërtimi dhe testimi realizohet në imazhin e tij, ku janë instaluar tashmë të gjitha paketat sistemore të nevojshme dhe janë përfunduar konfigurimet e tjera.
Megjithëse çdo njëri nga këto skripte në punët është interesant në mënyrën e vet, unë nuk do të flas për ta, sepse përshkrimi i secilit prej tyre do të zinte një kohë të konsiderueshme dhe ajo nuk është qëllimi i këtij artikulli. Do të theksoj vetëm se faza e deploimit përbëhet nga një renditje e thirrjeve të skripteve:
- createconfig.py â krijon skedarin settings.ini me konfigurimet e komponenteve nĂ« mjedise tĂ« ndryshme pĂ«r deploimin e mĂ«vonshĂ«m (Preproduction, Production, Testing, âŠ)
- install_venv.sh â krijon njĂ« mjedis virtual pĂ«r komponentĂ«t py nĂ« njĂ« direktorium tĂ« caktuar dhe e kopjon atĂ« nĂ« serverat e largĂ«t
- prepare_init.d.py â pĂ«rgatit skriptet e fillimit ndalimit tĂ« komponenteve mbi bazĂ«n e njĂ« shablloni
- deploy.py â shpĂ«rndan dhe ristarton komponentĂ«t e rinj
Kalimi kalon. Faza staging u zëvendësua me preproduction dhe production. U shtua mbështetje për produktin në një shpërndarje tjetër (CentOS). U shtuan edhe 5 serverë fizikë të fuqishëm dhe një duzinë virtualë. Dhe për zhvilluesit dhe testuesit bëhej gjithnjë e më e vështirë të praktikoheshin detyrat e tyre në një mjedis më afër gjendjes së punës. Në këtë kohë u bë e qartë se ishte e pamundur të largohem pa të...
Pjesa II

Pra, klasteri ynë përbën një sistem spektakolar prej disa dhjetëra komponenesh, të pa përshkruara në Dockerfile. Konfigurimi i tij për deploy në një mjedis të caktuar mund të bëhet vetëm në përgjithësi. Detyra jonë është të deploy-nim klasterin në mjedisin staging për testim para lëshimit.
Teorikisht, mund tĂ« ketĂ« disa klasterĂ« qĂ« punojnĂ« njĂ«herĂ«sh: aq sa ka detyra nĂ« gjendje tĂ« pĂ«rfunduar ose tĂ« afĂ«rt me pĂ«rfundimin. Kapacitetet e serverĂ«ve qĂ« kemi nĂ« dispozicion lejojnĂ« tĂ« nisim disa klasterĂ« nĂ« çdo server. Ădo klaster staging duhet tĂ« jetĂ« i izoluar (nuk duhet tĂ« ketĂ« pĂ«rputhje nĂ« porta, direktori etj.).
Burimi më i çmuar është koha jonë dhe e kemi pasur pak.
Për një fillim më të shpejtë zgjodhëm Docker Swarm për shkak të thjeshtësisë dhe fleksibilitetit të arkitekturës së tij. E para që bëmë ishte krijimi i menaxherit dhe disa nodash në serverët e largët:
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
kilqc94pi2upzvabttikrfr5d nop-test-1 Gatshëm Aktiv 19.03.2
jilwe56pl2zvabupryuosdj78 nop-test-2 Gatshëm Aktiv 19.03.2
j5a4yz1kr2xke6b1ohoqlnbq5 * nop-test-3 Gatshëm Aktiv Lider 19.03.2
Më pas, krijuam një rrjet:
$ docker network create --driver overlay --subnet 10.10.10.0/24 nw_swarm
Më pas, lidhëm Gitlab-CI dhe nodat Swarm për menaxhimin e largët të nodave nga CI: instalimi i certifikatat, konfigurimi i variablave të fshehta, si dhe konfigurimi i shërbimit Docker në serverin menaxher. Kjo na kursente shumë kohë.
Më pas, shtuam job'ët për krijimin dhe shkatërrimin e stack-ut në .gitlab-ci.yml.
NĂ« .gitlab-ci.yml u shtuan 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 i mësipërm të kodit, shihet se në Pipelines janë shtuar dy butona (deploy_staging, stop_staging), që kërkojnë ndërhyrje manuale.

Emri i stekĂ«s i korrespondon emrit tĂ« degĂ«s dhe kjo unikati duhet tĂ« jetĂ« e mjaftueshme. ShĂ«rbimet nĂ« stekĂ« marrin adresa IP unike, ndĂ«rsa portet, direktorĂ«t etj. do tĂ« jenĂ« tĂ« izoluar, por tĂ« njĂ«jta nga steka nĂ« stek (pasi skedari i konfigurimit Ă«shtĂ« i njĂ«jtĂ« pĂ«r tĂ« gjitha stekĂ«t) â ajo qĂ« kemi kĂ«rkuar. Ne zhvillojmĂ« stek (klasterin) me ndihmĂ«n e docker-compose.yml, ku Ă«shtĂ« pĂ«rshkruar klasteri 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ë bashkuar në një rrjet të vetëm (nw_swarm) dhe janë të aksesueshëm për njëri-tjetrin.
Komponentët sistemikë (të bazuar në redis, mysql) janë të ndarë nga kurore e përbashkët e komponentëve të personalizuar (planifikojmë të ndajmë dhe komponentët e personalizuar si shërbime). Faza e implementimit të klasterit tonë duket si transmetimi i CMD në imazhin tonë të madh të konfiguruar dhe praktikisht nuk dallon nga implementimi përshkruar në Pjesën I. Do të theksoj dallimet:
- git clone ⊠â marrim skedarĂ«t e nevojshĂ«m pĂ«r tĂ« prodhuar implementimin (createconfig.py, install_venv.sh etj.)
- curl⊠&& unzip ⊠â shkarkojmĂ« dhe nxjerrim artefaktet e ndĂ«rtimit (utilitete tĂ« kompiluar)
Ka mbetur vetëm një problem tjetër që nuk është përshkruar akoma: komponentët që kanë ndërfaqe web nuk janë të aksesueshë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 implementimit tĂ« grumbullit tĂ« klasterit, shtojmĂ« rreshtin e implementimit tĂ« balancuesit (i cili pas komitimi, pĂ«rditĂ«son vetĂ«m konfigurimin e tij (krijon skedarĂ« tĂ« rinj konfigurimi 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, shpërndarja e klastereve të izoluara staging është implementuar dhe zhvilluesit tani mund t'i provojnë ato në një numër të mjaftueshëm për të verifikuar detyrat e tyre.
Planet e mëtejshme:
- Të ndajmë komponentët tanë si shërbime
- Të krijojmë nga një Dockerfile për secilën
- Të identifikojmë automatikisht nodet me ngarkesë më të ulët në njësinë e sistemit
- Cakto nodet sipas emrit (nuk përdor id si në artikull)
- Shto kontroll që steku është shkatërruar
- âŠ
Faleminderit të veçantë për .
Burimi: habr.com
