我們正在開發的線上視訊內容推薦系統是一個封閉的商業開發,從技術上講是一個由專有和開源組件組成的多組件集群。 寫這篇文章的目的是描述在臨時平台上實作 docker swarm 叢集系統,並且在有限的時間條件下不破壞我們流程既定的工作流程。 呈現給您注意的敘述分為兩部分。 第一部分介紹了使用docker swarm之前的CI/CD,第二部分介紹了實現的過程。 那些對閱讀第一部分不感興趣的人可以安全地繼續閱讀第二部分。
第一部分
曾幾何時,需要盡快建立 CI/CD 流程。 條件之一是不使用Docker 用於部署 開發組件有幾個原因:
- 為了使生產中的組件更可靠、更穩定地運作(即本質上要求不使用虛擬化)
- 領先的開發人員不想使用 Docker(很奇怪,但事實就是如此)
- 出於研發管理的意識形態原因
MVP 的基礎架構、堆疊和大致初始要求如下:
- 4 台採用 Debian 的 Intel® X5650 伺服器(更強大的機器,完全用於開發)
- 使用 C++、Python3 開發您自己的自訂元件
- 主要使用的第三方工具:Kafka、Clickhouse、Airflow、Redis、Grafana、Postgresql、Mysql…
- 用於單獨建置和測試組件以進行調試和發布的管道
初始階段需要解決的首要問題之一是如何在任何環境(CI/CD)中部署自訂元件。
我們決定有系統地安裝第三方組件並有系統地更新它們。 用 C++ 或 Python 開發的自訂應用程式可以透過多種方式部署。 其中,例如:建立系統包,將它們傳送到收集的映像儲存庫以及隨後在伺服器上的安裝。 由於未知的原因,選擇了另一種方法,即:使用 CI,編譯應用程式可執行文件,創建虛擬專案環境,安裝requirements.txt 中的 py 模組,並將所有這些工件與配置、腳本和伺服器附帶的應用程式環境。 接下來,從沒有管理員權限的虛擬使用者啟動應用程式。
選擇 Gitlab-CI 作為 CI/CD 系統。 最終的管道看起來像這樣:
從結構上來說,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
值得注意的是,組裝和測試是在自己的映像上進行的,其中已經安裝了所有必要的系統軟體包並進行了其他設定。
雖然 jobs 中的每個腳本都有自己的有趣之處,但我當然不會談論它們;描述每個腳本將花費大量時間,這不是本文的目的。 讓我提請您注意部署階段由一系列呼叫腳本組成的事實:
- 建立配置.py — 建立一個 settings.ini 文件,其中包含不同環境中組件的設置,以供後續部署(預生產、生產、測試...)
- install_venv.sh — 在特定目錄中為py元件建立虛擬環境並將其複製到遠端伺服器
- 準備初始化.d.py — 根據範本準備啟動/停止組件的腳本
- 部署文件 — 部署並重新啟動新元件
時間飛逝。 舞台階段被預製作和製作所取代。 又一個發行版 (CentOS) 上新增了對該產品的支援。 另外還增加了 5 台強大的實體伺服器和十幾台虛擬伺服器。 對於開發人員和測試人員來說,在或多或少接近工作狀態的環境中測試他們的任務變得越來越困難。 這時我才明白,沒有他是不可能的…
第二部分
因此,我們的叢集是一個由數十個 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
接下來,我們連接了 Gitlab-CI 和 Swarm 節點,從 CI 遠端管理節點:安裝憑證、設定秘密變數以及在管理伺服器上設定 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
從上面的程式碼片段可以清楚地看出,管道中新增了兩個需要手動操作的按鈕(deploy_staging、stop_staging)。
堆疊名稱與分支名稱相匹配,而這種唯一性應該就足夠了。 堆疊中的服務接收唯一的 IP 位址、連接埠、目錄等。 將被隔離,但堆疊之間是相同的(因為所有堆疊的設定檔都是相同的)——這就是我們想要的。 我們使用以下方式部署堆疊(叢集) 泊塢窗,compose.yml,它描述了我們的集群。
泊塢窗,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
在這裡您可以看到元件透過一個網路 (nw_swarm) 連接並且可以相互存取。
系統元件(基於redis、mysql)與通用的自訂元件池分開(在計劃中,自訂元件也被劃分為服務)。 我們叢集的部署階段看起來就像將 CMD 傳輸到我們的一個大型配置映像,一般來說,實際上與第一部分中描述的部署沒有什麼不同。我將強調差異:
- git 克隆... — 我們取得執行部署所需的檔案(createconfig.py、install_venv.sh 等)
- 捲曲... && 解壓縮... — 下載並解壓縮建置工件(已編譯的實用程式)
只有一個尚未描述的問題:開發人員的瀏覽器無法存取具有 Web 介面的元件。 我們使用反向代理來解決這個問題,因此:
在 .gitlab-ci.yml 中,部署叢集堆疊後,新增一行用於部署平衡器(提交後,僅更新其配置(根據範本建立新的 nginx 設定檔:/etc/nginx/conf.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; 將 url 設定為 nginx:
10.50.173.106 staging_BRANCH-1831_cluster.dev
因此,隔離的臨時叢集的部署已經實現,開發人員現在可以啟動足以測試其任務的任意數量的叢集。
未來的計劃:
- 將我們的元件分開為服務
- 為每個檔案建立一個 Dockerfile
- 自動偵測堆疊中負載較少的節點
- 使用名稱模板指定節點(而不是像文章中那樣使用 id)
- 新增檢查堆疊是否已被銷毀
- ...
特別感謝
來源: www.habr.com