Docker Swarm yordamida ilovalarni joylashtiring

Biz ustida ishlayotgan onlayn video kontentni tavsiya qilish tizimi yopiq tijorat ishlanmasi boʻlib, texnik jihatdan xususiy va ochiq manba komponentlarining koʻp komponentli klasteridir. Ushbu maqolani yozishdan maqsad cheklangan vaqt sharoitida jarayonlarimizning belgilangan ish oqimini buzmasdan, bosqichma-bosqich platforma uchun docker to'dasi klasterlash tizimini amalga oshirishni tasvirlashdir. Sizning e'tiboringizga taqdim etilgan hikoya ikki qismga bo'lingan. Birinchi qismda docker to'dasidan foydalanishdan oldin CI/CD tasvirlangan, ikkinchi qismda uni amalga oshirish jarayoni tasvirlangan. Birinchi qismni o'qishga qiziqmaganlar ikkinchi qismga ishonch bilan o'tishlari mumkin.

I qism

Bir vaqtlar CI/CD jarayonini imkon qadar tezroq o'rnatish zarurati bor edi. Shartlardan biri Docker-dan foydalanmaslik edi joylashtirish uchun Komponentlar bir necha sabablarga ko'ra ishlab chiqilmoqda:

  • Ishlab chiqarishda komponentlarning yanada ishonchli va barqaror ishlashi uchun (ya'ni, virtualizatsiyadan foydalanmaslik talabi)
  • Etakchi ishlab chiquvchilar Docker bilan ishlashni xohlamadilar (g'alati, lekin shunday edi)
  • Ar-ge boshqaruvining mafkuraviy sabablari uchun

MVP uchun infratuzilma, stack va taxminiy dastlabki talablar quyidagicha edi:

  • Debian bilan 4 ta Intel® X5650 serverlari (ishlab chiqish uchun yana bitta kuchli mashina)
  • O'zingizning shaxsiy komponentlaringizni ishlab chiqish C++, Python3 da amalga oshiriladi
  • Ishlatilgan asosiy uchinchi tomon vositalari: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql,…
  • Nosozliklarni tuzatish va chiqarish uchun komponentlarni alohida qurish va sinovdan o'tkazish uchun quvurlar

Dastlabki bosqichda hal qilinishi kerak bo'lgan birinchi savollardan biri - har qanday muhitda (CI/CD) maxsus komponentlar qanday joylashtirilishi.

Biz uchinchi tomon komponentlarini tizimli ravishda o'rnatishga va ularni tizimli ravishda yangilashga qaror qildik. C++ yoki Python-da ishlab chiqilgan maxsus ilovalar bir necha usulda joylashtirilishi mumkin. Ular orasida, masalan: tizim paketlarini yaratish, ularni to'plangan tasvirlar omboriga yuborish va ularni keyinchalik serverlarga o'rnatish. Noma'lum sababga ko'ra, boshqa usul tanlandi, ya'ni: CI yordamida dasturning bajariladigan fayllari kompilyatsiya qilinadi, virtual loyiha muhiti yaratiladi, talablar.txt dan py modullari o'rnatiladi va bu barcha artefaktlar konfiguratsiyalar, skriptlar va fayllar bilan birga yuboriladi. serverlarga ilova muhiti. Keyinchalik, ilovalar administrator huquqlariga ega bo'lmagan virtual foydalanuvchidan ishga tushiriladi.

Gitlab-CI CI/CD tizimi sifatida tanlangan. Olingan quvur liniyasi quyidagicha ko'rinishga ega edi:

Docker Swarm yordamida ilovalarni joylashtiring
Strukturaviy ravishda gitlab-ci.yml quyidagicha ko'rinadi:

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

Ta'kidlash joizki, yig'ish va sinovdan o'tkazish o'z imidjida amalga oshiriladi, bu erda barcha kerakli tizim paketlari allaqachon o'rnatilgan va boshqa sozlamalar amalga oshiriladi.

Ishdagi ushbu skriptlarning har biri o'ziga xos tarzda qiziqarli bo'lsa-da, men ular haqida gapirmayman, ularning har birini tavsiflash ko'p vaqt talab etadi va bu maqolaning maqsadi emas. Sizning e'tiboringizni tarqatish bosqichi chaqiruv skriptlari ketma-ketligidan iboratligiga qarataman:

  1. createconfig.py — keyingi joylashtirish uchun turli muhitdagi komponentlar sozlamalari bilan settings.ini faylini yaratadi (Preproduction, Production, Testing, ...)
  2. install_venv.sh — maʼlum bir katalogdagi py komponentlari uchun virtual muhit yaratadi va uni masofaviy serverlarga koʻchiradi
  3. Preparat_init.d.py — shablon asosida start-stop komponentlari uchun skriptlarni tayyorlaydi
  4. deploy.py — yangi komponentlarni joylashtiradi va qayta ishga tushiradi

Vaqt o'tdi. Sahna bosqichi oldingi ishlab chiqarish va ishlab chiqarish bilan almashtirildi. Mahsulotni qo'llab-quvvatlash yana bir tarqatishda (CentOS) qo'shildi. Yana 5 ta kuchli jismoniy server va oʻnlab virtual serverlar qoʻshildi. Va ishlab chiquvchilar va testerlar uchun o'z vazifalarini ish holatiga ko'proq yoki kamroq yaqin muhitda sinab ko'rish tobora qiyinlashdi. Bu vaqtda usiz qilish mumkin emasligi ma'lum bo'ldi ...

II qism

Docker Swarm yordamida ilovalarni joylashtiring

Shunday qilib, bizning klasterimiz Dockerfiles tomonidan tasvirlanmagan o'nlab individual komponentlarning ajoyib tizimidir. Siz uni faqat umumiy holda ma'lum bir muhitga joylashtirish uchun sozlashingiz mumkin. Bizning vazifamiz klasterni relizdan oldingi sinovdan oldin sinab ko'rish uchun staging muhitiga joylashtirishdir.

Nazariy jihatdan, bir vaqtning o'zida bir nechta klasterlar bo'lishi mumkin: bajarilgan holatda yoki yakunlanishiga yaqin bo'lgan vazifalar qancha bo'lsa. Bizning ixtiyorimizdagi serverlarning kuchi har bir serverda bir nechta klasterlarni ishga tushirish imkonini beradi. Har bir bosqichli klaster izolyatsiyalangan bo'lishi kerak (portlar, kataloglar va hokazolarda bir-biriga o'xshash bo'lmasligi kerak).

Bizning eng qimmatli manbamiz - bu vaqt va bizda unchalik ko'p bo'lmagan.

Tezroq boshlash uchun biz soddaligi va moslashuvchan arxitekturasi tufayli Docker Swarm-ni tanladik. Biz qilgan birinchi narsa menejer va masofaviy serverlarda bir nechta tugunlarni yaratish edi:

$ 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

Keyin biz tarmoq yaratdik:


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

Keyinchalik, biz Gitlab-CI va Swarm tugunlarini CI-dan tugunlarni masofadan boshqarish nuqtai nazaridan bog'ladik: sertifikatlarni o'rnatish, maxfiy o'zgaruvchilarni o'rnatish, shuningdek boshqaruv serverida Docker xizmatini sozlash. Bunisi maqola ko'p vaqtimizni tejadi.

Keyinchalik, .gitlab-ci .yml da stekni yaratish va yo'q qilish uchun ishlarni qo'shdik.

.gitlab-ci .yml ga yana bir nechta ish o'rinlari qo'shildi

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

Yuqoridagi kod bo'lagidan ko'rinib turibdiki, qo'lda harakat talab qiladigan Pipelines (deploy_staging, stop_staging) ga ikkita tugma qo'shilgan.

Docker Swarm yordamida ilovalarni joylashtiring
Stack nomi filial nomiga mos keladi va bu o'ziga xoslik etarli bo'lishi kerak. Stackdagi xizmatlar noyob IP-manzillar va portlar, kataloglar va boshqalarni oladi. izolyatsiya qilinadi, lekin stekdan stekga bir xil (chunki konfiguratsiya fayli barcha steklar uchun bir xil) - biz shuni xohladik. Biz yordamida stekni (klaster) joylashtiramiz docker-compose.yml, bu bizning klasterimizni tavsiflaydi.

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

Bu yerda siz komponentlar bir tarmoq (nw_swarm) orqali ulanganligini va bir-biriga kirish mumkinligini ko'rishingiz mumkin.

Tizim komponentlari (redis, mysql asosida) maxsus komponentlarning umumiy pulidan ajratiladi (rejalarda moslashtirilgan komponentlar ham xizmatlar sifatida ajratiladi). Klasterimizni joylashtirish bosqichi CMD-ni bitta katta konfiguratsiya qilingan rasmga o'tkazishga o'xshaydi va umuman olganda, I qismda tasvirlangan joylashtirishdan deyarli farq qilmaydi. Men farqlarni ta'kidlayman:

  • git klon ... - biz joylashtirishni amalga oshirish uchun kerakli fayllarni olamiz (createconfig.py, install_venv.sh va boshqalar).
  • jingalaklash... && arxivni ochish... — qurilish artefaktlarini yuklab oling va oching (kompilyatsiya qilingan yordam dasturlari)

Hali ta'riflanmagan bitta muammo bor: veb-interfeysga ega bo'lgan komponentlarga ishlab chiquvchilar brauzeridan kirish imkoni yo'q. Biz bu muammoni teskari proksi-server yordamida hal qilamiz, shunday qilib:

.gitlab-ci.yml da, klaster stekini joylashtirgandan so'ng, balanslashtiruvchini joylashtirish uchun qatorni qo'shing (bu bajarilganda faqat uning konfiguratsiyasini yangilaydi (shablonga muvofiq yangi nginx konfiguratsiya fayllarini yaratadi: /etc/nginx/conf.d). /${CI_COMMIT_REF_NAME}.conf) - docker-compose-nginx.yml kodiga qarang)

    - 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

Ishlab chiquvchi kompyuterlarda /etc/hosts; urlni nginx ga o'rnating:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Shunday qilib, izolyatsiyalangan bosqichli klasterlarni joylashtirish amalga oshirildi va endi ishlab chiquvchilar ularni o'z vazifalarini sinab ko'rish uchun etarli bo'lgan miqdorda ishga tushirishlari mumkin.

Kelgusi rejalar:

  • Komponentlarimizni xizmatlar sifatida ajrating
  • Har biri uchun Dockerfile yarating
  • Stackdagi kamroq yuklangan tugunlarni avtomatik aniqlash
  • Nod shablonidan foydalanib tugunlarni belgilang (maqoladagi kabi identifikatordan foydalanish o'rniga)
  • Stack yo'q qilinganligini tasdiqlovchi belgi qo'shing
  • ...

uchun alohida rahmat maqola.

Manba: www.habr.com

a Izoh qo'shish