Implantar aplicativos usando o docker swarm

O sistema de recomendação de conteúdo de vídeo on-line em que estamos trabalhando é um desenvolvimento comercial fechado e, tecnicamente, é um cluster multicomponente de seus próprios componentes de código aberto. O objetivo deste artigo é descrever a introdução de um sistema de clustering swarm de docker para uma plataforma de teste, sem violar o fluxo de trabalho existente de nossos processos em um tempo limitado. A narrativa apresentada a sua atenção está dividida em duas partes. A primeira parte descreve o CI / CD antes de usar o docker swarm e a segunda descreve o processo de implementação. Aqueles que não estão interessados ​​em ler a primeira parte podem passar com segurança para a segunda.

Parte I


No ano distante, distante, era necessário configurar o processo de CI / CD o mais rápido possível. Uma das condições era não usar o Docker para implantar os componentes desenvolvidos por vários motivos:

  • para uma operação mais confiável e estável dos componentes na Produção (isto é, de fato, o requisito de não usar a virtualização)
  • Os principais desenvolvedores não queriam trabalhar com o Docker (estranho, mas era apenas isso)
  • por razões ideológicas, gestão de P&D

A infraestrutura, a pilha e os requisitos iniciais de amostra para MVP foram os seguintes:

  • 4 servidores Intel® X5650 com Debian (mais uma máquina poderosa para desenvolvimento)
  • O desenvolvimento de componentes personalizados é realizado em C ++, Python3
  • As principais ferramentas utilizadas por terceiros: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, ...
  • Montagem de tubulações e teste de componentes separadamente para depuração e liberação

Um dos primeiros problemas a serem resolvidos no estágio inicial é como implantar componentes personalizados em qualquer ambiente (CI / CD).

Componentes de terceiros decidiram instalar sistematicamente e atualizá-los sistemicamente. Aplicativos personalizados desenvolvidos em C ++ ou Python podem ser implantados de várias maneiras. Entre eles, por exemplo: criação de pacotes do sistema, enviando-os para o repositório de imagens coletadas e sua instalação subsequente em servidores. Por um motivo desconhecido, outro método foi escolhido, a saber: usando o IC, os arquivos de aplicativos executáveis ​​são compilados, um ambiente de projeto virtual é criado, py-modules a partir de requirements.txt são instalados e todos esses artefatos são enviados juntamente com configurações, scripts e o ambiente de aplicativo que os acompanha aos servidores. Em seguida, os aplicativos são iniciados a partir de um usuário virtual sem direitos de administrador.

O Gitlab-CI foi escolhido como o sistema CI / CD. O pipeline resultante ficou mais ou menos assim:

imagem
Estruturalmente, o gitlab-ci.yml ficou assim
--- 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 *\n\tStrictHostKeyChecking no\n\n" > ~/.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 


Vale ressaltar que a montagem e o teste são feitos em sua própria imagem, onde todos os pacotes de sistema necessários já estão instalados e outras configurações são feitas.

Embora cada um desses scripts no trabalho seja interessante à sua maneira, certamente não falarei sobre eles , mas a descrição de cada um deles levará um tempo considerável e esse não é o objetivo do artigo. Prestarei atenção apenas ao fato de que o estágio de implantação consiste em uma sequência de chamadas de script:

  1. createconfig.py - cria o arquivo settings.ini com as configurações dos componentes em um ambiente diferente para a implantação subsequente (pré-produção, produção, teste, ...)
  2. install_venv.sh - cria um ambiente virtual para componentes py em um diretório específico e o copia para servidores remotos
  3. prepare_init.d.py - prepara scripts de início / parada do componente com base em um modelo
  4. deploy.py - descomprima e reinicie novos componentes

O tempo passou. O estágio foi substituído pela pré-produção e produção. O suporte ao produto foi adicionado em outro kit de distribuição (CentOS). Foram adicionados 5 servidores físicos mais poderosos e uma dúzia de servidores virtuais. E ficou cada vez mais difícil para desenvolvedores e testadores executar suas tarefas em um ambiente mais ou menos próximo do estado de trabalho. Neste momento, ficou claro que é impossível ficar sem ele ...

Parte II


imagem

Portanto, nosso cluster ainda é o espetáculo de um sistema de duas dúzias de componentes separados que não são descritos pelo Dockerfiles. Você pode configurá-lo para implantação em um ambiente específico apenas como um todo. Nossa tarefa é implantar o cluster em um ambiente intermediário para executá-lo antes do teste de pré-lançamento.

Teoricamente, pode haver vários clusters trabalhando simultaneamente: tantas tarefas estão no estado concluído ou próximas da conclusão. As capacidades disponíveis para nossos servidores nos permitem executar vários clusters em cada servidor. Cada cluster de temporariedade deve ser isolado (não deve haver interseção em portas, diretórios etc.).

O recurso mais valioso é o nosso tempo, e não tínhamos muito.

Para um início mais rápido, eles escolheram o Docker Swarm devido à sua simplicidade e flexibilidade de arquitetura. A primeira coisa que fizemos foi criar nos servidores do gerenciador remoto e em vários nós:

 $ 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 

Em seguida, criamos uma rede:

 $ docker network create --driver overlay --subnet 10.10.10.0/24 nw_swarm 

Em seguida, eles conectaram os nós Gitlab-CI e Swarm em termos de gerenciamento remoto de nós de CI: instalação de certificados, configuração de variáveis ​​secretas e instalação do serviço Docker no servidor de gerenciamento. Este artigo economizou muito tempo.

Em seguida, adicionamos trabalhos para criar e destruir a pilha no .gitlab-ci .yml.

Mais alguns trabalhos foram adicionados ao .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 snippet de código acima, fica claro que dois botões (deploy_staging, stop_staging) foram adicionados aos pipelines que requerem intervenção manual.

imagem
O nome da pilha corresponde ao nome da ramificação e essa exclusividade deve ser suficiente. Os serviços na pilha recebem endereços IP exclusivos, portas, diretórios etc. será isolado, mas o mesmo de pilha para pilha (porque o arquivo de configuração é o mesmo para todas as pilhas) - foi o que alcançamos. Implementamos a pilha (cluster) usando o docker-compose.yml , que descreve nosso cluster.

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 


Aqui você pode ver que os componentes estão conectados por uma rede (nw_swarm) e são acessíveis um ao outro.

Os componentes do sistema (baseados em redis, mysql) são separados do pool comum de componentes customizados (os planos e customizados são divididos como serviços). O estágio de implantação de nosso cluster se parece com a transferência de CMD para uma imagem grande configurada e, como um todo, praticamente não difere da implantação descrita na Parte I. Destaco as diferenças:

  • git clone ... - obtemos os arquivos necessários para fazer uma implementação (createconfig.py, install_venv.sh, etc.)
  • enrolar ... && descompactar ... - baixar e descompactar artefatos de montagem (utilitários compilados)

Há apenas um problema que ainda não foi descrito: os componentes que possuem uma interface da web não podem ser acessados ​​pelos navegadores de desenvolvedor. Resolvemos esse problema usando o proxy reverso, assim:

Em .gitlab-ci.yml, após implantar a pilha do cluster, adicione a linha de implantação do balanceador (que, ao confirmar, atualiza apenas sua configuração (cria novos arquivos de configuração nginx usando o modelo: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME►.conf) - consulte 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 computadores de desenvolvimento, atualize / etc / hosts; registrar url no nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Portanto, a implantação de clusters de armazenamento temporário isolados foi implementada e os desenvolvedores agora podem iniciá-los em quantidade suficiente para testar suas tarefas.

Planos adicionais:

  • Separe nossos componentes como serviços
  • Crie para todos os arquivos do Docker
  • Detectar automaticamente nós menos carregados na pilha
  • Definir nós por padrão de nome (em vez de usar o id como no artigo)
  • Adicione verificação de que a pilha está destruída
  • ...

Agradecimentos especiais para o artigo .

Source: https://habr.com/ru/post/pt471528/


All Articles