Distribuer applikasjoner med Docker Swarm

Systemet for anbefaling av online videoinnhold vi jobber med er en lukket kommersiell utvikling og er teknisk sett en multikomponentklynge av proprietære og åpen kildekodekomponenter. Formålet med å skrive denne artikkelen er å beskrive implementeringen av docker swarm clustering-systemet for et oppsamlingssted uten å forstyrre den etablerte arbeidsflyten til prosessene våre i løpet av en begrenset tid. Fortellingen som presenteres for din oppmerksomhet er delt inn i to deler. Den første delen beskriver CI / CD før du bruker docker swarm, og den andre beskriver prosessen med implementeringen. De som ikke er interessert i å lese den første delen kan trygt gå videre til den andre.

Del I

Tilbake i det fjerne, fjerne året var det nødvendig å sette opp CI/CD-prosessen så raskt som mulig. En av betingelsene var å ikke bruke Docker for utplassering utviklet komponenter av flere grunner:

  • for mer pålitelig og stabil drift av komponenter i produksjon (det vil faktisk si kravet om å ikke bruke virtualisering)
  • ledende utviklere ønsket ikke å jobbe med Docker (rart, men det var slik det var)
  • etter FoU-ledelsens ideologiske betraktninger

Infrastruktur, stabel og omtrentlige startkrav for MVP ble presentert som følger:

  • 4 Intel® X5650-servere med Debian (en kraftigere maskin er fullt utviklet)
  • Utvikling av egne tilpassede komponenter utføres i C++, Python3
  • De viktigste tredjepartsverktøyene som brukes: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
  • Rørledninger for å bygge og teste komponenter separat for feilsøking og utgivelse

Et av de første spørsmålene som må tas opp i den innledende fasen er hvordan tilpassede komponenter vil bli distribuert i ethvert miljø (CI / CD).

Vi bestemte oss for å installere tredjepartskomponenter systemisk og oppdatere dem systemisk. Tilpassede applikasjoner utviklet i C++ eller Python kan distribueres på flere måter. Blant dem, for eksempel: å lage systempakker, sende dem til depotet for bygde bilder og deretter installere dem på servere. Av en ukjent grunn ble en annen metode valgt, nemlig: ved bruk av CI kompileres applikasjonskjørbare filer, et virtuelt prosjektmiljø opprettes, py-moduler installeres fra requirements.txt, og alle disse artefaktene sendes sammen med konfigurasjoner, skript og medfølgende applikasjonsmiljø til servere. Deretter lanseres applikasjoner som en virtuell bruker uten administratorrettigheter.

Gitlab-CI ble valgt som CI/CD-systemet. Den resulterende rørledningen så omtrent slik ut:

Distribuer applikasjoner med Docker Swarm
Strukturelt sett så gitlab-ci.yml slik ut

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

Det er verdt å merke seg at montering og testing utføres på sitt eget bilde, der alle nødvendige systempakker allerede er installert og andre innstillinger er gjort.

Selv om hvert av disse skriptene i jobber er interessant på sin egen måte, men jeg vil selvfølgelig ikke snakke om dem. Beskrivelsen av hver av dem vil ta mye tid, og dette er ikke formålet med artikkelen. Jeg vil bare trekke oppmerksomheten din til det faktum at distribusjonsstadiet består av en sekvens med kalleskript:

  1. createconfig.py - oppretter en settings.ini-fil med komponentinnstillinger i ulike miljøer for påfølgende distribusjon (forproduksjon, produksjon, testing, ...)
  2. install_venv.sh - oppretter et virtuelt miljø for py-komponenter i en bestemt katalog og kopierer det til eksterne servere
  3. prepare_init.d.py — forbereder start-stopp-skript for komponenten basert på malen
  4. deploy.py - bryter ned og starter nye komponenter på nytt

Tiden gikk. Iscenesettelsen ble erstattet av preproduksjon og produksjon. Lagt til støtte for produktet på en distribusjon til (CentOS). Lagt til 5 kraftigere fysiske servere og et dusin virtuelle. Og det ble stadig vanskeligere for utviklere og testere å teste oppgavene sine i et miljø mer eller mindre nær arbeidstilstanden. På dette tidspunktet ble det klart at det var umulig å klare seg uten ham ...

Del II

Distribuer applikasjoner med Docker Swarm

Så klyngen vår er et spektakulært system med et par dusin separate komponenter som ikke er beskrevet av Dockerfiles. Du kan bare konfigurere den for distribusjon til et spesifikt miljø generelt. Vår oppgave er å distribuere klyngen i et oppsamlingsmiljø for å teste det før testing før utgivelse.

Teoretisk sett kan det være flere klynger som kjører samtidig: så mange som det er oppgaver i fullført tilstand eller nær fullføring. Kapasiteten til serverne vi har til rådighet gjør at vi kan kjøre flere klynger på hver server. Hver iscenesettelsesklynge må være isolert (det må ikke være noe kryss i porter, kataloger osv.).

Vår mest verdifulle ressurs er vår tid, og vi hadde ikke mye av den.

For en raskere start valgte vi Docker Swarm på grunn av dens enkelhet og arkitekturfleksibilitet. Det første vi gjorde var å opprette en manager og flere noder på de eksterne serverne:

$ 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

Deretter oppretter du et nettverk:


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

Deretter koblet vi sammen Gitlab-CI og Swarm-noder når det gjelder fjernkontroll av noder fra CI: installasjon av sertifikater, innstilling av hemmelige variabler og oppsett av Docker-tjenesten på kontrollserveren. Denne artikkel sparte oss for mye tid.

Deretter la vi til stackopprettings- og destruksjonsjobber til .gitlab-ci .yml.

Noen flere jobber er lagt til .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

Fra kodebiten ovenfor kan du se at to knapper (deploy_staging, stop_staging) er lagt til Pipelines, som krever manuell handling.

Distribuer applikasjoner med Docker Swarm
Stabelnavnet samsvarer med filialnavnet, og denne unikheten bør være tilstrekkelig. Tjenester i stabelen mottar unike ip-adresser, og porter, kataloger osv. vil være isolert, men det samme fra stabel til stabel (fordi konfigurasjonsfilen er lik for alle stabler) - det vi ønsket. Vi distribuerer stabelen (klyngen) ved hjelp av Docker-compose.yml, som beskriver klyngen vår.

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

Her kan du se at komponentene er koblet sammen av ett nettverk (nw_swarm) og er tilgjengelige for hverandre.

Systemkomponenter (basert på redis, mysql) er atskilt fra den generelle utvalget av tilpassede komponenter (i planer og tilpassede er de delt inn som tjenester). Implementeringsstadiet av klyngen vår ser ut som å overføre CMD til vårt ene store konfigurerte bilde, og generelt sett skiller det seg praktisk talt ikke fra distribusjonen beskrevet i del I. Jeg vil fremheve forskjellene:

  • git klone... - få filene som trengs for å distribuere (createconfig.py, install_venv.sh, etc.)
  • krølle... && pakke ut... - last ned og pakk ut byggeartefakter (kompilerte verktøy)

Det er bare ett ennå ubeskrevet problem: komponenter som har et nettgrensesnitt er ikke tilgjengelige fra utviklerens nettlesere. Vi løser dette problemet ved å bruke omvendt proxy, slik:

I .gitlab-ci.yml, etter å ha distribuert klyngestakken, legger vi til linjen for å distribuere balanseren (som, når den er forpliktet, bare oppdaterer konfigurasjonen (oppretter nye nginx-konfigurasjonsfiler i henhold til malen: /etc/nginx/conf. d/${CI_COMMIT_REF_NAME}.conf) – se docker-compose-nginx.yml-kode)

    - 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

På utviklingsdatamaskiner, oppdater /etc/hosts; foreskriv url til nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Så utrullingen av isolerte staging-klynger er implementert, og utviklere kan nå kjøre dem i et hvilket som helst antall nok til å sjekke oppgavene deres.

Fremtidsplaner:

  • Skill komponentene våre som tjenester
  • Har for hver Dockerfile
  • Registrer automatisk mindre belastede noder i stabelen
  • Spesifiser noder etter navnemønster (i stedet for å bruke id som i artikkelen)
  • Legg til en sjekk for at stabelen er ødelagt
  • ...

Spesiell takk for artikkel.

Kilde: www.habr.com

Legg til en kommentar