疊甲前言
本文僅作為個人學習GitLab的CI/CD功能記錄,不適合作為專業性指導,如有紕漏,煩請君指正。
云主機注冊Gitlab Runner 自動化構建部署的弊端
在前一文中,我們在Linux云主機上注冊了Gitlab-runner, 每次在gitlab流水線上發起構建部署時,云主機上的gitLab-runner先校驗環境,再拉取項目依賴,然后將打包生成的產物放置到云主機的本地目錄里,最后用nginx將公共訪問端口映射到產物放置的目錄。它不可避免的會存在3個問題:
- 依賴沖突,資源未隔離。 如果項目很多,管理將會非常麻煩,尤其還存在項目之間依賴資源沖突【比如 A項目用node14,B項目用node20】,構建需要頻繁切換環境。同時一些項目如果要清理移除【或者更新】,那么需要去一一尋找并清理此項目獨有的冗余依賴和資源,同時又需要避免一些依賴可能其他項目正在使用,所以會不可避免存在項目與項目之間相互影響、資源泄漏。
- 可移植性差。 如果云主機故障、停用、資源超額、續費昂貴等,要將已有項目遷移,那么就存在新云主機環境配置等各種繁瑣操作【比如 不僅需要從新走一遍配置流程,同時不同操作系統對同一工具存在配置差異】,如果項目很多,尤其是項目迭代很久,使用到的各類資源多時,遷移工作會更加麻煩。
- 安全性問題。 云主機上放置了各種項目的各種依賴和資源,打包產物也是直接掛到云主機上,通過nginx將公共訪問端口映射到產物放置的目錄,會存在安全性問題,一些違規腳本可能可以直接操作到云主機上的目錄和文件。
針對這3個問題,我們采用目前主流的自動化容器化部署工具Docker來解決這些問題。
Docker
什么是Docker?Docker有什么用?
Docker 是一個開源的容器化平臺,它允許開發者將應用程序及其所有依賴(如代碼、運行時環境、庫、配置文件等)打包到一個標準化的容器中,從而實現 “一次構建,到處運行” 的目標。
大白話就是:Docker 解決了 “在我電腦上能運行,在你電腦上卻不行” 的環境一致性問題。
Docker由什么構成?
Docker由鏡像、容器、容器引擎構成。
鏡像是什么?
鏡像就是一個模版,包含了應用運行的所有環境、依賴、代碼。
舉個例子:
我們要打包一個前端項目,那么我們會先基于NodeJs鏡像為基礎,下載項目依賴到/node_modules,然后用webpack打包成一個靜態資源,再以此用Docker 構建成一個項目鏡像。
其中,NodeJs鏡像也可以被其他項目所使用,你構建出來的項目鏡像也可以在不同容器中加載,鏡像就像一個規定好具體功能的模版,一次創建后,可以多次使用,嵌套使用。
鏡像本身不可修改,只可讀。
容器是什么?
容器就是基于鏡像創建的運行實例,是一個完全獨立的運行單元。容器會牢牢包裹鏡像,在鏡像基礎上添加了一層可寫層,所有運行時的修改【如文件創建修改】都會保存到這一層,不會影響到原始鏡像。你可以理解為,鏡像是“類”, 容器是基于類構建的“對象”。
容器引擎是什么?
容器引擎是 Docker 的核心運行環境,負責管理鏡像和容器的生命周期(創建、啟動、停止、刪除等)
怎么使用Docker?
在使用之前,我們需要知道 配置這一套自動化部署流程的最終目地是什么:
即我們修改了前端項目代碼,提交到Gitlab后,只需要在Gitlab上 CI/CD流水線
點擊一下構建、發布就可以將最新的修改迅速部署到測試、線上環境。
Docker在上述流程中承擔的工作就是 處理構建打包,并將打包產物通過創建容器加載鏡像的方式部署。
其實流程很簡單,可以看下方簡化圖:
準備工作
- 擁有一臺云主機
- 擁有一個Gitlab管理員權限賬號,并將項目代碼遠程地址鏈接到Gitlab。
安裝Docker
云主機運行(我的是Linux):
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
添加官方GPG密鑰
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL --connect-timeout 10 --max-time 30 https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
添加Docker軟件源
echo \"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
安裝Docker Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
驗證安裝
sudo docker run hello-world
出現這條信息說明docker
安裝成功。
添加國內鏡像
考慮到官方鏡像倉庫服務器距離東大比較遠,建議添加國內鏡像:
新建一個daemon.json文件,位置在 /etc/docker/daemon.json
json文件填入以下內容:
{"registry-mirrors": ["https://docker.registry.cyou","https://docker-cf.registry.cyou","https://dockercf.jsdelivr.fyi","https://docker.jsdelivr.fyi","https://dockertest.jsdelivr.fyi","https://mirror.aliyuncs.com","https://dockerproxy.com","https://mirror.baidubce.com","https://docker.m.daocloud.io","https://docker.nju.edu.cn","https://docker.mirrors.sjtug.sjtu.edu.cn","https://docker.mirrors.ustc.edu.cn","https://mirror.iscas.ac.cn","https://docker.rainbond.cc"]
}
更新配置
sudo systemctl daemon-reload
重啟Docker
sudo systemctl restart docker
查看國內鏡像配置是否成功
docker info
如果出現鏡像地址,說明配置成功了
Docker拉取Nginx鏡像
docker pull nginx
Docker安裝NodeJS鏡像
docker pull node:20
Docker安裝GitLab-Runner鏡像
創建gitlab-runner配置文件目錄
(之后你的runner有什么配置需要改的可以直接在這個目錄找到 /srv/gitlab-runner/config )
sudo mkdir -p /srv/gitlab-runner/config
安裝鏡像并啟動gitlab-runner容器
sudo docker run -d --name gitlab-runner --restart always \-v /srv/gitlab-runner/config:/etc/gitlab-runner \-v /var/run/docker.sock:/var/run/docker.sock \gitlab/gitlab-runner:latest
在Docker的Gitlab-Runner容器中注冊Runner
什么是注冊?
Gitlab流水線自動化構建、部署等任務Job需要指定一個執行者,也就是用哪一個gitlab-runner去完成這個任務。而gitlab-runner只有注冊后才會與gitlab項目/項目群組關聯到。
生成一個群組gitlab-runner
在 項目的 setting->CI/CD->runner
選擇創建群組runner
群組runner允許在同一個群組下,所有的項目都可以使用它去執行流水線任務
拷貝到 url 和 token值,云主機執行:
sudo docker exec -it gitlab-runner gitlab-runner register \--non-interactive \--url "這里填Url" \--registration-token "這里填token" \--executor "docker" \--docker-image alpine:latest \--description "docker-runner" \--tag-list "docker,linux,可以填自定義標簽" \--run-untagged="true" \--locked="false" \--access-level="not_protected"
參數說明:
--tag-list
: 就是runner的標簽列表,我們在編輯項目.gitlab-ci.yml文件時,可以給Job任務設置標簽,這樣執行任務時,就會去找用于這個標簽的runner。
驗證是否注冊成功
在 項目的 setting->CI/CD->runner中可以看到狀態:
配置項目的Dockerfile
在前端項目的根目錄新建一個Dockerfile文件:
具體語法含義見 Docker官方文檔:Dockerfile配置參考 和注解:
它本身并不難理解,常用的也就那幾個,花點時間認真看一下文檔學習一下,
可以參考我的(你的項目具體要看你自己的業務):
# 使用Nodejs20鏡像環境,并命名為 builder階段
FROM node:20 As builder
# 將builder階段工作目錄設置為 /app
WORKDIR /app
# 將項目所有文件復制到/app目錄
COPY . .
# 允許傳入構建環境【stage測試環境, product 線上環境】
ARG BUILD_SCRIPT=stage
# 安裝項目依賴以及執行前端腳本【npm run xxx這個取決于你項目打包命令是怎樣的,我的會打包生成build文件夾[線上環境是build_online文件夾]】
RUN npm install && npm run $BUILD_SCRIPT
# 分環境統一輸出目錄
RUN if [ "$BUILD_SCRIPT" = "product" ]; then \cp -r /app/build_online /app/dist; \else \cp -r /app/build /app/dist; \fi# 使用nginx最新版鏡像環境
FROM nginx:latest
# 將builder階段的/app/dist目錄下的生成文件 復制到 nginx鏡像的默認靜態資源目錄
COPY --from=builder /app/dist /usr/share/nginx/html
# 聲明容器對外暴露80端口(nginx默認端口)
EXPOSE 80
# 啟動nginx映射服務
CMD ["nginx", "-g", "daemon off;"]
配置.gitlab-ci.yml文件
在前端項目的根目錄新建一個.gitlab-ci.yml文件:
具體語法含義見 GitLab 官方文檔:CI/CD 配置參考 和注解:
可以參考我的(你的項目具體要看你自己的業務):
# 定義Job任務腳本流程順序:構建 -> 部署(測試) -> 發布(線上)
stages:- build- deploy- publish# 執行構建測試環境腳本
Build_Test_Job:stage: build # 流程階段image: docker:latestservices:- docker:dind # dind意思是Docker in Docker, 讓你的 CI-Job能在Runner容器里直接執行 Docker 命令(比如 docker build、docker run 等),就像在物理機上一樣。when: manual # 手動觸發, 可以自定義條件觸發 only:- /^\d{8}$/ # 限制分支,正則表達式匹配只有分支名包含8個數字的才能生成此腳本【具體原因見末尾《1》】tags: - webPro # tags匹配Runner,只有Runner具有這個Tag,任務才能在這個Runner上執行,【查看Runner-Tag見《2》】script:- echo "開始構建測試環境Docker鏡像..." # echo 是輸出日志- docker build --build-arg BUILD_SCRIPT=stage -t webpack_study:latest . # docker構建一個鏡像 命名為webpack_study標簽為latest- docker save webpack_study:latest -o webpack_study.tar # 將構建好的鏡像保存為tar文件- echo "測試環境Docker鏡像構建并打包為tar文件完成!"- echo "嘗試導出構建產物dist文件夾和打包鏡像..."- docker create --name temp_container webpack_study:latest # 創建一個臨時容器加載鏡像- docker cp temp_container:/usr/share/nginx/html ./dist # 從臨時容器中導出build產物- docker rm temp_container # 刪除臨時容器- echo "導出構建產物dist文件夾和打包鏡像完成..."artifacts:paths:- webpack_study.tar # docker產物- distDeploy_Test_Job:stage: deployimage: docker:latestservices:- docker:dindwhen: manualonly:- /^\d{8}$/tags: - webProneeds: ["Build_Test_Job"] # 執行必要的前置條件script:- echo "開始在Docker容器上部署測試環境鏡像..."- docker load -i webpack_study.tar # 加載build階段產物(打包的鏡像)到本地- docker stop docker_webpack_study || true # 如果有 容器叫 docker_webpack_study, 將其停止并移除- docker rm docker_webpack_study || true- docker run -d --name docker_webpack_study -p 8087:80 webpack_study:latest # 啟動容器docker_webpack_study加載鏡像webpack_study:latest把容器的80端口映射到服務器的8087端口- echo "測試環境Docker容器上部署鏡像完成!"# 執行構建線上環境腳本
Build_Online_Job:stage: build # 流程階段image: docker:latestservices:- docker:dindwhen: manual # 手動觸發, 可以自定義條件觸發 only:- /^\d{8}$/ # 限制分支,正則表達式匹配只有分支名包含8個數字的才能生成此腳本【具體原因見末尾《1》】tags: - webPro # tags匹配Runner,只有Runner具有這個Tag,任務才能在這個Runner上執行,【查看Runner-Tag見《2》】script:- echo "開始構建線上環境Docker鏡像..." # echo 是輸出日志- docker build --build-arg BUILD_SCRIPT=product -t webpack_study_online:latest . # docker構建一個鏡像 命名為webpack_study標簽為latest- docker save webpack_study_online:latest -o webpack_study_online.tar # 將構建好的鏡像保存為tar文件- echo "線上環境Docker鏡像構建并打包為tar文件完成!"- echo "嘗試導出構建產物dist_online文件夾和打包鏡像..."- docker create --name temp_container webpack_study_online:latest # 創建一個臨時容器加載鏡像- docker cp temp_container:/usr/share/nginx/html ./dist_online # 從臨時容器中導出build產物- docker rm temp_container # 刪除臨時容器- echo "導出構建產物dist_online文件夾和打包鏡像完成..."artifacts:paths:- webpack_study_online.tar # docker產物- dist_onlinePublish_Online_Job:stage: publishimage: docker:latestservices:- docker:dindwhen: manualonly:- /^\d{8}$/tags: - webProneeds: ["Build_Online_Job"] # 執行必要的前置條件script:- echo "開始在Docker容器上部署線上環境鏡像..."- docker load -i webpack_study_online.tar # 加載build階段產物(打包的鏡像)到本地- docker stop docker_webpack_study_online || true # 如果有 容器叫 docker_webpack_study, 將其停止并移除- docker rm docker_webpack_study_online || true- docker run -d --name docker_webpack_study_online -p 8089:80 webpack_study_online:latest # 啟動容器docker_webpack_study加載鏡像webpack_study:latest把容器的80端口映射到服務器的8087端口- echo "線上環境Docker容器上部署鏡像完成!"#《1》
# 需求決定:前端項目因為每周五都要發一次版本,分支名字就是 20250718,分支上線后會合并到main分支,main分支的代碼就是穩定的,所以打包構建分支只允許周分支生成Job
#《2》
# GitLab -> Project -> setting -> CI/CD -> Runner
# 云服務器測試環境端口是:8087
# 云服務器線上環境端口是:8089
# 這里的端口需要你在云服務器上開通入口安全組。
到此,我們修改了前端項目代碼,提交到Gitlab后,只需要在Gitlab上 CI/CD流水線
點擊一下構建、發布就可以將最新的修改迅速部署到測試、線上環境。
其他
- 不止前端項目,什么安卓、IOS、Flutter、RN、后端項目等其他項目其實都可以用Docker與本文相同的方式實現容器化部署,唯一區別的就是在打包的腳本內容不同,比如 安卓的要到gradle命令, IOS要用到xcodebuild命令等。
完整Demo項目地址:
Github Docker部署前端項目倉庫地址
tips: 記得拉20240718
分支
可能遇到的問題
- Docker執行流水線構建時,出現 2375問題:
解決方案:
找到 /srv/gitlab-runner/config 目錄下的 config.toml文件:
在volumes
這一欄進行擴充:
volumes = ["/cache", "/usr/bin/docker:/usr/bin/docker", "/var/run/docker.sock:/var/run/docker.sock"]
解釋說明:
Docker 2375問題解決