上篇文章《經過 GitLab Runner 實現 CI/CD 工做流(上)》咱們講解了 GitLab Runner 的部署和配置優化,此次咱們來進行一次實戰演練,對一個完整的先後端分離的項目進行講解。這個項目分爲前端和後端兩個項目,他們有獨自的代碼倉庫,對於不一樣倉庫的代碼提交,會觸發對應的項目的代碼編譯、鏡像構建、鏡像發佈與項目部署。html
咱們先來設計下項目的總體架構:前端
項目須要運行四個容器,分別是反向代理、前端項目、後端項目和數據庫,他們都在同一個 bridge
類型的 docker 網絡下,只有反向代理容器暴露端口提供對外訪問,其餘容器均爲 bridge 網絡內部通訊。node
反向代理 Nginx 根據請求的 URL 將請求分發到前端項目或後端項目,前端項目將最終編譯生成的靜態資源打包在 Nginx 容器內提供訪問,後端項目是使用 Go 編寫的 API 服務,將編譯好的二進制文件打包進 alpine 容器運行,MySQL 容器提供數據庫訪問。linux
當咱們爲項目綁定好 Runner 以後,開發者提交代碼到 GitLab 項目倉庫,GitLab 根據項目中的 .gitlab-ci.yml
配置文件要求觸發 Runner 工做。咱們在 .gitlab-ci.yml
文件中這樣定義了整個流程:構建應用鏡像 -> 推送到鏡像倉庫 -> 觸發服務器的自部署腳本 -> 服務器拉取新鏡像、中止並移除舊容器、啓動新容器。nginx
能夠看到,咱們的部署方式爲中止舊版本應用並啓用新版本應用,這種部署策略叫作 重建(recreate),這種更新應用的實現方式比較簡單,但有一個顯著的問題是,若是是線上生產服務器的部署,可能會出現短暫的流量中斷的狀況。git
其餘的部署更新策略還有藍綠部署、滾動更新、灰度發佈(金絲雀發佈)等,若是使用 Kubernetes 進行集羣容器應用管理,它自帶了多種更新策略,會很是方便咱們進行 CD。docker
請閱讀參考這篇文章:《Kubernetes 部署策略詳解》。shell
前端項目是一個使用 Vue.js 腳手架的 SPA,另外咱們還須要在項目中添加三個配置文件:數據庫
.gitlab-ci.yml
CI 流程Dockerfile
鏡像構建流程default.conf
前端 Nginx 容器中的配置三者的關係是:CI 過程當中會調用 Dockerfile
進行鏡像構建,鏡像構建過程又會調用 default.conf
將文件寫進鏡像。npm
# 兩個階段:構建併發布鏡像、部署(經過執行遠程腳本的方式) stages: - build - deploy build_job: stage: build image: docker:latest tags: - jczh100 only: - dev script: # 構建鏡像(每一個 Job 默認都會將項目代碼 fetch 到 Job 容器中,這一步將用到 Dockerfile) - docker build -t ucfe:latest . # 登陸鏡像倉庫 - docker login -u $DOCKER_REGISTRY_USERNAME -p $DOCKER_REGISTRY_PASSWORD $DOCKER_REGISTRY_ADDR # 給鏡像打標籤 - docker tag ucfe:latest ${DOCKER_REGISTRY_ADDR}/xvrzhao/ucfe:latest # 發佈到鏡像倉庫 - docker push ${DOCKER_REGISTRY_ADDR}/xvrzhao/ucfe:latest deploy_job: stage: deploy image: alpine:latest variables: # 跳過 git 操做,加快流水線執行速度,本 job 不須要獲取倉庫代碼 GIT_STRATEGY: none tags: - jczh100 only: - dev before_script: # 更換 alpine apk 源爲阿里源 - sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories # 安裝 sshpass,sshpass 能夠將密碼傳入 ssh,免去交互式輸入密碼 - apk add --update --no-cache openssh sshpass script: # sshpass 參數: # -p 將密碼傳入 ssh # # ssh 參數: # -T 不申請僞終端 # -o 傳入 ssh 配置項,這裏傳入了 StrictHostKeyChecking 參數,默認值爲 ask,設置爲 no 表明 ssh 自動將遠程主機的 hostkeys 加入到 ~/.ssh/known_hosts,而且容許遠程主機的 hostkeys 發生變化 - sshpass -p $TEST_SERVER_XVR_PASSWORD ssh -T -o StrictHostKeyChecking=no xvrzhao@$TEST_SERVER_IP 'bash /home/xvrzhao/cd-scripts/ucenter-fe.sh'
注意:爲了不一些敏感數據(好比密碼等)暴露在代碼庫中,能夠在 GitLab 項目倉庫的 CI 設置中添加一些變量,只有項目維護者能夠管理和查看這些變量。在.gitlab-ci.yml
能夠經過$
符直接使用這些變量值。
# node 做爲基礎鏡像來執行前端項目的構建 FROM node:alpine AS builder WORKDIR /app # 根據鏡像分層原理,先拷貝依賴文件,若依賴文件沒有改動,則下一步安裝依賴會直接利用緩存 COPY ./package.json ./ RUN npm install --registry=https://registry.npm.taobao.org COPY ./ ./ RUN npm run build # 將前端項目 dist 打包進 nginx 鏡像 FROM nginx:alpine COPY --from=builder /app/dist/ /dist/ # 修改前端 Nginx 配置(這一步使用到 default.conf) COPY ./default.conf /etc/nginx/conf.d/
# 前端項目 dist 打包到了 nginx 鏡像,該文件爲 nginx server 配置 server { listen 80; location ^~ /ucenter { alias /dist; } }
關於 Nginx 提供靜態資源訪問的優化,請參考這篇文章:《Nginx 優化靜態文件訪問》。
經過 .gitlab-ci.yml
能夠看到,在 deploy_job
中咱們經過 SSH 遠程執行了所要部署的服務器上的腳本。腳本會拉取在 build_job
中推送的最新鏡像,而後中止並刪除目前服務器上運行的容器,啓動最新鏡像。
ucenter-fe.sh
腳本內容:
#!/bin/bash # 服務器 CD 腳本 # Author: Xavier Zhao <xvrzhao@gmail.com> # Date: 2020/05/06 echo -e -n "\n開始執行部署 ...\n\n(1/4) 拉取最新版本鏡像 ucfe:latest ...\t" res=$(docker pull registry.cn-shanghai.aliyuncs.com/xvrzhao/ucfe:latest 2>&1) if [ $? -eq 0 ]; then echo "完成!" else echo -e "錯誤!\n\n報錯信息:$res" exit 1 fi echo -e -n "(2/4) 中止舊版本容器 ucenter-fe ...\t" res=$(docker stop ucenter-fe 2>&1) if [ $? -eq 0 ]; then echo "完成!" else echo -e "錯誤!\n\n報錯信息:$res" exit 1 fi echo -e -n "(3/4) 移除舊版本容器 ucenter-fe ...\t" res=$(docker rm ucenter-fe 2>&1) if [ $? -eq 0 ]; then echo "完成!" else echo -e "錯誤!\n\n報錯信息:$res" exit 1 fi echo -e -n "(4/4) 啓動新版本容器 ucenter-fe ...\t" res=$(docker run -d --name ucenter-fe --network ucenter registry.cn-shanghai.aliyuncs.com/xvrzhao/ucfe:latest 2>&1) if [ $? -eq 0 ]; then echo "完成!" else echo -e "錯誤!\n\n報錯信息:$res" exit 1 fi echo -e "\n部署完成!\n"
執行效果:
$ ./ucenter-fe.sh 開始執行部署 ... (1/4) 拉取最新版本鏡像 ucfe:latest ... 完成! (2/4) 中止舊版本容器 ucenter-fe ... 完成! (3/4) 移除舊版本容器 ucenter-fe ... 完成! (4/4) 啓動新版本容器 ucenter-fe ... 完成! 部署完成!
或
$ ./ucenter-fe.sh 開始執行部署 ... (1/4) 拉取最新版本鏡像 ucfe:latest ... 完成! (2/4) 中止舊版本容器 ucenter-fe ... 錯誤! 報錯信息:Error response from daemon: No such container: ucenter-fe
// TODO