Implementacija aplikacij z Docker Swarm

Naša spletna priporočilna sistem za video vsebine, na katerem trenutno delamo, je zaprta komercialna rešitev, ki tehnično predstavlja večkomponentni grozd iz lastnih in odprtokodnih komponent. Namen tega članka je opisati uvedbo sistemske klastracije Docker Swarm za staging okolje, ne da bi kršili ustaljen delovni proces v pogojih omejenega časa. Predstavljena zgodba je razdeljena na dva dela. Prvi del opisuje CI/CD pred uporabo Docker Swarm, drugi pa proces njegove uvedbe. Kdor ni zainteresiran za branje prvega dela, lahko takoj preide na drugega.

Del I

V nekem oddaljenem letu je bilo potrebno čim hitreje nastaviti CI/CD proces. Ena od zahtev je bila, da ne uporabljamo Dockera za izvajanje razvijajočih se komponent iz več razlogov:

  • zaradi bolj zanesljive in stabilne delovanja komponent v produkciji (torej v bistvu zahteva po neprenosu virtualizacije)
  • glavni razvijalci niso želeli delati z Dockerjem (nenavadno, ampak bilo je tako)
  • iz ideoloških razlogov vodstva R&D

Infrastruktura, tehnološki sklad in približne izvorne zahteve za MVP so bile naslednje:

  • 4 strežniki Intel® X5650 z Debianom (ena močnejša naprava povsem namenjena razvoju)
  • Razvoj lastnih prilagojenih komponent poteka v C++, Python3
  • Glavna 3rdparty orodja: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
  • Cevi za sestavljanje in testiranje komponent posebej za debug in release

Ena prvih vprašanj, ki jih je treba rešiti v začetni fazi, je, kako bo potekalo uvajanje prilagojenih komponent v katerem koli okolju (CI/CD).

Tretja komponenta se je odločila, da jih postavi in posodobi sistemsko. Prilagojene aplikacije, razvite v C++ ali Pythonu, pa je mogoče postaviti na več načinov. Med njimi so, na primer: ustvarjanje sistemskih paketov, njihovo pošiljanje v repozitorij zbranih slik in njihova nadaljnja namestitev na strežnike. Iz neznanega razloga je bila izbrana drugačna pot, in sicer: s CI se kompilirajo izvršljive datoteke aplikacij, ustvarja se virtualno okolje projekta, nameščajo se py-moduli iz requirements.txt in vsi ti artefakti se skupaj s konfiguracijami, skripti in pripadajočim okoljem aplikacij pošljejo na strežnike. Nato se aplikacije zaženejo z virtualnim uporabnikom brez administrativnih pravic.

Kot CI/CD sistem je bil izbran Gitlab-CI. Nastali pipeline je izgledal nekako takole:

Implementacija aplikacij z Docker Swarm
Strukturno je gitlab-ci.yml izgledal tako

---
variables:
  # minimalna različica CPU na strežnikih, kjer se postavi grozd
  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

Pomembno je omeniti, da se gradnja in testiranje izvajata na svoji lastni sliki, kjer so že nameščeni vsi potrebni sistemski paketi in opravljene druge nastavitve.

Čeprav je vsak od teh skriptov v nalogah zanimiv po svoje, o njih seveda ne bom govoril, opis vsakega od njih bi zahteval znatno časa in to ni namen članka. Le opozoril bom, da faza implementacije vključuje zaporedje klicev skriptov:

  1. createconfig.py — ustvari datoteko settings.ini s konfiguracijami komponent v različnih okoljih za nadaljnjo implementacijo (Preproduction, Production, Testing, …)
  2. install_venv.sh — ustvari virtualno okolje za py-komponente v določeni mapi in ga kopira na oddaljen strežnik
  3. prepare_init.d.py — pripravi skripte za zagon-in-ustavitev komponent na podlagi predloge
  4. deploy.py — razporedi in znova zažene nove komponente

Minil je čas. Fazo staging je nadomestila preproduction in production. Dodana je bila podpora za izdelek na še eni distribuciji (CentOS). Dodanih je bilo še 5 močnih fizičnih strežnikov in deset virtualnih. Razvijalcem in testerjem je postajalo vse težje preizkušati svoje naloge v okolju, ki je bilo vsaj delno blizu delovnemu stanju. V tem času je postalo jasno, da brez njega ne gre…

Del II

Implementacija aplikacij z Docker Swarm

Naš klaster predstavlja pravi spektakel, sistem iz več deset posameznih komponent, ki niso opisane z Dockerfile-ji. Konfigurirati ga za implementacijo v določeno okolje je možno le celovito. Naša naloga je, da uvedemo klaster v staging okolje za preizkušanje pred predizdajnim testiranjem.

Tehnično lahko deluje več klastrov hkrati: toliko, kot je nalog v zaključeni ali skoraj zaključeni fazi. Moč, ki jo imamo na voljo na naših strežnikih, omogoča zagon več klastrov na vsakem strežniku. Vsak staging klaster mora biti izoliran (brez prekrivanja po vratih, mapah itd.).

Najdragocenejši vir je naš čas, in tega imamo malo.

Za hitrejši zagon smo izbrali Docker Swarm zaradi njegove preprostosti in fleksibilne arhitekture. Prvo, kar smo naredili, je, da smo na oddaljenih strežnikih ustvarili upravljalca in več vozlišč:

$ 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

Nato smo ustvarili omrežje:


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

Nato smo povezali Gitlab-CI in vozlišča Swarm za oddaljeno upravljanje vozlišč iz CI: namestitev certifikatov, nastavitev tajnih spremenljivk ter nastavitev storitve Docker na nadzornem strežniku. To nam članek je močno prihranilo čas.

Nato smo dodali naloge za ustvarjanje in uničenje sklada v .gitlab-ci.yml.

V .gitlab-ci.yml je bilo dodanih še nekaj nalog.

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

Iz zgornjega dela kode je razvidno, da sta se v ceveh dodali dve gumbki (deploy_staging, stop_staging), ki zahtevata ročno posredovanje.

Implementacija aplikacij z Docker Swarm
Ime sklada ustreza imenu veje in te edinstvenosti bi moralo biti dovolj. Storitev v skladu pridobi edinstvene IP-naslove, medtem ko so vrata, imeniki itd. izolirani, vendar enaki od sklada do sklada (ker je konfiguracijska datoteka enaka za vse sklope) — kar je ravno to, kar smo želeli. Sklad (grozd) postavljamo z docker-compose.yml, v katerem je opisan naš grozd.

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

Tukaj lahko vidimo, da so komponente združene v eno omrežje (nw_swarm) in so med seboj dostopne.

Sistemske komponente (na osnovi redis in mysql) so ločene od skupnega bazena prilagojenih komponent (v načrtu je še ločevanje prilagojenih storitev). Faza nameščanja našega klasterja je videti kot prenos CMD v našo veliko konfigurirano sliko in se na splošno praktično ne razlikuje od nameščanja, opisanega v delu I. Poudarjam razlike:

  • git clone … — pridobimo datoteke, potrebne za izvedbo nameščanja (createconfig.py, install_venv.sh itd.)
  • curl… && unzip … — prenesemo in razpakiramo artefakte zgradnje (skompilirane pripomočke)

Ostala je samo jedan još neizvešten problem: komponente koje imaju veb interfejs nisu dostupne iz pretraživača programera. Ovaj problem rešavamo pomoću reverse proxy-a na sledeći način:

U .gitlab-ci.yml, nakon implementacije steka klastera, dodajemo liniju za implementaciju balansiranja opterećenja (koji pri commitima samo ažurira svoju konfiguraciju (stvara nove konfiguracione fajlove nginx prema šablonu: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME}.conf) — vidi kod 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

Na računarima programera ažuriramo /etc/hosts; upisujemo url do nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Dakle, implementacija izolovanih staging klastera je realizovana i programeri sada mogu pokretati ih u bilo kojem dovoljnom broju za testiranje svojih zadataka.

Dalji planovi:

  • Podeliti naše komponente kao usluge
  • Napravi za svaki Dockerfile
  • Automatski određivati manje opterećene čvorove u steku
  • Postavljati čvorove po šablonu imena (a ne koristiti id kao u članku)
  • Dodati proveru da je stek uništen
  • …

Posebna zahvalnost za članak.

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster