ArgoCD · GitOps 引擎
ArgoCD 是这套自托管集群的核心:它持续 watch yldm-tech/k8s-config 仓库的 main 分支,把仓库里 ~1300 份 YAML manifest 自动 reconcile 进集群。合并到 main 几分钟后改动就会落地,没有手动 kubectl apply 这一步。完整的开发者流程见 GitOps 工作流。
automated: {prune: true, selfHeal: true},所以合并前一定要先在本地跑通校验。ArgoCD 本身也是被自己管理的:它的 manifest 就在 applications/infrastructure/argocd/ 下,由 root-app 同步。组件构成
ArgoCD 部署在 argocd namespace,由 Helm chart argo-cd-9.1.0 渲染后落到仓库,所有核心组件镜像统一为 quay.io/argoproj/argocd:v3.4.3。
- Name
- application-controller
- Description
StatefulSet,1 副本。负责 reconcile 所有 Application —— 比对 git 期望状态与集群实际状态,执行同步与自愈。
- Name
- repo-server
- Description
Deployment,1 副本。拉取 git 仓库、渲染 kustomize/Helm,把 manifest 交给 controller。监听argocd-repo-server:8081。
- Name
- server
- Description
Deployment,1 副本。提供 Web UI / API / gRPC。server.insecure: "true"(TLS 在 Traefik ingress 终结),ingress 后端走 80 端口。
- Name
- applicationset-controller
- Description
Deployment,1 副本。展开 ApplicationSet(如微服务的yldm-services)为一组具体 Application。enable.leader.election关闭(单副本无需选主)。
- Name
- notifications-controller
- Description
Deployment,1 副本。按argocd-notifications-cm的触发器推送同步/健康通知。
- Name
- redis
- Description
Deployment,1 副本,镜像redis:8.8.0-alpine。作为 controller / repo-server 的缓存。配套一个argocd-redis-watchdogCronJob。
reconcile 周期由 argocd-cm 控制:timeout.reconciliation: 180s(软周期 3 分钟),timeout.hard.reconciliation: 0s(关闭硬性强制刷新)。exec.enabled: "false" 禁掉了 UI 里的 pod exec 终端。
两层编排
ArgoCD 把仓库变成运行中应用靠两层结构,这是理解整个 repo 的关键。详细机制见 GitOps 工作流。
第一层是 app-of-apps 根应用:bootstrap/applications/root-app.yaml 定义的 Application root-app,source 指向 bootstrap/applications 且 directory.recurse: true(排除自身 root-app.yaml)。任何放进 bootstrap/applications/ 的 Application / ApplicationSet YAML 都会被自动发现 —— 不需要手动注册。这一层覆盖单例组件:infrastructure、databases、monitoring、networking、storage、cluster/ 配置等。
第二层是 微服务 ApplicationSet:bootstrap/applications/orchestration/production-services.yaml 里的 ApplicationSet yldm-services,用 List generator + goTemplate: true。它是一个静态列表,不是 git 目录生成器 —— 列表里每个元素(service / serviceType / namespace / replicas)生成一个名为 <serviceType>-<service> 的 Application,source 指向 applications/<serviceType>/<service>。
- service: fluxa
serviceType: platform
namespace: platform
replicas: 2
category: service
# → 生成 Application platform-fluxa,path: applications/platform/fluxa
applications/app/ 下新建目录不会自动上线,必须同步往列表里加一个元素。停用某服务 = 把它的元素注释掉 —— ApplicationSet 开了 syncPolicy.applicationsSync: sync,注释后对应的 live Application 会被 prune 掉。这就是为什么 crashloop / 闲置服务是内联注释而不是删除的(如当前被禁用的 boardserver / cardserver / pvpserver / rpgserver)。模板侧给每个生成的 Application 打上 yldm.tech/deploy-tracking: 'true' 注解,接入 deploy-tracker,把 rollout 状态镜像成 GitHub Deployment status。
AppProject 边界
bootstrap/projects/*.yaml 定义 AppProject,给 Application 划权限边界。共 6 个:yldm-app、yldm-platform、yldm-game、infrastructure、ci-cd、monitoring。微服务 ApplicationSet 模板里 spec.project: '{{.serviceType}}',即 app/platform/game 服务分别落进同名 project。
app / platform / game 是 namespace-scoped 的;infrastructure / ci-cd / monitoring 通过显式 clusterResourceWhitelist 允许集群级资源。例如 infrastructure project 放行 Namespace、ClusterRole(Binding)、CRD、Validating/Mutating WebhookConfiguration、IngressClass、StorageClass、PriorityClass、cert-manager ClusterIssuer,并对 namespace 级资源全放行('*'/'*')。所有 project 的 sourceRepos 都锁定 k8s-config 仓库的 SSH/HTTPS 两种 URL。
automated prune / selfHeal
绝大多数 Application 都开了自动同步:
syncPolicy:
automated:
prune: true # git 里删掉的资源,集群里也删
selfHeal: true # 集群被手动改动会被拉回 git 状态
- selfHeal 意味着直接
kubectl edit改 live 资源是徒劳的 —— 下一次 reconcile(≤180s)会把它改回 git 里的样子。要改就改仓库。 - prune 意味着从仓库删掉一个 manifest,集群里对应资源会被删除。微服务 ApplicationSet 额外用
PrunePropagationPolicy=foreground+PruneLast=true,并带retry(5 次,指数退避到 3m),prune 的 Application 还设了allowEmpty: false防止误删空目录把整个 app 抹掉。 - 微服务模板对 Deployment 的
/spec/replicas设了ignoreDifferences,让 HPA 调整副本数时不会被 selfHeal 拉回。
sync-wave 排序
bootstrap/applications/ 里被 root-app 一次性发现的 Application 用 argocd.argoproj.io/sync-wave 注解控制相对顺序,wave 数字小的先同步。这保证依赖在被依赖者之后上线,例如:
| wave | 组件(示例) |
|---|---|
| 0 | nfs-provisioner |
| 1 | kube-vip、cluster-config、reloader、argocd-image-updater |
| 2 | external-dns、nodelocaldns |
| 3 | vault |
| 5 | kyverno、各 runner |
| 6 | minio |
| 8 | loki、tempo、prometheus、registry(晚于 minio,依赖其 bucket) |
| 9 | grafana、velero |
| 15 | cloudflare-tunnel |
同样的机制也用在单个组件内部排序资源。argocd-image-updater 的 ImageUpdater CR 用 sync-wave: "2" 加 SkipDryRunOnMissingResource=true,确保它们在 CRD(wave 0)建立之后才同步,否则首次同步会报 no matches for kind ImageUpdater。
配合 argocd-image-updater
镜像 tag 不靠手改,由 argocd-image-updater(quay.io/argoprojlabs/argocd-image-updater:v1.2.1,部署在 argocd namespace)管理。它扫描镜像仓库发现新版本,把新 tag git commit 回写到对应 app 的 kustomization images: 块(仓库历史里的 build: automatic update of … commit),再由 ArgoCD 同步上线。
每个被管理的服务在 applications/infrastructure/argocd-image-updater/imageupdaters/<app>.yaml 有一份 ImageUpdater CR(当前 24 份)。以 fluxa 为例:
spec:
writeBackConfig:
method: 'git:secret:argocd/git-creds' # SSH 写回
gitConfig:
repository: 'git@github.com:yldm-tech/k8s-config.git'
branch: 'main'
writeBackTarget: 'kustomization:/applications/platform/fluxa'
applicationRefs:
- namePattern: 'platform-fluxa'
commonUpdateSettings:
updateStrategy: 'semver'
allowTags: "regexp:^[0-9]+\\.[0-9]+\\.[0-9]+$" # 只认 X.Y.Z
控制器配置(config-patch.yaml):watch.namespaces: argocd(CR 与 Application 同 ns),扫描间隔 interval: 2m,写回身份 argocd-image-updater@yldm.tech。镜像仓库定义两个 —— ghcr.io(复用从 Vault 同步的 argocd/ghcr-secret pull secret 列 private tag)和集群内 registry.dunaifen.games(DNF 镜像,匿名只读)。写回用的 SSH key 来自 ExternalSecret git-creds,从 Vault argocd/repo/golang-server 取 sshPrivateKey,与 ArgoCD repo 凭据同一把写权限 key。
dependencyDashboardApproval 的包要把上游 manifest 改动放进同一个 PR,不要单独合 tag。vault、mongo、metallb、meilisearch 都因此踩过坑。访问与认证
ArgoCD Web UI 通过 ingress 暴露在 argocd.yldm.tech:ingressClassName: traefik,TLS 证书由 cert-manager letsencrypt-prod-dns01 ClusterIssuer 签发(secret argocd-server-tls),并经 Cloudflare Tunnel 代理。
认证走独立的 Dex 做 OIDC(issuer: https://dex.yldm.tech,clientID: argocd-cli,请求 openid/profile/email/groups);admin.enabled: "true" 保留本地 admin 兜底。argocd-secret 由 ExternalSecret 从 Vault argocd/argocd-secret 同步。会话有效期 timeout.session: 168h(7 天)。