叠甲前言
本文仅作为个人学习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):
bash
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
添加官方GPG密钥
bash
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软件源
bash
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
bash
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
验证安装
bash
sudo docker run hello-world
出现这条信息说明docker
安装成功。
添加国内镜像
考虑到官方镜像仓库服务器距离东大比较远,建议添加国内镜像:
新建一个daemon.json文件,位置在 /etc/docker/daemon.json
json文件填入以下内容:
bash
{
"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"
]
}
更新配置
bash
sudo systemctl daemon-reload
重启Docker
bash
sudo systemctl restart docker
查看国内镜像配置是否成功
bash
docker info
如果出现镜像地址,说明配置成功了
Docker拉取Nginx镜像
bash
docker pull nginx

Docker安装NodeJS镜像
bash
docker pull node:20

Docker安装GitLab-Runner镜像
创建gitlab-runner配置文件目录
(之后你的runner有什么配置需要改的可以直接在这个目录找到 /srv/gitlab-runner/config )
bash
sudo mkdir -p /srv/gitlab-runner/config
安装镜像并启动gitlab-runner容器
bash
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值,云主机执行:
bash
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配置参考 和注解:
它本身并不难理解,常用的也就那几个,花点时间认真看一下文档学习一下,
可以参考我的(你的项目具体要看你自己的业务):
bash
# 使用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 配置参考 和注解:
可以参考我的(你的项目具体要看你自己的业务):
bash
# 定义Job任务脚本流程顺序:构建 -> 部署(测试) -> 发布(线上)
stages:
- build
- deploy
- publish
# 执行构建测试环境脚本
Build_Test_Job:
stage: build # 流程阶段
image: docker:latest
services:
- 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产物
- dist
Deploy_Test_Job:
stage: deploy
image: docker:latest
services:
- docker:dind
when: manual
only:
- /^\d{8}$/
tags:
- webPro
needs: ["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:latest
services:
- docker:dind
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=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_online
Publish_Online_Job:
stage: publish
image: docker:latest
services:
- docker:dind
when: manual
only:
- /^\d{8}$/
tags:
- webPro
needs: ["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
这一栏进行扩充:
bash
volumes = ["/cache", "/usr/bin/docker:/usr/bin/docker", "/var/run/docker.sock:/var/run/docker.sock"]
解释说明:
Docker 2375问题解决