Die Veröffentlichung des Webbrowsers ist erfolgt.

Das Online-Empfehlungssystem fĂŒr Video-Content, an dem wir arbeiten, ist eine geschlossene kommerzielle Entwicklung und stellt technisch einen mehrkomponentigen Cluster aus eigenen und Open-Source-Komponenten dar. Ziel dieses Artikels ist es, die Implementierung eines Docker Swarm-Clustering-Systems fĂŒr eine Staging-Plattform zu beschreiben, ohne den bestehenden Workflow unserer Prozesse unter Zeitdruck 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, der zweite Teil den Implementierungsprozess. Wer an der ersten Teil nicht interessiert ist, kann direkt zum zweiten Teil ĂŒbergehen.

Teil I

Im weit entfernten Jahr war es erforderlich, den CI/CD-Prozess so schnell wie möglich einzurichten. Eine der Bedingungen war, Docker nicht zu verwenden fĂŒr das Deployment entwickelter Komponenten aus mehreren GrĂŒnden:

  • fĂŒr eine zuverlĂ€ssigere und stabilere Funktion der Komponenten in der Produktion (d.h. im Grunde die Anforderung, keine Virtualisierung zu verwenden)
  • die fĂŒhrenden Entwickler wollten nicht mit Docker arbeiten (seltsam, aber so war es)
  • aus ideologischen GrĂŒnden der R&D-Leitung

Die Infrastruktur, der Stack und die ungefĂ€hren Anforderungen fĂŒr das MVP sahen wie folgt aus:

  • 4 IntelÂź X5650-Server mit Debian (eine leistungsstĂ€rkere Maschine ausschließlich fĂŒr die Entwicklung)
  • Die Entwicklung eigener benutzerdefinierter Komponenten erfolgt in C++, Python3
  • Die wichtigsten 3rdparty-Tools: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, 

  • Pipelines fĂŒr die Erstellung und das Testen von Komponenten getrennt fĂŒr Debug und Release

Eine der ersten Fragen, die in der Anfangsphase zu klÀren ist, ist, wie die Bereitstellung der benutzerdefinierten Komponenten in einer Umgebung (CI/CD) erfolgen wird.

Drittanbieter-Komponenten wurden systematisch installiert und aktualisiert. Kundenspezifische Anwendungen, die in C++ oder Python entwickelt werden, können jedoch auf verschiedene Weisen bereitgestellt werden. Dazu gehört beispielsweise: die Erstellung von Systempaketen, das Versenden dieser in ein Repository von erstellten Images und deren anschließende Installation auf Servern. Aus bisher unbekannten GrĂŒnden wurde ein anderer Ansatz gewĂ€hlt, und zwar: mit Hilfe von CI werden ausfĂŒhrbare Dateien der Anwendungen kompiliert, eine virtuelle Projektumgebung erstellt, py-Module aus requirements.txt installiert und all diese Artefakte werden zusammen mit Konfigurationen, Skripten und der begleitenden Umgebung der Anwendungen auf die Server versendet. Anschließend werden die Anwendungen von einem virtuellen Benutzer ohne Administratorrechte gestartet.

Als CI/CD-System wurde Gitlab-CI ausgewÀhlt. Der resultierende Pipeline sah ungefÀhr so aus:

Die Veröffentlichung des Webbrowsers ist erfolgt.
Strukturell sah die gitlab-ci.yml folgendermaßen aus

---
variables:
  # Minimale CPU-Version auf den Servern, auf denen der Cluster bereitgestellt wird
  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

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

Es ist zu beachten, dass der Build und die Tests auf einem eigenen Image durchgefĂŒhrt werden, auf dem bereits alle erforderlichen Systempakete installiert und weitere Konfigurationen vorgenommen wurden.

Obwohl jedes dieser Skripte in den Jobs auf seine Weise interessant ist, werde ich natĂŒrlich keine Beschreibung jedes einzelnen geben, da dies erheblich Zeit in Anspruch nehmen wĂŒrde und nicht das Ziel des Artikels ist. Ich möchte nur darauf hinweisen, dass die Deploy-Phase aus einer Abfolge von Skriptaufrufen besteht:

  1. createconfig.py — erstellt die Datei settings.ini mit den Konfigurationen der Komponenten in verschiedenen Umgebungen fĂŒr die anschließende Bereitstellung (Preproduction, Production, Testing, 
)
  2. install_venv.sh — erstellt eine virtuelle Umgebung fĂŒr Python-Komponenten in einem bestimmten Verzeichnis und kopiert sie auf die Remote-Server
  3. prepare_init.d.py — bereitet die Start-Stopp-Skripte der Komponenten auf der Grundlage einer Vorlage vor
  4. deploy.py — verteilt und startet die neuen Komponenten neu

Die Zeit verging. Die Staging-Phase wurde durch Preproduction und Production ersetzt. Die UnterstĂŒtzung fĂŒr ein weiteres Produkt auf einem weiteren Distributionstyp (CentOS) wurde hinzugefĂŒgt. Es kamen fĂŒnf leistungsstarke physische Server und ein Dutzend virtuelle Server hinzu. FĂŒr die Entwickler und Tester wurde es immer schwieriger, ihre Aufgaben in einer Umgebung zu testen, die dem Betriebszustand mehr oder weniger nahe kam. Zu diesem Zeitpunkt wurde deutlich, dass wir nicht darauf verzichten konnten...

Teil II

Die Veröffentlichung des Webbrowsers ist erfolgt.

Unser Cluster ist ein ziemlich beeindruckendes System aus mehreren Dutzend einzelnen Komponenten, die nicht durch Dockerfiles beschrieben sind. Es kann nur als Ganzes fĂŒr die Bereitstellung in eine bestimmte Umgebung konfiguriert werden. Unsere Aufgabe besteht darin, den Cluster in einer Staging-Umgebung bereitzustellen, um ihn vor dem Release-Testing zu testen.

Theoretisch kann es mehrere gleichzeitig betriebene Cluster geben: so viele, wie Aufgaben im abgeschlossenen oder nahezu abgeschlossenen Zustand vorhanden sind. Die Rechenleistung der Server, die uns zur VerfĂŒgung stehen, erlaubt es, mehrere Cluster auf jedem Server auszufĂŒhren. Jeder Staging-Cluster muss isoliert sein (es darf keine Überschneidungen bei Ports, Verzeichnissen usw. geben).

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

FĂŒr einen schnelleren Start haben wir Docker Swarm aufgrund seiner Einfachheit und FlexibilitĂ€t der Architektur ausgewĂ€hlt. Das Erste, was wir taten, war, auf den entfernten Servern einen Manager und mehrere Knoten zu erstellen:

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

Als nÀchstes haben wir ein Netzwerk erstellt:


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

Anschließend haben wir Gitlab-CI und die Swarm-Knoten fĂŒr die Remote-Verwaltung der Knoten aus CI verbunden: Installation von Zertifikaten, Konfiguration von geheimen Variablen sowie Konfiguration des Docker-Dienstes auf dem verwaltenden Server. Das hat uns Artikel viel Zeit gespart.

Als nĂ€chstes haben wir Jobs fĂŒr die Erstellung und Zerstörung des Stacks in .gitlab-ci.yml hinzugefĂŒgt.

In der .gitlab-ci.yml wurden noch einige 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 oben genannten Codefragment ist ersichtlich, dass in die Pipelines zwei SchaltflĂ€chen (deploy_staging, stop_staging) hinzugefĂŒgt wurden, die manuelle Eingriffe erfordern.

Die Veröffentlichung des Webbrowsers ist erfolgt.
Der Name des Stacks entspricht dem Namen des Branches und diese Einzigartigkeit sollte ausreichend sein. Die Dienste im Stack erhalten einzigartige IP-Adressen, wĂ€hrend Ports, Verzeichnisse usw. isoliert, aber in jedem Stack gleich sein werden (da die Konfigurationsdatei fĂŒr alle Stacks identisch ist) - genau das, was wir erreichen wollten. Den Stack (Cluster) setzen wir mit Hilfe von docker-compose.yml, in dem unser Cluster beschrieben ist.

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 sehen, dass die Komponenten ĂŒber ein Netzwerk (nw_swarm) verbunden sind und einander zugĂ€nglich sind.

Die Systemkomponenten (basierend auf Redis, MySQL) sind vom gemeinsamen Pool der benutzerdefinierten Komponenten getrennt (es ist geplant, auch die benutzerdefinierten als Dienste zu trennen). Die Bereitstellungsphase unseres Clusters sieht aus wie die Übergabe von CMD an unser großes konfiguriertes Image und unterscheidet sich insgesamt kaum von der in Teil I beschriebenen Bereitstellung. Ich möchte die Unterschiede hervorheben:

  • git clone 
 – wir erhalten die Dateien, die fĂŒr die Bereitstellung erforderlich sind (createconfig.py, install_venv.sh usw.)
  • curl
 && unzip 
 – wir laden die Build-Artefakte herunter und entpacken sie (kompilierte Utilities)

Es gibt nur noch ein bisher nicht beschriebenes Problem: Komponenten, die eine WeboberflÀche haben, sind aus den Entwickler-Browsern nicht zugÀnglich. Wir lösen dieses Problem mit einem Reverse Proxy, und zwar so:

In der .gitlab-ci.yml fĂŒgen wir nach dem Deployment des Cluster-Stacks die Zeile fĂŒr das Deployment des Load Balancers hinzu (der bei Commits nur seine Konfiguration aktualisiert (neue Konfigurationsdateien fĂŒr nginx nach dem Muster: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME}.conf erstellt) – siehe den Code 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

Auf den Entwicklermaschinen aktualisieren wir /etc/hosts; wir tragen die URL bis nginx ein:

10.50.173.106 staging_BRANCH-1831_cluster.dev

Somit ist das Deployment der isolierten Staging-Cluster umgesetzt, und die Entwickler können diese nun in der erforderlichen Menge fĂŒr ihre Aufgaben testen.

Weitere PlÀne:

  • Unsere Komponenten als Dienste trennen
  • FĂŒr jede Dockerfile einen eigenen anlegen
  • Automatisch weniger ausgelastete Knoten im Stack bestimmen
  • Knoten anhand des Namensmusters zuweisen (anstatt der ID wie im Artikel zu verwenden)
  • ÜberprĂŒfen, dass der Stack zerstört wurde
  • 


Besonderer Dank fĂŒr einen Artikel.

Quelle: habr.com

ZuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz kaufen, VPS VDS Server đŸ”„ ZuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz kaufen, VPS VDS Server - ProHoster