Stellen Sie Anwendungen mit Docker Swarm bereit

Das Online-Empfehlungssystem für Videoinhalte, an dem wir arbeiten, ist eine geschlossene kommerzielle Entwicklung und technisch gesehen ein Multikomponenten-Cluster aus proprietären und Open-Source-Komponenten. Der Zweck des Schreibens dieses Artikels besteht darin, die Implementierung des Docker-Swarm-Clustering-Systems für eine Staging-Site zu beschreiben, ohne den etablierten Arbeitsablauf unserer Prozesse in einer begrenzten Zeit zu stören. Die Ihnen präsentierte Erzählung ist in zwei Teile gegliedert. Der erste Teil beschreibt CI/CD vor der Verwendung von Docker Swarm und der zweite Teil beschreibt den Prozess seiner Implementierung. Wer kein Interesse daran hat, den ersten Teil zu lesen, kann getrost zum zweiten übergehen.

Teil I

Damals, in einem fernen, fernen Jahr, galt es, den CI/CD-Prozess so schnell wie möglich einzurichten. Eine der Bedingungen war, Docker nicht zu verwenden für den Einsatz entwickelte Komponenten aus mehreren Gründen:

  • für einen zuverlässigeren und stabileren Betrieb von Komponenten in der Produktion (das ist eigentlich die Anforderung, keine Virtualisierung zu verwenden)
  • führende Entwickler wollten nicht mit Docker arbeiten (komisch, aber so war es)
  • entsprechend den ideologischen Überlegungen der F&E-Leitung

Infrastruktur, Stack und ungefähre Anfangsanforderungen für MVP wurden wie folgt dargestellt:

  • 4 Intel® X5650-Server mit Debian (eine weitere leistungsstarke Maschine ist vollständig entwickelt)
  • Die Entwicklung eigener benutzerdefinierter Komponenten erfolgt in C++, Python3
  • Wichtigste verwendete Tools von Drittanbietern: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, …
  • Pipelines zum separaten Erstellen und Testen von Komponenten für Debug und Release

Eine der ersten Fragen, die in der Anfangsphase geklärt werden muss, ist, wie benutzerdefinierte Komponenten in einer beliebigen Umgebung (CI/CD) bereitgestellt werden.

Wir haben uns entschieden, Komponenten von Drittanbietern systematisch zu installieren und systematisch zu aktualisieren. Benutzerdefinierte Anwendungen, die in C++ oder Python entwickelt wurden, können auf verschiedene Arten bereitgestellt werden. Darunter zum Beispiel: Systempakete erstellen, sie an das Repository der erstellten Images senden und sie dann auf Servern installieren. Aus einem unbekannten Grund wurde eine andere Methode gewählt, nämlich: Mit CI werden ausführbare Anwendungsdateien kompiliert, eine virtuelle Projektumgebung erstellt, Py-Module aus „requirements.txt“ installiert und alle diese Artefakte werden zusammen mit Konfigurationen, Skripten usw. gesendet begleitende Anwendungsumgebung bis hin zu Servern. Anschließend werden Anwendungen als virtueller Benutzer ohne Administratorrechte gestartet.

Als CI/CD-System wurde Gitlab-CI gewählt. Die resultierende Pipeline sah etwa so aus:

Stellen Sie Anwendungen mit Docker Swarm bereit
Strukturell sah gitlab-ci.yml so aus

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

Bemerkenswert ist, dass der Zusammenbau und Test auf einem eigenen Image erfolgt, auf dem bereits alle notwendigen Systempakete installiert und weitere Einstellungen vorgenommen wurden.

Obwohl jedes dieser Skripte in Jobs auf seine Art interessant ist, werde ich natürlich nicht darüber sprechen. Die Beschreibung jedes einzelnen von ihnen wird viel Zeit in Anspruch nehmen und das ist nicht der Zweck des Artikels. Ich möchte Ihre Aufmerksamkeit nur auf die Tatsache lenken, dass die Bereitstellungsphase aus einer Folge von Aufrufskripten besteht:

  1. createconfig.py - Erstellt eine Settings.ini-Datei mit Komponenteneinstellungen in verschiedenen Umgebungen für die spätere Bereitstellung (Vorproduktion, Produktion, Tests, ...)
  2. install_venv.sh – Erstellt eine virtuelle Umgebung für Py-Komponenten in einem bestimmten Verzeichnis und kopiert sie auf Remote-Server
  3. Prepare_init.d.py – bereitet Start-Stopp-Skripte für die Komponente basierend auf der Vorlage vor
  4. Deploy.py - Zerlegt und startet neue Komponenten neu

Zeit verging. Die Inszenierungsphase wurde durch Vorproduktion und Produktion ersetzt. Unterstützung für das Produkt auf einer weiteren Distribution (CentOS) hinzugefügt. Fünf leistungsstärkere physische Server und ein Dutzend virtuelle Server hinzugefügt. Und es wurde für Entwickler und Tester immer schwieriger, ihre Aufgaben in einer Umgebung zu testen, die mehr oder weniger betriebsbereit war. Zu diesem Zeitpunkt wurde klar, dass es ohne ihn unmöglich war ...

Teil II

Stellen Sie Anwendungen mit Docker Swarm bereit

Unser Cluster ist also ein spektakuläres System aus ein paar Dutzend separaten Komponenten, die nicht von Dockerfiles beschrieben werden. Sie können es im Allgemeinen nur für die Bereitstellung in einer bestimmten Umgebung konfigurieren. Unsere Aufgabe besteht darin, den Cluster in einer Staging-Umgebung bereitzustellen, um ihn vor den Tests vor der Veröffentlichung zu testen.

Theoretisch können mehrere Cluster gleichzeitig laufen: so viele, wie es Aufgaben im abgeschlossenen Zustand oder kurz vor dem Abschluss gibt. Die Kapazitäten der uns zur Verfügung stehenden Server ermöglichen es uns, auf jedem Server mehrere Cluster zu betreiben. Jeder Staging-Cluster muss isoliert sein (es darf keine Überschneidungen bei Ports, Verzeichnissen usw. geben).

Unsere wertvollste Ressource ist unsere Zeit, und wir hatten nicht viel davon.

Für einen schnelleren Start haben wir uns aufgrund seiner Einfachheit und Architekturflexibilität für Docker Swarm entschieden. Als erstes haben wir einen Manager und mehrere Knoten auf den Remote-Servern erstellt:

$ 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

Als nächstes erstellen Sie ein Netzwerk:


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

Als nächstes haben wir Gitlab-CI- und Swarm-Knoten im Hinblick auf die Fernsteuerung von Knoten von CI verbunden: Installieren von Zertifikaten, Festlegen geheimer Variablen und Einrichten des Docker-Dienstes auf dem Steuerungsserver. Dieses hier Beitrag hat uns viel Zeit gespart.

Als nächstes haben wir Jobs zur Stapelerstellung und -zerstörung zu .gitlab-ci .yml hinzugefügt.

Zu .gitlab-ci .yml wurden einige weitere Jobs hinzugefügt

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

Aus dem obigen Codeausschnitt können Sie ersehen, dass Pipelines zwei Schaltflächen (deploy_staging, stop_staging) hinzugefügt wurden, die eine manuelle Aktion erfordern.

Stellen Sie Anwendungen mit Docker Swarm bereit
Der Stack-Name stimmt mit dem Branch-Namen überein und diese Eindeutigkeit sollte ausreichend sein. Dienste im Stapel erhalten eindeutige IP-Adressen sowie Ports, Verzeichnisse usw. wird isoliert, aber von Stapel zu Stapel gleich (da die Konfigurationsdatei für alle Stapel gleich ist) – was wir wollten. Wir stellen den Stack (Cluster) mithilfe von bereit docker-compose.yml, was unseren Cluster beschreibt.

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

Hier ist zu erkennen, dass die Komponenten über ein Netzwerk (nw_swarm) verbunden sind und untereinander verfügbar sind.

Systemkomponenten (basierend auf Redis, MySQL) werden vom allgemeinen Pool benutzerdefinierter Komponenten getrennt (in Plänen werden benutzerdefinierte Komponenten als Dienste unterteilt). Die Bereitstellungsphase unseres Clusters ähnelt der Übergabe von CMD an unser einziges großes konfiguriertes Image und unterscheidet sich im Allgemeinen praktisch nicht von der in Teil I beschriebenen Bereitstellung. Ich werde die Unterschiede hervorheben:

  • Git-Klon... - Holen Sie sich die für die Bereitstellung erforderlichen Dateien (createconfig.py, install_venv.sh usw.)
  • locken... && entpacken... - Build-Artefakte (kompilierte Dienstprogramme) herunterladen und entpacken

Es gibt nur ein noch unbeschriebenes Problem: Komponenten, die über eine Weboberfläche verfügen, sind über die Browser der Entwickler nicht zugänglich. Wir lösen dieses Problem mithilfe eines Reverse-Proxys, also:

In .gitlab-ci.yml fügen wir nach der Bereitstellung des Cluster-Stacks die Zeile zur Bereitstellung des Balancers hinzu (der beim Festschreiben nur seine Konfiguration aktualisiert (erstellt neue Nginx-Konfigurationsdateien gemäß der Vorlage: /etc/nginx/conf). d/${CI_COMMIT_REF_NAME}.conf) – siehe docker-compose-nginx.yml-Code)

    - 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

Aktualisieren Sie auf den Entwicklungscomputern /etc/hosts; Verschreiben Sie die URL für Nginx:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Daher wurde die Bereitstellung isolierter Staging-Cluster implementiert und Entwickler können diese nun in beliebiger Anzahl ausführen, um ihre Aufgaben zu überprüfen.

Zukunftspläne:

  • Trennen Sie unsere Komponenten als Dienste
  • Haben Sie für jede Docker-Datei
  • Erkennen Sie automatisch weniger belastete Knoten im Stapel
  • Geben Sie Knoten nach Namensmuster an (anstatt wie im Artikel die ID zu verwenden).
  • Fügen Sie eine Überprüfung hinzu, ob der Stapel zerstört ist
  • ...

Besonderer Dank dafür Artikel.

Source: habr.com

Kommentar hinzufügen