Udrul applikationer med Docker Swarm

Det online anbefalingssystem for videoinhold, som vi arbejder på, er et lukket kommercielt udviklingsprojekt og består teknisk af en flerkomponent cluster bestående af både egne og open source komponenter. Formålet med denne artikel er at beskrive implementeringen af en Docker Swarm klustersystem til staging-miljøet, uden at forstyrre den etablerede workflow af vores processer under begrænset tid. Denne fortælling er opdelt i to dele. Den første del beskriver CI/CD før brugen af Docker Swarm, mens den anden del omhandler processen for dens implementering. For dem, der ikke er interesserede i at læse første del, kan de roligt springe til anden del.

Del I

I et fjernt år var det nødvendigt at opsætte CI/CD-processen så hurtigt som muligt. Et af betingelserne var ikke at bruge Docker til deployment af de udviklede komponenter af flere grunde:

  • for at sikre mere pålidelig og stabil drift af komponenter i produktion (dvs., det var i realiteten en krav om ikke at bruge virtualisering)
  • de førende udviklere ønskede ikke at arbejde med Docker (mærkeligt, men sådan var det)
  • af ideologiske grunde fra R&D ledelsen

Infrastrukturen, stakken og de omtrentlige indledende krav til MVP var som følger:

  • 4 servere Intel® X5650 med Debian (en mere kraftfuld maskine til udvikling)
  • Udviklingen af egne specialbyggede komponenter foregår i C++, Python3
  • De primære 3rd party værktøjer der anvendes: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
  • Pipelines til opbygning og test af komponenter separat for debug og release

Et af de første spørgsmål, der skal besvares i den indledende fase, er, hvordan de specialbyggede komponenter vil blive rullet ud i et givent miljø (CI/CD).

Tredjeparts komponenter besluttede at installere og opdatere systemisk. Custom-apps, der udvikles i C++ eller Python, kan implementeres på flere måder. Blandt dem er for eksempel: oprettelse af systempakker, afsendelse til et repository af sammensatte billeder og derefter installation på servere. Af en eller anden grund blev der valgt en anden metode, nemlig: med CI kompilere de eksekverbare filer til applikationerne, oprette et virtuelt projektmiljø, installere py-moduler fra requirements.txt, og alle disse artefakter sendes sammen med konfigurationer, scripts og det tilhørende applikationsmiljø til serverne. Derefter startes applikationerne af en virtuel bruger uden administratorrettigheder.

Gitlab-CI blev valgt som CI/CD-system. Den resulterende pipeline så omtrent sådan ud:

Udrul applikationer med Docker Swarm
Strukturelt så gitlab-ci.yml således ud

---
variables:
  # Minimum CPU-version på serverne, hvor klyngen implementeres
  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

Det skal bemærkes, at bygning og testning udføres på sit eget billede, hvor alle nødvendige systempakker allerede er installeret, og andre indstillinger er blevet udført.

Selvom hver af disse scripts i jobberne er interessante på sin egen måde, vil jeg selvfølgelig ikke beskrive dem, da det ville tage betydelig tid, og det er ikke formålet med artiklen. Jeg vil blot påpege, at deploy-stadiet består af en sekvens af scriptopkald:

  1. createconfig.py — opretter en fil settings.ini med komponentindstillinger i forskellige miljøer til efterfølgende deployment (Preproduction, Production, Testing, …)
  2. install_venv.sh — opretter et virtuelt miljø for py-komponenter i en bestemt mappe og kopierer det til fjernservere
  3. prepare_init.d.py — forbereder start-stop scripts for komponenter baseret på en skabelon
  4. deploy.py — placer og genstart nye komponenter

Tiden gik. Staging-fasen blev erstattet af preproduction og production. Der kom support til produktet på endnu en distribution (CentOS). Der kom yderligere 5 kraftige fysiske servere og et dusin virtuelle. Det blev gradvist mere udfordrende for udviklere og testere at køre deres opgaver i et miljø, der var nogenlunde tæt på en driftssituation. I den tid blev det klart, at det var umuligt at klare sig uden det...

Del II

Udrul applikationer med Docker Swarm

Så, vores cluster er et imponerende syn – et system af et par dusiner separate komponenter, der ikke er beskrevet med Dockerfiles. At konfigurere det til deployment i et bestemt miljø kan kun gøres samlet. Vores opgave er at deploye clustret i staging-miljøet for at teste det før pre-release test.

Teoretisk set kan der være flere kørende clustre: så mange som der er opgaver i afsluttet eller næsten afsluttet tilstand. Kapaciteten på de servere, vi har til rådighed, tillader opstart af flere clustre på hver server. Hver staging-cluster skal være isoleret (der må ikke være overlap i porte, mapper osv.).

Den mest værdifulde ressource er vores tid, og vi havde ikke meget af den.

For at få en hurtigere start valgte vi Docker Swarm på grund af dets enkelhed og fleksible arkitektur. Det første, vi gjorde, var at oprette en manager og flere noder på de fjernservere:

$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
kilqc94pi2upzvabttikrfr5d     nop-test-1     Klar               Aktiv                                  19.03.2
jilwe56pl2zvabupryuosdj78     nop-test-2     Klar               Aktiv                                  19.03.2
j5a4yz1kr2xke6b1ohoqlnbq5 *   nop-test-3     Klar               Aktiv              Leder              19.03.2

Herefter oprettede vi et netværk:


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

Herefter forbinde vi Gitlab-CI og Swarm noderne med hensyn til fjernstyring af noder fra CI: installation af certifikater, opsætning af hemmelige variabler samt konfiguration af Docker-tjenesten på administratorserveren. Dette artikel sparede os betydelig tid.

Herefter tilføjede vi job til oprettelse og sletning af stakken i .gitlab-ci.yml.

Der er tilføjet flere job i .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 det ovenstående kodeudsnit kan vi se, at der er tilføjet to knapper (deploy_staging, stop_staging) i Pipelines, som kræver manuel interaktion.

Udrul applikationer med Docker Swarm
Stakens navn svarer til navnene på grenen, og denne unikke identitet bør være tilstrækkelig. Tjenester i stakken får unikke IP-adresser, og porte, mapper osv. vil være isolerede, men ens fra stak til stak (da konfigurationsfilen er identisk for alle stakke) — dette var vores mål. Stak (klynge) ruller vi ud med hjælp af docker-compose.yml, der beskriver vores klynge.

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 man se, at komponenterne er forbundet via et netværk (nw_swarm) og kan tilgå hinanden.

Systemkomponenter (baseret på redis, mysql) er adskilt fra det almindelige pool af brugerdefinerede komponenter (planer om også at segmentere brugerdefinerede som tjenester). Deploy-stadiet af vores klynge ser ud som overførsel af CMD til vores store konfigurerede image og adskiller sig næsten ikke fra den deploy, der er beskrevet i Del I. Jeg vil understrege forskellene:

  • git clone … — vi henter de nødvendige filer for at gennemføre deploy (createconfig.py, install_venv.sh osv.)
  • curl… && unzip … — vi downloader og udpakker byggearteen (de kompilerede værktøjer)

Der er kun ét problem tilbage, som endnu ikke er beskrevet: komponenter, der har et webinterface, er ikke tilgængelige fra udviklernes browsere. Vi løser dette problem ved hjælp af en reverse proxy, således:

I .gitlab-ci.yml, efter deployment af klyngens stack, tilføjer vi en linje til deployment af load-balancer (som kun opdaterer sin konfiguration ved commits (skaber nye konfigurationsfiler for nginx efter skabelonen: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME}.conf) — se koden 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

På udviklernes computere opdaterer vi /etc/hosts; vi skriver URL'en til nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Så deployment af isolerede staging-klynger er implementeret, og udviklerne kan nu starte dem i det antal, der er tilstrækkeligt til at teste deres opgaver.

Fremtidige planer:

  • At opdele vores komponenter som tjenester
  • At oprette en Dockerfile for hver
  • At automatisk identificere mindre belastede noder i stacken
  • At angive noder efter navneskabelon (i stedet for at bruge id som i artiklen)
  • At tilføje kontrol, så stacken er destrueret
  • …

En særlig tak for artiklen.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster