Alkalmazások telepítése Docker Swarm segítségével

Az online videótartalom-ajánló rendszer, amelyen dolgozunk, egy zárt kereskedelmi fejlesztés, és technikailag egy saját és nyílt forráskódú összetevőkből álló többkomponensű klasztert képvisel. A cikk megírásának célja a Docker Swarm klaszterizációs rendszer bevezetésének leírása egy staging környezetben, anélkül, hogy megsértenénk a folyamataink megszokott munkafolyamatát a korlátozott idő alatt. Az Ön elé tárt beszámoló két részre van osztva. Az első rész bemutatja a CI/CD folyamatot a Docker Swarm használata előtt, a második a bevezetés folyamatát írja le. Aki nem kíváncsi az első részre, átugorhat a másodikra.

I. rész

Réges-régen szükség volt a CI/CD folyamat gyors beállítására. Az egyik feltétel az volt, hogy ne használjunk Dockert a telepítéshez kifejlesztett komponensekhez több okból:

  • a komponensek megbízhatóbb és stabilabb működéséhez a Production-ban (tehát gyakorlatilag a virtualizáció használatának elkerülése)
  • a vezető fejlesztők nem akartak dolgozni a Dockerrel (ez furcsa, de így történt)
  • az R&D vezetésének elvi okokból

Az infrastruktúra, a technológiai stack és a MVP-re vonatkozó általános követelmények a következőképpen néztek ki:

  • 4 Intel® X5650 szerver Debian rendszerrel (egy erősebb gép teljesen a fejlesztéshez)
  • A saját egyedi komponensek fejlesztése C++ és Python3 nyelveken történik
  • A fő harmadik féltől származó eszközök: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
  • A komponensek építése és tesztelése különböző csatornákra debug és release

Az egyik első kérdés, amit a kezdeti szakaszban meg kell oldani, hogy hogyan valósul meg a testreszabott komponensek telepítése bármilyen környezetben (CI/CD).

A külső komponenseket rendszerszinten telepítik és frissítik. A C++ vagy Python nyelven fejlesztett egyedi alkalmazásokat többféleképpen is telepíthetjük. Például: rendszerszintű csomagok létrehozása, azok küldése a becsomagolt képek tárolójába és azok későbbi telepítése a szervereken. Ismeretlen ok miatt egy másik módszer lett kiválasztva, nevezetesen: CI segítségével végzik el az alkalmazások végrehajtható fájljainak fordítását, létrejön a projekt virtuális környezete, a requirements.txt-ből telepítődnek a py-modulok, és ezeket az artefaktumokat a konfigurációkkal, szkriptekkel és a kapcsolódó alkalmazás környezettel együtt küldik a szerverekre. Ezt követően az alkalmazásokat virtuális felhasználóként indítják, rendszergazdai jogok nélkül.

A CI/CD rendszerként a Gitlab-CI-t választották. Az elkészült pipeline körülbelül így nézett ki:

Alkalmazások telepítése Docker Swarm segítségével
A gitlab-ci.yml struktúrája a következőképpen alakult:

---
variables:
  # minimális CPU verzió a klaszterek telepítéséhez használt szervereken
  CMAKE_CPUTYPE: "westmere"

  DEBIAN: "MYREGISTRY:5000/debian:latest"

before_script:
  - eval $(ssh-agent -s)
  - ssh-add  ~/.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

Érdemes megjegyezni, hogy a build és tesztelés saját képen történik, ahol már telepítve vannak az összes szükséges rendszerkomponens és egyéb beállítások.

Bár ezek a szkriptek a job-okban mindegyike érdekes a maga módján, de természetesen nem fogok mindegyikről beszélni, mivel a leírásuk jelentős időt venne igénybe, és nem ez a cikk célja. Csak arra szeretnék figyelmet fordítani, hogy a telepítési szakasz a szkriptek sorozatának meghívásából áll:

  1. createconfig.py — létrehozza a settings.ini fájlt a különböző környezetekhez tartozó komponensbeállításokkal a további telepítéshez (Preproduction, Production, Testing stb.)
  2. install_venv.sh — létrehozza a virtuális környezetet a py-komponensek számára egy adott könyvtárban, és átmásolja azt a távoli szerverekre
  3. prepare_init.d.py — előkészíti a komponens indításához és leállításához szükséges szkripteket egy sablon alapján
  4. deploy.py — elhelyezi és újraindítja az új komponenseket

Eltelt az idő. A staging szakaszt preproduction és production váltotta fel. Támogatást kaptunk egy új disztribúcióra (CentOS) is. Öt új, erőteljes fizikai szerver és egy tucat virtuális szerver került bevezetésre. A fejlesztőknek és tesztelőknek egyre nehezebb volt a feladataikat egy munkához közelítő környezetben tesztelni. Ekkor vált világossá, hogy nélküle nem lehet boldogulni…

II. rész

Alkalmazások telepítése Docker Swarm segítségével

Tehát, a klaszterünk tényleg lenyűgöző látványt nyújt, egy pár tucat különálló komponens rendszere, amelyek nincsenek leírva Dockerfile-okban. A telepítéshez a megfelelő környezetbe csak általánosan lehet konfigurálni. A feladatunk az, hogy a klasztert staging környezetbe telepítsük, hogy tesztelhessük a megjelenés előtti tesztelés előtt.

Elméletileg több párhuzamosan működő klaszter is lehet: annyi, ahány feladat van befejezett állapotban vagy közel a befejezéshez. A rendelkezésünkre álló szerverek teljesítménye lehetővé teszi, hogy minden szerveren több klasztert indítsunk. Minden staging klaszternek izoláltnak kell lennie (nem lehet átfedés portokon, könyvtárakon stb.).

A legértékesebb erőforrásunk az időnk, és abból nem volt sok.

A gyorsabb indítás érdekében a Docker Swarm-ot választottuk, mivel egyszerűsége és architektúrájának rugalmassága miatt. Az első, amit tettünk, az volt, hogy létrehoztunk egy menedzsert és néhány csomópontot a távoli szervereken:

$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
kilqc94pi2upzvabttikrfr5d     nop-test-1     Kész                   Aktív                                   19.03.2
jilwe56pl2zvabupryuosdj78     nop-test-2     Kész                   Aktív                                   19.03.2
j5a4yz1kr2xke6b1ohoqlnbq5 *   nop-test-3     Kész                   Aktív              Vezető              19.03.2

Ezután létrehoztuk a hálózatot:


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

Ezután összekapcsoltuk a Gitlab-CI-t és a Swarm node-okat a távoli node-ok CI-ból való irányítása érdekében: tanúsítványok telepítése, titkos változók beállítása, valamint a Docker szolgáltatás konfigurálása a vezérlő szerveren. Ez cikk sokat spórolt nekünk az időnkből.

Ezután hozzátettük a stack létrehozására és megsemmisítésére vonatkozó jobokat a .gitlab-ci.yml fájlhoz.

A .gitlab-ci.yml fájlhoz még néhány job lett hozzáadva

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

A fenti kódrészletből látható, hogy a Pipelines-hoz két gomb (deploy_staging, stop_staging) került, amelyek kézi beavatkozást igényelnek.

Alkalmazások telepítése Docker Swarm segítségével
A stack neve megfelel az ág nevének, és ennek az egyediségnek elegendőnek kell lennie. A stackben lévő szolgáltatások egyedi IP-címeket kapnak, míg a portok, könyvtárak stb. izoláltak, de azonosak maradnak az egyes stackek között (mivel a konfigurációs fájl azonos minden stack esetén) — ezt akartuk elérni. A stack-et (klasztert) a következőkkel kezeljük: docker-compose.yml, amely leírja klaszterünket.

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

Itt látható, hogy a komponensek egy hálózatba (nw_swarm) vannak összekapcsolva, és elérhetők egymás számára.

A rendszerkomponensek (redis, mysql alapú) elkülönülnek az általános egyedi komponens-olvasztótól (továbbiakban az egyedi komponenseket is szolgáltatásként osztjuk meg). A klaszterünk telepítési fázisa úgy tűnik, hogy CMD átadása a nagy konfigurált image-ünknek, és egyáltalán nem különbözik a I. Részben leírt telepítéstől. Kiemelném a különbségeket:

  • git clone … — beszerzzük a telepítéshez szükséges fájlokat (createconfig.py, install_venv.sh stb.)
  • curl… && unzip … — letölthetjük és kitömöríthetjük a build artefaktumokat (kompilált segédeszközök)

Csak egyetlen, még nem dokumentált probléma maradt: az olyan komponensek, amelyek webes interfésszel rendelkeznek, nem érhetők el a fejlesztők böngészőiből. Ezt a problémát reverse proxy segítségével oldjuk meg így:

A .gitlab-ci.yml fájlban a klaszter stack telepítése után hozzáadjuk a terheléselosztó telepítési sorát (amely a commitok során csak a konfigurációját frissíti, új nginx konfigurációs fájlokat hoz létre a következő sablon szerint: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME}.conf) — lásd a docker-compose-nginx.yml kódot

    - 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

A fejlesztők számítógépein frissítjük az /etc/hosts fájlt; beírjuk az nginx-hez vezető URL-t:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Tehát az elszigetelt staging klaszterek telepítése megvalósult, és a fejlesztők most már bármilyen megfelelő mennyiségben elindíthatják őket a feladataik ellenőrzésére.

További tervek:

  • A komponenseinket szolgáltatásokra bontani
  • Mindegyik Dockerfile számára létrehozni
  • Automatikusan meghatározni a terhelésmentesebb node-okat a stackben
  • A node-okat név sablon szerint megadni (nem pedig az azonosítókat használni, mint a cikkben)
  • Ellenőrzést hozzáadni, hogy a stack megsemmisült-e
  • …

Külön köszönet a cikket.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster