O sistema de recomendación de contidos de vídeo en liña no que estamos a traballar é un desenvolvemento comercial pechado e, tecnicamente, é un clúster multicomponente de compoñentes propietarios e de código aberto. O propósito de escribir este artigo é describir a implementación do sistema de agrupación docker swarm para un sitio de proba sen interromper o fluxo de traballo establecido dos nosos procesos nun tempo limitado. A narración que se presenta á súa atención divídese en dúas partes. A primeira parte describe CI/CD antes de usar docker swarm, e a segunda describe o proceso da súa implementación. Os que non estean interesados en ler a primeira parte poden pasar á segunda con seguridade.
Primeira parte
De volta ao ano afastado e afastado, foi necesario configurar o proceso CI/CD o máis rápido posible. Unha das condicións era non usar Docker para o despregamento compoñentes desenvolvidos por varias razóns:
- para un funcionamento máis fiable e estable dos compoñentes en Produción (é dicir, o requisito de non usar a virtualización)
- os principais desenvolvedores non querían traballar con Docker (raro, pero así foi)
- segundo as consideracións ideolóxicas da xestión da I+D+i
A infraestrutura, a pila e os requisitos iniciais aproximados para MVP presentáronse do seguinte xeito:
- 4 servidores Intel® X5650 con Debian (unha máquina máis potente está totalmente desenvolvida)
- O desenvolvemento de compoñentes personalizados propios realízase en C++, Python3
- Principais ferramentas de terceiros utilizadas: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
- Canalizacións para construír e probar compoñentes por separado para a depuración e a liberación
Unha das primeiras preguntas que hai que abordar na fase inicial é como se implementarán os compoñentes personalizados en calquera ambiente (CI/CD).
Decidimos instalar compoñentes de terceiros sistemáticamente e actualizalos sistemáticamente. As aplicacións personalizadas desenvolvidas en C++ ou Python pódense implantar de varias maneiras. Entre elas, por exemplo: crear paquetes do sistema, envialos ao repositorio de imaxes construídas e despois instalalos en servidores. Por unha razón descoñecida, escolleuse outro método, a saber: mediante CI, compílanse ficheiros executables de aplicacións, créase un entorno de proxecto virtual, instálanse módulos py desde requirements.txt e todos estes artefactos envíanse xunto con configuracións, scripts e ambiente de aplicación que acompaña aos servidores. A continuación, lánzanse as aplicacións como un usuario virtual sen dereitos de administrador.
Elixiuse Gitlab-CI como sistema CI/CD. A canalización resultante parecía algo así:
Estruturalmente, gitlab-ci.yml tiña este aspecto
---
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
Cabe destacar que a montaxe e probas realízanse na súa propia imaxe, onde xa se instalaron todos os paquetes de sistema necesarios e se realizaron outras configuracións.
Aínda que cada un destes guións nos traballos é interesante ao seu xeito, pero por suposto non falarei deles.A descrición de cada un deles levará moito tempo e este non é o propósito do artigo. Só chamarei a súa atención sobre o feito de que a fase de implantación consiste nunha secuencia de scripts de chamada:
- createconfig.py - crea un ficheiro settings.ini con axustes de compoñentes en varios ambientes para a súa posterior implantación (preprodución, produción, probas, ...)
- install_venv.sh - crea un ambiente virtual para compoñentes py nun directorio específico e cópiao a servidores remotos
- prepare_init.d.py — prepara guións de inicio e parada para o compoñente baseándose no modelo
- despregar.py - descompón e reinicia novos compoñentes
O tempo pasou. A etapa de posta en escena foi substituída pola preprodución e produción. Engadido soporte para o produto nunha distribución máis (CentOS). Engadíronse 5 servidores físicos máis potentes e unha ducia de virtuais. E facíase cada vez máis difícil para os desenvolvedores e probadores probar as súas tarefas nun ambiente máis ou menos próximo ao estado de traballo. Neste momento, quedou claro que era imposible prescindir del...
Parte II
Polo tanto, o noso clúster é un sistema espectacular dun par de ducias de compoñentes separados que non están descritos por Dockerfiles. Só pode configuralo para a súa implantación nun ambiente específico en xeral. A nosa tarefa é implantar o clúster nun ambiente de proba para probalo antes da proba previa ao lanzamento.
Teoricamente, pode haber varios clústeres en execución simultáneamente: tantos como tarefas haxa en estado completado ou próximas á súa finalización. As capacidades dos servidores á nosa disposición permítennos executar varios clústeres en cada servidor. Cada clúster de proba debe estar illado (non debe haber intersección en portos, directorios, etc.).
O noso recurso máis valioso é o noso tempo, e non tiñamos moito del.
Para comezar máis rápido, escollemos Docker Swarm debido á súa sinxeleza e flexibilidade de arquitectura. O primeiro que fixemos foi crear un xestor e varios nodos nos servidores remotos:
$ 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
A continuación, crea unha rede:
$ docker network create --driver overlay --subnet 10.10.10.0/24 nw_swarm
A continuación, conectamos os nós Gitlab-CI e Swarm en termos de control remoto de nós desde CI: instalación de certificados, configuración de variables secretas e configuración do servizo Docker no servidor de control. Este
A continuación, engadimos traballos de creación e destrución de pilas a .gitlab-ci .yml.
Engadíronse algúns traballos máis a .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
No fragmento de código anterior, podes ver que se engadiron dous botóns (deploy_staging, stop_staging) a Pipelines, que requiren unha acción manual.
O nome da pila coincide co nome da rama e esta singularidade debería ser suficiente. Os servizos da pila reciben enderezos IP únicos e portos, directorios, etc. estará illado, pero o mesmo de pila en pila (porque o ficheiro de configuración é o mesmo para todas as pilas) - o que queriamos. Implementamos a pila (clúster) usando docker-compose.yml, que describe o noso clúster.
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
Aquí podes ver que os compoñentes están conectados por unha rede (nw_swarm) e están dispoñibles entre si.
Os compoñentes do sistema (baseados en redis, mysql) están separados do conxunto xeral de compoñentes personalizados (nos plans e os personalizados divídense como servizos). A fase de implantación do noso clúster parece pasar CMD á nosa imaxe grande configurada e, en xeral, practicamente non difire da implantación descrita na Parte I. Destacarei as diferenzas:
- clon git... - obter os ficheiros necesarios para implementar (createconfig.py, install_venv.sh, etc.)
- enrolar... && descomprimir... - descargar e descomprimir artefactos de compilación (utilidades compiladas)
Só hai un problema aínda sen describir: os compoñentes que teñen unha interface web non son accesibles desde os navegadores dos desenvolvedores. Resolvemos este problema usando un proxy inverso, así:
En .gitlab-ci.yml, despois de despregar a pila de clúster, engade unha liña para despregar o equilibrador (que, cando se compromete, só actualiza a súa configuración (crea novos ficheiros de configuración de nginx segundo o modelo: /etc/nginx/conf.d) /${CI_COMMIT_REF_NAME}.conf) - ver o código 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
Nos ordenadores de desenvolvemento, actualice /etc/hosts; prescribe URL para nginx:
10.50.173.106 staging_BRANCH-1831_cluster.dev
Así, implantouse o despregamento de clústeres illados e os desenvolvedores agora poden executalos en calquera número suficiente para comprobar as súas tarefas.
Plans futuros:
- Separe os nosos compoñentes como servizos
- Teña para cada Dockerfile
- Detecta automaticamente os nós menos cargados na pila
- Especifique os nós polo patrón de nome (en lugar de usar id como no artigo)
- Engade unha comprobación de que a pila está destruída
- ...
Agradecemento especial para
Fonte: www.habr.com