2025年11月16日
背景
平常使用的CI/CD主要是用Jenkins,git的本地hook,但是对于代码上传后执行差异代码优化这个技术场景流程场景来说:
- Jenkins流程只会做到全量排查,如果中途遇到问题代码导致失败,得不偿失,且一个仓库可能会有不再维护代码与无关代码,造成资源浪费
- git本地hook问题在于,更新时每个组员都需要做,并且git commit的时候可以通过--no-verify 规避本地check,同时如果直接在gitlab上面IDE直接修改,则本地git hook脚本不会执行
因此为了优化这一块的流水线,避免上述2个问题,我打算通过git合入时通过git 的CI/CD,执行脚本分析差异化文件
(本文因为离职后所有消息记录会被删除,因此图片是没有的,请见谅)
部署与验证
gitlab自带部署runner文档 docs.gitlab.cn/docs/jh/ins... ,同时csdn也有对应的文档可以借鉴 blog.csdn.net/qq_39826207... 不过实际上我在开发时也遇到了一点问题,所以也可以通过以下方式部署:
-
准备Linux环境(Centos为例)
-
通过wget或者离线等方式获取gitlab runner的安装包
sh
# 创建专用用户(不创建 home 目录)
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner
# 创建 runner 工作目录
sudo mkdir /home/gitlab-runner/runner
sudo chown gitlab-runner:gitlab-runner /home/gitlab-runner/runner
-
初始化并启动 GitLab Runner(作为服务)
虽然使用二进制,但建议以系统服务方式运行。
-
创建 systemd 服务文件
shsudo tee /etc/systemd/system/gitlab-runner.service <<EOF [Unit] Description=GitLab Runner After=syslog.target network.target ConditionFileIsExecutable=/usr/local/bin/gitlab-runner [Service] StartLimitInterval=5 StartLimitBurst=10 ExecStart=/usr/local/bin/gitlab-runner run --working-directory /home/gitlab-runner/runner SuccessExitStatus=0 143 Restart=always RestartSec=5 KillMode=mixed WorkingDirectory=/home/gitlab-runner/runner User=gitlab-runner Group=gitlab-runner Environment=PATH=/bin:/usr/local/bin:/usr/bin CacheDirectory=gitlab-runner CacheDirectoryMode=0750 [Install] WantedBy=multi-user.target EOF -
重载 systemd 并启动服务
bashsudo systemctl daemon-reexec sudo systemctl daemon-reload sudo systemctl enable gitlab-runner sudo systemctl start gitlab-runner -
检查服务状态
shsudo systemctl status gitlab-runner
-
-
注册GitLab Runner 到 GitLab 实例
(如果是多次注册,则需要优先按照第一步切换成对应的gitlab用户进行命令操作,整个的安装流程,我按照官方流程没成功,和当前流程唯一的差别就是官方没有切换到gitlab用户)
-
获取注册令牌 登录 GitLab Web 界面。 进入你的项目 → Settings → CI / CD → Runners。 复制 Registration token(项目级 Runner)。 或者在 Admin Area → Overview → Runners 获取实例级令牌。
-
运行注册命令: 切换到 gitlab-runner 用户并注册:
shsudo -u gitlab-runner -H /usr/local/bin/gitlab-runner register按提示输入:
- GitLab instance URL: gitlab.com(或你的自建 GitLab 地址)
- Registration token: 你复制的令牌
- Description: my-runner(自定义描述)
- Tags: linux,docker(可选,用于任务匹配)
- Executor: shell(或 docker, docker+machine 等)
- Default Docker image: alpine:latest(如果使用 docker executor)
-
按提示输入:
- GitLab instance URL: gitlab.com(或你的自建 GitLab 地址)
- Registration token: 你复制的令牌
- Description: my-runner(自定义描述)
- Tags: linux,docker(可选,用于任务匹配)、
- Executor: shell(或 docker, docker+machine 等)
- Default Docker image: alpine:latest(如果使用 docker executor)
- 注册成功后,配置会保存在 /home/gitlab-runner/runner/config.toml。
-
验证注册: 在 GitLab Web 界面的 Runners 页面,应看到新注册的 Runner 处于"在线"状态。
卸载
有时候可能是安装错误,可能是需要迁移业务,需要现在原有的环境的服务,可以按照以下步骤
-
停止并禁用服务
shsudo systemctl stop gitlab-runner sudo systemctl disable gitlab-runner -
删除服务文件
shsudo rm /etc/systemd/system/gitlab-runner.service sudo systemctl daemon-reload -
删除二进制文件和用户
shsudo rm /usr/local/bin/gitlab-runner # -r 删除用户主目录 sudo userdel -r gitlab-runner -
清理残留文件(可选)
shsudo rm -rf /home/gitlab-runner
脚本分享
.gitlab-ci.yml
这个是仓库全局的git CI/CD的配置文件,根据触发阶段可以执行对应的script相关的脚本,在该脚本可以执行更多shell脚本,方便管理
该例子是合入时执行2个脚本,可供参考
yml
stages:
- check
# 全局变量
variables:
GIT_DEPTH: "" # 完整克隆
# 只在 Merge Request 时触发流水线(即"合入前检查")
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- when: never
check:
stage: check
interruptible: true # 可中断:当 MR 更新时取消旧流水线
script:
# 脚本1:权限/配置检查
- chmod +x ./cloud/env/install/script/config_checkstyle.sh
- ./cloud/env/install/script/config_checkstyle.sh ucs-server $GITLAB_USER_EMAIL $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
# 脚本2:另一个检查脚本(示例)
- chmod +x ./cloud/env/install/script/another_check.sh
- ./cloud/env/install/script/another_check.sh
# 只在 MR 中运行
only:
- merge_requests
tags:
- ucs_server
# 可选:指定 MR 的目标分支(比如只对 main/develop 的合入做检查)
# rules:
# - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^(main|develop)$/
sh脚本文件
Java验证差异文件
用于验证合入时两个分支之间的差异文件,对差异文件进行checkstyle(通过执行jar包)有问题就返回0,无问题会返回1,gitlab合并检测会根据返回值判定流水线成功/失败
原有脚本已经遗失,判断逻辑应当是jar包产生的输出,应当只看[WARN][ERROR]开头的的行是否>0,有则有问题(需要jar包启动checkstyle,需要xml配置文件进行规则配置)
Java程序的checkstyle需要以不同版本为大前提创建各自环境,其他的如本地执行hook,Jenkins执行,git CI/CD执行流程,需要的各种静态资源需要一致
sh
#!/bin/bash
# ================== 参数校验 ==================
if [ $# -ne 4 ]; then
echo "Usage: $0 <namespace> <email> <source_branch> <target_branch>"
echo "Example: $0 ucs-server dev@example.com feature/add-config main"
exit 1
fi
NAMESPACE=$1
EMAIL=$2
SOURCE_BRANCH=$3
TARGET_BRANCH=$4
# ================== 配置区 ==================
# Checkstyle jar 包路径(请根据实际路径修改)
CHECKSTYLE_JAR="./cloud/env/install/script/checkstyle.jar"
# Checkstyle 配置文件路径(checkstyle.xml)
CHECKSTYLE_CONFIG="./cloud/env/install/script/checkstyle.xml"
# 临时文件用于保存 Checkstyle 输出
CHECKSTYLE_LOG="/tmp/checkstyle_output_$$_$(date +%s).txt"
# ===========================================
echo "Analyzing Java files changed between '$SOURCE_BRANCH' and '$TARGET_BRANCH'..."
# 1. 确保远程分支存在
if ! git rev-parse --verify "origin/$SOURCE_BRANCH" > /dev/null 2>&1; then
echo "Error: Remote branch 'origin/$SOURCE_BRANCH' not found."
echo "Available branches:"; git branch -r
exit 1
fi
if ! git rev-parse --verify "origin/$TARGET_BRANCH" > /dev/null 2>&1; then
echo "Error: Remote branch 'origin/$TARGET_BRANCH' not found."
echo "Available branches:"; git branch -r
exit 1
fi
# 2. 获取两个分支之间变更的 Java 文件
CHANGED_JAVA_FILES=()
while IFS= read -r file; do
if [[ "$file" == *.java ]] && [ -f "$file" ]; then
CHANGED_JAVA_FILES+=("$file")
fi
done < <(git diff --name-only "origin/$TARGET_BRANCH"...origin/$SOURCE_BRANCH)
# 3. 检查是否有变更的 Java 文件
if [ ${#CHANGED_JAVA_FILES[@]} -eq 0 ]; then
echo "No Java files changed between 'origin/$TARGET_BRANCH' and 'origin/$SOURCE_BRANCH'."
echo "No Java changes detected." | mail -s "$NAMESPACE: No Java changes" "$EMAIL"
exit 0 # 无变更视为通过
fi
echo "Found ${#CHANGED_JAVA_FILES[@]} Java file(s) to check:"
printf ' %s\n' "${CHANGED_JAVA_FILES[@]}"
# 4. 执行 Checkstyle 检查(一次性传入所有文件)
echo "Running Checkstyle..."
# 使用 -c 指定配置,-f xml 或 summary 可选,这里使用简洁格式
# 如果你希望详细输出,可以使用 -f xml 并用 xmllint 格式化
if java -jar "$CHECKSTYLE_JAR" -c "$CHECKSTYLE_CONFIG" "${CHANGED_JAVA_FILES[@]}" > "$CHECKSTYLE_LOG" 2>&1; then
# Checkstyle 命令执行成功(语法正确),但不代表无警告/错误
OUTPUT=$(cat "$CHECKSTYLE_LOG")
echo "$OUTPUT"
# 检查输出中是否包含违规信息(Checkstyle 通常会在有违规时输出内容)
if [[ -s "$CHECKSTYLE_LOG" && "$(echo "$OUTPUT" | grep -v '^$' | wc -l)" -gt 0 ]]; then
echo "Checkstyle found issues in changed Java files."
echo "Checkstyle Report:" | mail -s "[FAIL] $NAMESPACE: Checkstyle failed" "$EMAIL" -A "$CHECKSTYLE_LOG"
rm -f "$CHECKSTYLE_LOG"
exit 1 # 有违规 → 失败
else
echo "All changed Java files passed Checkstyle."
echo "No checkstyle violations found." | mail -s "[OK] $NAMESPACE: Checkstyle passed" "$EMAIL"
rm -f "$CHECKSTYLE_LOG"
exit 0 # 无违规 → 成功
fi
else
# Checkstyle 命令执行失败(如 jar 报错、配置错误等)
echo "Checkstyle execution failed!"
cat "$CHECKSTYLE_LOG"
echo "Checkstyle execution failed. See details." | mail -s "[ERROR] $NAMESPACE: Checkstyle execution error" "$EMAIL" -A "$CHECKSTYLE_LOG"
rm -f "$CHECKSTYLE_LOG"
exit 1
fi
yml验证差异文件
与上面的Java验证类似,就是查询差异文件yml与yaml,checkstyle有问题就返回0,无问题会返回1,gitlab合并检测会根据返回值判定流水线成功/失败
(如果是spring可以跳过所有的application.yml文件,因为yml 验证主要是为了交付给运维同事配置文件才会有的流水线步骤)
原有脚本已经遗失,判断逻辑应当通过yamllint进行验证,根据输出产生行数>0则有问题(需要ymlconf文件作为配置规则)
sh
#!/bin/bash
if [ $# -ne 2 ]; then
echo "Usage: $0 <namespace> <email>"
exit 1
fi
NAMESPACE=$1
EMAIL=$2
# ================== 配置区 ==================
TARGET_BRANCH="main" # 目标合并分支,可根据需要改为 dev、release 等
CONFIG_FILE="./cloud/env/install/script/ymlconf"
# ===========================================
echo "🔍 Checking YAML files changed between current branch and '$TARGET_BRANCH'..."
# 1. 获取当前分支相对于目标分支的差异文件(仅新增或修改)
# 注意:GitLab CI 会自动 fetch 所有分支,可以直接使用
CHANGED_YAML_FILES=()
while IFS= read -r file; do
if [[ "$file" == *.yml || "$file" == *.yaml ]]; then
if [ -f "$file" ]; then # 确保文件存在(避免删除的文件)
CHANGED_YAML_FILES+=("$file")
fi
fi
done < <(git diff --name-only "origin/$TARGET_BRANCH"...HEAD)
# 2. 检查是否有变更的 YAML 文件
if [ ${#CHANGED_YAML_FILES[@]} -eq 0 ]; then
echo "✅ No YAML files changed. Skipping yamllint."
echo "No YAML changes detected." | mail -s "$NAMESPACE: No YAML changes" "$EMAIL"
exit 0
fi
echo "📄 Found ${#CHANGED_YAML_FILES[@]} YAML file(s) to check:"
printf ' %s\n' "${CHANGED_YAML_FILES[@]}"
# 3. 对差异文件逐个运行 yamllint
ERRORS_FOUND=""
for file in "${CHANGED_YAML_FILES[@]}"; do
echo " linting $file"
if ! yamllint -c "$CONFIG_FILE" "$file" > /tmp/yamllint.log 2>&1; then
echo "❌ yamllint failed on $file"
ERRORS_FOUND+="$file:\n"
ERRORS_FOUND+=$(cat /tmp/yamllint.log)
ERRORS_FOUND+="\n\n"
fi
done
# 4. 处理结果
if [ -n "$ERRORS_FOUND" ]; then
echo "YAML file has syntax errors!"
echo -e "$ERRORS_FOUND"
echo -e "$ERRORS_FOUND" | mail -s "$NAMESPACE YAML file has syntax errors!" "$EMAIL"
rm -f /tmp/yamllint.log
exit 1
else
echo "✅ All changed YAML files are valid."
echo "All changed YAML files passed lint." | mail -s "$NAMESPACE: YAML lint passed" "$EMAIL"
rm -f /tmp/yamllint.log
exit 0
fi
其他问题
gitlab runner实例在shell模式下怎么占用Centos资源的
在 shell executor 模式下,GitLab Runner 的工作方式如下:
-
直接在宿主机(Centos)上执行命令:Runner 会以运行它的用户身份(如 gitlab-runner 用户),在系统 shell 中直接执行 .gitlab-ci.yml 中定义的 script 命令。
-
资源占用特点:
- CPU/内存:由实际执行的脚本决定(如编译、测试、打包等),直接消耗宿主机资源。
- 磁盘:会在临时目录(默认 /home/gitlab-runner/builds//...)克隆代码并执行任务,任务结束后默认清理(可通过 builds_dir 配置)。
- 环境依赖:所有依赖(如 Python、Node.js、Docker CLI 等)必须预先安装在 Centos 系统中。
- 无隔离性:多个 job 共享同一系统环境,可能互相干扰(比如修改全局 PATH、写文件到 /tmp)。
- 并发控制:通过 concurrent(全局)和 limit(每个 runner)控制同时运行的 job 数,避免资源耗尽。
建议:
- 生产环境慎用 shell 模式,推荐用 docker 或 kubernetes 提供隔离。
- 如果必须用 shell,确保 runner 用户权限最小化,并监控系统负载。
gitlab CI/CD如果失败,可不可以阻止合并任务
实现方式:
-
启用"合并前必须通过流水线":
- 进入项目 → Settings > General > Merge requests
- 勾选 Pipelines must succeed
- (可选)勾选 Require approval from code owners
-
保护目标分支(如 main / develop):(这个对于有规模的开发团队,已经有CMO进行配置过了)
-
Settings > Repository > Protected Branches
-
对目标分支设置:
- Allowed to merge: Maintainers(或指定角色)
- Require status checks to pass before merging(如果启用了外部状态检查)
-
-
结果:
- 如果 CI pipeline 失败(任何 job 失败且未被 allow_failure: true 忽略),Merge 按钮变灰,无法合并。
- 即使有 Approver 同意,只要 pipeline 失败,也无法合并。 工作流程:
-
注册:Runner 向 GitLab Server 注册,获得唯一 token,并绑定到项目或 group。
-
轮询/长连接:Runner 定期向 GitLab Server 的 /jobs/request API 发起请求(默认每几秒一次),询问是否有待处理的 job。
-
领取任务:当有匹配标签(tags)的 job 到来,Runner 领取任务(包含 .gitlab-ci.yml 中的 script、variables、artifacts 等信息)。
-
执行任务 :
- 根据配置的 executor(shell/docker/ssh/k8s 等)启动执行环境;
- 克隆代码;
- 执行 before_script → script → after_script;
- 上传 artifacts/logs;
- 上报状态(success/failure)。
-
释放资源:任务结束,清理临时文件(除非配置保留)。
gitlab CI/CD,如果要执行脚本,有哪些参数是gitlab这边可以提供
GitLab 自动注入大量 预定义 CI/CD 变量(Predefined Variables),你可以在脚本中直接使用,例如:
常用内置变量示例:
| 变量 | 说明 |
|---|---|
| CI_COMMIT_REF_NAME | 当前分支或 tag 名(如 main, v1.0) |
| CI_PROJECT_DIR | CI 任务的工作目录(代码被 clone 到这里) |
| GITLAB_USER_EMAIL | 触发 pipeline 的用户邮箱 |
其他的像是两个分支的分支hash也可以通过git提供的参数获取到,用于差异文件的排查。更多的可以看官网 docs.gitlab.com/ci/variable...
合并任务下,两个分支都有脚本,改用哪一个,如果任意一方缺少脚本,该用哪一个
GitLab 的规则非常明确: 始终使用 source branch(源分支)中的 .gitlab-ci.yml 文件
详细说明:
- 当你创建一个从 feature → main 的 MR,
- GitLab 会 checkout feature 分支的代码,
- 并读取 feature 分支根目录下的 .gitlab-ci.yml 来执行 pipeline。
- 不会去读 main 分支的 .gitlab-ci.yml。
| 情况 | 使用哪个 .gitlab-ci.yml? |
|---|---|
| feature 有,main 有 | ✅ 用 feature 的 |
| feature 有,main 没有 | ✅ 用 feature 的 |
| feature 没有,main 有 | ❌ pipeline 不会触发(因为 source branch 没有 .gitlab-ci.yml) |
| 两者都没有 | ❌ 无 CI pipeline |
gitlab-ci.yml会执行更多脚本,这些脚本/静态资源怎么存放
推荐将大资源(如git CI/CD可以接入通过checkstyle.jar执行checkstyle,对应的jar文件)放在服务器上,其他的则可以放在各自的代码仓库内
为什么 GitLab Runner 安装时没有 gitlab-runner 用户就无法注册?这个用户代表什么?
当你通过官方方式安装 GitLab Runner(如 yum install gitlab-runner 或 dpkg -i gitlab-runner_xxx.deb),安装脚本会自动:
- 创建系统用户 gitlab-runner
- 以该用户身份运行 runner 服务
- Runner 在执行 job 时,所有命令都以 gitlab-runner 身份运行
所以如果手动的安装,可能会错过这一步,最终导致无法注册runner到gitlab上面
.gitlab-ci.yml的完整克隆是什么意思
答案:GitLab 会完整克隆(checkout)源分支(source branch)的代码,并使用该分支中的 .gitlab-ci.yml 文件
-
这个过程包括:
- 在 Runner 上执行 git clone
- git checkout (例如 feature/add-login 的最新 commit)
- 读取这个 commit 中的 .gitlab-ci.yml
- 按照它的定义执行 job
所以,"完整克隆"在这里的意思是:
不是只复制 .gitlab-ci.yml 文件,而是把整个源分支的代码仓库完整拉下来,确保 CI 配置和代码版本严格一致。
为什么重要?
- 如果你修改了 .gitlab-ci.yml(比如加了一个测试 job),只有推送到 源分支 才会在 MR 中生效。
- 目标分支(如 main)的 .gitlab-ci.yml完全不会被使用。
gitlab runner拉代码的时候有什么级别嘛,比如一个级别只拉代码,一个级别把全部的信息都拉取下来?如果有不同级别,gitlabcicd提供的参数还会生效吗?如何配置?如何选择?
gitlab runner拉代码的时候有什么级别嘛,比如一个级别只拉代码,一个级别把全部的信息都拉取下来?
GitLab Runner 在拉取代码时,确实支持"不同深度"的克隆(clone depth),但没有"只拉代码 / 拉全部信息"这种二元级别。
你可以通过以下方式控制 拉取多少 Git 历史(commit history):
- 浅克隆(shallow clone):只拉最近 N 个 commit(默认行为)
- 深克隆(full clone):拉取完整历史(所有分支、tags、完整 commit graph)
注意:无论哪种方式,都会拉取完整的当前 commit 的文件内容(即"代码")。
所谓"只拉代码"其实是误解 ------ 代码文件总是完整的,区别只在 历史记录是否完整。 浅拷贝,深拷贝的在各个方面的不同
| 配置 | 行为 | 适用场景 |
|---|---|---|
| GIT_DEPTH: 1(默认) | 浅克隆:只拉当前 commit + 最近 1 层历史 | 快速构建、节省带宽 |
| GIT_DEPTH: 0 或未设置 | 深克隆:拉取完整仓库历史(等价于 git clone --mirror) | 需要 git log、git describe、计算版本号等 |
| GIT_DEPTH: 10 | 拉最近 10 个 commit 的历史 | 折中方案 |
不同的配置推荐场景如下:
| 场景 | 推荐配置 |
|---|---|
| 普通构建、测试、部署 | GIT_DEPTH: 1(默认,最快) |
| 需要生成版本号(如 v1.2.3-5-gabc123) | GIT_DEPTH: 0 + git fetch --tags |
| 需要分析 commit 差异(如 changelog) | GIT_DEPTH: 0 |
| 使用 Lerna / Nx 等工具依赖 Git 历史 | GIT_DEPTH: 0 |
| 节省 CI 时间和带宽 | 保持默认(GIT_DEPTH: 1) |
GitLab CI 提供的参数还会生效吗?
会!完全不受影响。
无论你用浅克隆还是深克隆,以下内容都 正常可用
如何配置?
GitLab CI 通过一个内置变量 GIT_DEPTH 控制克隆深度:
如:
yaml
variables:
GIT_DEPTH: 0 # 拉取完整历史
build:
script:
- git log --oneline -n 5 # 只有 GIT_DEPTH > 1 或 =0 时才有多个 commit
- git describe --tags # 需要完整 tag 历史
默认行为是什么?
GIT_DEPTH: 1(浅克隆,高效)