Det online rekommendationssystemet för videoinnehÄll som vi arbetar med Àr en sluten kommersiell utveckling och representerar tekniskt sett en flerkomponentskluster av eget och öppen kÀllkods komponenter. Syftet med denna artikel Àr att beskriva implementeringen av ett docker swarm kluster under staging-miljön, utan att störa den befintliga arbetsflödet i vÄra processer under begrÀnsad tid. Den redovisning som presenteras för er Àr indelad i tvÄ delar. Den första delen beskriver CI/CD innan anvÀndningen av docker swarm, och den andra - processen för dess implementering. Den som inte Àr intresserad av att lÀsa den första delen kan gÀrna gÄ direkt till den andra.
Del I
I ett avlÀgset Är krÀvdes det att snabbt stÀlla in CI/CD-processen. Ett av villkoren var att inte anvÀnda Docker för distribution av de utvecklade komponenterna av flera skÀl:
- för att komponenterna skulle fungera mer pÄlitligt och stabilt i produktion (det vill sÀga ett krav pÄ att inte anvÀnda virtualisering)
- ledande utvecklare ville inte arbeta med Docker (konstigt nog, men sÄ var det)
- pÄ grund av ledningens ideologiska övertygelser i R&D
Infrastrukturen, stacken och ungefÀrliga krav för MVP sÄg ut som följande:
- 4 servrar IntelŸ X5650 med Debian (en kraftfullare maskin uteslutande för utveckling)
- Utvecklingen av egna anpassade komponenter pÄgÄr i C++, Python3
- De huvudsakliga tredjepartsverktygen som anvĂ€nds: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, âŠ
- Bygg- och testpipelines för komponenter separat för debug och release
En av de första frÄgorna som mÄste lösas i det inledande skedet Àr hur distributionen av de anpassade komponenterna kommer att göras i nÄgon miljö (CI/CD).
Tredjepartskomponenter vÀljer att installera och uppdatera systematiskt. Anpassade applikationer, som utvecklas i C++ eller Python, kan distribueras pÄ flera sÀtt. Bland dem, till exempel, skapande av systempaket, skickande av dessa till ett repository med byggda bilder och deras efterföljande installation pÄ servrar. Av okÀnda anledningar valdes en annan metod, nÀmligen: med hjÀlp av CI kompileras körbara filer för applikationerna, en virtuell projektmiljö skapas, py-moduler installeras frÄn requirements.txt och alla dessa artefakter skickas tillsammans med konfigureringsfiler, skript och relaterad miljö för applikationer till servrarna. DÀrefter startas applikationerna av en virtuell anvÀndare utan administratörsrÀttigheter.
Gitlab-CI valdes som CI/CD-system. Den resulterande pipeline sÄg ungefÀr ut sÄ hÀr:

Strukturen för gitlab-ci.yml sÄg ut pÄ följande sÀtt
---
variables:
# minimi CPU-version pÄ servrarna dÀr klustret distribueras
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
Det Àr vÀrt att notera att byggandet och testningen utförs pÄ sin egen bild, dÀr alla nödvÀndiga systempaket redan Àr installerade och andra instÀllningar Àr genomförda.
Ăven om varje av dessa skript i jobben Ă€r intressanta pĂ„ sitt sĂ€tt, kommer jag sjĂ€lvklart inte att berĂ€tta om dem, eftersom en beskrivning av varje skulle ta betydande tid och det inte Ă€r syftet med artikeln. Jag vill bara pĂ„peka att deploy-stadiet bestĂ„r av en sekvens av anrop av skript:
- createconfig.py â skapar en fil settings.ini med komponentinstĂ€llningar i olika miljöer för efterföljande deployment (Preproduction, Production, Testing, âŠ)
- install_venv.sh â skapar en virtuell miljö för py-komponenter i en viss katalog och kopierar den till fjĂ€rrservrar
- prepare_init.d.py â förbereder start- och stopp-skript för komponenter baserade pĂ„ en mall
- deploy.py â distribuerar och startar om nya komponenter
Tiden gick. Staging-fasen ersattes av preproduction och production. Produktsupport lades till för ytterligare en distribution (CentOS). Fem kraftfulla fysiska servrar och ett dussin virtuella tillkom. Och för utvecklare och testare blev det allt svĂ„rare att testa sina uppgifter i en miljö som nĂ€rmade sig ett produktionslĂ€ge. Vid denna tidpunkt blev det uppenbart att vi inte kunde klara oss utan detâŠ
Del II

SÄ vÄrt kluster utgör verkligen ett synnerligen intressant system med ett par dussin separata komponenter, som inte Àr beskrivna av Dockerfile:er. Det Àr endast möjligt att konfigurera det för deployment i en viss miljö som helhet. VÄrt mÄl Àr att deploya klustret i staging-miljön för att testa det innan pre-release-testning.
Teoretiskt kan det finnas flera samtidiga kluster: sÄ mÄnga som det finns uppgifter i fÀrdigt tillstÄnd eller nÀra fÀrdigstÀllande. De resurser som finns tillgÀngliga pÄ vÄra servrar tillÄter oss att köra flera kluster pÄ varje server. Varje staging-kluster mÄste vara isolerat (det fÄr inte förekomma överlappning av portar, kataloger och sÄdant).
Den mest vÀrdefulla resursen Àr vÄr tid, och vi hade inte mycket av den.
För en snabbare start valde vi Docker Swarm pÄ grund av dess enkelhet och flexibilitet i arkitekturen. Det första vi gjorde var att skapa en manager och flera noder pÄ fjÀrrservrar:
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
kilqc94pi2upzvabttikrfr5d nop-test-1 Klar Aktiv 19.03.2
jilwe56pl2zvabupryuosdj78 nop-test-2 Klar Aktiv 19.03.2
j5a4yz1kr2xke6b1ohoqlnbq5 * nop-test-3 Klar Aktiv Ledare 19.03.2
Vidare, skapade vi ett nÀtverk:
$ docker network create --driver overlay --subnet 10.10.10.0/24 nw_swarm
Vidare kopplade vi Gitlab-CI och Swarm-noderna för fjÀrrhantering av noder frÄn CI: installation av certifikat, instÀllning av hemliga variabler, samt konfiguration av Docker-tjÀnsten pÄ styrservern. HÀr Àr den sparade oss mycket tid.
Vidare lade vi till jobb för skapande och radering av stackar i .gitlab-ci.yml.
Det lades till nÄgra fler jobb i .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
Av ovanstÄende kodsnutt kan vi se att det tillades tvÄ knappar (deploy_staging, stop_staging) i Pipelines, som krÀver manuell ÄtgÀrd.

Namn pĂ„ stacken motsvarar grenenamn och denna unikhet bör vara tillrĂ€cklig. TjĂ€nster i stacken fĂ„r unika ip-adresser, och portar, kataloger osv. kommer att vara isolerade men konsekventa frĂ„n stack till stack (eftersom konfigurationsfilen Ă€r densamma för alla stackar) â vilket var vad vi efterstrĂ€vade. Stacken (klustret) lanserar vi med hjĂ€lp av docker-compose.yml, som beskriver vĂ„rt kluster.
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
Det syns att komponenterna Àr sammankopplade via ett nÀtverk (nw_swarm) och Àr tillgÀngliga för varandra.
Systemkomponenter (baserade pÄ redis, mysql) Àr separerade frÄn den allmÀnna poolen av anpassade komponenter (i planerna ska Àven de anpassade delas upp som tjÀnster). Deploy-stadiet för vÄr kluster ser ut som en överföring av CMD till vÄr stora, konfigurerade bild och skiljer sig i stort sett inte frÄn den deployment som beskrivs i del I. Jag vill understryka skillnaderna:
- git clone ⊠â vi fĂ„r filer som behövs för att genomföra deploy (createconfig.py, install_venv.sh etc.)
- curl⊠&& unzip ⊠â vi laddar ner och packar upp byggartefakter (kompilerade verktyg)
Det finns fortfarande ett problem som inte har beskrivits: komponenterna med webbgrÀnssnitt Àr inte tillgÀngliga frÄn utvecklarens webblÀsare. Vi löser detta problem med hjÀlp av en reverse proxy pÄ följande sÀtt:
I .gitlab-ci.yml, efter deployment av klusterstacken, lĂ€gger vi till en rad för deployment av lastbalanseraren (som vid commits endast uppdaterar sin konfiguration (skapar nya konfigurationsfiler för nginx enligt mallen: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME}.conf) â se kod 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
PĂ„ utvecklarnas datorer uppdaterar vi /etc/hosts; vi skriver in URL:en till nginx:
10.50.173.106 staging_BRANCH-1831_cluster.dev
SÄ, deployment av de isolerade staging-klustren har genomförts och utvecklarna kan nu köra dem i vilket antal som helst som krÀvs för att kontrollera sina uppgifter.
Framtida planer:
- Dela upp vÄra komponenter som tjÀnster
- Skapa en separat Dockerfile för varje komponent
- Automatiskt identifiera mindre belastade noder i stacken
- Angiv noder enligt namnmall (och anvÀnd inte id som i artikeln)
- LÀgg till kontroll för att sÀkerstÀlla att stacken har raderats
- âŠ
Ett sÀrskilt tack för .
KĂ€lla: habr.com
