3年前,我曾以为“自动化部署”是大厂的专利,直到我在周五晚上因为传错了一个jar包,被迫在机房待到了凌晨2点。
那时我还在一家几十人的软件外包公司,每次发布版本,整个流程充满了“原始部落”的气息:本地打包 -> 打开FTP/Xshell -> 停止服务 -> 备份 -> 上传新包 -> 重启。
这一套动作,熟练的运维可能只要10分钟,但如果是一个刚入职的开发,或者是疲惫的周五下午,出错率几乎是100%。
也就是那个凌晨,我痛定思痛,决定必须把CI/CD(持续集成/持续部署)这块骨头啃下来。今天我想站在一个“过来人”的角度,聊聊如何利用 GitLab Runner 搭建一套私有化的自动化流水线。不讲枯燥的概念,只聊我踩过的坑和落地的实操。
一、 为什么要自己搞Runner?(别信“共享”的鬼话)
刚开始接触GitLab CI的时候,我犯过一个新手常犯的错:直接用GitLab官方提供的Shared Runners(共享构建器)。
起初觉得挺香,不用配置服务器,提交代码自动跑。但很快,痛点就来了:
- 慢到怀疑人生:作为免费用户,排队是常态。有时候改一行代码,等Runner分配资源要等10分钟,黄花菜都凉了。
- 环境不可控:今天要用Node 14,明天项目升级要Node 18,共享环境里各种依赖冲突,每次构建都在“赌运气”。
真实的转折点发生在一次紧急热修复。客户系统崩了,我代码改好了,结果卡在GitLab的共享队列里排队了半小时。老板在后面站着,我在前面汗流浃背。
那一刻我明白:只有私有化部署Runner,才能把控制权握在自己手里。
简单理解,GitLab Server是“包工头”,负责发号施令;GitLab Runner就是“搬砖工”,负责干活。我们现在要做的,就是雇一个只听命于我们自己的“搬砖工”,安在自己的服务器上。
二、 只要两步,给你的代码配个“私人管家”
很多人被官方文档里长篇大论的安装劝退。其实,对于中小团队,最稳妥、最干净的方式只有一种:Docker安装。
我大概每周三下午会抽空检查一遍基础设施,用了两年Docker方式部署Runner,它最大的好处是——即使搞挂了,删了容器重来就行,不会污染宿主机环境。
第一步:启动Runner容器
别去折腾什么RPM包安装了,直接在你的服务器(可以是测试服)上跑这个:
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
这行命令里有个核心细节:-v /var/run/docker.sock:/var/run/docker.sock。
踩坑预警:如果你希望你的Runner能在构建过程中也能使用Docker(比如打包镜像),这个挂载必须加!这叫“Docker in Docker”或者是让容器控制宿主机的Docker守护进程。
第二步:把“管家”介绍给“包工头”(注册)
Runner启动了,但它还不知道要听谁的指挥。你需要去GitLab项目页面 -> Settings -> CI/CD -> Runners,找到 URL 和 Registration token。
然后进入容器内部执行注册:
docker exec -it gitlab-runner gitlab-runner register
接下来会是一次简单的问答交互,我建议的配置如下:
- URL: 填你在GitLab页面看到的。
- Token: 填页面上的Token。
- Description: 给Runner起个名,比如
dev-runner-01。 - Tags: 关键点! 填
docker,deploy(逗号分隔)。后续在代码里指定tag,只有匹配的Runner才会接单。 - Executor: 重中之重! 选
docker。 - Docker Image: 填个默认的,比如
alpine:latest或者node:16。
搞定!回到GitLab页面,看到那个绿色的圆点亮起,你就拥有了一个专属的自动化构建工人。
三、 编写“施工图纸”:让自动化动起来
有了工人,还得有图纸。这就是项目根目录下的 .gitlab-ci.yml 文件。
记得带我的实习生小王第一次写这个文件时,他恨不得把所有脚本都堆在一个stage里。结果只要一步出错,查日志查到眼瞎。
经验告诉我,流水线必须分层。 一个标准的后端项目(比如Spring Boot或Go),我通常这么设计:
stages:
- build
- test
- deploy
# 全局变量定义,方便维护
variables:
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
# 缓存依赖,不用每次都重新下载jar包,速度提升50%
cache:
paths:
- .m2/repository/
# 第一步:打包
build_job:
stage: build
image: maven:3.8-jdk-11
script:
- echo "开始打包..."
- mvn clean package -DskipTests
artifacts: # 产物传递,把打好的jar包传给下一步
paths:
- target/*.jar
tags:
- docker # 指定用我们刚才注册的Runner
# 第二步:测试(哪怕只是跑个简单的单元测试,也是心理安慰)
test_job:
stage: test
image: maven:3.8-jdk-11
script:
- echo "运行测试..."
- mvn test
tags:
- docker
# 第三步:部署(利用SSH免密登录部署到目标服)
deploy_job:
stage: deploy
image: ubuntu:latest
before_script:
# 这里通常需要配置SSH私钥,稍微复杂点,但只需配一次
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
script:
- echo "开始部署..."
- scp target/*.jar user@192.168.1.100:/app/
- ssh user@192.168.1.100 "sh /app/restart.sh"
tags:
- docker
only:
- main # 只有合并到main分支才触发部署
这里有个反直觉的设计:部署操作为什么要单独用一个ubuntu镜像?
很多新手喜欢直接在Runner所在的宿主机上操作。但我建议,让Runner保持纯净,通过SSH远程去操作目标服务器(即使目标服务器就是Runner所在的那台机器)。这样能最大程度解耦,就算以后应用扩容到10台机器,改改脚本就行,不用动Runner架构。
结尾:解放双手,去喝杯咖啡吧
自从落地了这套GitLab Runner方案,那种“周五傍晚不敢提交代码”的恐惧感彻底消失了。现在,我的日常变成了:写代码 -> 提交 -> 泡咖啡 -> 收到钉钉/企业微信通知“部署成功”。
如果你还在犹豫是否要引入CI/CD,不妨先从一个小项目试起。
这里给大家留一个小调研:
在配置Runner执行器(Executor)时,你更倾向于简单粗暴的 Shell 模式,还是隔离性更好的 Docker 模式? 为什么?欢迎在评论区告诉我你的选择。
最后,总结一下今天能落地的3个行动步骤:
- 找台空闲服务器(2核4G足矣),安装Docker。
- 用
docker run启动并注册 一个私有GitLab Runner。 - 在项目根目录新建
.gitlab-ci.yml,先写一个最简单的echo "Hello World"跑通流程。
别光看,去试试。当你第一次看到那个绿色的 “Passed” 对勾亮起时,你会发现,技术带来的不仅是效率,更是尊严。