feat(deployer): add Argo CD / GitOps workflow with MR-based deployment
This commit is contained in:
@@ -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 对项目 404(token 看不到仓库)而 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。
|
||||
|
||||
## Secret(ns 级,不入库)
|
||||
|
||||
`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 Synced(automated 关闭时用户需手动 Sync)
|
||||
- 升级:集群里容器镜像 tag 与清单一致
|
||||
- 有 Ingress:curl 域名 healthz / 业务路径;HTTPS 失败先查 TLS Secret 是否在该 ns
|
||||
|
||||
## 不要做
|
||||
|
||||
- 把 Compose 的 rsync/`remote.py` 套到集群
|
||||
- 常规发布用 `kubectl apply` 工作负载绕过 GitOps
|
||||
- 提交 Harbor 密码、`dockerconfigjson`、TLS 私钥
|
||||
- 在未获「合并」授权时 `glab mr merge` / `gh pr merge`
|
||||
Reference in New Issue
Block a user