Implementați aplicații cu Docker Swarm

Sistemul de recomandare a conținutului video online la care lucrăm este o dezvoltare comercială închisă și, din punct de vedere tehnic, este un cluster multicomponent de componente proprietare și open source. Scopul scrierii acestui articol este de a descrie implementarea sistemului de clustering docker swarm pentru o platformă intermediară, fără a perturba fluxul de lucru stabilit al proceselor noastre în condiții de timp limitat. Narațiunea prezentată atenției dumneavoastră este împărțită în două părți. Prima parte descrie CI/CD înainte de a utiliza docker swarm, iar a doua parte descrie procesul de implementare a acestuia. Cei care nu sunt interesați să citească prima parte pot trece în siguranță la a doua.

Partea I

Pe vremuri, era nevoie să se înființeze un proces CI/CD cât mai repede posibil. Una dintre condiții a fost să nu folosești Docker pentru desfășurare componentele fiind dezvoltate din mai multe motive:

  • pentru o funcționare mai fiabilă și mai stabilă a componentelor în producție (adică, în esență, cerința de a nu utiliza virtualizarea)
  • dezvoltatorii de top nu au vrut să lucreze cu Docker (ciudat, dar așa a fost)
  • din motive ideologice ale managementului C&D

Infrastructura, stiva și cerințele inițiale aproximative pentru MVP au fost următoarele:

  • 4 servere Intel® X5650 cu Debian (o mașină mai puternică complet pentru dezvoltare)
  • Dezvoltarea propriilor componente personalizate se realizează în C++, Python3
  • Principalele instrumente terțe utilizate: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
  • Conducte pentru construirea și testarea componentelor separat pentru depanare și lansare

Una dintre primele întrebări care trebuie rezolvată în etapa inițială este modul în care componentele personalizate vor fi implementate în orice mediu (CI/CD).

Am decis să instalăm componente terțe în mod sistemic și să le actualizăm sistemic. Aplicațiile personalizate dezvoltate în C++ sau Python pot fi implementate în mai multe moduri. Printre acestea, de exemplu: crearea pachetelor de sistem, trimiterea lor către depozitul de imagini colectate și instalarea ulterioară a acestora pe servere. Dintr-un motiv deja necunoscut, s-a ales o altă metodă, și anume: folosind CI, sunt compilate fișierele executabile ale aplicației, se creează un mediu de proiect virtual, sunt instalate module py din requirements.txt și toate aceste artefacte sunt trimise împreună cu configurațiile, scripturile și mediul de aplicație însoțitor către servere. Apoi, aplicațiile sunt lansate de la un utilizator virtual fără drepturi de administrator.

Gitlab-CI a fost ales ca sistem CI/CD. Conducta rezultată arăta cam așa:

Implementați aplicații cu Docker Swarm
Structural, gitlab-ci.yml arăta astfel:

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

Este de remarcat faptul că asamblarea și testarea se efectuează pe propria sa imagine, unde toate pachetele de sistem necesare sunt deja instalate și sunt făcute alte setări.

Deși fiecare dintre aceste scripturi în joburi este interesant în felul său, cu siguranță nu voi vorbi despre ele pentru a descrie fiecare dintre ele va dura mult timp și nu acesta este scopul articolului. Permiteți-mi să vă atrag atenția asupra faptului că etapa de implementare constă dintr-o secvență de scripturi de apelare:

  1. createconfig.py — creează un fișier settings.ini cu setări pentru componente în diferite medii pentru implementarea ulterioară (preproducție, producție, testare, ...)
  2. install_venv.sh — creează un mediu virtual pentru componentele py într-un anumit director și îl copiază pe servere la distanță
  3. prepare_init.d.py — pregătește scripturi pentru componentele start-stop pe baza șablonului
  4. deploy.py — implementează și repornește noi componente

A trecut timpul. Etapa de montaj a fost înlocuită cu preproducție și producție. S-a adăugat suport pentru produs pe încă o distribuție (CentOS). Au fost adăugate alte 5 servere fizice puternice și o duzină de servere virtuale. Și a devenit din ce în ce mai dificil pentru dezvoltatori și testeri să-și testeze sarcinile într-un mediu mai mult sau mai puțin apropiat de starea de lucru. În acest moment a devenit clar că era imposibil să te descurci fără el...

Partea a II-a

Implementați aplicații cu Docker Swarm

Deci, clusterul nostru este un sistem spectaculos de câteva zeci de componente individuale care nu sunt descrise de Dockerfiles. Îl puteți configura pentru implementare într-un anumit mediu doar în general. Sarcina noastră este să implementăm clusterul într-un mediu de testare pentru a-l testa înainte de testarea pre-lansare.

Teoretic, pot exista mai multe clustere care lucrează simultan: atâtea câte sarcini sunt în stare finalizată sau aproape de finalizare. Puterea serverelor de care dispunem ne permite să rulăm mai multe clustere pe fiecare server. Fiecare cluster de staging trebuie izolat (nu ar trebui să existe suprapuneri în porturi, directoare etc.).

Cea mai valoroasă resursă a noastră este timpul nostru și nu prea aveam din el.

Pentru un început mai rapid, am ales Docker Swarm datorită simplității și arhitecturii sale flexibile. Primul lucru pe care l-am făcut a fost să creăm un manager și mai multe noduri pe servere la distanță:

$ 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

Apoi, am creat o rețea:


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

Apoi, am conectat nodurile Gitlab-CI și Swarm în ceea ce privește gestionarea de la distanță a nodurilor din CI: instalarea certificatelor, setarea variabilelor secrete și, de asemenea, configurarea serviciului Docker pe serverul de management. Aceasta articol ne-a economisit mult timp.

Apoi, am adăugat joburi pentru crearea și distrugerea stivei în .gitlab-ci .yml.

Mai multe locuri de muncă au fost adăugate la .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

Din fragmentul de cod de mai sus este clar că două butoane au fost adăugate la Pipelines (deploy_staging, stop_staging) care necesită acțiune manuală.

Implementați aplicații cu Docker Swarm
Numele stivei se potrivește cu numele sucursalei și această unicitate ar trebui să fie suficientă. Serviciile din stivă primesc adrese IP unice și porturi, directoare etc. va fi izolat, dar la fel de la stivă la stivă (deoarece fișierul de configurare este același pentru toate stivele) - asta ne-am dorit. Implementăm stiva (clusterul) folosind Docker-compose.yml, care descrie clusterul nostru.

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

Aici puteți vedea că componentele sunt conectate printr-o singură rețea (nw_swarm) și sunt accesibile unul altuia.

Componentele sistemului (bazate pe redis, mysql) sunt separate de grupul general de componente personalizate (în planuri, componentele personalizate sunt, de asemenea, împărțite ca servicii). Etapa de implementare a cluster-ului nostru arată ca transferul CMD către singura noastră imagine configurată mare și, în general, nu este practic diferită de implementarea descrisă în partea I. Voi sublinia diferențele:

  • clona git... — obținem fișierele necesare pentru a efectua implementarea (createconfig.py, install_venv.sh etc.)
  • curl... && dezarhivați... - descărcați și dezarhivați artefactele de construcție (utilități compilate)

Există o singură problemă încă nedescrisă: componentele care au o interfață web nu sunt accesibile din browserele dezvoltatorilor. Rezolvăm această problemă folosind proxy invers, astfel:

În .gitlab-ci.yml, după implementarea stivei de cluster, adăugați o linie pentru implementarea echilibrerului (care, atunci când este confirmat, își actualizează doar configurația (creează noi fișiere de configurare nginx conform șablonului: /etc/nginx/conf.d) /${CI_COMMIT_REF_NAME}.conf) - vezi codul 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

Pe computerele dezvoltatorilor, actualizați /etc/hosts; setați adresa URL la nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Deci, implementarea clusterelor de staging izolate a fost implementată, iar dezvoltatorii le pot lansa acum în orice cantitate suficientă pentru a-și testa sarcinile.

Planuri de viitor:

  • Separați componentele noastre ca servicii
  • Creați un Dockerfile pentru fiecare
  • Detectează automat nodurile mai puțin încărcate din stivă
  • Specificați nodurile folosind un șablon de nume (în loc să utilizați id ca în articol)
  • Adăugați o verificare că stiva a fost distrusă
  • ...

Mulțumiri speciale pentru статью.

Sursa: www.habr.com

Adauga un comentariu