Das Online-Empfehlungssystem für Videoinhalte, an dem wir arbeiten, ist eine geschlossene kommerzielle Entwicklung und technisch gesehen ein Mehrkomponentencluster aus eigenen und Open-Source-Komponenten. In diesem Artikel wird die Einführung eines Docker-Schwarm-Clustering-Systems unter einer Staging-Plattform beschrieben, ohne den vorhandenen Workflow unserer Prozesse in einer begrenzten Zeit zu stören. Die Erzählung, auf die Sie aufmerksam gemacht werden, ist in zwei Teile unterteilt. Der erste Teil beschreibt das CI / CD vor der Verwendung von Docker Swarm und der zweite Teil beschreibt den Implementierungsprozess. Wer den ersten Teil nicht lesen möchte, kann sicher zum zweiten übergehen.
Teil I.
Im fernen, fernen Jahr musste der CI / CD-Prozess so schnell wie möglich konfiguriert werden. Eine der Bedingungen bestand darin, Docker aus mehreren Gründen nicht zum
Bereitstellen der entwickelten Komponenten zu verwenden:
- für einen zuverlässigeren und stabileren Betrieb von Komponenten in der Produktion (d. h. tatsächlich die Anforderung, keine Virtualisierung zu verwenden)
- Führende Entwickler wollten nicht mit Docker arbeiten (seltsam, aber es war genau das)
- aus ideologischen Gründen F & E-Management
Die anfänglichen Anforderungen an Infrastruktur, Stack und Beispiel für MVP waren wie folgt:
- 4 Intel® X5650 Server mit Debian (eine weitere leistungsstarke Maschine, die vollständig für die Entwicklung vorgesehen ist)
- Die Entwicklung benutzerdefinierter Komponenten erfolgt in C ++, Python3
- Die wichtigsten von Drittanbietern verwendeten Tools: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, MySQL, ...
- Pipelines Montage und Test von Komponenten separat für Debug und Release
Eines der ersten Probleme, die in der Anfangsphase gelöst werden müssen, ist die Bereitstellung benutzerdefinierter Komponenten in einer beliebigen Umgebung (CI / CD).
Komponenten von Drittanbietern haben beschlossen, sie systemisch zu installieren und systematisch zu aktualisieren. Benutzerdefinierte Anwendungen, die in C ++ oder Python entwickelt wurden, können auf verschiedene Arten bereitgestellt werden. Dazu gehören beispielsweise das Erstellen von Systempaketen, das Senden an das Repository der gesammelten Images und die anschließende Installation auf Servern. Aus einem unbekannten Grund wurde eine andere Methode gewählt: Mit CI werden ausführbare Anwendungsdateien kompiliert, eine virtuelle Projektumgebung erstellt, py-Module aus require.txt installiert und alle diese Artefakte zusammen mit Konfigurationen, Skripten und der zugehörigen Anwendungsumgebung an die Server gesendet. Als Nächstes werden Anwendungen von einem virtuellen Benutzer ohne Administratorrechte gestartet.
Gitlab-CI wurde als CI / CD-System ausgewählt. Die resultierende Pipeline sah ungefähr so aus:

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 *\n\tStrictHostKeyChecking no\n\n" > ~/.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
Es ist anzumerken, dass die Montage und das Testen auf einem eigenen Image durchgeführt werden, auf dem bereits alle erforderlichen Systempakete installiert und andere Einstellungen vorgenommen wurden.
Obwohl jedes dieser Skripte im Job auf seine Weise interessant ist, werde
ich sicherlich nicht darüber sprechen , aber die Beschreibung jedes einzelnen wird einige Zeit in Anspruch nehmen, und dies ist nicht der Zweck des Artikels. Ich werde nur darauf achten, dass die Bereitstellungsphase aus einer Folge von Skriptaufrufen besteht:
- createconfig.py - Erstellt die Datei settings.ini mit den Einstellungen der Komponenten in einer anderen Umgebung für die nachfolgende Bereitstellung (Vorproduktion, Produktion, Test, ...).
- install_venv.sh - Erstellt eine virtuelle Umgebung für py-Komponenten in einem bestimmten Verzeichnis und kopiert sie auf Remoteserver
- prepare_init.d.py - bereitet Komponenten-Start-Stopp-Skripte basierend auf einer Vorlage vor
- deploy.py - Dekomprimieren Sie neue Komponenten und starten Sie sie neu
Die Zeit verging. Die Inszenierungsphase wurde durch Vorproduktion und Produktion ersetzt. Die Produktunterstützung wurde für ein anderes Distributionskit (CentOS) hinzugefügt. 5 leistungsstärkere physische Server und ein Dutzend virtuelle wurden hinzugefügt. Und es wurde für Entwickler und Tester immer schwieriger, ihre Aufgaben in einer Umgebung auszuführen, die mehr oder weniger nahe am Arbeitszustand liegt. Zu diesem Zeitpunkt wurde klar, dass es unmöglich ist, ohne ihn auszukommen ...
Teil II

Unser Cluster ist also
immer noch ein Spektakel eines Systems aus ein paar Dutzend separaten Komponenten, die von Dockerfiles nicht beschrieben werden. Sie können es nur für die Bereitstellung in einer bestimmten Umgebung als Ganzes konfigurieren. Unsere Aufgabe besteht darin, den Cluster in einer Staging-Umgebung bereitzustellen, um ihn vor dem Testen vor der Veröffentlichung auszuführen.
Theoretisch kann es mehrere gleichzeitig arbeitende Cluster geben: so viele Aufgaben wie erledigt oder kurz vor dem Abschluss. Die Kapazitäten, die unseren Servern zur Verfügung stehen, ermöglichen es uns, mehrere Cluster auf jedem Server auszuführen. Jeder Staging-Cluster muss isoliert sein (es sollte keine Schnittmenge an Ports, Verzeichnissen usw. geben).
Die wertvollste Ressource ist unsere Zeit, und wir hatten nicht viel.
Für einen schnelleren Start entschieden sie sich aufgrund seiner Einfachheit und Flexibilität in der Architektur für Docker Swarm. Das erste, was wir gemacht haben, war das Erstellen auf den Remote-Manager-Servern und mehreren Knoten:
$ 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 haben wir ein Netzwerk erstellt:
$ docker network create --driver overlay --subnet 10.10.10.0/24 nw_swarm
Als Nächstes verbanden sie Gitlab-CI- und Swarm-Knoten im Hinblick auf die Remoteverwaltung von CI-Knoten: Installieren von Zertifikaten, Einrichten geheimer Variablen und Einrichten des Docker-Dienstes auf dem Verwaltungsserver. Dieser
Artikel hat uns viel Zeit gespart.
Als Nächstes haben wir Jobs zum Erstellen und Zerstören des Stapels in .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 Codefragment geht hervor, dass Pipelines zwei Schaltflächen (deploy_staging, stop_staging) hinzugefügt wurden, für die ein manueller Eingriff erforderlich ist.

Der Name des Stapels entspricht dem Namen des Zweigs, und diese Eindeutigkeit sollte ausreichen. Dienste im Stapel erhalten eindeutige IP-Adressen sowie Ports, Verzeichnisse usw. wird isoliert, aber von Stapel zu Stapel gleich (weil die Konfigurationsdatei für alle Stapel gleich ist) - das haben wir erreicht. Wir stellen
den Stack (Cluster) mit
docker-compose.yml bereit , das 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 sehen Sie, dass die Komponenten über ein Netzwerk (nw_swarm) verbunden und für einander zugänglich sind.
Systemkomponenten (basierend auf redis, mysql) werden vom gemeinsamen Pool benutzerdefinierter Komponenten getrennt (Pläne und benutzerdefinierte Komponenten werden als Services unterteilt). Die Bereitstellungsphase unseres Clusters sieht aus wie die CMD-Übertragung auf unser einziges großes konfiguriertes Image und unterscheidet sich im Großen und Ganzen praktisch nicht von der in Teil I beschriebenen Bereitstellung. Ich betone die Unterschiede:
- Git-Klon ... - Wir erhalten die Dateien, die für eine Bereitstellung erforderlich sind (createconfig.py, install_venv.sh usw.).
- curl ... && entpacken ... - Baugruppenartefakte herunterladen und entpacken (kompilierte Dienstprogramme)
Es gibt nur ein Problem, das noch nicht beschrieben wurde: Komponenten mit einer Weboberfläche sind über Entwicklerbrowser nicht zugänglich. Wir lösen dieses Problem mit Reverse Proxy, also:
Fügen Sie in .gitlab-ci.yml nach der Bereitstellung des Cluster-Stacks die Balancer-Bereitstellungszeile hinzu (die beim Festschreiben nur die Konfiguration aktualisiert (erstellt neue Nginx-Konfigurationsdateien mithilfe der Vorlage: /etc/nginx/conf.d/${CI_COMMIT_REF_NAME►.conf) - siehe 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
Aktualisieren Sie auf Entwicklungscomputern / etc / hosts. URL bei Nginx registrieren:
10.50.173.106 staging_BRANCH-1831_cluster.dev
Daher wurde die Bereitstellung isolierter Staging-Cluster implementiert, und Entwickler können sie jetzt in
einer ausreichenden Menge starten, um ihre Aufgaben zu testen.
Weitere Pläne:
- Trennen Sie unsere Komponenten als Dienstleistungen
- Machen Sie für jede Docker-Datei
- Weniger geladene Knoten im Stapel automatisch erkennen
- Legen Sie Knoten nach Namensmuster fest (anstatt ID wie im Artikel zu verwenden).
- Fügen Sie hinzu, überprüfen Sie, ob der Stapel zerstört ist
- ...
Besonderer Dank für den
Artikel .