I-deploy ang mga Application gamit ang Docker Swarm

Ang online na video content recommendation system na pinagtatrabahuhan namin ay isang closed commercial development at teknikal na isang multi-component cluster ng proprietary at open source na mga bahagi. Ang layunin ng pagsulat ng artikulong ito ay upang ilarawan ang pagpapatupad ng docker swarm clustering system para sa isang staging site nang hindi naaabala ang naitatag na daloy ng trabaho ng aming mga proseso sa isang limitadong oras. Ang salaysay na ipinakita sa iyong pansin ay nahahati sa dalawang bahagi. Inilalarawan ng unang bahagi ang CI / CD bago gamitin ang docker swarm, at ang pangalawa ay naglalarawan sa proseso ng pagpapatupad nito. Ang mga hindi interesadong basahin ang unang bahagi ay maaaring ligtas na lumipat sa pangalawa.

Bahagi I

Bumalik sa malayong, malayong taon, kinakailangan na i-set up ang proseso ng CI / CD sa lalong madaling panahon. Isa sa mga kundisyon ay hindi gumamit ng Docker para sa deployment binuo ng mga bahagi para sa ilang mga kadahilanan:

  • para sa mas maaasahan at matatag na operasyon ng mga bahagi sa Produksyon (iyon ay, sa katunayan, ang kinakailangan na huwag gumamit ng virtualization)
  • ang nangungunang mga developer ay hindi nais na magtrabaho kasama ang Docker (kakaiba, ngunit ganoon iyon)
  • ayon sa ideolohikal na pagsasaalang-alang ng R&D management

Ang imprastraktura, stack at tinatayang mga paunang kinakailangan para sa MVP ay ipinakita tulad ng sumusunod:

  • 4 na Intel® X5650 server na may Debian (isang mas malakas na makina ay ganap na binuo)
  • Ang pagbuo ng sariling pasadyang mga bahagi ay isinasagawa sa C ++, Python3
  • Pangunahing 3rd party na tool na ginamit: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
  • Mga pipeline para sa pagbuo at pagsubok ng mga bahagi nang hiwalay para sa pag-debug at pagpapalabas

Ang isa sa mga unang tanong na kailangang matugunan sa paunang yugto ay kung paano ide-deploy ang mga custom na bahagi sa anumang kapaligiran (CI / CD).

Napagpasyahan naming i-install ang mga bahagi ng third-party sa sistematikong paraan at sistematikong i-update ang mga ito. Ang mga custom na application na binuo sa C++ o Python ay maaaring i-deploy sa maraming paraan. Kabilang sa mga ito, halimbawa: paglikha ng mga pakete ng system, pagpapadala sa kanila sa repositoryo ng mga built na imahe at pagkatapos ay i-install ang mga ito sa mga server. Para sa hindi malamang dahilan, isa pang paraan ang napili, katulad ng: gamit ang CI, ang mga application executable file ay pinagsama-sama, isang virtual na kapaligiran ng proyekto ay nilikha, ang mga py module ay naka-install mula sa requirements.txt, at lahat ng mga artifact na ito ay ipinadala kasama ng mga config, script at ang kasamang kapaligiran ng application sa mga server. Susunod, ang mga application ay inilunsad bilang isang virtual na gumagamit nang walang mga karapatan ng administrator.

Napili ang Gitlab-CI bilang CI/CD system. Ang resultang pipeline ay mukhang ganito:

I-deploy ang mga Application gamit ang Docker Swarm
Sa istruktura, ganito ang hitsura ng gitlab-ci.yml

---
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

Kapansin-pansin na ang pagpupulong at pagsubok ay isinasagawa sa sarili nitong imahe, kung saan ang lahat ng kinakailangang mga pakete ng system ay na-install na at ang iba pang mga setting ay ginawa.

Bagaman ang bawat isa sa mga script na ito sa mga trabaho ay kawili-wili sa sarili nitong paraan, ngunit siyempre hindi ko pag-uusapan ang mga ito. Ang paglalarawan ng bawat isa sa kanila ay aabutin ng maraming oras at hindi ito ang layunin ng artikulo. Dadalhin ko lang ang iyong pansin sa katotohanan na ang yugto ng deployment ay binubuo ng isang pagkakasunud-sunod ng mga script ng pagtawag:

  1. createconfig.py - lumilikha ng settings.ini file na may mga component settings sa iba't ibang environment para sa kasunod na deployment (Preproduction, Production, Testing, ...)
  2. install_venv.sh - lumilikha ng isang virtual na kapaligiran para sa mga bahagi ng py sa isang partikular na direktoryo at kinokopya ito sa mga malalayong server
  3. prepare_init.d.py — naghahanda ng mga start-stop na script para sa component batay sa template
  4. deploy.py - nabubulok at nagre-restart ng mga bagong bahagi

Lumipas ang oras. Ang yugto ng pagtatanghal ay pinalitan ng preproduction at production. Nagdagdag ng suporta para sa produkto sa isa pang pamamahagi (CentOS). Nagdagdag ng 5 mas malakas na pisikal na server at isang dosenang virtual na server. At naging mas mahirap para sa mga developer at tester na subukan ang kanilang mga gawain sa isang kapaligiran na halos malapit sa nagtatrabaho na estado. Sa oras na ito, naging malinaw na imposibleng gawin nang wala siya ...

Bahagi II

I-deploy ang mga Application gamit ang Docker Swarm

Kaya, ang aming cluster ay isang kamangha-manghang sistema ng ilang dosenang magkahiwalay na bahagi na hindi inilarawan ng Dockerfiles. Maaari mo lamang itong i-configure para sa pag-deploy sa isang partikular na kapaligiran sa pangkalahatan. Ang aming gawain ay i-deploy ang cluster sa isang staging environment upang subukan ito bago ang pre-release na pagsubok.

Sa teorya, maaaring mayroong ilang mga kumpol na tumatakbo nang sabay-sabay: kasing dami ng mga gawain sa nakumpletong estado o malapit nang matapos. Ang mga kapasidad ng mga server sa aming pagtatapon ay nagpapahintulot sa amin na magpatakbo ng ilang mga kumpol sa bawat server. Ang bawat staging cluster ay dapat na nakahiwalay (dapat walang intersection sa mga port, direktoryo, atbp.).

Ang aming pinakamahalagang mapagkukunan ay ang aming oras, at wala kami nito.

Para sa mas mabilis na pagsisimula, pinili namin ang Docker Swarm dahil sa pagiging simple nito at flexibility ng arkitektura. Ang unang bagay na ginawa namin ay lumikha ng isang manager at ilang mga node sa mga malalayong server:

$ 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

Susunod, lumikha ng isang network:


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

Susunod, ikinonekta namin ang Gitlab-CI at Swarm node sa mga tuntunin ng remote control ng mga node mula sa CI: pag-install ng mga certificate, pagtatakda ng mga lihim na variable, at pag-set up ng serbisyo ng Docker sa control server. Itong isa artikulo nagligtas sa amin ng maraming oras.

Susunod, nagdagdag kami ng mga trabaho sa paggawa at pagsira ng stack sa .gitlab-ci .yml.

Ilang trabaho pa ang naidagdag sa .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

Mula sa snippet ng code sa itaas, makikita mo na ang dalawang button (deploy_staging, stop_staging) ay naidagdag sa Pipelines, na nangangailangan ng manu-manong pagkilos.

I-deploy ang mga Application gamit ang Docker Swarm
Ang pangalan ng stack ay tumutugma sa pangalan ng sangay at dapat na sapat ang pagiging natatangi na ito. Ang mga serbisyo sa stack ay tumatanggap ng mga natatanging ip address, at mga port, mga direktoryo, atbp. ay ihiwalay, ngunit pareho mula sa stack hanggang sa stack (dahil ang configuration file ay pareho para sa lahat ng stack) - kung ano ang gusto namin. I-deploy namin ang stack (cluster) gamit docker-compose.yml, na naglalarawan sa aming 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

Dito makikita mo na ang mga bahagi ay konektado ng isang network (nw_swarm) at available sa isa't isa.

Ang mga bahagi ng system (batay sa redis, mysql) ay pinaghihiwalay mula sa pangkalahatang pool ng mga custom na bahagi (sa mga plano at ang mga custom ay hinati bilang mga serbisyo). Ang yugto ng deployment ng aming cluster ay mukhang pagpasa ng CMD sa aming isang malaking naka-configure na imahe at, sa pangkalahatan, halos hindi naiiba sa deployment na inilarawan sa Bahagi I. Iha-highlight ko ang mga pagkakaiba:

  • git clone... - kunin ang mga file na kailangan para i-deploy (createconfig.py, install_venv.sh, atbp.)
  • curl... && unzip... - I-download at i-unzip ang pagbuo ng mga artifact (compiled utilities)

Mayroon lamang isang hindi pa nailalarawan na problema: ang mga bahagi na may web interface ay hindi naa-access mula sa mga browser ng mga developer. Niresolba namin ang problemang ito gamit ang reverse proxy, kaya:

Sa .gitlab-ci.yml, pagkatapos i-deploy ang cluster stack, idinaragdag namin ang linya ng pag-deploy ng balancer (na, kapag nag-commit, ina-update lang ang configuration nito (gumawa ng mga bagong nginx configuration file ayon sa template: /etc/nginx/conf. d/${CI_COMMIT_REF_NAME}.conf) - tingnan ang docker-compose-nginx.yml code)

    - 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

Sa mga development computer, i-update ang /etc/hosts; magreseta ng url sa nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Kaya, ang deployment ng mga nakahiwalay na staging cluster ay naipatupad na at maaari na ngayong patakbuhin ng mga developer ang mga ito sa anumang bilang na sapat upang suriin ang kanilang mga gawain.

Mga plano sa hinaharap:

  • Paghiwalayin ang aming mga bahagi bilang mga serbisyo
  • Magkaroon para sa bawat Dockerfile
  • Awtomatikong makita ang mas kaunting load na mga node sa stack
  • Tukuyin ang mga node ayon sa pattern ng pangalan (sa halip na gumamit ng id tulad ng sa artikulo)
  • Magdagdag ng tseke na ang stack ay nawasak
  • ...

Espesyal na salamat sa isang artikulo.

Pinagmulan: www.habr.com

Magdagdag ng komento