Sistem za priporočanje spletnih video vsebin, na katerem delamo, je zaprt komercialni razvoj in je tehnično večkomponentna skupina lastniških in odprtokodnih komponent. Namen pisanja tega članka je opisati implementacijo sistema združevanja rojev dockerjev za uprizoritveno mesto, ne da bi v omejenem času motili ustaljeni potek dela naših procesov. Predstavljena pripoved je razdeljena na dva dela. Prvi del opisuje CI/CD pred uporabo docker swarma, drugi del pa opisuje postopek njegove implementacije. Tisti, ki jih prvi del ne zanima, lahko mirno preidejo na drugega.
I. del
Nekoč je bilo treba čim hitreje vzpostaviti proces CI/CD. Eden od pogojev je bil neuporaba Dockerja za uvajanje razvil komponente iz več razlogov:
- za bolj zanesljivo in stabilno delovanje komponent v produkciji (to je pravzaprav zahteva, da se ne uporablja virtualizacija)
- vodilni razvijalci niso želeli delati z Dockerjem (čudno, ampak tako je bilo)
- glede na ideološke premisleke vodstva R&R
Infrastruktura, sklad in približne začetne zahteve za MVP so bile predstavljene na naslednji način:
- 4 strežniki Intel® X5650 z Debianom (en zmogljivejši stroj je v celoti razvit)
- Razvoj lastnih komponent po meri poteka v C ++, Python3
- Glavna uporabljena orodja tretjih oseb: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
- Cevovodi za izdelavo in testiranje komponent ločeno za odpravljanje napak in izdajo
Eno od prvih vprašanj, ki jih je treba obravnavati v začetni fazi, je, kako bodo komponente po meri nameščene v katerem koli okolju (CI / CD).
Odločili smo se za sistemsko namestitev komponent tretjih oseb in njihovo sistemsko posodobitev. Aplikacije po meri, razvite v C++ ali Python, je mogoče namestiti na več načinov. Med njimi na primer: ustvarjanje sistemskih paketov, pošiljanje v repozitorij zgrajenih slik in nato namestitev na strežnike. Iz neznanega razloga je bila izbrana druga metoda, in sicer: z uporabo CI se prevedejo izvršljive datoteke aplikacije, ustvari navidezno projektno okolje, namestijo se moduli py iz requirements.txt in vsi ti artefakti se pošljejo skupaj s konfiguracijami, skripti in spremljevalno aplikacijsko okolje do strežnikov. Nato se aplikacije zaženejo kot navidezni uporabnik brez skrbniških pravic.
Za sistem CI/CD je bil izbran Gitlab-CI. Nastali cevovod je izgledal nekako takole:
Strukturno je bil gitlab-ci.yml videti takole:
---
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
Omeniti velja, da montaža in testiranje poteka na lastni podobi, kjer so že nameščeni vsi potrebni sistemski paketi in opravljene druge nastavitve.
Čeprav je vsaka od teh skript na delovnem mestu zanimiva na svoj način, seveda ne bom govoril o njih, saj bo opis vsakega od njih vzel veliko časa in to ni namen članka. Opozoril bom le na dejstvo, da je stopnja uvajanja sestavljena iz zaporedja klicnih skriptov:
- createconfig.py - ustvari datoteko settings.ini z nastavitvami komponent v različnih okoljih za kasnejšo uvedbo (predprodukcija, produkcija, testiranje, ...)
- install_venv.sh - ustvari virtualno okolje za komponente py v določenem imeniku in ga kopira na oddaljene strežnike
- pripravi_init.d.py — pripravi start-stop skripte za komponento na podlagi predloge
- deploy.py - razgradi in ponovno zažene nove komponente
Čas je minil. Fazo uprizoritve sta nadomestili predprodukcija in produkcija. Dodana podpora za izdelek v še eni distribuciji (CentOS). Dodanih je 5 zmogljivejših fizičnih strežnikov in ducat virtualnih. Razvijalcem in preizkuševalcem je postajalo vse težje preizkušati svoje naloge v okolju, ki je bolj ali manj blizu delovnemu stanju. Takrat je postalo jasno, da brez njega ni mogoče ...
XNUMX. del
Naša gruča je torej spektakularen sistem nekaj ducatov ločenih komponent, ki jih Dockerfiles ne opisuje. Na splošno ga lahko konfigurirate le za uvajanje v določeno okolje. Naša naloga je razmestiti gručo v uprizoritveno okolje, da jo preizkusimo pred testiranjem pred izdajo.
Teoretično lahko hkrati deluje več gruč: toliko, kolikor je nalog v stanju dokončanja ali blizu dokončanja. Zmogljivosti strežnikov, s katerimi razpolagamo, nam omogočajo, da na posameznem strežniku izvajamo več gruč. Vsaka uprizoritvena gruča mora biti izolirana (ne sme biti presečišč v vratih, imenikih itd.).
Naš najdragocenejši vir je naš čas in nismo ga imeli veliko.
Za hitrejši začetek smo izbrali Docker Swarm zaradi njegove enostavnosti in prilagodljive arhitekture. Najprej smo ustvarili upravitelja in več vozlišč na oddaljenih strežnikih:
$ 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
Nato ustvarite omrežje:
$ docker network create --driver overlay --subnet 10.10.10.0/24 nw_swarm
Nato smo povezali vozlišča Gitlab-CI in Swarm v smislu oddaljenega nadzora vozlišč iz CI: namestitev certifikatov, nastavitev tajnih spremenljivk in nastavitev storitve Docker na nadzornem strežniku. Tale
Nato smo v .gitlab-ci .yml dodali opravila ustvarjanja in uničenja skladov.
V .gitlab-ci .yml je bilo dodanih še nekaj delovnih mest
## 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
Iz zgornjega delčka kode lahko vidite, da sta bila v Cevovodi dodana dva gumba (deploy_staging, stop_staging), ki zahtevata ročno ukrepanje.
Ime sklada se ujema z imenom veje in ta edinstvenost bi morala zadostovati. Storitve v skladu prejmejo edinstvene naslove IP, vrata, imenike itd. bo izoliran, vendar enak od sklada do sklada (ker je konfiguracijska datoteka enaka za vse sklade) - kar smo želeli. Sklad (gručo) razmestimo z uporabo docker-compose.yml, ki opisuje naš grozd.
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
Tukaj lahko vidite, da so komponente povezane z enim omrežjem (nw_swarm) in so na voljo ena drugi.
Sistemske komponente (temelji na redis, mysql) so ločene od splošnega nabora komponent po meri (v načrtih in tiste po meri so razdeljene kot storitve). Stopnja uvajanja naše gruče je videti kot prenos CMD v našo eno veliko konfigurirano sliko in se na splošno praktično ne razlikuje od uvajanja, opisanega v I. delu. Poudaril bom razlike:
- git klon... - pridobite datoteke, potrebne za namestitev (createconfig.py, install_venv.sh itd.)
- zvijaj ... && odpakiraj ... — prenesite in razpakirajte artefakte gradnje (prevedeni pripomočki)
Obstaja le ena še neopisana težava: komponente, ki imajo spletni vmesnik, niso dostopne iz brskalnikov razvijalcev. To težavo rešimo z uporabo povratnega proxyja, torej:
V .gitlab-ci.yml po razmestitvi sklada gruč dodamo vrstico za razmestitev izravnalnika (ki ob objavi samo posodobi svojo konfiguracijo (ustvari nove konfiguracijske datoteke nginx v skladu s predlogo: /etc/nginx/conf. d/${CI_COMMIT_REF_NAME}.conf) – glejte kodo 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 razvojnih računalnikih posodobite /etc/hosts; predpiši url za nginx:
10.50.173.106 staging_BRANCH-1831_cluster.dev
Tako je bila implementirana uvedba izoliranih uprizoritvenih gruč in razvijalci jih lahko zdaj zaženejo v poljubni količini, ki zadostuje za testiranje njihovih nalog.
Načrti za prihodnost:
- Ločite naše komponente kot storitve
- Za vsako Dockerfile
- Samodejno zazna manj obremenjena vozlišča v skladu
- Določite vozlišča po vzorcu imena (namesto uporabe ID-ja kot v članku)
- Dodajte preverjanje, da je sklad uničen
- ...
Posebna zahvala za
Vir: www.habr.com