Implementer applikationer med Docker Swarm

Det online videoindholdsanbefalingssystem, vi arbejder på, er en lukket kommerciel udvikling og er teknisk set en multikomponent-klynge af proprietære og open source-komponenter. Formålet med at skrive denne artikel er at beskrive implementeringen af ​​docker swarm clustering-systemet til et mellemstation uden at forstyrre den etablerede arbejdsgang af vores processer i en begrænset tid. Den fortælling, der præsenteres for din opmærksomhed, er opdelt i to dele. Den første del beskriver CI / CD før brug af docker swarm, og den anden beskriver processen med dens implementering. De, der ikke er interesserede i at læse første del, kan roligt gå videre til den anden.

Del I

Tilbage i det fjerne, fjerne år var det nødvendigt at sætte CI/CD-processen op så hurtigt som muligt. En af betingelserne var ikke at bruge Docker til indsættelse udviklet komponenter af flere grunde:

  • for mere pålidelig og stabil drift af komponenter i produktionen (det er faktisk kravet om ikke at bruge virtualisering)
  • førende udviklere ønskede ikke at arbejde med Docker (underligt, men sådan var det)
  • efter FoU-ledelsens ideologiske overvejelser

Infrastruktur, stak og omtrentlige indledende krav til MVP blev præsenteret som følger:

  • 4 Intel® X5650-servere med Debian (en mere kraftfuld maskine er fuldt udviklet)
  • Udvikling af egne brugerdefinerede komponenter udføres i C++, Python3
  • Vigtigste tredjepartsværktøjer, der anvendes: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
  • Rørledninger til opbygning og test af komponenter separat til debug og frigivelse

Et af de første spørgsmål, der skal behandles i den indledende fase, er, hvordan brugerdefinerede komponenter vil blive implementeret i ethvert miljø (CI/CD).

Vi besluttede at installere tredjepartskomponenter systemisk og opdatere dem systemisk. Brugerdefinerede applikationer udviklet i C++ eller Python kan implementeres på flere måder. Blandt dem, for eksempel: at oprette systempakker, sende dem til lageret af indbyggede billeder og derefter installere dem på servere. Af en ukendt årsag blev en anden metode valgt, nemlig: ved hjælp af CI kompileres applikations eksekverbare filer, et virtuelt projektmiljø oprettes, py-moduler installeres fra requirements.txt, og alle disse artefakter sendes sammen med konfigurationer, scripts og medfølgende applikationsmiljø til servere. Dernæst lanceres applikationer som en virtuel bruger uden administratorrettigheder.

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

Implementer applikationer med Docker Swarm
Strukturelt så gitlab-ci.yml sådan ud

---
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 værd at bemærke, at montering og test udføres på sit eget billede, hvor alle de nødvendige systempakker allerede er installeret og andre indstillinger er foretaget.

Selvom hver af disse scripts i job er interessant på sin egen måde, men selvfølgelig vil jeg ikke tale om dem. Beskrivelsen af ​​hver af dem vil tage meget tid, og dette er ikke formålet med artiklen. Jeg vil kun henlede din opmærksomhed på det faktum, at implementeringsfasen består af en sekvens af kaldende scripts:

  1. createconfig.py - opretter en settings.ini-fil med komponentindstillinger i forskellige miljøer til efterfølgende implementering (Preproduction, Production, Testing, ...)
  2. install_venv.sh - opretter et virtuelt miljø for py-komponenter i en specifik mappe og kopierer den til fjernservere
  3. prepare_init.d.py — forbereder komponent start-stop scripts baseret på skabelonen
  4. deploy.py - nedbryder og genstarter nye komponenter

Tiden gik. Iscenesættelsesfasen blev erstattet af præproduktion og produktion. Tilføjet support til produktet på endnu en distribution (CentOS). Tilføjet 5 mere kraftfulde fysiske servere og et dusin virtuelle. Og det blev mere og mere vanskeligt for udviklere og testere at teste deres opgaver i et miljø mere eller mindre tæt på arbejdstilstanden. På dette tidspunkt blev det klart, at det var umuligt at undvære ham ...

Del II

Implementer applikationer med Docker Swarm

Så vores klynge er et spektakulært system med et par dusin separate komponenter, der ikke er beskrevet af Dockerfiles. Du kan kun konfigurere den til udrulning til et specifikt miljø generelt. Vores opgave er at implementere klyngen i et iscenesættelsesmiljø for at teste det før test før udgivelse.

Teoretisk set kan der være flere klynger kørende samtidigt: lige så mange som der er opgaver i fuldført tilstand eller tæt på færdiggørelse. Kapaciteten på de servere, vi har til rådighed, giver os mulighed for at køre flere klynger på hver server. Hver iscenesættelsesklynge skal være isoleret (der må ikke være kryds i havne, mapper osv.).

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

For en hurtigere start valgte vi Docker Swarm på grund af dens enkelhed og arkitekturfleksibilitet. Det første vi gjorde var at oprette en manager og flere noder på fjernserverne:

$ 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

Opret derefter et netværk:


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

Dernæst forbandt vi Gitlab-CI og Swarm noder med hensyn til fjernstyring af noder fra CI: installation af certifikater, indstilling af hemmelige variabler og opsætning af Docker-tjenesten på kontrolserveren. Denne artiklen sparede os for meget tid.

Dernæst tilføjede vi stackoprettelse og -destruktionsjob til .gitlab-ci .yml.

Et par flere job er blevet tilføjet 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 ovenstående kodestykke kan du se, at to knapper (deploy_staging, stop_staging) er blevet tilføjet til Pipelines, hvilket kræver manuel handling.

Implementer applikationer med Docker Swarm
Staknavnet matcher filialnavnet, og denne unikhed burde være tilstrækkelig. Tjenester i stakken modtager unikke ip-adresser og porte, mapper osv. vil være isoleret, men det samme fra stak til stak (fordi konfigurationsfilen er den samme for alle stakke) - hvad vi ønskede. Vi implementerer stakken (klyngen) vha havnearbeider-compose.yml, som beskriver vores klynge.

havnearbeider-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 komponenterne er forbundet af ét netværk (nw_swarm) og er tilgængelige for hinanden.

Systemkomponenter (baseret på redis, mysql) er adskilt fra den generelle pulje af brugerdefinerede komponenter (i planer og brugerdefinerede er opdelt som tjenester). Implementeringsstadiet af vores klynge ligner at overføre CMD til vores ene store konfigurerede image og adskiller sig generelt set ikke fra implementeringen beskrevet i del I. Jeg vil fremhæve forskellene:

  • git klon... - Hent de filer, der er nødvendige for at implementere (createconfig.py, install_venv.sh osv.)
  • krølle... && pak ud... - download og udpak byggeartefakter (kompilerede hjælpeprogrammer)

Der er kun ét endnu ubeskrevet problem: Komponenter, der har en webgrænseflade, er ikke tilgængelige fra udviklernes browsere. Vi løser dette problem ved hjælp af omvendt proxy, således:

I .gitlab-ci.yml, efter at have installeret klyngestakken, tilføjer vi linjen for implementering af balanceren (som, når den forpligtes, kun opdaterer dens konfiguration (opretter nye nginx-konfigurationsfiler i henhold til skabelonen: /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å udviklingscomputere skal du opdatere /etc/hosts; ordiner url til nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Så udrulningen af ​​isolerede staging-klynger er blevet implementeret, og udviklere kan nu køre dem i et hvilket som helst antal, der er tilstrækkeligt til at kontrollere deres opgaver.

Fremtidsplaner:

  • Adskil vores komponenter som tjenester
  • Har for hver Dockerfile
  • Registrer automatisk mindre belastede noder i stakken
  • Angiv noder efter navnemønster (i stedet for at bruge id som i artiklen)
  • Tilføj en check på, at stakken er ødelagt
  • ...

Særlig tak for en artikel.

Kilde: www.habr.com

Tilføj en kommentar