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的安装包
-
1undefined
# 创建专用用户(不创建 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
4. 初始化并启动 GitLab Runner(作为服务)
虽然使用二进制,但建议以系统服务方式运行。
1. 创建 systemd 服务文件
```sh
sudo 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 并启动服务
1 sudo systemctl daemon-reexec 2 sudo systemctl daemon-reload 3 sudo systemctl enable gitlab-runner 4 sudo systemctl start gitlab-runner
- 检查服务状态
1 sudo systemctl status gitlab-runner
- 注册GitLab Runner 到 GitLab 实例
(如果是多次注册,则需要优先按照第一步切换成对应的gitlab用户进行命令操作,整个的安装流程,我按照官方流程没成功,和当前流程唯一的差别就是官方没有切换到gitlab用户)- 获取注册令牌 登录 GitLab Web 界面。 进入你的项目 → Settings → CI / CD → Runners。 复制 Registration token(项目级 Runner)。 或者在 Admin Area → Overview → Runners 获取实例级令牌。
- 运行注册命令: 切换到 gitlab-runner 用户并注册:
按提示输入:1 sudo -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)
3. 按提示输入:
* 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 处于“在线”状态。
卸载
有时候可能是安装错误,可能是需要迁移业务,需要现在原有的环境的服务,可以按照以下步骤
- 停止并禁用服务
1 sudo systemctl stop gitlab-runner 2 sudo systemctl disable gitlab-runner
- 删除服务文件
1 sudo rm /etc/systemd/system/gitlab-runner.service 2 sudo systemctl daemon-reload
- 删除二进制文件和用户
1 sudo rm /usr/local/bin/gitlab-runner 2 # -r 删除用户主目录 3 sudo userdel -r gitlab-runner
- 清理残留文件(可选)
1sudo rm -rf /home/gitlab-runner
脚本分享
.gitlab-ci.yml
这个是仓库全局的git CI/CD的配置文件,根据触发阶段可以执行对应的script相关的脚本,在该脚本可以执行更多shell脚本,方便管理
该例子是合入时执行2个脚本,可供参考
1stages: 2 - check 3 4# 全局变量 5variables: 6 GIT_DEPTH: "" # 完整克隆 7 8# 只在 Merge Request 时触发流水线(即“合入前检查”) 9workflow: 10 rules: 11 - if: $CI_PIPELINE_SOURCE == "merge_request_event" 12 - when: never 13 14check: 15 stage: check 16 interruptible: true # 可中断:当 MR 更新时取消旧流水线 17 script: 18 # 脚本1:权限/配置检查 19 - chmod +x ./cloud/env/install/script/config_checkstyle.sh 20 - ./cloud/env/install/script/config_checkstyle.sh ucs-server $GITLAB_USER_EMAIL $CI_MERGE_REQUEST_TARGET_BRANCH_NAME 21 22 # 脚本2:另一个检查脚本(示例) 23 - chmod +x ./cloud/env/install/script/another_check.sh 24 - ./cloud/env/install/script/another_check.sh 25 26 # 只在 MR 中运行 27 only: 28 - merge_requests 29 30 tags: 31 - ucs_server 32 33 # 可选:指定 MR 的目标分支(比如只对 main/develop 的合入做检查) 34 # rules: 35 # - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^(main|develop)$/ 36
sh脚本文件
Java验证差异文件
用于验证合入时两个分支之间的差异文件,对差异文件进行checkstyle(通过执行jar包)有问题就返回0,无问题会返回1,gitlab合并检测会根据返回值判定流水线成功/失败
原有脚本已经遗失,判断逻辑应当是jar包产生的输出,应当只看[WARN][ERROR]开头的的行是否>0,有则有问题(需要jar包启动checkstyle,需要xml配置文件进行规则配置)
Java程序的checkstyle需要以不同版本为大前提创建各自环境,其他的如本地执行hook,Jenkins执行,git CI/CD执行流程,需要的各种静态资源需要一致
1#!/bin/bash 2 3# ================== 参数校验 ================== 4if [ $# -ne 4 ]; then 5 echo "Usage: $0 <namespace> <email> <source_branch> <target_branch>" 6 echo "Example: $0 ucs-server [email protected] feature/add-config main" 7 exit 1 8fi 9 10NAMESPACE=$1 11EMAIL=$2 12SOURCE_BRANCH=$3 13TARGET_BRANCH=$4 14 15# ================== 配置区 ================== 16# Checkstyle jar 包路径(请根据实际路径修改) 17CHECKSTYLE_JAR="./cloud/env/install/script/checkstyle.jar" 18 19# Checkstyle 配置文件路径(checkstyle.xml) 20CHECKSTYLE_CONFIG="./cloud/env/install/script/checkstyle.xml" 21 22# 临时文件用于保存 Checkstyle 输出 23CHECKSTYLE_LOG="/tmp/checkstyle_output_$$_$(date +%s).txt" 24# =========================================== 25 26echo "Analyzing Java files changed between '$SOURCE_BRANCH' and '$TARGET_BRANCH'..." 27 28# 1. 确保远程分支存在 29if ! git rev-parse --verify "origin/$SOURCE_BRANCH" > /dev/null 2>&1; then 30 echo "Error: Remote branch 'origin/$SOURCE_BRANCH' not found." 31 echo "Available branches:"; git branch -r 32 exit 1 33fi 34 35if ! git rev-parse --verify "origin/$TARGET_BRANCH" > /dev/null 2>&1; then 36 echo "Error: Remote branch 'origin/$TARGET_BRANCH' not found." 37 echo "Available branches:"; git branch -r 38 exit 1 39fi 40 41# 2. 获取两个分支之间变更的 Java 文件 42CHANGED_JAVA_FILES=() 43 44while IFS= read -r file; do 45 if [[ "$file" == *.java ]] && [ -f "$file" ]; then 46 CHANGED_JAVA_FILES+=("$file") 47 fi 48done < <(git diff --name-only "origin/$TARGET_BRANCH"...origin/$SOURCE_BRANCH) 49 50# 3. 检查是否有变更的 Java 文件 51if [ ${#CHANGED_JAVA_FILES[@]} -eq 0 ]; then 52 echo "No Java files changed between 'origin/$TARGET_BRANCH' and 'origin/$SOURCE_BRANCH'." 53 echo "No Java changes detected." | mail -s "$NAMESPACE: No Java changes" "$EMAIL" 54 exit 0 # 无变更视为通过 55fi 56 57echo "Found ${#CHANGED_JAVA_FILES[@]} Java file(s) to check:" 58printf ' %s\n' "${CHANGED_JAVA_FILES[@]}" 59 60# 4. 执行 Checkstyle 检查(一次性传入所有文件) 61echo "Running Checkstyle..." 62# 使用 -c 指定配置,-f xml 或 summary 可选,这里使用简洁格式 63# 如果你希望详细输出,可以使用 -f xml 并用 xmllint 格式化 64if java -jar "$CHECKSTYLE_JAR" -c "$CHECKSTYLE_CONFIG" "${CHANGED_JAVA_FILES[@]}" > "$CHECKSTYLE_LOG" 2>&1; then 65 # Checkstyle 命令执行成功(语法正确),但不代表无警告/错误 66 OUTPUT=$(cat "$CHECKSTYLE_LOG") 67 echo "$OUTPUT" 68 69 # 检查输出中是否包含违规信息(Checkstyle 通常会在有违规时输出内容) 70 if [[ -s "$CHECKSTYLE_LOG" && "$(echo "$OUTPUT" | grep -v '^$' | wc -l)" -gt 0 ]]; then 71 echo "Checkstyle found issues in changed Java files." 72 echo "Checkstyle Report:" | mail -s "[FAIL] $NAMESPACE: Checkstyle failed" "$EMAIL" -A "$CHECKSTYLE_LOG" 73 rm -f "$CHECKSTYLE_LOG" 74 exit 1 # 有违规 → 失败 75 else 76 echo "All changed Java files passed Checkstyle." 77 echo "No checkstyle violations found." | mail -s "[OK] $NAMESPACE: Checkstyle passed" "$EMAIL" 78 rm -f "$CHECKSTYLE_LOG" 79 exit 0 # 无违规 → 成功 80 fi 81else 82 # Checkstyle 命令执行失败(如 jar 报错、配置错误等) 83 echo "Checkstyle execution failed!" 84 cat "$CHECKSTYLE_LOG" 85 echo "Checkstyle execution failed. See details." | mail -s "[ERROR] $NAMESPACE: Checkstyle execution error" "$EMAIL" -A "$CHECKSTYLE_LOG" 86 rm -f "$CHECKSTYLE_LOG" 87 exit 1 88fi 89
yml验证差异文件
与上面的Java验证类似,就是查询差异文件yml与yaml,checkstyle有问题就返回0,无问题会返回1,gitlab合并检测会根据返回值判定流水线成功/失败
(如果是spring可以跳过所有的application.yml文件,因为yml 验证主要是为了交付给运维同事配置文件才会有的流水线步骤)
原有脚本已经遗失,判断逻辑应当通过yamllint进行验证,根据输出产生行数>0则有问题(需要ymlconf文件作为配置规则)
1#!/bin/bash 2 3if [ $# -ne 2 ]; then 4 echo "Usage: $0 <namespace> <email>" 5 exit 1 6fi 7 8NAMESPACE=$1 9EMAIL=$2 10 11# ================== 配置区 ================== 12TARGET_BRANCH="main" # 目标合并分支,可根据需要改为 dev、release 等 13CONFIG_FILE="./cloud/env/install/script/ymlconf" 14# =========================================== 15 16echo "🔍 Checking YAML files changed between current branch and '$TARGET_BRANCH'..." 17 18# 1. 获取当前分支相对于目标分支的差异文件(仅新增或修改) 19# 注意:GitLab CI 会自动 fetch 所有分支,可以直接使用 20CHANGED_YAML_FILES=() 21while IFS= read -r file; do 22 if [[ "$file" == *.yml || "$file" == *.yaml ]]; then 23 if [ -f "$file" ]; then # 确保文件存在(避免删除的文件) 24 CHANGED_YAML_FILES+=("$file") 25 fi 26 fi 27done < <(git diff --name-only "origin/$TARGET_BRANCH"...HEAD) 28 29# 2. 检查是否有变更的 YAML 文件 30if [ ${#CHANGED_YAML_FILES[@]} -eq 0 ]; then 31 echo "✅ No YAML files changed. Skipping yamllint." 32 echo "No YAML changes detected." | mail -s "$NAMESPACE: No YAML changes" "$EMAIL" 33 exit 0 34fi 35 36echo "📄 Found ${#CHANGED_YAML_FILES[@]} YAML file(s) to check:" 37printf ' %s\n' "${CHANGED_YAML_FILES[@]}" 38 39# 3. 对差异文件逐个运行 yamllint 40ERRORS_FOUND="" 41for file in "${CHANGED_YAML_FILES[@]}"; do 42 echo " linting $file" 43 if ! yamllint -c "$CONFIG_FILE" "$file" > /tmp/yamllint.log 2>&1; then 44 echo "❌ yamllint failed on $file" 45 ERRORS_FOUND+="$file:\n" 46 ERRORS_FOUND+=$(cat /tmp/yamllint.log) 47 ERRORS_FOUND+="\n\n" 48 fi 49done 50 51# 4. 处理结果 52if [ -n "$ERRORS_FOUND" ]; then 53 echo "YAML file has syntax errors!" 54 echo -e "$ERRORS_FOUND" 55 echo -e "$ERRORS_FOUND" | mail -s "$NAMESPACE YAML file has syntax errors!" "$EMAIL" 56 rm -f /tmp/yamllint.log 57 exit 1 58else 59 echo "✅ All changed YAML files are valid." 60 echo "All changed YAML files passed lint." | mail -s "$NAMESPACE: YAML lint passed" "$EMAIL" 61 rm -f /tmp/yamllint.log 62 exit 0 63fi 64
其他问题
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 控制克隆深度:
如:
1variables: 2 GIT_DEPTH: 0 # 拉取完整历史 3 4build: 5 script: 6 - git log --oneline -n 5 # 只有 GIT_DEPTH > 1 或 =0 时才有多个 commit 7 - git describe --tags # 需要完整 tag 历史 8
默认行为是什么?
GIT_DEPTH: 1(浅克隆,高效)
《linux上gitlab runner部署文档》 是转载文章,点击查看原文。
