小团队 + 1 个 AI agent,如何让「提需求 → 写代码 → 测试 → 上线 → 管服务器」全流程不依赖人脑记忆。
这一章回答第一个疑问:我们到底在解决什么麻烦?为什么小团队也需要一套「系统」?
想象一下没有这套系统时的日常:需求记在聊天软件里,聊着聊着就沉底了;代码在各自电脑上,靠 U 盘和口头同步;测试靠人肉点一遍,点漏了就上线即事故;部署靠「我记得那台服务器上好像是这么改的」;crontab(定时任务)改完就忘,三个月没人知道某台机器凌晨 3 点在跑什么。
这套系统要解决的,就是把这一切从人脑记忆里搬出来,变成代码和自动化。源文档把需求拆成了 7 块,外加 1 条贯穿性需求,用大白话说就是:
| # | 需求(大白话版) | 用什么解决 |
|---|---|---|
| 1 | 需求、Bug 得有地方提、有人跟、有状态可查 | GitLab CE 自带 Issues |
| 2 | 代码要有版本管理,谁改了什么一目了然 | GitLab CE 自带 Git 仓库 |
| 3 | 每次改代码自动跑测试,别等上线才发现问题 | GitLab CI |
| 4 | 测试、构建这些重复劳动全部自动化(CI,持续集成) | GitLab CI + Runner |
| 5 | 点一下按钮就能把新版本部署到服务器(CD,持续部署) | GitLab CI + Ansible(SSH 推送) |
| 6 | 各台服务器上的服务、定时任务、脚本要管得起来 | Ansible(代码化管理,存 Git) |
| 7 | 密码、密钥不能明文裸奔,也不能记不住 | SOPS + age + GitLab CI 变量 |
| 8 | AI agent 参与干活,且操作要统一走固定流程 | 自建 skill 库(操作手册 + 模板 + 脚本) |
注意第 8 条:这个团队里有一位特殊成员——运行在你个人工作电脑上的 AI coding agent(可以理解为一位会读写代码、会执行命令的 AI 同事)。整套系统的每一个设计决策,都会考虑到「人和 AI 都能用」:一切配置都是文本文件、一切操作都有固定流程,因为 AI 擅长读写文本、按手册执行,不擅长点网页、靠感觉办事。
这一章解决「东西太多记不住」的问题:先用六句话记住骨架,再学会认颜色——后文所有图和标记都用同一套颜色。
源文档的最后一页有个「一页纸总结」,它就是整套系统的骨架:
ops 仓库(服务器的一切)+ 各项目仓库(代码的一切),一切改动走合并请求(MR);本文给系统里的每类「角色」分配了一个固定颜色,架构图、数据流图、正文标记全部通用。读完你会自然形成条件反射:
一句话概括这套系统的工作方式:所有事情从办公网一侧发起,经 SSH 推到目标机器;所有事实写在 Git 仓库里;所有重复劳动交给流水线和 AI agent。后面的每一章,都是对这句话某一个部分的展开。
这一章回答「我要准备什么硬件」:先给一张采购/盘点清单,再用一张可点击的拓扑图看懂它们怎么连。
| 机器 | 配置要求 | 在哪个网 | 干什么 / 不干什么 |
|---|---|---|---|
| GitLab 服务器 | 建议 4 核 8G 内存、100G 磁盘;最低 2 核 4G。装好 Docker | 办公网 | 跑 GitLab CE(Docker 容器)+ Runner(与 GitLab 同机)+ 容器镜像仓库 |
| 你的工作电脑 | 日常开发机即可 | 办公网 | 你本人 + AI agent + skill 库都在这台机器上;装好 Ansible、sops、glab CLI,按 5.2 节配好 SSH |
| 文件服务器 | 保持现状,不加任何负担 | 双网卡:同时接办公网和纯内网 | 只做跳板/中转:在两网之间转发 SSH 的 TCP 流量。不在上面跑构建和部署——它是共享基础设施,稳定性优先 |
| 办公网生产机(若干) | 按业务需要 | 办公网 | 零 agent:什么都不用装,只要 SSH 可达;建一个权限最小的 deploy 部署账号 |
| 纯内网生产机(若干) | 按业务需要 | 纯内网(无公网) | 零 agent、零出网需求:只要能被文件服务器 SSH 到即可,连 GitLab 都不需要知道 |
一句话:新增的只有一台 4C8G 的办公网机器,其余全是现有机器的角色明确化。
整套系统的心脏:Issue、Git 仓库、MR 评审、CI/CD 调度、容器镜像仓库、CI 变量全在它身上。建议 4 核 8G、100G 磁盘,放在办公网。Runner 和它同机,流水线的测试、构建、部署任务都在这台机器的一次性 Docker 容器里执行。
候选位置有三个,源文档逐一分析过:
| 候选位置 | 优点 | 缺点 | 结论 |
|---|---|---|---|
| 办公网专用小服务器/虚拟机 | 你和队友随时可访问;GitLab 自身能连公网(拉镜像、发通知);Runner 到办公网生产机直连 | 需要一台 4C8G 的机器 | 首选 |
| 文件服务器上 | 不用找新机器;双网卡,理论上两边都能访问 | 文件服务器是共享基础设施,GitLab 吃内存,一旦出问题影响面大 | 资源充足时的备选 |
| 你的工作电脑(Docker) | 零成本 | 你关机/休假队友就用不了;电脑重装系统风险大 | 仅适合过渡期 |
为什么不能放纯内网?因为纯内网里的 GitLab 无法访问公网,装依赖、拉基础镜像、更新系统都极痛苦;而且你在办公网访问它还要绕跳板,日常体验差。
这里藏着全篇最重要的一个洞察:内网机器从头到尾都不需要主动访问 GitLab。想通这一点,就引出了下一章的核心决策。
这一章讲透整套系统最有价值的设计选择——为什么内网机器上可以什么都不装。理解了它,后面所有工具的选择都顺理成章。
「把代码部署到服务器」这件事,业界有两种经典做法:
| 拉取模型(pull) | 推送模型(push) | |
|---|---|---|
| 怎么工作 | 每台目标服务器上装一个 agent(如 GitLab Runner、ArgoCD、Salt minion),agent 主动连中心服务器领任务、拉制品 | 中心侧(CI Runner 或你的电脑)通过 SSH 主动连到目标服务器,把东西推过去、把命令执行掉 |
| 网络要求 | 目标机必须能连到中心 | 只要求中心能 SSH 到目标 |
| 在你们网络里的下场 | 纯内网机器连不到办公网的 GitLab,硬用就得在内网再搭一套 GitLab/Runner 并做两边同步——复杂度直接翻倍 | 天然存在「办公网 → 文件服务器 → 内网」这条 SSH 链路,直接用 |
你们的网络里恰好存在这样一条链路:
办公网(GitLab Runner / 你的电脑)──SSH──► 文件服务器 ──SSH──► 纯内网机器
SSH 原生支持多级跳板(ProxyJump,下面详解),Ansible 也是纯 SSH 推送。所以整套系统的控制流和数据流统一为:
从办公网一侧发起,经 SSH 推送。纯内网机器上不需要安装任何常驻 agent,不需要任何特殊网络配置,只要能被文件服务器 SSH 到即可。
这就是「内网机器零 agent」的全部秘密——不是用了什么黑科技,而是选了一个贴合你们网络条件的模型。
从 Runner(或你的电脑)到纯内网机器,中间隔着文件服务器。SSH 的 -J / ProxyJump 选项能把多级跳转压缩成一条命令:
ssh -J ops@<文件服务器IP> deploy@<内网机器IP>
很多人在这里有误解,以为「跳板机替我登录了内网机器」。实际上它的工作原理分三步,务必看清:
结论非常关键:文件服务器上不需要存任何到内网机器的私钥,它只要网络可达内网机器即可。即使文件服务器被入侵,攻击者拿到的也只是「能向内网机器发起 TCP 连接」的能力,没有钥匙。这比「先 ssh 到文件服务器、再在文件服务器上存一把私钥 ssh 到内网」安全得多。
# 跳板:文件服务器
Host fileserver
HostName <文件服务器IP>
User ops # 文件服务器上专门开的跳板账号,权限最小化
IdentityFile ~/.ssh/id_ed25519
# 办公网生产机(直连)
Host office-*
User deploy
IdentityFile ~/.ssh/id_ed25519
# 纯内网生产机(自动走跳板)
Host intranet-*
User deploy
ProxyJump fileserver
IdentityFile ~/.ssh/id_ed25519
这段在干什么:给所有服务器起统一的主机名前缀(office-xxx / intranet-xxx),用通配符各写一条规则。以后 ssh intranet-app1 一条命令直连内网机器,跳板自动进行。
deploy 是目标机上的部署专用账号,只给部署需要的权限(写应用目录、重启自己的服务),不用 root。你的电脑和 Runner 各持一对独立密钥对,公钥登记到目标机的 deploy 账号和文件服务器的 ops 账号上——钥匙按「人/系统」分开,哪把泄露了单独吊销哪把。
这一章把每个工具单独讲透:它是什么、本身能做什么、在这套系统里承担哪几项功能、为什么选它。顺序按「从代码到服务器」的链路排列。
它是什么
Git 是代码的「时光机」:每一次修改都存成一个快照(commit,提交),谁改的、改了什么、为什么改,全部可查可回退。它是这一切的地基——后面说的「事实来源」「改动可审计」,全靠它。
在这套系统里干什么
main 是保护分支(永远可发布),每个 Issue 开一个短分支,命名 类型/Issue号-简述,如 fix/128-null-pointer;git tag(如 v1.4.0),tag 同时触发 CD 流水线;它是什么
把 GitLab 想成「你们团队的私有小 GitHub + 项目管家 + 机器人调度中心」三合一。CE 是完整开源的社区版,下面列的功能一个都不少,不需要任何付费许可。
在这套系统里干什么(逐项)
bug/feature/chore/ops + P0/P1/P2)、指派人、里程碑、看板(Open → Doing → Review → Done),并与分支/MR 双向关联。Issue 模板(.gitlab/issue_templates/)统一「报 bug 必须带复现步骤/期望行为/实际行为」——这对 AI agent 尤其重要,agent 的工作质量直接取决于 Issue 描述的质量;为什么是它
小团队,「少维护一套系统、少学一套概念」比「省内存」更重要。Issue、代码、MR、CI 在同一个系统里,Issue 可以直接关联分支和提交,不用维护第二套工具。一台 4C8G 的机器就跑得很舒服。
services:
gitlab:
image: gitlab/gitlab-ce:latest
container_name: gitlab
hostname: gitlab.<公司内网域名或直接用IP>
restart: always
ports:
- "8080:80" # Web 界面:浏览器访问 http://<服务器IP>:8080
- "8929:22" # Git over SSH:clone 地址里会带这个端口
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'http://<服务器IP>:8080'
gitlab_rails['gitlab_shell_ssh_port'] = 8929
volumes:
- /opt/gitlab/config:/etc/gitlab
- /opt/gitlab/logs:/var/log/gitlab
- /opt/gitlab/data:/var/opt/gitlab
shm_size: '256m'
这段在干什么:用一个 Docker 容器跑起整个 GitLab。Web 用 8080、Git over SSH 用 8929 而不是标准端口,因为服务器自身的 SSH 已占 22 端口,映射到别的端口避开冲突,代价只是 clone 地址里多个端口号。
三个数据卷 config(配置)、logs(日志)、data(仓库、数据库等全部数据)——备份就是备份这三个目录。首次启动需 3~5 分钟,初始 root 密码在 config/initial_root_password 里,登录后立刻改掉。
它是什么
GitLab 本体只负责「调度」(决定该跑什么),真正跑测试、构建、部署的体力活由 Runner 干。它装在 GitLab 同一台服务器上,像个待命工人:GitLab 派单,它开工。
在这套系统里干什么
office,deploy,流水线任务可以指定「只让带某标签的 Runner 跑」。这台 Runner 在办公网、拿着部署用的 SSH 钥匙,部署任务必须钉在它身上——这是网络层面的天然隔离;/opt/runner-ssh/(部署私钥 + ssh config + known_hosts,只读)挂进任务容器;公钥发到各目标机的 deploy 账号和文件服务器的 ops 账号上。sudo gitlab-runner register \
--url "http://<服务器IP>:8080" \
--token "<注册token>" \
--executor "docker" \
--docker-image "docker:26" \
--description "office-runner" \
--tag-list "office,deploy" \
--docker-volumes "/var/run/docker.sock:/var/run/docker.sock"
[[runners]]
[runners.docker]
volumes = ["/var/run/docker.sock:/var/run/docker.sock",
"/opt/runner-ssh:/home/gitlab-runner/.ssh:ro"]
它是什么
CI(持续集成)的意思:每次代码变更自动跑测试/构建,问题立刻暴露,而不是攒到发布前。GitLab CI 的配置文件是仓库根目录的 .gitlab-ci.yml,和代码一起版本管理——CI 配置的修改也走 MR 评审,这对 AI agent 友好(它会读文件,不需要懂网页操作)。
在这套系统里干什么
test(自动闸门)→ build(产出可部署制品)→ deploy(默认手动触发);ansible-lint → --check --diff 预演 → 合并后手动 apply。stages:
- test # 自动化测试:代码质量的自动闸门
- build # 构建:产出可部署的制品
- deploy # 部署:推到服务器(默认手动触发)
lint:
stage: test
script:
- <你的lint命令> # 例如 ruff check . / npm run lint
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" # 开 MR 时跑
- if: $CI_COMMIT_BRANCH == "main"
unit-test:
stage: test
script:
- <你的单测命令> # 例如 pytest / npm test / go test ./...
artifacts:
reports:
junit: report.xml # 测试报告直接显示在 MR 页面
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
build:
stage: build
script:
- <你的构建命令>
- tar czf app-$CI_COMMIT_SHORT_SHA.tar.gz <产物路径>
artifacts:
paths:
- app-*.tar.gz # 制品存到 GitLab,回滚也能取到
expire_in: 30 days
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_TAG # 打 tag 时构建正式发布版
这段在干什么:三行 stages 定义流水线顺序;每个缩进块是一个「任务」,script 是要执行的命令,rules 控制什么时候跑。注意测试阶段挂在「开 MR」和「main 分支」两个时机——两道自动闸门。
关于测试分层,不必一步到位:单元测试必须有(快、是 MR 的强制闸门);集成测试有则更好(GitLab CI 的 services: 能在任务旁临时起一个 PostgreSQL/Redis 容器,用完即弃);端到端测试可选(慢且脆,建议只在 main 或发版前跑)。skill 里会规定「agent 提交前必须本地先跑一遍单测」,流水线是复核——两道闸门都比人肉检查可靠。
它是什么
Ansible 是「批量操作服务器的遥控器」。你写一份 YAML 剧本(playbook),描述「每台机器应该是什么样子」,Ansible 负责通过 SSH 连上去,让现实收敛到你描述的样子。这叫 Infrastructure as Code(基础设施即代码)。
它的四个关键特性(也是选它的理由)
在这套系统里干什么(四大职责)
ansible-playbook,把制品推到目标机并重启服务;/etc/systemd/system/ → daemon-reload → 确保服务处于期望状态,有变更时重启;crontab -e;/opt/ops-scripts/,各机版本永远一致。核心概念速览
intranet_prod 组声明了 ansible_ssh_common_args: '-o ProxyJump fileserver',Ansible 连这组机器时自动走跳板,所以同一个 playbook 能部署两种网络的机器;.j2 模板,变量(服务名、用户、启动命令)按机器填值后下发;--check --diff 预演:Ansible 的「排练模式」,列出会改什么但不真改。MR 阶段就能看到「合并这个改动会对服务器造成什么具体影响」,这是运维变更的评审依据;AI agent 部署前也必须先 dry-run 给你看变更清单;ansible office-web1 -m systemd -a "name=app state=restarted" # 重启某服务
ansible prod -m shell -a "systemctl status app --no-pager" # 看所有生产机服务状态
ansible intranet-app1 -m command -a "/opt/ops-scripts/cleanup-logs.sh --days 30"
cron_jobs:
- name: 清理30天前的日志
hosts: prod
user: deploy
minute: "30"
hour: "3"
job: /opt/ops-scripts/cleanup-logs.sh --days 30
- name: 每日对账
hosts: intranet-app1 # 只在指定机器跑
user: deploy
minute: "0"
hour: "6"
job: /opt/ops-scripts/daily-reconcile.sh
这段在干什么:哪台机器、几点、跑什么,Git 里看得清清楚楚,改动有评审、有历史。playbook 循环这份清单调用 cron 模块下发到各机。
它是什么
SSH 是远程登录服务器的加密协议,也是整套系统唯一的「搬运通道」——人登录、Ansible 推送、git 推拉代码,全走它。ProxyJump 是它的跳板功能,三步原理和配置已在第 04 章讲透:端到端认证,跳板机只转发流量、看不到内容,也不存任何到内网的私钥。
在这套系统里干什么
office-* / intranet-*)同时被 ssh config 和 Ansible inventory 复用;它是什么
systemd 是 Linux 自带的服务管理器,负责服务的启动/停止/开机自启/崩溃自动重启。你不需要学它的全部,只要知道:每个常驻服务对应一个 unit 文件,写清楚「用什么用户、在哪个目录、跑什么命令、挂了怎么重启」。
在这套系统里干什么
EnvironmentFile= 注入服务进程,明文只在目标机的 600 权限文件里。[Unit]
Description={{ app_name }}
After=network.target
[Service]
Type=simple
User={{ app_user }}
WorkingDirectory=/opt/{{ app_name }}
ExecStart=/opt/{{ app_name }}/current/<启动命令>
Restart=always
RestartSec=5
EnvironmentFile=/opt/{{ app_name }}/.env # 密钥从这里注入
[Install]
WantedBy=multi-user.target
这段在干什么:{{ }} 是占位变量,Ansible 按机器填值后推到 /etc/systemd/system/。Restart=always 保证崩溃自动拉起;EnvironmentFile 指向部署时生成的 .env,密钥不进 unit 文件本身。
它是什么
Docker 把一个软件连同它需要的运行环境打包成一个「标准盒子」(容器),在哪台机器上打开都长一个样;Docker Compose 用一个 YAML 文件描述「起哪几个盒子、怎么连」,一条 docker compose up -d 全部启动。
在这套系统里干什么
它是什么
SOPS(Mozilla 开源)是文件加密工具,age 是现代加密算法(可理解为 GPG 的极简现代版)。组合起来的思路一句话:把密钥加密后当普通文件存 Git。没有私钥的人拉到仓库也只是一堆乱码。
在这套系统里干什么
secrets/,改密钥和改配置一样走 MR,有评审有历史有回滚;.env(权限 600),明文永不进 Git、不进日志;sops secrets/prod.sops.yaml 自动解密 → 打开编辑器 → 保存时自动重新加密。db_password: ENC[AES256_GCM,data:K8s3j...,type:str]
api_key: ENC[AES256_GCM,data:mV92k...,type:str]
sops:
age:
- recipient: age1ql3z7hjy54pw3hyww5sny43kmt9acvz... # 谁能解密,写在文件里
这段在干什么:每个值都是 ENC[...] 密文;文件尾部的 recipient 记录「用哪把公钥加的密」。这个文件可以放心提交进 Git。
为什么适合 小团队:零常驻服务(Vault 要养一个服务还要管解封,SOPS 就是文件 + 一把钥匙)、密钥跟着代码走、天然防呆(明文根本不存在于 Git 历史里,不可能误提交)。私钥怎么放,见第 07 章。
它是什么
GitLab 官方命令行工具。浏览器里能做的事(看 Issue、开 MR、查流水线),终端里敲命令也能做。
在这套系统里干什么
glab auth login 登录一次即可,也可以用 GITLAB_TOKEN 环境变量 + API);它是什么
「制品」(artifact)是构建产出的可部署文件。这套系统的制品就是最朴素的 tar 压缩包:构建阶段 tar czf app-版本号.tar.gz 打包,部署时 rsync(增量同步工具)推到服务器解压就能跑。
为什么不用 Docker 镜像做制品
纯内网机器访问不到办公网的镜像仓库,用镜像就得再搭内网仓库或走 docker save | ssh | docker load 的中转,多一层复杂度。tar 包两种网络通吃。制品存在 GitLab 里(保留 30 天),回滚 = 重新部署上一个版本的 tar 包。未来办公网服务容器化时,GitLab CE 自带的镜像仓库开箱即用,那时再演进。
它是什么
Skill 是给 AI agent 的「标准作业程序」:每个 skill 是一个目录,内含 SKILL.md(说明书:何时用、输入什么、一步步怎么做、红线是什么)+ templates/(内嵌模板)+ scripts/(封装脚本)。放在你电脑的 ~/skills/,纳入 Git 版本管理。
为什么不是「给 agent 一堆模板」
| 裸模板 | Skill | |
|---|---|---|
| 本质 | 一段静态文本/文件样板 | 一份操作手册:说明书 + 模板 + 脚本 + 检查清单 |
| agent 拿到后 | 不知道何时用、用完做什么、有什么坑 | 知道触发条件、完整步骤、安全边界、验收标准 |
| 出错概率 | 高(每次靠 agent 自由发挥串联流程) | 低(流程被固化,agent 按手册执行) |
在这套系统里干什么
deploy-service 里执行的 ansible 命令和流水线里是同一套 playbook——agent 不是另起炉灶,而是「在你电脑上以你的身份触发同一套代码化流程」;---
name: deploy-service
description: 将指定服务部署到指定环境。当用户要求部署、发版、上线、回滚时使用。
---
## 执行步骤
1. 运行 scripts/precheck.sh:确认目标机可达(内网自动走 ProxyJump)、
磁盘 >20%、负载正常。任一失败则停止并报告。
2. Dry-run:ansible-playbook ... --check --diff,将 diff 摘要汇报给用户。
3. 等待用户明确确认。未确认前禁止进入下一步。
4. 正式执行同一命令(去掉 --check --diff)。
5. 运行 scripts/healthcheck.sh:检查 systemd 状态 + 应用健康接口。
6. 汇报结果。失败时先运行 scripts/rollback.sh 再汇报。
## 红线
- 禁止跳过 dry-run 直接部署生产环境
- 禁止用 shell 模块临时拼凑部署命令,一切走 deploy-app.yml
- 部署涉及密钥时只通过 sops 解密注入,禁止把明文写入任何日志或回复
这段在干什么:注意写法——步骤可机械执行、每步有验证、失败有兜底、红线明确。agent 的执行质量取决于手册写得多清楚。
12 个 skill 的完整规划表见第 08 章。
这一章把前面所有零件串成一条完整流水线:从有人提 bug,到修复上线,每一步谁在干活、经过哪些颜色。点击步骤条逐步看。
有人在 GitLab 上建 Issue(或 AI agent 通过 API 帮你建),按模板写清复现步骤/期望行为/实际行为,打上 bug 标签和优先级。Issue 是一切的起点——没有 Issue 就没有后续的分支、MR 和部署。
涉及角色:GitLab(Issues)
上面 7 步里,人的动作只有三个:提需求、评审点合并、确认部署。其余的重复劳动,你电脑上的 AI agent 都能按 skill 手册干完。源文档给的典型闭环对话是这样的:
你:「处理一下 Issue #128,登录超时那个 bug。」
agent(加载 work-on-issue skill):读 Issue → 建 fix/128-login-timeout 分支 → 改代码 → 本地跑单测通过 → 推送 → 开 MR(描述按模板写好,自动关联 Issue)→ CI 自动跑测试 → 汇报你「MR 已就绪,测试全绿,请评审」。
你评审合并、打 tag → CI 构建出制品 → 出现部署按钮。
你:「部署到内网生产。」
agent(加载 deploy-service skill):precheck → dry-run 给你看变更 → 你确认 → 经文件服务器跳板推到内网机 → 健康检查 → 「部署完成,v1.4.1 已上线,服务健康。」
注意两个设计细节:一是 推送模型的附带好处——CI 能做的事,agent 在你电脑上全能做,反之亦然,因为用的是同一套 playbook,不会出现「CI 里一套、本地一套」;二是 agent 的全部操作都落在 git 提交和 MR 上,天然可审计。
这一章回答「密码、私钥放哪才安全」:先把密钥分三类(不同类型的管理方式完全不同),再看私钥这把「钥匙的钥匙」怎么分布。
| 类别 | 例子 | 放哪 |
|---|---|---|
| A. 业务密钥(要落到服务器上的) | 数据库密码、第三方 API Key、证书 | SOPS 加密后存 ops 仓库的 secrets/,部署时解密注入 |
| B. 流水线密钥(只在 CI 里用的) | age 私钥本身、临时用的 token | GitLab CI/CD Variables(页面设置,不进 Git),勾 Masked + Protected |
| C. 身份密钥(SSH 私钥等) | 你的个人私钥、Runner 的部署私钥 | 各自所在机器的本机,永不进 Git、永不上网盘 |
~/.config/sops/age/keys.txt(AI agent 与你同机同权限,自然也能用);SOPS_AGE_KEY,勾选 Masked(日志自动打码)和 Protected(只有保护分支的流水线能读到——防止有人开个分支改 .gitlab-ci.yml 把密钥 echo 出来);密钥「落盘即结束」:部署时 sops -d --extract 只解密需要的字段,渲染进目标机的 /opt/app/.env(权限 600,属主 deploy),服务通过 systemd 的 EnvironmentFile= 读取。明文只存在于目标机的 600 权限文件和内存里——不进 Git、不进日志(Ansible 任务记得设 no_log: true)。
轮换也很简单:改密钥 = sops 编辑 → MR(diff 显示的是密文变化,安全)→ 合并 → 重跑部署 playbook → 重启服务,全程可审计。人员变动、怀疑泄露时轮换,每半年例行一次。
这一章回答「东西都放在哪」:代码在项目仓库,服务器的一切在 ops 仓库,agent 的手册在 skills 目录。看完你就拿到了整套系统的「地图」。
从此以后,任何对服务器的改动 = 改这个仓库 + 跑 Ansible,禁止登服务器手工改。
ops/
├── ansible.cfg # Ansible 全局配置
├── inventories/
│ └── prod/
│ ├── hosts.ini # 所有服务器的清单(含网络分组)
│ └── group_vars/ # 按组的变量(如内网组统一配 ProxyJump)
│ ├── office_prod.yml
│ └── intranet_prod.yml
├── playbooks/
│ ├── deploy-app.yml # 部署应用
│ ├── services.yml # 管理常驻服务(systemd)
│ ├── cron.yml # 管理定时任务
│ ├── scripts.yml # 下发/更新运维脚本
│ └── bootstrap.yml # 新服务器初始化
├── templates/
│ └── systemd/ # systemd 服务文件模板
│ └── app.service.j2
├── files/
│ └── scripts/ # 要下发到服务器的脚本的"母本"
│ ├── cleanup-logs.sh
│ └── sync-data.sh
└── secrets/ # SOPS 加密的密钥文件
└── prod.sops.yaml
# 办公网生产机
[office_prod]
office-web1
office-web2
# 纯内网生产机
[intranet_prod]
intranet-app1
intranet-db1
# 所有生产机
[prod:children]
office_prod
intranet_prod
这段在干什么:把服务器按网络分两组,名字直接复用 ssh config 里的 office-* / intranet-* 前缀。配合 group_vars/intranet_prod.yml 里的一行 ansible_ssh_common_args: '-o ProxyJump fileserver',内网组自动走跳板——网络拓扑的差异被完全封装在 inventory 里,部署逻辑一份代码、两种环境复用。
ansible.cfg 里有两条值得知道的配置:host_key_checking = True(生产环境不要关主机指纹校验)和 pipelining = True(减少 SSH 往返,跑得快)。
skills/
└── deploy-service/ # skill 名 = 目录名
├── SKILL.md # 说明书:何时用、输入什么、一步步怎么做、红线是什么
├── templates/ # 该操作需要的模板(systemd unit、MR 描述等)
└── scripts/ # 封装好的可执行脚本(precheck.sh、rollback.sh)
落地时不必一次写全,第 5 周先写最高频的 5 个(work-on-issue、deploy-service、service-ops、cron-ops、secret-ops),其余随用随补:
| Skill | 干什么 | 内含模板/脚本 |
|---|---|---|
issue-triage | 读 Issue → 补全信息(按模板追问缺失项)→ 打标签/排优先级 → 拆任务 | issue 模板、标签规范 |
work-on-issue | 领 Issue → 建规范分支 → 实现 → 本地跑测试 → 提交 → 推送开 MR | commit 规范、MR 描述模板 |
review-mr | 拉取 MR → 对照 checklist 评审(正确性/安全/测试覆盖)→ 留意见 | 评审 checklist |
fix-ci | 流水线红了 → 拉日志 → 定位 → 修复 → 重跑验证 | 常见失败模式清单 |
release | 汇总变更 → 定版本号 → 打 tag → 建 Release → 触发部署流水线 | Release notes 模板 |
deploy-service | 部署某服务到某环境:先 dry-run 出变更清单 → 汇报 → 你确认 → 执行 → 验证健康 | precheck/healthcheck 脚本、回滚脚本 |
service-ops | 查状态/重启/看日志(封装 ansible -m systemd 等) | 常用命令封装 |
cron-ops | 增删改定时任务:一律改 ops 仓库走 MR,禁止直接改目标机 | cron 条目模板 |
script-ops | 新增/更新脚本 → 下发 → 在指定机器执行并回收输出 | 脚本样板(带日志/错误处理) |
secret-ops | sops 加解密/轮换/新增字段;检查「明文是否误进 Git」 | sops 操作流程 |
server-onboard | 新机器接入:建 deploy 账号、发公钥、装基础组件、登记进 inventory | bootstrap playbook 片段 |
incident-response | 线上故障:收集状态/日志 → 初步定位 → 给出处置建议 → 事后整理记录 | 故障报告模板 |
这一章回答「别人都用的 X,我们为什么不用」:每个被劝退的工具都很有名,但对 小团队来说,不选它们的理由都很具体。
| 明星工具 | 它很好,但是…… |
|---|---|
| Gitea / Forgejo + Woodpecker CI | 比 GitLab 轻量(内存占用约为 GitLab 的 1/10),但 Issue 功能弱、CI 是另一套系统要学。小团队,「少维护一套系统、少学一套概念」比「省内存」更重要。GitLab CE 一台 4C8G 的机器就跑得很舒服。 |
| Jenkins | 功能强但插件体系老旧,配置靠点页面、难以代码化,对 AI agent 不友好——agent 擅长读写文本文件,不擅长点网页。 |
| HashiCorp Vault | 密钥管理的「重武器」,需要常驻服务、要处理初始化/解封/高可用,小团队维护成本过高。SOPS 是「把密钥加密后当普通文件存 Git」,零常驻服务。 |
| Rundeck / Cronicle 定时任务面板 | 功能与 Ansible 重叠。定时任务用面板管、部署用 Ansible 管,就有了两个「事实来源」。我们选择一切代码化存 Git,Git 仓库是唯一事实来源。 |
| Kubernetes | 小团队 + 少量服务器,上 K8s 是给自己找班加。用 systemd 管理服务足够。 |
共同的原则只有一条:每多一个系统,就多一份要维护的东西、多一个「事实可能不一致」的地方。这套设计宁愿用朴素的工具把流程打通,也不用强大的工具把复杂度搬回家。
这一章回答「从哪开始动手」:不必一次到位,按依赖关系分五周走,每周结束都有可用的成果。
| 周 | 目标 | 具体动作 | 验收标准 |
|---|---|---|---|
| 第 1 周 | 平台就位 | 装 GitLab CE;建群组/项目/成员;迁移代码仓库;配保护分支和 MR 规则 | 团队都能推拉代码、开 MR |
| 第 2 周 | CI 跑起来 | 装 Runner;给主项目写 .gitlab-ci.yml 的 test/build 阶段;补 issue 模板和标签 |
MR 上能看到测试自动跑,且红的合不进 |
| 第 3 周 | 运维代码化 | 建 ops 仓库;盘点所有服务器登记进 inventory;服务/定时任务/脚本逐台迁进 Ansible;配好 ssh config 和 ProxyJump | 不再登服务器手工改任何东西;ansible prod -m ping 全通(含内网) |
| 第 4 周 | CD 与密钥 | 写 deploy playbook 并接入流水线;初始化 SOPS+age,把散落密钥收编进 secrets/;配 CI 变量 |
打 tag 后能一键部署到两种网络的机器;Git 里搜不到明文密钥 |
| 第 5 周 | Agent 上岗 | 按规划建 skill 库(先写最高频的 5 个);配好本机环境(glab、Ansible、sops、SSH) | agent 独立完成「领 Issue → MR → 部署」全流程一次 |
之后的迭代方向(用到再加,不提前建设):监控告警(Uptime Kuma,一个容器就够)、预发(staging)环境、更多测试分层、更多 skill。
看到黑话回来查这张表。每条都用人话解释。
| 术语 | 大白话解释 |
|---|---|
| CI(持续集成) | 每次代码变更自动跑测试/构建,问题立刻暴露,而不是攒到发布前 |
| CD(持续部署/交付) | 通过流水线把构建产物部署到服务器;本系统采用「自动到门前、人工按按钮」 |
| MR(Merge Request) | 合并请求:把分支合进 main 之前的评审单,带讨论、测试状态、diff |
| Pipeline(流水线) | .gitlab-ci.yml 定义的一串自动化阶段(test → build → deploy) |
| Runner | 实际执行流水线任务的工人进程,装在办公网 GitLab 服务器上 |
| Artifact(制品) | 构建产出的可部署文件(本系统是 tar 包),GitLab 负责存 |
| Git | 代码版本管理工具:每次修改存成快照,谁改了什么全部可查可回退 |
| Docker / 容器 | 把软件连同运行环境打包成「标准盒子」,在哪台机器打开都一个样 |
| SSH | 远程登录服务器的加密协议;本系统里人、Ansible、git 全走它 |
| ProxyJump | SSH 的跳板功能:经中间机转发,端到端认证,跳板机看不到内容 |
| Ansible / Playbook | 无代理的运维自动化工具 / 用 YAML 写的「操作剧本」,描述目标状态 |
| Inventory | Ansible 的服务器清单,含分组和连接参数 |
| 幂等 | 同一操作执行 N 遍和执行 1 遍效果相同,可以放心重跑 |
| systemd | Linux 的服务管理器,负责服务的启动/停止/开机自启/崩溃重启 |
| SOPS / age | 文件级加密工具 / 现代加密算法;组合实现「密钥加密存 Git」 |
| Skill | 给 AI agent 的操作手册目录:SKILL.md(说明)+ 模板 + 脚本 |
| IaC(基础设施即代码) | 把服务器该是什么样子写成代码存 Git,工具负责落实 |
| 唯一事实来源(SSOT) | 任何信息只有一个权威出处(本系统 = Git 仓库),其余都是它的投影 |