P3 Apollo
GitOps 入门:声明式部署
把"应用该长什么样"写进一段 YAML(期望状态),放进本地目录模拟的 Git 仓库,登记同步目标、同步落库、激活、发起部署,让 Spoke Agent 自己去对齐。看完这 4 个故事,你就能理解"声明即期望、同步即登记、激活即可部署"的 GitOps 核心玩法。
应用开发
运维工程师
GitOps
声明式
期望状态
本地仓库
共 4 个故事
能 / 不能速览
✅ 这个主题能做
- 用一段 YAML 声明应用的期望状态:app / version / 组件 / 依赖 DAG / 探针 / 策略
- 登记本地目录模拟的 Git 仓库 + 同步目标,手动 / Webhook / cron 三种方式触发同步
- 同步把仓库内声明落库为期望状态(bundle_version 单调递增),auto 策略自动激活
- 激活前过部署左移门禁(SecurityGate),deny 的声明不生效
- 每次同步写 sync_history 审计,可按历史回滚重建(回滚 = 生成新行)
⛔ 这个主题做不了
- Git 仓库用本地目录模拟,不是真实 GitHub/GitLab/Gitee(真实平台对接规划中)
- fsnotify 目录监听已实现未接线,仓库轮询自动同步暂未启用(需手动/Webhook/cron 触发)
- name 全局唯一,同名期望状态重复登记返回 409 DESIRED_STATE_CONFLICT
- depends_on 引用不存在的组件或依赖成环会被拒绝(400)
- 未激活期望状态不可部署;require_approval 通道的声明须先经审批(approve)才可激活
适用角色
本主题面向两个角色:
- 应用开发工程师:把服务的期望状态写成 YAML(组件、依赖、探针),放进 Git 仓库维护,负责登记与版本演进——是"声明"的写作者。
- 运维工程师 / SRE:登记仓库与同步目标、触发同步、激活、发起部署、观察组件状态——是"执行"的把关人。
安全合规工程师关注单调版本、签名信任锚与部署左移门禁;平台管理员负责权限与审计。每个故事都可单独阅读,建议按顺序看一遍建立全局观。
能力速览(能做什么)
期望状态声明
YAML 定义 app / version / components,组件带 kind、source、depends_on、探针与重启策略,解析后整体落库为 JSON declaration。
本地仓库 GitOps
git_repositories.url 指向本地目录(不用 go-git),Snapshot 列文件 + 模拟 commit SHA;SyncEngine 同步落库期望状态。
三种触发
手动 POST /sync-targets/:id/sync、Webhook POST /webhooks/git/:repo_id(可选 HMAC-SHA256 验签)、scheduled 策略由 cron 定时同步。
状态机管理
draft → active → deprecated 受控流转;仅 draft 可改声明、仅 active 可部署、仅 active 可弃用;审批通道需先 approve。
单调版本防回滚
bundle_version 按应用自动 +1(per app 唯一),回滚也生成更高版本的新行(rollback_of 指回源行)。
同步历史审计
每次同步/回滚写 sync_history(OperationSummary 记录 created/reused/rolled_back),按最近历史可回滚重建。
调整指南(怎么调整)
- 改声明:仅 draft 状态可 PUT 更新声明;active 想改需先走回滚或新登记,不能直接改线上声明。
- 改同步策略:auto 同步后自动激活;manual 仅登记草稿;scheduled 配 SyncCron 定时同步(cron 表达式)。
- 改触发方式:仓库配 webhook_secret 后可用 X-LightApollo-Signature 验签触发;也可直接手动 sync。
- 改探针:readiness 是部署门控判据务必配准;liveness 判进程是否重启(自愈);四形态 http / tcp / process / file。
- 改忽略字段:policy.ignore_paths 配置动态字段(pid / restart_count)避免漂移误报。
做得好的场景
Apollo 把"部署长什么样"从"部署怎么做"里剥离出来,特别适合以下场景:
- 声明一次、处处复用:同一份期望状态可在多环境对齐,环境一致性不再靠人肉保证。
- 同步可审计:每次同步写 sync_history,声明变更"谁在何时触发、落库成什么"可回溯。
- 受控变更:激活前是草稿、激活后留痕、弃用可追溯,每一步状态转移都有审计事件。
- 拉取式对齐:Spoke 出向拉取期望状态,控制面永不主动连入客户环境,天然适配内网与离线。
限制与不足
以下是明确的边界,使用前先知道:
- 本地目录模拟 Git:真实 Git 托管平台(GitHub/GitLab/Gitee/Gitea)clone/push 属规划中,演示用本地目录。
- 轮询自动同步未接线:sync_interval_seconds 仅记录,fsnotify Watch / PollChanges 已实现未启用,需手动/Webhook/cron 触发。
- 演示 Poller 不真实部署:演示 Agent 仅拉取 / 上报,不真正拉起进程(ProcManager 未挂载时跳过应用)。
- 规模目标值未实测:控制平面 50+ 集群 / 1000+ 应用是目标值,演示环境未做压测。
场景故事
故事 1
应用开发阿强把服务写进"期望状态"并放进本地 Git 仓库
场景:声明登记
角色:应用开发
耗时:约 8 分钟
- 背景
- 阿强是应用开发工程师,负责一个叫 demo-app 的内部服务:一个 web 前端加一个 db 后端。以前发版靠给运维写部署邮件。今天他要亲手把 demo-app 的"期望状态"写出来——两个组件、web 依赖 db、各带 tcp 就绪探针,并把 YAML 放进本地目录模拟的 Git 仓库。
- 传统做法对比
- 以前发一个版本,要把包、启动参数、依赖关系写成一封部署邮件,运维照着在 6 台服务器上一台台手工配置,一次上线常常要 1 天,还容易漏改;现在把期望状态写进一段 YAML 放到 Git 仓库,登记仓库后一次同步就能让平台拿到声明。
- 角色
- 阿强(应用开发工程师,拥有期望状态登记权限);随后由运维陈工接手同步目标与激活。
- 操作步骤
-
- 登录 Apollo(admin / admin1,端口 18082,或经 Vite 5173 的 /apollo 前缀)
- 准备本地目录仓库(如 C:/temp/gitops-demo),写入 demo-app 的 desired-state.yaml
- 在"Git 仓库"页登记仓库:url 指向本地目录
- 在"同步目标"页创建 auto 同步目标(git_path 指向仓库内声明目录)
- 系统响应
- 登记成功返回结构示例:
POST /api/v1/projects/1/git-repos
{ "code": 0, "data": { "id": 1, "name": "demo-repo",
"url": "C:/temp/gitops-demo", "status": "active" } }
同步目标创建后,手动同步把声明落库为 demo-app(status=draft、bundle_version=1)。
- 结果洞察
- 期望状态 = 把"应用该长什么样"写下来,平台负责让它变成现实。依赖 DAG 自动校验(无环才通过);bundle_version 由平台按应用自动 +1,为后续防回滚打底。GitOps 的核心是"声明进仓库、同步即登记",声明与执行彻底分离。
- 调整建议
- readiness 探针是部署门控判据,务必按真实端口配(demo 里 web=8080、db=5432);本地目录里只放期望状态声明文件,别把源码一起放进去——Snapshot 会把整个目录当仓库。
- 动手试一试
- 登录:http://127.0.0.1:18082(或 Vite 5173),admin / admin1。页面路径:Git 仓库 → 新建。输入内容:url 填一个含 demo-app desired-state.yaml 的本地目录。注意:演示库已预置同名 demo-app,重复登记会返回 409 DESIRED_STATE_CONFLICT;练习时请改名 demo-app-lab。
- 限制提示
- name 全局唯一(同名 409);app / version / components 必填缺失返回 400;depends_on 引用不存在组件或依赖成环会被拒绝;Git 仓库为本地目录模拟,真实 GitHub/GitLab/Gitee/Gitea 对接规划中。
故事 2
运维陈工创建同步目标,手动同步并看到 auto 激活
场景:同步激活
角色:运维工程师
耗时:约 5 分钟
- 背景
- 阿强把声明放进本地仓库后,运维陈工登录平台:登记仓库已由阿强完成,他要创建同步目标并手动触发同步。仓库里是 demo-app 的期望状态,同步策略选 auto——同步后平台应自动把声明激活为 active,让应用进入"可部署"状态。
- 传统做法对比
- 以前上线要先填审批单、等 leader 签字、再挑维护窗口执行,从"写完配置"到"能部署"往往隔 1~2 天;现在建好同步目标点一下手动同步,平台自动解析声明、落库草稿、auto 激活,全程留 sync_history 审计。
- 角色
- 陈工(平台运维 / SRE,负责同步目标创建、触发同步与激活把关)。
- 操作步骤
-
- 进入"Git 仓库"页确认 demo-repo 状态 active
- 创建同步目标:git_repo_id=demo-repo、git_path=/、sync_policy=auto
- 点击"手动同步"(POST /sync-targets/:id/sync)
- 打开"期望状态"列表,看 demo-app 出现且 status=active
- 系统响应
- 同步成功返回历史记录:
POST /api/v1/sync-targets/1/sync
{ "code": 0, "data": { "id": 3, "trigger_type": "manual",
"status": "success", "git_commit_sha": "a3f9c21d4e5b",
"operation_summary": [ { "name": "demo-app", "app": "demo-app",
"version": "1.0.0", "desired_state_id": 1, "status": "reused" } ] } }
期望状态列表 demo-app 徽章从"草稿"变为"已激活"。
- 结果洞察
- sync_history 是每次同步的审计轨迹:谁触发(trigger_type=manual)、模拟 commit SHA、每条声明落库结果(created/reused/rolled_back)都记着。auto 策略下同步即激活;声明有变化再同步,同名冲突会幂等复用(status=reused)而不是报错。
- 调整建议
- 声明走 require_approval 通道时,同步后保持 pending 不激活,需在"期望状态"页点"审批通过"(POST /desired-states/:id/approve)才生效;想定时同步就把 sync_policy 改成 scheduled 并配 SyncCron。
- 动手试一试
- 登录:admin / admin1。页面路径:同步目标 → 新建 → 手动同步。预期结果:sync_history 出现 success 记录,期望状态 demo-app(或 demo-app-lab)变为 active;再同步一次,同名声明的 status 为 reused。
- 限制提示
- 同步前激活路径会过部署左移门禁(SecurityGate):策略 deny 时该声明保持 draft、整次同步置 failed;回滚(Rollback)属自愈应急恢复不走门禁。仓库/同步目标状态非 active 时 SyncEngine 拒绝同步。
故事 3
发起第一次部署,看 DAG 首批放行 db
场景:发起部署
角色:运维工程师 + 应用开发
耗时:约 5 分钟
- 背景
- demo-app 已激活,陈工把它部署到测试环境 Agent "spoke-01"。web 依赖 db,平台按依赖 DAG 分层:无依赖的 db 先放行,web 等 db 就绪后才启动。
- 传统做法对比
- 以前手工 ssh 进服务器,先装数据库、配好端口再装 web,顺序错了 web 连不上库,光排查连接问题就花 2 小时;现在发起部署后,平台按依赖拓扑自动分批放行,顺序不会再错。
- 角色
- 陈工(平台运维,发起部署)+ 阿强(应用开发,观察组件状态确认启动顺序)。
- 操作步骤
-
- 进入"部署"页,点击"发起部署"
- 填 desired_state_id=demo-app 的 id、agent_name=spoke-01(策略可选 policy_name=default)
- 提交,观察部署记录进入 deploying
- 打开组件状态视图,看 db=deploying、web=pending
- 系统响应
- 发起成功返回部署 id;组件状态视图返回:
GET /api/v1/deployments/:id/components
[ { "name": "db", "status": "deploying" },
{ "name": "web", "status": "pending" } ]
首批只放行无依赖的 db。
- 结果洞察
- Kahn 分层 DAG 是编排核心:同层可并行、下一层必须等前序层全部就绪。第一批只放行 db,正是"依赖就绪门控"在起作用,也是声明式部署与手工脚本最大的不同。
- 调整建议
- 想给 web 加版本约束,在组件声明里写 requires(component: db、min_version);同一 Agent 串行锁——上一部署未结束前再次发起会返回 409 DEPLOYMENT_CONFLICT。
- 动手试一试
- 登录:admin / admin1。页面路径:部署 → 发起部署。输入内容:desired_state_id=demo-app、agent_name=spoke-01。预期结果:部署进入 deploying,组件状态 db=deploying、web=pending;再次发起会被部署锁 409 拦截。
- 限制提示
- 期望状态未激活不可部署(400);演示 Poller 默认不自动启动,组件就绪需在部署详情页手动"推进"或调用 advance 上报模拟;同一 Agent 同时只能有一个进行中部署。
故事 4
从"手工登录服务器改配置"到"声明式部署"的理念转变
场景:理念转变
角色:运维工程师
耗时:约 6 分钟
- 背景
- 陈工负责的旧系统有 6 台服务器,每次发版都要逐台登录改配置、重启服务、再人工核对。用 Apollo 跑通 demo-app 后,他第一次体会到"写期望状态"和"执行部署"可以彻底分离,而且声明可以从 Git 仓库同步进来。
- 传统做法对比
- 手工流程:改 1 处配置 × 6 台服务器 × 每台 40 分钟 = 一个下午,还容易漏改某台导致环境不一致;声明式:期望状态只维护一份,Git 仓库同步 + Spoke 拉取后自行对齐,5 分钟完成,6 台机器结果一致。
- 角色
- 陈工(平台运维,理念转变的主角)+ 阿强(应用开发,配合观察 Agent 上报)。
- 操作步骤
-
- 把期望状态 YAML 纳入本地 Git 仓库维护(GitOps 真相源思路)
- 登记仓库 / 同步目标,手动或 Webhook 触发同步落库并激活
- 发起部署,Spoke Agent 按周期出向拉取(GET /api/v1/agent/pull?agent=spoke-01)
- Agent 上报实际状态(POST /api/v1/agent/report),平台汇总视图可见
- 系统响应
- 拉取端点返回结构示例:
GET /api/v1/agent/pull?agent=spoke-01
{ "desired_state": { "app": "demo-app", "version": "1.0.0", "...": "..." },
"bundle_digest": "", "has_update": true }
上报端点返回 { "agent_name": "spoke-01", "accepted": true }。
- 结果洞察
- 核心转变:从"告诉机器怎么一步步做"到"告诉平台目标状态是什么"。声明式带来环境一致性(同一份期望状态处处相同)、可复现(同步历史随时回看)、可审计(每次变更留痕 sync_history)。Apollo 控制面永不主动连入客户环境,Agent 全程出向拉取 / 上报,天然适配内网与离线场景。
- 调整建议
- 给不同环境(dev / staging / prod)各建一条期望状态,用发布通道区分;仓库目录建议只放声明文件,配合 Webhook 在"提交"时自动触发同步(当前本地目录模拟下需手动 sync 或配 scheduled cron)。
- 动手试一试
- 登录:admin / admin1。页面路径:/apollo/agents 页启动一个模拟 Agent(spoke-01)。预期结果:Agent 周期拉取并上报,节点列表出现 spoke-01,含 last_seen、reconcile_state=synced 与实际状态快照。
- 限制提示
- 当前 Git 仓库为本地目录模拟(真实 GitHub/GitLab/Gitee/Gitea 对接规划中),且 fsnotify 轮询自动同步未接线,需手动 / Webhook / cron 触发;演示 Poller 仅拉取 / 上报、不真实部署进程(ProcManager 未挂载时不应用);gRPC / mTLS 通信属规划,当前为 HTTP + 轮询。
常见问题
期望状态和"部署"是什么关系?
期望状态是"目标"(应用该长什么样),部署是"达成目标的过程"。一条期望状态可被多次部署(不同 Agent、不同批次推进);部署记录是执行史,期望状态是意图。
为什么先激活才能部署?
激活(draft → active)是一个受控门:草稿期间声明可以被反复修改、校验;只有确认无误的 active 期望状态才允许编排器消费。require_approval 通道还需先审批通过(approve)才会被 Spoke 拉取。
GitOps 和 Jenkins / Ansible 有什么区别?
Jenkins / Ansible 偏"命令式"(告诉机器一步步做什么),GitOps 是"声明式"(定义目标状态,系统自行调和差异)。Apollo 的期望状态就是声明式载体,当前用本地目录模拟 Git 仓库做声明触发源与审计源。
bundle_version 为什么自动递增?
它是防回滚攻击的判据:同一应用的版本必须单调递增,Spoke 应用前校验"新版本 > 已应用版本",否则拒绝。回滚不是降版本,而是生成更高版本的新期望状态行(rollback_of 指回源行)。
演示数据每次启动会重建吗?
会。demo-app、demo-signer、stable 通道、default 策略由 seed 幂等重建(重复启动跳过);你新建的期望状态 / 部署记录 / 同步历史保存在平台库中,重启保留。
主题小结
一句话:GitOps 入门 = 写期望状态(声明)→ 放本地仓库登记同步(同步即登记)→ 激活(受控)→ 发起部署(DAG 分层放行)→ Spoke 拉取对齐。记住几个边界:Git 仓库是本地目录模拟(真实平台规划中)、轮询自动同步未接线需手动/Webhook/cron 触发、同名登记 409、依赖成环 400、演示 Poller 不真实部署进程。