De online aanbevelingssysteem voor videocontent waaraan we werken, is een gesloten commerciële ontwikkeling en technisch gezien een multi-componentencluster bestaande uit eigen en open source componenten. Het doel van dit artikel is het beschrijven van de implementatie van het Docker Swarm-cluster voor de staging-omgeving, zonder de bestaande workflow van onze processen te verstoren binnen de tijdsbeperkingen. Het verhaal dat aan u wordt gepresenteerd, is verdeeld in twee delen. Het eerste deel beschrijft de CI/CD vóór het gebruik van Docker Swarm, en het tweede deel beschrijft het implementatieproces. Wie geen interesse heeft in het lezen van het eerste deel, kan gerust doorgaan naar het tweede.
Deel I
In een verre, verre tijd was er behoefte om het CI/CD-proces zo snel mogelijk in te stellen. Een van de voorwaarden was om Docker niet te gebruiken voor de deployment van de ontwikkelde componenten om verschillende redenen:
- voor een betrouwbaardere en stabielere werking van componenten in productie (met andere woorden, de eis om virtualisatie niet te gebruiken)
- de belangrijkste ontwikkelaars wilden niet met Docker werken (vreemd, maar dat was precies hoe het was)
- om ideologische redenen van het management van R&D
De infrastructuur, techstack en de algemene initiƫle vereisten voor de MVP waren als volgt:
- 4 servers Intel® X5650 met Debian (één krachtiger machine volledig voor ontwikkeling)
- De ontwikkeling van eigen aangepaste componenten vindt plaats in C++, Python3
- De belangrijkste gebruikte 3rd-party tools: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, ā¦
- Pipelines voor het bouwen en testen van componenten zijn apart voor debug en release
Een van de eerste vragen die op de beginfase moeten worden beantwoord, is op welke manier de implementatie van aangepaste componenten in een bepaalde omgeving (CI/CD) zal plaatsvinden.
Der centrale componenten worden systemisch geĆÆnstalleerd en systemisch bijgewerkt. Aangepaste applicaties, ontwikkeld in C++ of Python, kunnen op verschillende manieren worden uitgerold. Onder andere door: systeem pakketten te maken, deze naar een repository van samengestelde afbeeldingen te sturen en ze vervolgens op servers te installeren. Om onbekende redenen is een andere methode gekozen, namelijk: met behulp van CI worden uitvoerbare bestanden van applicaties gecompileerd, wordt er een virtuele omgeving voor het project aangemaakt, worden py-modules uit requirements.txt geĆÆnstalleerd en worden al deze artefacten samen met configuraties, scripts en bijbehorende omgevingen van applicaties naar de servers gestuurd. Daarna worden de applicaties gestart vanaf een virtuele gebruiker zonder administratorrechten.
Gitlab-CI is gekozen als CI/CD-systeem. De resulterende pipeline zag er ongeveer zo uit:

De structuur van gitlab-ci.yml zag er als volgt uit
---
variables:
# minimale CPU-versie op de servers waar het cluster wordt uitgerold
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
Het is vermeldenswaardig dat de bouw en het testen plaatsvinden op hun eigen afbeelding, waarin al de nodige systeempakketten zijn geĆÆnstalleerd en andere instellingen zijn uitgevoerd.
Hoewel elk van deze scripts in jobs op zijn eigen manier interessant is, zal ik ze natuurlijk niet apart beschrijven; een uitgebreide uitleg van elk zou veel tijd vergen en dat is niet het doel van dit artikel. Ik wil alleen opmerken dat de deploy-fase bestaat uit een reeks scriptaanroepen:
- createconfig.py ā creĆ«ert het settings.ini-bestand met configuraties van componenten in verschillende omgevingen voor de daaropvolgende deploy (Preproductie, Productie, Testen, ā¦)
- install_venv.sh ā creĆ«ert een virtuele omgeving voor py-componenten in een bepaalde map en kopieert deze naar de externe servers
- prepare_init.d.py ā bereidt start-stop scripts van componenten voor op basis van een sjabloon
- deploy.py ā verspreidt en herstart nieuwe componenten
De tijd verstreek. De staging-fase werd vervangen door preproductie en productie. Ondersteuning voor het product op een andere distributie (CentOS) werd toegevoegd. Vijf krachtige fysieke servers en een dozijn virtuele servers werden toegevoegd. En het werd steeds moeilijker voor ontwikkelaars en testers om hun taken te testen in een omgeving die meer of minder leek op een operationele staat. Op dat moment werd het duidelijk dat we niet zonder konden ā¦
Deel II

Dus, onze cluster is een kleurrijke schakeling van een paar tientallen afzonderlijke componenten die niet door Dockerfile's zijn beschreven. Het configureren voor deploy naar een specifieke omgeving kan slechts globaal worden gedaan. Onze taak is om de cluster in een staging-omgeving te deployen ter voorbereiding op pre-release testen.
Theoretisch kunnen er meerdere actieve clusters tegelijk zijn: zoveel als er taken in een voltooid of bijna voltooid stadium zijn. De capaciteiten van onze servers maken het mogelijk om meerdere clusters op elke server te draaien. Elke staging-cluster moet geĆÆsoleerd zijn (er mag geen overlap zijn in poorten, mappen, enz.).
De meest waardevolle hulpbron is onze tijd, en die hadden we maar weinig.
Voor een snellere start hebben we Docker Swarm gekozen vanwege zijn eenvoud en flexibiliteit van de architectuur. Het eerste wat we deden was het creƫren van een manager en meerdere nodes op de externe servers:
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
kilqc94pi2upzvabttikrfr5d nop-test-1 Klaar Actief 19.03.2
jilwe56pl2zvabupryuosdj78 nop-test-2 Klaar Actief 19.03.2
j5a4yz1kr2xke6b1ohoqlnbq5 * nop-test-3 Klaar Actief Leider 19.03.2
Daarna hebben we een netwerk aangemaakt:
$ docker network create --driver overlay --subnet 10.10.10.0/24 nw_swarm
Vervolgens hebben we Gitlab-CI en de Swarm-nodes gekoppeld voor het op afstand beheren van de nodes vanuit CI: certificaten installeren, geheimen configureren, en ook de Docker-service op de beheerserver instellen. Dit heeft ons enorm veel tijd bespaard. De volgende stap was het toevoegen van jobs voor het creƫren en vernietigen van de stack in .gitlab-ci.yml.
In .gitlab-ci.yml zijn nog enkele jobs toegevoegd.
Uit het bovenstaande codefragment blijkt dat er twee knoppen (deploy_staging, stop_staging) aan de Pipelines zijn toegevoegd, die handmatige interventie vereisen.
## 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
De naam van de stack komt overeen met de naam van de branch en deze uniciteit zou voldoende moeten zijn. Diensten in de stack krijgen unieke IP-adressen, en poorten, directories, enzovoort, zullen geĆÆsoleerd zijn maar identiek van stack tot stack (aangezien het configuratiebestand voor alle stacks identiek is) ā dat is precies wat we wilden bereiken. We implementeren de stack (cluster) met behulp van

, waarin ons cluster is beschreven. docker-compose.ymlwaarin ons cluster wordt beschreven.
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
Hier zien we dat de componenten verbonden zijn door ƩƩn netwerk (nw_swarm) en voor elkaar toegankelijk zijn.
De systeemelementen (ondersteund door redis, mysql) zijn gescheiden van de algemene pool van aangepaste componenten (de plannen zijn om ook de aangepaste componenten als diensten te scheiden). De implementatiefase van onze cluster ziet eruit als het doorgeven van CMD naar onze grote, geconfigureerde image en verschilt in principe niet van de implementatie die in Deel I is beschreven. Ik zal de verschillen benadrukken:
- git clone ⦠ā we verkrijgen de bestanden die nodig zijn om de implementatie uit te voeren (createconfig.py, install_venv.sh, enz.).
- curl⦠&& unzip ⦠ā we downloaden en unzippen de build-artifacten (gecompileerde utilities).
Er is nog maar ƩƩn niet-beschreven probleem: de componenten met een webinterface zijn niet toegankelijk vanuit de browsers van de ontwikkelaars. We lossen dit probleem op met behulp van een reverse proxy, op de volgende manier:
In .gitlab-ci.yml voegen we na de implementatie van de clusterstack een regel toe voor de implementatie van de load balancer (die bij commits alleen zijn configuratie bijwerkt (nieuwe configuratiebestanden voor nginx aanmaakt op basis van sjabloon: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME}.conf) ā zie de code 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
Op de computers van de ontwikkelaars updaten we /etc/hosts; we schrijven de URL naar nginx:
10.50.173.106 staging_BRANCH-1831_cluster.dev
Dus de implementatie van geĆÆsoleerde staging-clusters is gerealiseerd en ontwikkelaars kunnen deze nu in een voldoende aantal voor het testen van hun taken opstarten.
Toekomstige plannen:
- Onze componenten als diensten splitsen
- Een Dockerfile voor elk aanmaken
- Automatisch minder drukke nodes in de stack bepalen
- Nodes op naam sjabloon definiƫren (en niet id gebruiken zoals in het artikel)
- Controleren of de stack is vernietigd
- ā¦
Bijzonder dank voor .
Bron: habr.com
