私たちが取り組んでいるオンライン ビデオ コンテンツ推奨システムは非公開の商業開発であり、技術的には独自のオープンソース コンポーネントのマルチコンポーネント クラスターです。 この記事を書く目的は、限られた時間内でプロセスの確立されたワークフローを中断することなく、ステージング サイト向けの Docker swarm クラスタリング システムの実装について説明することです。 あなたの注意を引く物語は XNUMX つの部分に分かれています。 最初の部分では docker swarm を使用する前の CI / CD について説明し、XNUMX 番目の部分ではその実装プロセスについて説明します。 最初の部分を読むことに興味がない人は、安全に XNUMX 番目の部分に進んでください。
パート1
遠い昔、CI/CD プロセスをできるだけ早くセットアップする必要がありました。 条件のXNUMXつはDockerを使用しないことでした 導入用 いくつかの理由からコンポーネントを開発しました。
- 本番環境でのコンポーネントの動作の信頼性と安定性を高めるため (つまり、実際には仮想化を使用しない要件)
- 主要な開発者は Docker を使いたくありませんでした (奇妙ですが、実際はそうでした)
- 研究開発管理のイデオロギー的考察によると
MVP のインフラストラクチャ、スタック、およびおおよその初期要件は次のように示されています。
- Debian を搭載した 4 台の Intel® X5650 サーバー (もう XNUMX 台の強力なマシンが完全に開発されています)
- 独自のカスタムコンポーネントの開発はC++、Python3で行われます。
- 使用される主なサードパーティ ツール: Kafka、Clickhouse、Airflow、Redis、Grafana、Postgresql、Mysql など
- デバッグとリリースのためにコンポーネントを個別に構築およびテストするためのパイプライン
初期段階で対処する必要がある最初の質問の XNUMX つは、カスタム コンポーネントを環境 (CI / CD) にどのようにデプロイするかです。
私たちは、サードパーティのコンポーネントをシステム的にインストールし、システム的に更新することにしました。 C++ または Python で開発されたカスタム アプリケーションは、いくつかの方法でデプロイできます。 その中には、たとえば、システム パッケージを作成し、ビルドされたイメージのリポジトリに送信して、サーバーにインストールすることが含まれます。 理由は不明ですが、別の方法が選択されました。つまり、CI を使用して、アプリケーションの実行可能ファイルがコンパイルされ、仮想プロジェクト環境が作成され、py モジュールがrequirements.txt からインストールされ、これらすべてのアーティファクトが構成、スクリプト、およびサーバーに付随するアプリケーション環境。 次に、アプリケーションは管理者権限のない仮想ユーザーとして起動されます。
CI/CD システムとして Gitlab-CI が選択されました。 結果のパイプラインは次のようになります。

gitlab-ci.yml の構造は次のようになります。
---
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
アセンブリとテストは、必要なすべてのシステム パッケージがすでにインストールされ、その他の設定が行われている独自のイメージ上で実行されることに注意してください。
ジョブ内のこれらのスクリプトはそれぞれ興味深いものですが、それぞれについて説明すると非常に時間がかかるので、もちろん説明しません。これはこの記事の目的ではありません。 デプロイメント段階が一連の呼び出しスクリプトで構成されているという事実のみに注目していただきます。
- createconfig.py - 後続の展開 (実稼働前、実稼働、テストなど) に備えて、さまざまな環境でコンポーネント設定を含む settings.ini ファイルを作成します。
- install_venv.sh - 特定のディレクトリに py コンポーネントの仮想環境を作成し、それをリモート サーバーにコピーします
- prepare_init.d.py — テンプレートに基づいてコンポーネントの開始/停止スクリプトを準備します
- デプロイ.py - 新しいコンポーネントを分解して再起動します
時は過ぎた。 ステージング段階はプリプロダクションとプロダクションに置き換えられました。 もう 5 つのディストリビューション (CentOS) での製品のサポートが追加されました。 さらに強力な XNUMX 台の物理サーバーと XNUMX 台の仮想サーバーを追加しました。 そして、開発者やテスターにとって、多かれ少なかれ動作状態に近い環境でタスクをテストすることがますます困難になってきました。 このとき、彼なしでは不可能であることが明らかになりました...
パートII

したがって、私たちのクラスターは、Dockerfile では記述されない数十の個別のコンポーネントからなる素晴らしいシステムです。 通常は、特定の環境に展開するようにのみ構成できます。 私たちのタスクは、リリース前テストの前にクラスターをステージング環境にデプロイしてテストすることです。
理論的には、完了状態または完了に近いタスクの数と同数の複数のクラスターを同時に実行できます。 自由に使えるサーバーの容量により、各サーバーで複数のクラスターを実行できます。 各ステージング クラスターは分離されている必要があります (ポート、ディレクトリなどに交差があってはなりません)。
私たちの最も貴重なリソースは時間ですが、私たちにはそれがあまりありませんでした。
より迅速に開始するために、そのシンプルさとアーキテクチャの柔軟性により Docker Swarm を選択しました。 最初に、リモート サーバー上にマネージャーといくつかのノードを作成しました。
$ 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
次に、ネットワークを作成します。
$ docker network create --driver overlay --subnet 10.10.10.0/24 nw_swarm
次に、CI からのノードのリモート制御に関して、Gitlab-CI と Swarm ノードを接続しました。つまり、証明書のインストール、シークレット変数の設定、およびコントロール サーバーでの Docker サービスのセットアップを行いました。 これです 時間を大幅に節約できました。
次に、スタックの作成ジョブと破棄ジョブを .gitlab-ci .yml に追加しました。
さらにいくつかのジョブが .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
上記のコード スニペットから、XNUMX つのボタン (deploy_staging、stop_staging) がパイプラインに追加されており、手動操作が必要であることがわかります。

スタック名はブランチ名と一致しており、この一意性で十分です。 スタック内のサービスは、一意の IP アドレス、ポート、ディレクトリなどを受け取ります。 分離されますが、スタック間で同じです (構成ファイルはすべてのスタックで同じであるため) - 私たちが望んでいたものです。 次を使用してスタック (クラスター) をデプロイします。 docker-compose.yml、クラスターについて説明します。
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
ここでは、コンポーネントが XNUMX つのネットワーク (nw_swarm) によって接続されており、相互に利用できることがわかります。
システム コンポーネント (redis、mysql に基づく) は、カスタム コンポーネントの一般的なプールから分離されます (プラン内で、カスタム コンポーネントはサービスとして分割されます)。 クラスターのデプロイメント段階は、CMD を XNUMX つの大規模な構成済みイメージに渡すように見え、一般に、パート I で説明したデプロイメントと実質的に変わりません。相違点を強調します。
- gitクローン... - デプロイに必要なファイルを取得します (createconfig.py、install_venv.sh など)。
- カール...&& 解凍... - ビルド アーティファクト (コンパイルされたユーティリティ) をダウンロードして解凍します。
まだ説明されていない問題が XNUMX つだけあります。それは、Web インターフェイスを持つコンポーネントに開発者のブラウザからアクセスできないことです。 この問題はリバース プロキシを使用して解決します。次のようになります。
.gitlab-ci.yml では、クラスター スタックをデプロイした後、バランサーをデプロイする行を追加します (コミット時に、その構成のみが更新されます (テンプレート /etc/nginx/conf に従って新しい nginx 構成ファイルが作成されます)。 d/${CI_COMMIT_REF_NAME}.conf) - 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
開発用コンピューターで /etc/hosts を更新します。 nginx に URL を指定します:
10.50.173.106 staging_BRANCH-1831_cluster.dev
そのため、分離されたステージング クラスターのデプロイメントが実装され、開発者はタスクを確認するのに十分な数だけクラスターを実行できるようになりました。
今後の計画:
- コンポーネントをサービスとして分離する
- Dockerfileごとに持つ
- スタック内で負荷の低いノードを自動的に検出します
- (記事のように ID を使用するのではなく) 名前パターンでノードを指定します。
- スタックが破棄されたことを確認するチェックを追加する
- ...
特に感謝します .
出所: habr.com
