Online systém odporúčaní videoobsahu, na ktorom pracujeme, je uzavretý komerčný vývoj a je technicky viaczložkovým klastrom proprietárnych a open source komponentov. Účelom napísania tohto článku je opísať implementáciu systému klastrovania docker swarm pre pracovisko bez narušenia zavedeného pracovného toku našich procesov v obmedzenom čase. Rozprávanie, ktoré vám je predložené, je rozdelené na dve časti. Prvá časť popisuje CI/CD pred použitím docker swarm a druhá popisuje proces jeho implementácie. Tí, ktorí nemajú záujem čítať prvú časť, môžu pokojne prejsť na druhú.
Časť I
Späť vo vzdialenom, vzdialenom roku bolo potrebné nastaviť proces CI / CD čo najrýchlejšie. Jednou z podmienok bolo nepoužívať Docker na nasadenie vyvinuté komponenty z niekoľkých dôvodov:
- pre spoľahlivejšiu a stabilnejšiu prevádzku komponentov vo výrobe (teda v skutočnosti požiadavka nepoužívať virtualizáciu)
- poprední vývojári nechceli spolupracovať s Dockerom (čudné, ale bolo to tak)
- podľa ideových úvah vedenia výskumu a vývoja
Infraštruktúra, zásobník a približné počiatočné požiadavky na MVP boli prezentované takto:
- 4 servery Intel® X5650 s Debianom (jeden výkonnejší počítač je plne vyvinutý)
- Vývoj vlastných vlastných komponentov prebieha v C++, Python3
- Hlavné používané nástroje tretích strán: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
- Potrubia na vytváranie a testovanie komponentov oddelene na ladenie a uvoľnenie
Jednou z prvých otázok, ktoré je potrebné vyriešiť v počiatočnej fáze, je spôsob nasadenia vlastných komponentov v akomkoľvek prostredí (CI / CD).
Rozhodli sme sa systémovo nainštalovať komponenty tretích strán a systémovo ich aktualizovať. Vlastné aplikácie vyvinuté v C++ alebo Pythone je možné nasadiť niekoľkými spôsobmi. Medzi nimi napríklad: vytváranie systémových balíkov, ich odosielanie do úložiska vytvorených obrazov a ich inštalácia na servery. Z neznámeho dôvodu bola zvolená iná metóda, a to: pomocou CI sa skompilujú spustiteľné súbory aplikácie, vytvorí sa prostredie virtuálneho projektu, nainštalujú sa py moduly z requirements.txt a všetky tieto artefakty sa odošlú spolu s konfiguráciami, skriptami a sprievodné aplikačné prostredie k serverom. Ďalej sa aplikácie spúšťajú ako virtuálny používateľ bez administrátorských práv.
Ako CI/CD systém bol vybraný Gitlab-CI. Výsledné potrubie vyzeralo asi takto:
Štrukturálne vyzeral gitlab-ci.yml takto
---
variables:
# минимальная версия ЦПУ на серверах, где разворачивается кластер
CMAKE_CPUTYPE: "westmere"
DEBIAN: "MYREGISTRY:5000/debian:latest"
before_script:
- eval $(ssh-agent -s)
- ssh-add <(echo "$SSH_PRIVATE_KEY")
- mkdir -p ~/.ssh && echo -e "Host *ntStrictHostKeyChecking nonn" > ~/.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
Stojí za zmienku, že zostava a testovanie sa vykonáva na vlastnom obrázku, kde už boli nainštalované všetky potrebné systémové balíčky a boli vykonané ďalšie nastavenia.
Hoci každý z týchto skriptov v úlohách je zaujímavý svojím vlastným spôsobom, ale o nich samozrejme nebudem hovoriť. Popis každého z nich zaberie veľa času a to nie je účelom článku. Len upriamim vašu pozornosť na skutočnosť, že fáza nasadenia pozostáva zo sekvencie volania skriptov:
- createconfig.py - vytvorí súbor settings.ini s nastaveniami komponentov v rôznych prostrediach pre následné nasadenie (Preproduction, Production, Testing, ...)
- install_venv.sh - vytvorí virtuálne prostredie pre py komponenty v konkrétnom adresári a skopíruje ho na vzdialené servery
- pripraviť_init.d.py — pripraví štart-stop skripty pre komponent na základe šablóny
- deploy.py - rozkladá a reštartuje nové komponenty
Čas uplynul. Etapu inscenácie nahradila predprodukcia a produkcia. Pridaná podpora pre produkt v jednej ďalšej distribúcii (CentOS). Pridaných 5 výkonnejších fyzických serverov a tucet virtuálnych. A pre vývojárov a testerov bolo čoraz ťažšie otestovať svoje úlohy v prostredí viac-menej blízkom pracovnému stavu. V tom čase sa ukázalo, že bez neho to nie je možné ...
Časť II
Náš klaster je teda veľkolepý systém niekoľkých desiatok samostatných komponentov, ktoré Dockerfiles nepopisuje. Môžete ho nakonfigurovať len na nasadenie do konkrétneho prostredia vo všeobecnosti. Našou úlohou je nasadiť klaster do prípravného prostredia, aby sme ho otestovali pred testovaním pred vydaním.
Teoreticky môže súčasne bežať niekoľko klastrov: toľko, koľko je úloh v stave dokončenia alebo takmer dokončenia. Kapacity serverov, ktoré máme k dispozícii, nám umožňujú prevádzkovať niekoľko klastrov na každom serveri. Každý stagingový klaster musí byť izolovaný (nesmie existovať priesečník v portoch, adresároch atď.).
Naším najcennejším zdrojom je náš čas a toho sme nemali veľa.
Pre rýchlejší štart sme si vybrali Docker Swarm kvôli jeho jednoduchosti a flexibilite architektúry. Prvá vec, ktorú sme urobili, bolo vytvorenie manažéra a niekoľkých uzlov na vzdialených serveroch:
$ 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
Ďalej vytvorte sieť:
$ docker network create --driver overlay --subnet 10.10.10.0/24 nw_swarm
Ďalej sme prepojili uzly Gitlab-CI a Swarm z hľadiska vzdialeného ovládania uzlov z CI: inštalácia certifikátov, nastavenie tajných premenných a nastavenie služby Docker na riadiacom serveri. Toto
Ďalej sme do súboru .gitlab-ci .yml pridali úlohy na vytváranie a ničenie zásobníkov.
Do súboru .gitlab-ci .yml pribudlo niekoľko ďalších úloh
## 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
Z útržku kódu vyššie môžete vidieť, že do Pipelines boli pridané dve tlačidlá (deploy_staging, stop_staging), ktoré si vyžadujú manuálnu akciu.
Názov zásobníka sa zhoduje s názvom pobočky a táto jedinečnosť by mala byť dostatočná. Služby v zásobníku dostávajú jedinečné adresy IP a porty, adresáre atď. bude izolovaný, ale rovnaký od zásobníka k zásobníku (pretože konfiguračný súbor je rovnaký pre všetky zásobníky) - čo sme chceli. Zásobník (klaster) nasadíme pomocou prístavný robotník-compose.yml, ktorý popisuje náš klaster.
prístavný robotník-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
Tu vidíte, že komponenty sú prepojené jednou sieťou (nw_swarm) a sú si navzájom dostupné.
Systémové komponenty (založené na redis, mysql) sú oddelené od všeobecného fondu vlastných komponentov (v plánoch sú vlastné komponenty rozdelené ako služby). Fáza nasadenia nášho klastra vyzerá ako odovzdanie CMD do nášho jedného veľkého nakonfigurovaného obrazu a vo všeobecnosti sa prakticky nelíši od nasadenia opísaného v časti I. Zdôrazním rozdiely:
- git klon... - získajte súbory potrebné na nasadenie (createconfig.py, install_venv.sh atď.)
- zvlniť... && rozopnúť... - stiahnite a rozbaľte artefakty zostavy (kompilované pomôcky)
Existuje len jeden zatiaľ nepopísaný problém: komponenty, ktoré majú webové rozhranie, nie sú prístupné z prehliadačov vývojárov. Tento problém riešime pomocou reverzného proxy, teda:
V .gitlab-ci.yml po nasadení zásobníka klastrov pridáme riadok nasadenia balancera (ktorý pri potvrdení iba aktualizuje svoju konfiguráciu (vytvorí nové konfiguračné súbory nginx podľa šablóny: /etc/nginx/conf. d/${CI_COMMIT_REF_NAME}.conf) – pozri kód 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
Na vývojových počítačoch aktualizujte súbor /etc/hosts; predpísať url pre nginx:
10.50.173.106 staging_BRANCH-1831_cluster.dev
Takže nasadenie izolovaných fázovacích klastrov bolo implementované a vývojári ich teraz môžu spustiť v ľubovoľnom počte dostatočnom na kontrolu ich úloh.
Budúce plány:
- Oddeľte naše komponenty ako služby
- Majte pre každý Dockerfile
- Automaticky zisťuje menej zaťažené uzly v zásobníku
- Zadajte uzly podľa vzoru názvu (namiesto použitia id ako v článku)
- Pridajte kontrolu, či je zásobník zničený
- ...