即便一個小專案也有它的CI/CD流水線
【編者的話】本文作者通過一個簡單的小專案詳細介紹瞭如何使用Docker, GitLab, Portainer等元件搭建一套CICD流水線
{<11>}長文預警
現如今,使用市面上的一些工具配置一套簡單的CI/CD流水線並不是一件難事。給一個副專案弄一套這樣的流水線也是一個學習許多東西的好方法。Docker,Gitlab,Portainer這些優秀的元件可以用來搭建這個流水線。
示例專案
作為一名法國索菲亞科技園區(位於法國南部)的技術活動組織者,我經常被問到是否有辦法知道所有即將舉行的活動(會議,灌水,由當地協會組織的聚會等...)。由於此前並沒有一個單獨的地方列出所有的這些活動,我便開發了 https://sophia.events ,這是一個非常簡單的網站頁面,它會嘗試維護一份最新的活動列表。此專案的程式碼可以在 Gitlab 上找到。
宣告:這個專案超級簡單,但是專案本身的複雜度並不是本文的重點。這裡我們將詳細介紹到的CI/CD流水線的各個元件可以用幾乎相同的方式應用到更復雜的專案上。它們也非常適合微服務的場景。
快速過一下程式碼
為了簡化起見,這裡有一份events.json檔案,每個新事件均會被新增到裡面。該檔案的部分內容見下面的程式碼段(抱歉裡面摻雜了一些法語):
{ “events”: [ { “title”: “All Day DevOps 2018”, “desc”: “We’re back with 100, 30-minute practitioner-led sessions and live Q&A on Slack. Our 5 tracks include CI/CD, Cloud-Native Infrastructure, DevSecOps, Cultural Transformations, and Site Reliability Engineering. 24 hours. 112 speakers. Free online.”, “date”: “17 octobre 2018, online event”, “ts”: “20181017T000000”, “link”: “https://www.alldaydevops.com/", “sponsors”: [{“name”: “all-day-devops”}] }, { “title”: “Création d’une Blockchain d’entreprise (lab) & introduction aux smart contracts”, “desc”: “Venez avec votre laptop ! Nous vous proposons de nous rejoindre pour réaliser la création d’un premier prototype d’une Blockchain d’entreprise (Lab) et avoir une introduction aux smart contracts.”, “ts”: “20181004T181500”, “date”: “4 octobre à 18h15 au CEEI”, “link”: “https://www.meetup.com/fr-FR/IBM-Cloud-Cote-d-Azur-Meetup/events/254472667/", “sponsors”: [{“name”: “ibm”}] }, … ] }
此檔案將會被一個mustache 模板 渲染並生成最終的網站素材。
Docker多階段構建
一旦生成了最終的網站素材,它們將會被拷貝到一個nginx映象裡,該映象將會被部署到目標機器上。
得益於多階段構建(multi-stage build),本次構建分為兩部分:
- 網站素材的生成
- 包含網站素材的最終映象的建立
用來構建映象的Dockerfile如下:
生成素材
FROM node:8.12.0-alpine AS build
COPY . /build
WORKDIR /build
RUN npm i
RUN node clean.js
RUN ./node_modules/mustache/bin/mustache events.json index.mustache > index.html
構建託管它們的最終映象
FROM nginx:1.14.0
COPY --from=build /build/*.html /usr/share/nginx/html/
COPY events.json /usr/share/nginx/html/
COPY css /usr/share/nginx/html/css
COPY js /usr/share/nginx/html/js
COPY img /usr/share/nginx/html/img
本地測試
為了測試生成站點,只需克隆該倉庫然後執行test.sh指令碼即可。它將隨後創建出一個映象並執行一個容器:
$ git clone [email protected]:lucj/sophia.events.git $ cd sophia.events $ ./test.sh Sending build context to Docker daemon2.588MB Step 1/12 : FROM node:8.12.0-alpine AS build ---> df48b68da02a Step 2/12 : COPY . /build ---> f4005274aadf Step 3/12 : WORKDIR /build ---> Running in 5222c3b6cf12 Removing intermediate container 5222c3b6cf12 ---> 81947306e4af Step 4/12 : RUN npm i ---> Running in de4e6182036b npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN [email protected] No repository field. added 2 packages from 3 contributors and audited 2 packages in 1.675s found 0 vulnerabilities Removing intermediate container de4e6182036b ---> d0eb4627e01f Step 5/12 : RUN node clean.js ---> Running in f4d3c4745901 Removing intermediate container f4d3c4745901 ---> 602987ce7162 Step 6/12 : RUN ./node_modules/mustache/bin/mustache events.json index.mustache > index.html ---> Running in 05b5ebd73b89 Removing intermediate container 05b5ebd73b89 ---> d982ff9cc61c Step 7/12 : FROM nginx:1.14.0 ---> 86898218889a Step 8/12 : COPY --from=build /build/*.html /usr/share/nginx/html/ ---> Using cache ---> e0c25127223f Step 9/12 : COPY events.json /usr/share/nginx/html/ ---> Using cache ---> 64e8a1c5e79d Step 10/12 : COPY css /usr/share/nginx/html/css ---> Using cache ---> e524c31b64c2 Step 11/12 : COPY js /usr/share/nginx/html/js ---> Using cache ---> 1ef9dece9bb4 Step 12/12 : COPY img /usr/share/nginx/html/img ---> e50bf7836d2f Successfully built e50bf7836d2f Successfully tagged registry.gitlab.com/lucj/sophia.events:latest => web site available on http://localhost:32768
我們可以使用上述輸出的末尾提供的URL訪問網站頁面。
{<2>}目標環境
雲廠商建立的一臺虛擬機器
或許你也注意到了,這個網站並不是那麼關鍵(每天只有幾十次訪問),也因此它只需要跑在一臺單個的虛擬機器上即可。該虛擬機器是由 Exoscale ,一個偉大的歐洲雲廠商,它上面的Docker Machine創建出來的。
順便一提,如果你想試試Exoscale的服務的話,知會我一聲,我可以提供20歐元的優惠券。
以swarm模式啟動的docker守護程序
在上面這臺虛擬機器上執行的Docker守護程序被配置成以Swarm模式執行,因此它支援使用Docker Swarm原生提供的stack,service,config以及secret等原語和它強大(且易於使用)的編排功能。
以docker stack形式執行的應用
下述檔案內容裡定義了一個包含網站素材的nginx web伺服器作為一個服務(service)執行。
version: "3.7" services: www: image: registry.gitlab.com/lucj/sophia.events networks: - proxy deploy: mode: replicated replicas: 2 update_config: parallelism: 1 delay: 10s restart_policy: condition: on-failure networks: proxy: external: true
這裡有幾處需要解釋下:
- 映象儲存在託管到gitlab.com的私有映象倉庫(這裡沒涉及到Docker Hub)
- 服務是以2個副本的形式執行在副本模式下,這也就意味著同一時間該服務會有兩個正在執行中的任務/容器。Swarm的service會關聯一個VIP(虛擬IP地址),這樣一來目標是該服務的每個請求會在兩個副本之間實現負載均衡。
- 每次完成服務更新時(部署一個新版本的網站),其中一個副本會被更新,然後在10秒後更新第二個副本。這可以確保在更新期間整個網站仍然可用。我們也可以使用回滾策略,但是在這裡沒有必要。
- 服務會被繫結到一個外部的代理網路,這樣一來TLS termination(在swarm裡部署的,跑在另外一個服務裡,但是超出本專案的範疇)可以傳送請求給www服務。
要執行這個stack只需要執行如下命令:
$ docker stack deploy -c sophia.yml sophia_events
統御一切的Portainer
Portainer 是一套很棒的wbe UI工具,它可以很方便地管理Docker宿主機和Docker Swarm叢集。下面是Portainer操作介面的一張截圖,裡面列出了swarm叢集裡當前可用的stack。
{<3>}當前設定下有3個stack:
- Portainer自己
- 包含了跑著我們網站的服務的sophia_events
- tls,TLS termination服務
如果列出跑在sophia_events stack裡的 www 服務的明細的話,我們將可以看到 該服務的webhook 已經處於啟用狀態。Portainer 1.19.2(迄今為止最新的版本)已經加入了這一功能的支援,它允許定義一個HTTP Post端點,可以在被呼叫後觸發一次服務的更新。正如我們稍後將會看到的,Gitlab runner會負責呼叫這個webhook。
{<4>}備註:從螢幕截圖中可以看到,筆者是通過 localhost:8888 這個地址訪問Portainer的使用者介面。由於筆者不想將Portainer例項對外暴露,因此是通過ssh隧道訪問,該隧道可以通過如下命令開啟:
ssh -i ~/.docker/machine/machines/labs/id_rsa -NL 8888:localhost:9000 $USER@$HOST
這樣一來,目標是本地機器上的8888埠的所有請求均會通過ssh轉發到虛擬機器上的9000埠上。9000埠是Portainer在虛擬機器上執行時監聽的埠,但是並未對外開放,因為它被Exoscale配置的一個安全組禁用了。
備註:在上述命令裡,用來連線虛擬機器的ssh key是在虛擬機器建立時由Docker Machine生成的一個key。
GitLab runner
Gitlab的runner是一個負責執行定義在.gitlab-ci.yml檔案裡的一組action的程序。就我們這個專案來說,我們定義了一個我們自己的runner,它在虛擬機器上以一個容器的形式執行。
第一步就是帶上一堆引數來註冊該runner。
CONFIG_FOLDER=/tmp/gitlab-runner-config docker run — rm -t -i \ -v $CONFIG_FOLDER:/etc/gitlab-runner \ gitlab/gitlab-runner register \ --non-interactive \ --executor "docker" \ —-docker-image docker:stable \ --url "https://gitlab.com/" \ —-registration-token "$PROJECT_TOKEN" \ —-description "Exoscale Docker Runner" \ --tag-list "docker" \ --run-untagged \ —-locked="false" \ --docker-privileged
在上述引數中,PROJECT_TOKEN可以在Gitlab.com的專案頁面上找到,並可以用來註冊外部的runner。
{<5>}用來註冊一個新的runner的註冊token。
一旦runner註冊上了,我們需要啟動它:
CONFIG_FOLDER=/tmp/gitlab-runner-config docker run -d \ --name gitlab-runner \ —-restart always \ -v $CONFIG_FOLDER:/etc/gitlab-runner \ -v /var/run/docker.sock:/var/run/docker.sock \ gitlab/gitlab-runner:latest
等到它註冊上了而且啟動起來了,該runner便會出現在gitlab.com上的專案頁面裡。
{<6>}為此專案建立的runner。
每當有新的commit推送到倉庫,此runner隨後便會接收到一些要做的任務。它會按順序執行.gitlab-ci.yml檔案裡定義好的測試、構建和部署幾個階段。
variables: CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH DOCKER_HOST: tcp://docker:2375 stages: - test - build - deploy test: stage: test image: node:8.12.0-alpine script: - npm i - npm test build: stage: build image: docker:stable services: - docker:dind script: - docker image build -t $CONTAINER_IMAGE:$CI_BUILD_REF -t $CONTAINER_IMAGE:latest . - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com - docker image push $CONTAINER_IMAGE:latest - docker image push $CONTAINER_IMAGE:$CI_BUILD_REF only: - master deploy: stage: deploy image: alpine script: - apk add --update curl - curl -XPOST $WWW_WEBHOOK only: - master
- 測試階段(test stage)將會執行一些預備檢查,確保events.json檔案格式正確,並且這裡沒有遺漏映象
- 構建階段(build stage)會做映象的構建並將它推送到Gitlab上的映象倉庫
- 部署階段(deploy stage)將會通過傳送給Portainer的一個webhook觸發一次服務的更新。WWW_WEBHOOK變數的定義可以在Gitlab.com上專案頁面的CI/CD設定裡找到。
備註:
- runner在swarm上是以一個容器的形式執行。我們可以使用一個共享的runner,這是一些公用的runner,它們會在託管到Gitlab的不同專案所需的任務之間分配時間。但是,由於runner需要訪問Portainer的端點(用來發送webhook),也因為筆者不希望Portainer能夠從外界訪問到,將runner跑在叢集裡會更安全一些。
- 再者,由於runner跑在一個容器裡,為了能夠通過Portainer暴露在宿主機上的9000埠連到Portainer,它會將webhook請求傳送到Docker0橋接網路上的IP地址。也因此,webhook將遵循如下格式: http://172.17.0.1:9000/api […]a7-4af2-a95b-b748d92f1b3b
部署流程
新版本的站點更新遵循如下流程:
{<8>}- 一個開發者推送了一些變更到Gitlab。這些變更基本上囊括了events.json檔案裡一個或多個新的事件加上一些額外贊助商的logo。
- Gitlab runner執行在.gitlab-ci.yml裡定義好的一組action。
- Gitlab runner呼叫在Portainer中定義的webhook。
- 在接收到webhook後,Portainer將會部署新版本的www服務。它通過呼叫Docker Swarm的API實現這一點。Portainer可以通過在啟動時繫結掛載的/var/run/docker.sock套接字來訪問該API。
如果你想知道更多此unix套接字用法的相關資訊,也許你會對之前這篇文章 About /var/run/docker.sock 感興趣。
- 隨後,使用者便能看到新版本的站點。
示例
讓我們一起來修改程式碼裡的一些內容隨後提交/推送這些變更。
$ git commit -m 'Fix image' $ git push origin master
如下截圖展示了Gitlab.com上的專案頁面裡的commit觸發的流水線作業。
{<9>}在Portainer一側,它將會收到一個webhook請求,隨後會執行一次服務的更新操作。這裡可能看不太清,但是一個副本已經完成了更新,通過第二個副本可以訪問站點。隨後,幾秒鐘之後,第二個副本也更新完畢。
{<10>}小結
即便對於這樣一個小專案,為它建立一套CI/CD流水線也是一個很好的練習,尤其是可以更加熟悉GitLab(這一直在筆者要學習的列表裡面),它是一個非常出色而且專業的產品。這也是一次體驗大家期待已久的Portainer的最新版本(1.19.2)推出的webhook功能的機會。此外,對於像這樣的副專案,Docker Swarm的使用是無腦上手的,很酷而且易於使用......
原文連結: even the smallest side project deserves its ci cd pipeline (譯者:吳佳興)