
CI/CD 把「提交代码到服务上线」这段人工操作变成自动执行的流水线,减少重复劳动和人为失误。本文基于自建 Git 服务的 Actions 能力搭建一条完整流水线,涵盖执行器部署与注册、工作流编写、构建与测试、镜像构建、自动部署到目标服务器、密钥管理与安全边界,以及构建失败和部署卡住的排查方法。
一条典型的流水线包含这些环节:
前置条件:
项目 | 说明 |
|---|---|
Git 服务 | 已部署并可正常推送代码的自建 Git 服务 |
执行器机器 | 运行构建任务的机器,可与 Git 服务同机或独立 |
容器环境 | 执行器上需要 Docker |
目标环境 | 部署目标服务器,可与执行器分开 |
执行器建议独立部署。构建任务会占用大量 CPU 和磁盘 IO,与 Git 服务共用一台机器时,构建高峰会拖慢代码托管服务的响应。如果资源有限,至少要给构建设置资源上限。
执行器的资源规划:
项目 | 最低配置 | 建议配置 |
|---|---|---|
CPU | 2 核 | 4 核及以上 |
内存 | 2 GB | 4~8 GB |
磁盘 | 40 GB | 100 GB 以上 |
磁盘往往被低估。每次构建都会拉取依赖、生成中间产物、缓存镜像层,累积很快。构建频繁的项目一周就能占掉几十 GB。
先在 Git 服务的管理后台开启 Actions 功能,并获取执行器注册令牌。令牌位于管理后台的执行器管理页面,可以创建实例级、组织级或仓库级的令牌,按需要的作用范围选择。
在执行器机器上部署。用 Docker 方式运行:
mkdir -p ~/apps/act-runner && cd ~/apps/act-runner编写 compose.yaml:
services:
runner:
image: gitea/act_runner:0.2.11
environment:
GITEA_INSTANCE_URL: https://git.example.com
GITEA_RUNNER_REGISTRATION_TOKEN: 替换为注册令牌
GITEA_RUNNER_NAME: runner-01
GITEA_RUNNER_LABELS: ubuntu-latest:docker://node:20-bookworm
volumes:
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped这里有个必须说明的安全事项:挂载 Docker socket 意味着执行器容器可以完全控制宿主机的 Docker,等同于宿主机的 root 权限。这是运行容器化构建任务的常见做法,但需要清楚它的影响:
启动并确认注册成功:
docker compose up -d
docker compose logs runner --tail 30日志中出现注册成功和监听任务的提示后,到 Git 服务的管理后台确认执行器已在线。
在仓库中创建工作流文件 .gitea/workflows/ci.yaml:
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 安装依赖
run: |
npm ci
- name: 代码检查
run: |
npm run lint
- name: 运行测试
run: |
npm test推送后到仓库的 Actions 页面查看执行情况。首次运行需要拉取容器镜像,耗时较长。
先让这条最简流水线跑通再加功能。 一次性写一个复杂的多阶段流水线,出问题时很难定位是哪个环节的配置问题。
在流水线中构建镜像并推送到镜像仓库:
build:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 登录镜像仓库
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login ${{ secrets.REGISTRY_URL }} \
-u "${{ secrets.REGISTRY_USER }}" --password-stdin
- name: 构建并推送镜像
run: |
IMAGE="${{ secrets.REGISTRY_URL }}/myapp:${GITHUB_SHA::7}"
docker build -t "$IMAGE" .
docker push "$IMAGE"
echo "IMAGE=$IMAGE" >> $GITHUB_ENV几个要点:
needs: test 表示这个任务依赖测试通过。测试失败时不会执行构建,避免把有问题的代码打成镜像。
if 条件限制只在主分支执行构建和推送,特性分支只跑测试。这能避免每个分支都产生一堆镜像。
镜像标签使用提交哈希而不是滚动标签。这样每个镜像对应一个确定的代码版本,出问题时能准确回滚到某次提交。用滚动标签会让「当前部署的是哪个版本」变得无法追溯。
凭据通过 secrets 传入,不要写在工作流文件里。工作流文件在仓库中是可见的,任何有读权限的人都能看到。
在仓库设置的 Actions secrets 中添加所需的凭据变量。
部署环节有几种做法,安全性和复杂度不同。
做法一:SSH 执行部署命令
最直接的方式:
deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
steps:
- name: 配置 SSH 密钥
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts
- name: 执行部署
run: |
ssh -i ~/.ssh/id_ed25519 \
${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} \
"cd /opt/myapp && docker compose pull && docker compose up -d"这种做法的安全要点必须讲清楚:
ssh-keyscan 这一步是把目标主机的指纹加入信任列表。更严格的做法是预先把已知的主机指纹存在 secrets 中并校验,避免中间人风险。做法二:目标服务器主动拉取
反向思路:目标服务器定期检查镜像仓库,发现新版本后自行更新。这样执行器不需要持有目标服务器的登录凭据,攻击面更小。代价是部署不是即时的,有检查间隔的延迟。
做法三:部署到容器集群
如果目标是 Kubernetes 集群,通过 kubectl 更新镜像版本。同样要注意 kubeconfig 属于高权限凭据,应当使用权限受限的服务账号,而不是集群管理员凭据。
分层验证,不要只看流水线显示成功。
执行器在线:管理后台的执行器列表中状态为空闲或运行中。
推送能触发流水线:向配置的分支推送一次提交,确认 Actions 页面出现新的执行记录。
测试环节真的在跑:故意引入一个会失败的测试,确认流水线状态变为失败。这一项很重要——如果测试步骤配置有误导致实际跳过了测试,流水线会一直显示成功,但完全没起到把关作用。验证后记得改回来。
构建产物正确:确认镜像已推送到仓库,且标签符合预期:
docker pull 镜像仓库地址/myapp:提交哈希前7位
docker inspect 镜像名 | grep -i created部署确实生效:这一步最容易被忽略。流水线显示部署成功,不代表服务真的更新了。到目标服务器确认:
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"确认运行中的镜像标签是刚构建的那个。再访问服务确认新功能已生效。
失败能被感知:确认流水线失败时有通知。没有通知的流水线,失败了往往几天后才被发现。
执行器一直显示离线
检查执行器容器是否运行、注册令牌是否有效、执行器机器能否访问 Git 服务地址。查看日志:
docker compose logs runner --tail 50任务一直处于排队状态
多为标签不匹配。工作流中 runs-on 指定的标签必须与执行器注册时声明的标签一致。检查执行器的标签配置。
构建时提示找不到命令
执行器使用的容器镜像中不包含该命令。要么换一个包含所需工具的镜像,要么在工作流中先安装。
依赖安装很慢
国内环境拉取境外源较慢。可以在工作流中配置国内镜像源,或使用缓存机制避免每次重新下载。
镜像推送失败
检查凭据是否正确、镜像仓库地址是否可达、是否有推送权限。凭据错误时日志会明确提示认证失败。
部署步骤卡住不动
常见原因是 SSH 命令等待交互输入。确保命令是非交互的,必要时加上相应参数。也要确认目标服务器的 SSH 端口对执行器可达。
磁盘被构建缓存占满
docker system df
df -h清理未使用的镜像和构建缓存:
docker system prune -af --volumes建议在流水线中加入定期清理步骤,或用定时任务处理。这是自建执行器最常见的运维问题。
并发构建互相干扰
多个任务同时构建时可能争抢端口或临时目录。给构建任务使用唯一的临时目录名,避免固定名称冲突。
凭据管理是重点
限制外部贡献的执行
如果仓库接受外部提交,来自不受信任分支的工作流可能执行任意代码。配置为需要人工批准后才运行流水线,或对外部贡献使用隔离的执行器。
执行器隔离
执行器机器不要与生产服务混用。构建过程会拉取大量第三方依赖,存在供应链风险,隔离能限制影响范围。
变更前创建快照
调整执行器配置或升级前给实例创建快照。回滚会把整块系统盘恢复到快照时间点,之后写入的数据会被清除,运行中的实例会自动关机。使用存储型套餐的实例不支持创建快照。
备份要点
需要备份的是工作流定义(它们在代码仓库里,随仓库备份即可)和执行器配置。构建缓存和中间产物不需要备份,可以重新生成。
保留可回滚的能力
这是 CI/CD 中容易被忽视的一点。自动部署提高了效率,也提高了把问题快速推向生产的速度。建议:
日常检查
流水线跑通之后,如果需要为构建和部署环节建立监控告警,或把制品统一归档管理,可以作为下一步方向。
构建任务对算力和磁盘 IO 要求较高,云服务器 CVM 的规格可按需调整,适合作为执行器;构建产物和备份归档可使用对象存储 COS;部署目标为容器集群时可使用容器服务。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。