Ynsette applikaasjes mei Docker Swarm

It online oanbefellingssysteem foar fideo-ynhâld dêr't wy oan wurkje is in sletten kommersjele ûntwikkeling en is technysk in multi-komponint kluster fan proprietêre en iepen boarne komponinten. It doel fan it skriuwen fan dit artikel is om de ymplemintaasje te beskriuwen fan it docker-swarm-klustersysteem foar in stagingplatfoarm, sûnder de fêststelde workflow fan ús prosessen ûnder beheinde tiidbetingsten te fersteuren. It ferhaal dat oan jo oandacht wurdt presintearre is ferdield yn twa dielen. It earste diel beskriuwt CI / CD foardat jo docker swarm brûke, en it twadde diel beskriuwt it proses fan it útfieren dêrfan. Wa't net ynteressearre is yn it lêzen fan it earste diel, kin feilich trochgean nei it twadde.

Diel I

Eartiids wie der ferlet om sa gau mooglik in CI/CD-proses op te setten. Ien fan 'e betingsten wie Docker net te brûken foar ynset komponinten wurde ûntwikkele om ferskate redenen:

  • foar mear betroubere en stabile wurking fan komponinten yn produksje (dat wol sizze, yn essinsje, de eask om gjin virtualisaasje te brûken)
  • liedende ûntwikkelders woene net mei Docker wurkje (raar, mar sa wie it)
  • om ideologyske redenen fan R&D-behear

De ynfrastruktuer, stack en sawat inisjele easken foar de MVP wiene as folget:

  • 4 Intel® X5650-tsjinners mei Debian (ien mear krêftige masine folslein foar ûntwikkeling)
  • Untwikkeling fan jo eigen oanpaste komponinten wurdt útfierd yn C ++, Python3
  • Wichtichste ark fan tredden brûkt: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, ...
  • Pipelines foar it bouwen en testen fan komponinten apart foar debug en frijlitting

Ien fan 'e earste fragen dy't yn' e earste faze moatte wurde oplost is hoe oanpaste komponinten sille wurde ynset yn elke omjouwing (CI / CD).

Wy besletten om komponinten fan tredden systemysk te ynstallearjen en systemysk te aktualisearjen. Oanpaste applikaasjes ûntwikkele yn C++ of Python kinne op ferskate manieren ynset wurde. Under harren, bygelyks: it meitsjen fan systeempakketten, it ferstjoeren nei it repository fan sammele ôfbyldings en har folgjende ynstallaasje op servers. Foar in al ûnbekende reden waard in oare metoade keazen, nammentlik: mei help fan CI wurde útfierbere bestannen fan applikaasjes kompilearre, in firtuele projektomjouwing wurdt makke, py-modules fan requirements.txt wurde ynstalleare, en al dizze artefakten wurde tegearre mei konfiguraasjes, skripts en de byhearrende applikaasje omjouwing oan de tsjinners. Folgjende wurde applikaasjes lansearre fan in firtuele brûker sûnder administratorrjochten.

Gitlab-CI waard keazen as it CI / CD-systeem. De resultearjende pipeline seach der sa út:

Ynsette applikaasjes mei Docker Swarm
Struktureel seach gitlab-ci.yml der sa út:

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

It is de muoite wurdich op te merken dat de gearstalling en testen wurdt útfierd op syn eigen byld, dêr't al de nedige systeem pakketten binne ynstallearre en oare ynstellings wurde makke.

Hoewol elk fan dizze skripts yn banen op syn eigen manier ynteressant is, sil ik der wis net oer prate; elk fan har beskriuwe sil in protte tiid duorje en dit is net it doel fan it artikel. Lit my gewoan jo oandacht vestige op it feit dat de ynsetfase bestiet út in sesje fan opropskripts:

  1. createconfig.py - makket in settings.ini-bestân mei ynstellingen foar komponinten yn ferskate omjouwings foar folgjende ynset (foarproduksje, produksje, testen, ...)
  2. install_venv.sh - makket in firtuele omjouwing foar py-komponinten yn in spesifike map en kopiearret it nei tsjinners op ôfstân
  3. prepare_init.d.py - taret skripts op foar start-stop-komponinten basearre op it sjabloan
  4. deploy.py - ynsette en opnij starte nije komponinten

De tiid gie foarby. It poadium waard ferfongen troch preproduksje en produksje. Stipe foar it produkt is tafoege op noch ien distribúsje (CentOS). In oare 5 krêftige fysike servers en in tsiental firtuele waarden tafoege. En it waard foar ûntwikkelders en testers hieltyd dreger om har taken te testen yn in omjouwing min of mear ticht by de wurkstân. Op dit stuit waard dúdlik dat it net sûnder him koe...

Diel II

Ynsette applikaasjes mei Docker Swarm

Dat, ús kluster is in spektakulêr systeem fan in pear tsientallen yndividuele komponinten net beskreaun troch Dockerfiles. Jo kinne it ynstelle foar ynset yn in spesifike omjouwing allinich yn 't algemien. Us taak is it kluster yn te setten yn in staging-omjouwing om it te testen foardat it testen foarôfgeand is.

Teoretysk kinne d'r ferskate klusters tagelyk wurkje: safolle as d'r taken binne yn in foltôge steat of tichtby foltôging. De krêft fan 'e servers dy't ús beskikke lit ús ferskate klusters op elke server útfiere. Elk staging-kluster moat isolearre wurde (d'r moat gjin oerlaap wêze yn havens, mappen, ensfh.).

Us meast weardefolle boarne is ús tiid, en wy hienen der net folle fan.

Foar in rapper start hawwe wy Docker Swarm keazen fanwegen syn ienfâld en fleksibele arsjitektuer. It earste ding dat wy diene wie in manager en ferskate knopen oanmeitsje op servers op ôfstân:

$ 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

Dêrnei hawwe wy in netwurk makke:


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

Dêrnei ferbûnen wy Gitlab-CI- en Swarm-knooppunten yn termen fan behear op ôfstân fan knopen fan CI: sertifikaten ynstallearje, geheime fariabelen ynstelle, en ek de Docker-tsjinst ynstelle op 'e behearserver. Dizze artikel hat ús in protte tiid bewarre.

Folgjende, wy tafoege banen foar it meitsjen en ferneatigjen fan de steapel yn .gitlab-ci .yml.

Ferskate mear banen binne tafoege oan .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

Ut it boppesteande koade fragmint is it dúdlik dat twa knoppen binne tafoege oan Pipelines (deploy_staging, stop_staging) dy't nedich hânmjittich aksje.

Ynsette applikaasjes mei Docker Swarm
De stacknamme komt oerien mei de branchnamme en dizze unykens soe genôch wêze moatte. Tsjinsten yn 'e stapel ûntfange unike IP-adressen, en havens, mappen, ensfh. sil isolearre wurde, mar itselde fan stapel nei stapel (sûnt it konfiguraasjetriem itselde is foar alle stapels) - dat is wat wy woenen. Wy ynsette de stack (cluster) mei help fan docker-compose.yml, dy't ús kluster beskriuwt.

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

Hjir kinne jo sjen dat de komponinten binne ferbûn troch ien netwurk (nw_swarm) en binne tagonklik foar elkoar.

Systeemkomponinten (basearre op redis, mysql) wurde skieden fan 'e algemiene pool fan oanpaste komponinten (yn plannen wurde oanpaste komponinten ek ferdield as tsjinsten). It ynsetstadium fan ús kluster liket it oerbringen fan CMD nei ús ien grutte konfigureare ôfbylding en is yn 't algemien praktysk net oars as de ynset beskreaun yn diel I. Ik sil de ferskillen beklamje:

  • git kloon... - wy krije de bestannen dy't nedich binne om de ynset út te fieren (createconfig.py, install_venv.sh, ensfh.)
  • krul... && unzip... - download en unzip de build-artefakten (kompilearre nutsbedriuwen)

D'r is mar ien noch net beskreaun probleem: komponinten dy't in webynterface hawwe binne net tagonklik fan 'e browsers fan' e ûntwikkelders. Wy losse dit probleem op mei reverse proxy, sa:

Yn .gitlab-ci.yml, nei it ynsetten fan de klusterstapel, foegje in rigel ta foar it ynsetten fan de balancer (dy't, as ynset, allinich syn konfiguraasje bywurket (nije nginx-konfiguraasjebestannen makket neffens it sjabloan: /etc/nginx/conf.d /${CI_COMMIT_REF_NAME}.conf) - sjoch koade 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

Op ûntwikkelderskompjûters, update /etc/hosts; set de url yn op nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Dat, de ynset fan isolearre staging-klusters is ymplementearre en ûntwikkelders kinne se no lansearje yn elke kwantiteit genôch om har taken te testen.

Takomstplannen:

  • Skied ús komponinten as tsjinsten
  • Meitsje in Dockerfile foar elk
  • Detektearje automatysk minder laden knopen yn 'e stapel
  • Spesifisearje knopen mei in nammesjabloan (ynstee fan id te brûken lykas yn it artikel)
  • Foegje in sjek ta dat de steapel is ferneatige
  • ...

Spesjale tank foar in artikel.

Boarne: www.habr.com

Add a comment