feat(deployer): add Argo CD / GitOps workflow with MR-based deployment

This commit is contained in:
2026-08-24 20:37:55 +08:00
parent 282a6809ac
commit ee31278947
4 changed files with 279 additions and 36 deletions
+150
View File
@@ -0,0 +1,150 @@
# Argo CD GitOps 轨道
改 GitOps 仓库里的 Application / 清单 → 开 PR/MR → **用户合并** → Argo CD 同步到集群。
Agent 不直接 `kubectl apply` 工作负载,也不合并 MR。
Compose 的 `_config.yaml` / `sync.py` / `remote.py` 不用于本轨道。
## 项目配置
`argocd.yaml` 只做指针:GitOps 仓库的 Git 地址,以及可选的本地目录。Application 名、镜像、namespace、域名、Secret 名的权威在 GitOps 清单里,不要再抄一份。
不要嵌进 `_config.yaml`(脚本解析器不支持嵌套映射)。
两种接法(人类说明见 skill `README.md`):
```yaml
# 1)只写地址:部署时浅 clone 到临时目录,开完 MR 删掉
repo: git@git.example.com:org/infra-gitops.git
# 2)已有 checkout:不 clone,直接在目录里改
# repo: git@git.example.com:org/infra-gitops.git # 可选;缺省用该目录 origin
repo_dir: ../infra-gitops # 相对路径相对项目根
# path: argocd/applications/other-name # 仅当目录不是 argocd/applications/<源项目名>
```
| 字段 | 必填 | 说明 |
|------|------|------|
| `repo` | 与 `repo_dir` 至少一个 | Git 远程地址(`git@host:group/name.git``https://...` |
| `repo_dir` | 与 `repo` 至少一个 | 本地 checkout;相对**项目根**或绝对路径 |
| `path` | 否 | 默认 `argocd/applications/<源项目 git 仓库名>` |
`repo` 写成本地路径(`../``/``~/`)时,当作 `repo_dir`(旧写法兼容)。
`repo_dir` 且目录是 git 仓库 → **不 clone**,在里面改。`repo` 同时存在且与 `origin` 指向不同仓库时停下问用户。
只有 `repo` 地址 → `git clone --depth 1``mktemp -d`;开完 MR(或确认失败)后删掉临时目录。不要 clone 进源项目。
无此文件:只问 Git 地址,写成 `repo:` 再动手。
其余一律推导,不要写进这个文件:
| 需要的信息 | 从哪来 |
|------------|--------|
| 推送 remote | `repo` 地址,或 `git -C <repo_dir> remote get-url origin` |
| Application 名 | `path` 末级,或 `application.yaml``metadata.name` |
| 镜像(无 tag | 清单里的 `image:`;首次无清单则项目 Makefile / `.env` 的 registry + name |
| 升 tag 改哪个文件 | 在 `path` 下搜该镜像 |
| namespace / host / imagePullSecret / TLS Secret | `application.yaml` 与 Ingress/Deployment |
默认 `path` 不存在:再按 `metadata.name == 源项目名` 搜;还没有则走「新增」,不要把 namespace/host 写回 `argocd.yaml`
## 摸底
1.`argocd.yaml`,按上一节得到工作副本(浅 clone 或 `repo_dir`)。`repo_dir``git fetch`;工作区有无关改动则 `git worktree add` 隔离。
2. 打开 `path/`:有 `application.yaml` 视为已接入;只有惯例目录、没有本应用则走「新增」。
3. **清单写法跟目标 GitOps 仓库已有应用走**,不要另起一套:
- 自建镜像:`application.yaml` + `manifests/`Deployment/Service/Ingress
- 上游 Helm`sources[]` 里 chart + 本仓库 `values.yaml`
4. 从兄弟应用抄:`spec.project``destination.server``syncPolicy`、IngressClass、TLS 引用方式。
5. 仓库 README 写明「无 app-of-apps / 需逐个创建 Application」时,合入 main **不会**自动注册新 Application。
## 新增 Application
向用户确认清单里写不出的项:namespace、对外 host(或仅 ClusterIP)、是否要 `imagePullSecrets`、TLS Secret 名、镜像 tag。
然后在工作副本里:
1. 从最新默认分支拉出 `feat/<application>`(浅 clone 已在默认分支;`repo_dir``origin/<default>` 拉)。
2.`path/application.yaml` + 清单(或 values),字段对齐兄弟应用。
3. 镜像用固定 tag,不用 `:latest`
4. Secret、dockerconfigjson、口令 **不入库**Deployment 只引用 Secret 名。
## 升级 / 改清单
已有 Application 时:
1.`path` 下搜到的镜像行,把 `image: <name>:<old>` 改成新 tag。
2. 只改本次要求的清单;不要顺手改无关 values。
3. tag 必须已经推进镜像仓库(本轨道不负责 `docker push`,那是 builder)。
## 提交 MR
1. 分支从最新 `origin/<default>` 拉出,名称如 `feat/<application>``chore/<application>-<tag>`
2. 只提交 GitOps 仓库内本次文件。
3. `git push -u``origin`;不 `--force`、不硬 reset。
4. 按 remote 选 CLI**GitLab `glab mr create`**、**GitHub `gh pr create`**、**Gitea/Forgejo `tea pulls create`**。
创建前读该子命令 `--help`;指定 title、body、base、head。
5. CLI 对项目 404token 看不到仓库)而 SSH push 已成功:把 `git push` 打印的「create merge request」网页链接交给用户,不要读 token 配置、不要改用 curl 带 token。
6. **停在 MR**。不合并、不替用户点 Sync。浅 clone 的临时目录在得到 MR 链接(或确认失败)后删除。
MR 正文写清:改了什么、镜像引用、合入后用户还要做的事(首次 apply Application、拷 Secret、验 healthz/域名)。
## 合入后交给用户
无 app-of-apps 时,**第一次**注册 Application(之后改 manifests 合入即可):
```bash
kubectl apply -f <path>/application.yaml
```
TLS / 拉镜像 Secret 必须已在**工作负载所在 ns** 存在。没有则从已有 ns 拷贝(见下),不要新建一份写进 Git。
## Secretns 级,不入库)
`imagePullSecrets` 与 Ingress `tls.secretName` 都只在 Pod/Ingress 所在 namespace 生效。
不是集群每个 ns 都要建,只给**实际跑这个负载的 ns** 准备。
列出已有拉镜像 Secret(名字以 Deployment 的 `imagePullSecrets` 为准):
```bash
kubectl get secret <secret> -A
```
拷到目标 ns(类型须为 `kubernetes.io/dockerconfigjson`):
```bash
kubectl get secret <secret> -n SOURCE -o json \
| jq 'del(
.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp,
.metadata.selfLink, .metadata.ownerReferences, .metadata.managedFields,
.metadata.annotations, .status
) | .metadata.namespace = "DEST"' \
| kubectl apply -f -
```
没有 `jq`
```bash
kubectl get secret <secret> -n SOURCE \
-o jsonpath='{.data.\.dockerconfigjson}' | base64 -d > /tmp/dockerconfig.json
kubectl -n DEST create secret generic <secret> \
--type=kubernetes.io/dockerconfigjson \
--from-file=.dockerconfigjson=/tmp/dockerconfig.json
rm /tmp/dockerconfig.json
```
本环境没有 kubeconfig 时,把命令交给用户执行,不要假装已经建好。
## 验证
- MR URL 可打开,head 含本次清单,base 为约定默认分支
- 用户合并后:Argo CD 中该 Application Syncedautomated 关闭时用户需手动 Sync
- 升级:集群里容器镜像 tag 与清单一致
- 有 Ingresscurl 域名 healthz / 业务路径;HTTPS 失败先查 TLS Secret 是否在该 ns
## 不要做
- 把 Compose 的 rsync/`remote.py` 套到集群
- 常规发布用 `kubectl apply` 工作负载绕过 GitOps
- 提交 Harbor 密码、`dockerconfigjson`、TLS 私钥
- 在未获「合并」授权时 `glab mr merge` / `gh pr merge`
@@ -1,6 +1,9 @@
# `_config.yaml` 配置参考
`_config.yaml`skill 部署脚本解析,决定同步目标与排除规则。可放在**服务目录、部署根或其任意祖先目录**;子目录中的字段覆盖父目录(继承合并)。
`_config.yaml`**Compose 轨道**脚本解析,决定同步目标与排除规则。可放在**服务目录、部署根或其任意祖先目录**;子目录中的字段覆盖父目录(继承合并)。
Argo CD 轨道用独立文件 `.skiff/deployer/argocd.yaml`,字段见 [argocd.md](argocd.md)。
不要把 `argocd:` 嵌进本文件(脚本解析器不支持嵌套映射)。
## 放置位置(两种布局)