GitHub Actions 与 Kubernetes GitOps 部署:ArgoCD 与 Flux 协同

GitHub Actions 与 Kubernetes GitOps 部署实战:CI 与 CD 职责切分、镜像标签与 digest 策略、GitOps 仓库分层与 Kustomize 覆盖、ArgoCD Application 与 ApplicationSet、Flux GitRepository 与 Kustomization、Actions 更新 manifest 的三种做法、Argo Rollouts 金丝雀、推送式部署取舍、漂移检测与回滚


一、CI 与 CD 的职责切分

传统流水线把编译、测试、打镜像、kubectl apply 全塞进一个 workflow,问题在于谁都能从 CI 直接改生产集群,集群的最终状态没有单一事实来源。

CI 侧(GitHub Actions)
  触发:push / pull_request
  职责:lint、测试、构建镜像、推送 registry、产出 SBOM
  产出:一个不可变的镜像 digest
  不接触:集群 kubeconfig 与任何集群写权限

CD 侧(ArgoCD / Flux 控制器)
  触发:部署仓库的 manifest 变更
  职责:把期望状态同步到集群,检测并纠正漂移
  产出:集群实际状态等于 Git 声明状态
  不接触:源码与构建过程

1.1 边界在哪里

属于 CI
  - 单元与集成测试、镜像构建与扫描
  - 更新部署仓库里的镜像 tag(见第六章)

属于 CD
  - 把 manifest 应用到集群
  - 渐进式发布、漂移检测、自动同步、回滚

不该做
  - CI 里 kubectl apply,等于绕过 Git 成为事实来源
  - CD 控制器里跑测试或构建,违反单一职责

切分后有两个收益:CI 的凭证里不再需要集群管理员 kubeconfig,攻击面收窄;集群状态永远可从 Git 历史回溯,回滚等于 git revert。


二、镜像仓库与标签策略

2.1 短哈希与语义化标签

- name: Compute image tag
  id: meta
  run: echo "tag=sha-$(git rev-parse --short=7 HEAD)" >> "$GITHUB_OUTPUT"

- uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: registry.example.com/app:${{ steps.meta.outputs.tag }}
标签类型            可变性    用途
sha-abc1234        不可变    生产部署的唯一引用,推荐
v1.4.2             不可变    发布里程碑,人工识别
1.4                可变      指向 1.4.x 最新,慎用于生产
latest             可变      仅本地开发
sha256:xxxx...     不可变    真正的内容寻址,最严格

短哈希与 commit 一一对应、便于追溯、天然去重,缺点是人工读起来无意义。多架构镜像推送后生成 manifest list,digest 指向 list 而非单架构镜像,Kubernetes 按节点架构自动选层。

2.2 用 digest 锁定

image: registry.example.com/app@sha256:9f2c1a...e3b

最稳的做法是引用 digest 而非 tag。嫌 digest 难读时,折中是 tag + digest 同时写入,tag 供人看、digest 供机器校验。


三、GitOps 仓库结构与覆盖方式

3.1 应用仓库与部署仓库分离

app-repo(源码)
  .github/workflows/ci.yml      构建、测试、推镜像
  src/ ...  Dockerfile

deploy-repo(部署清单)
  apps/web/
    base/{deployment.yaml,service.yaml,kustomization.yaml}
    overlays/{dev,staging,prod}/kustomization.yaml
  clusters/prod-ap-northeast-1/apps.yaml

分离的好处:部署仓库的提交历史就是一部发布史,且可以给它设置比源码仓库更严的 CODEOWNERS 与审批规则。

3.2 Kustomize 覆盖

# apps/web/overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: web-prod
resources:
  - ../../base
images:
  - name: registry.example.com/app
    newTag: sha-abc1234
patches:
  - path: replicas-patch.yaml

images 字段是 CI 更新 manifest 最省事的入口:只改一行 newTag 即可完成一次发布。patches 指向的补丁文件只写差异字段,例如只声明 spec.replicas: 6。

3.3 Helm values 覆盖

# deploy-repo/charts/web/values-prod.yaml
image:
  repository: registry.example.com/app
  tag: sha-abc1234
replicaCount: 6
选择建议
  Kustomize  无模板、纯覆盖、被 ArgoCD 原生渲染,适合自有服务
  Helm       有模板与依赖管理,适合引入第三方 chart
  混合       用 Helm 生成 base,再用 Kustomize 打补丁

四、ArgoCD Application 与 ApplicationSet

4.1 一个 Application 清单

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: web-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/my-org/deploy-repo.git
    targetRevision: main
    path: apps/web/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: web-prod
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

selfHeal: true 让控制器在被手工改动后自动拉回 Git 状态,是漂移检测的第一道防线;prune: true 会删除 Git 中已移除的资源,生产首次启用前务必确认无误删风险。

4.2 ApplicationSet 批量生成

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: web-all-envs
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
    - matrix:
        generators:
          - list:
              elements:
                - { env: dev, cluster: https://kubernetes.default.svc }
                - { env: prod, cluster: https://prod.example.com }
          - git:
              repoURL: https://github.com/my-org/deploy-repo.git
              revision: main
              files:
                - path: "apps/*/config.json"
  template:
    metadata:
      name: "{{.path.basename}}-{{.env}}"
    spec:
      source:
        repoURL: https://github.com/my-org/deploy-repo.git
        targetRevision: main
        path: "apps/{{.path.basename}}/overlays/{{.env}}"
      destination:
        server: "{{.cluster}}"

goTemplateOptions: ["missingkey=error"] 能避免模板变量拼错时静默生成一个名为 <no value> 的应用。权限上再用 AppProject 限定 sourceRepos 与 destinations,把「谁能 sync prod」收进 RBAC。


五、Flux 的 GitRepository 与 Kustomization

5.1 源与同步单元

apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: deploy-repo
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/my-org/deploy-repo.git
  ref:
    branch: main
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: web-prod
  namespace: flux-system
spec:
  interval: 5m
  path: ./apps/web/overlays/prod
  prune: true
  wait: true
  targetNamespace: web-prod
  sourceRef:
    kind: GitRepository
    name: deploy-repo
  dependsOn:
    - name: infra-crds

dependsOn 是 Flux 相对 ArgoCD 的显式能力:CRD 必须先就绪,业务 Kustomization 才能应用,否则会因找不到 CRD 而反复重试。

5.2 镜像自动化

apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: app
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: app
  policy:
    semver:
      range: ">=1.4.0"

配合 ImageUpdateAutomation 与 manifest 里的标记注释,控制器才知道该改哪一行:

image: registry.example.com/app:1.4.2 # {"$imagepolicy": "flux-system:app"}

5.3 ArgoCD 与 Flux 的取舍

维度          ArgoCD                      Flux
界面          Web UI 丰富、可视化强        以 CLI 与 CRD 为主
多集群        单实例管多集群,集中式       每集群一套控制器,分布式
同步粒度      Application 为单位           Kustomization 为单位
镜像更新      argocd-image-updater(外挂)  ImageUpdateAutomation(内置)
适用          团队要可视化、集中治理        平台团队偏好声明式

两者可以共存:Flux 管集群基础组件,ArgoCD 管业务应用,互不干扰。


六、Actions 更新 manifest 的三种做法

6.1 直接提交

- name: Update image tag
  run: |
    git clone https://x-access-token:${{ secrets.DEPLOY_REPO_TOKEN }}@github.com/my-org/deploy-repo.git /tmp/deploy
    cd /tmp/deploy
    sed -i "s|newTag: .*|newTag: sha-${{ github.sha }}|" apps/web/overlays/prod/kustomization.yaml
    git commit -am "chore: bump web to sha-${{ github.sha }}" && git push

链路最短、无额外组件,但没有评审、没有审计,main 分支保护形同虚设,只适合个人项目与内部工具。

6.2 argocd-image-updater

让 ArgoCD 侧自己发现新镜像,Actions 完全不需要写部署仓库:

metadata:
  annotations:
    argocd-image-updater.argoproj.io/image-list: app=registry.example.com/app
    argocd-image-updater.argoproj.io/app.update-strategy: newest-build
    argocd-image-updater.argoproj.io/app.allow-tags: regexp:^sha-[0-9a-f]{7}$
    argocd-image-updater.argoproj.io/write-back-method: git:secret:argocd/git-creds

allow-tags 必须写死成哈希正则,否则任何被推送的 tag(包括误推的 latest)都会触发更新。这种做法 CI 零改动、职责彻底分离,代价是更新时机不由 CI 决定,无法与测试门禁串联。

6.3 PR 化更新

生产环境最推荐的做法:CI 开一个 PR,由人评审合并。

git clone https://x-access-token:$TOKEN@github.com/my-org/deploy-repo.git /tmp/deploy
cd /tmp/deploy
git checkout -b "release/web-sha-${GITHUB_SHA:0:7}"
sed -i "s|newTag: .*|newTag: sha-${GITHUB_SHA:0:7}|" apps/web/overlays/prod/kustomization.yaml
git commit -am "chore: bump web to sha-${GITHUB_SHA:0:7}" && git push -u origin HEAD
gh pr create --repo my-org/deploy-repo \
  --title "chore: bump web to sha-${GITHUB_SHA:0:7}" \
  --body "Automated image bump from ${GITHUB_REPOSITORY}@${GITHUB_SHA}"
维度            直接提交        image-updater      PR 化
CI 改动         需要            不需要             需要
人工评审        无              无                 有
审计追溯        弱              中                 强
生产适用度      不推荐          中                 推荐

七、Argo Rollouts 金丝雀与渐进式交付

把 Deployment 换成 Rollout 即可获得金丝雀能力:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: web
  namespace: web-prod
spec:
  replicas: 10
  strategy:
    canary:
      canaryService: web-canary
      stableService: web-stable
      steps:
        - setWeight: 10
        - pause: { duration: 5m }
        - analysis:
            templates:
              - templateName: success-rate
        - setWeight: 50
        - pause: { duration: 10m }
        - setWeight: 100
  selector:
    matchLabels:
      app: web
  template:
    spec:
      containers:
        - name: web
          image: registry.example.com/app:sha-abc1234

分析模板决定何时自动中止:

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
  namespace: web-prod
spec:
  metrics:
    - name: success-rate
      interval: 1m
      count: 5
      successCondition: result[0] >= 0.99
      failureLimit: 1
      provider:
        prometheus:
          address: http://prometheus.monitoring:9090
          query: |
            sum(rate(http_requests_total{code!~"5.."}[1m]))
            / sum(rate(http_requests_total[1m]))

指标不达标时 Rollout 自动中止并回退到 stable 版本,这是 GitOps 里「发布失败自动止损」的关键一环。日常操作只需三条命令:

kubectl argo rollouts get rollout web -n web-prod     # 查看进度
kubectl argo rollouts promote web -n web-prod         # 手动晋级
kubectl argo rollouts abort web -n web-prod           # 中止并回退

八、推送式部署的取舍

- uses: azure/login@v2
  with:
    client-id: ${{ secrets.AZURE_CLIENT_ID }}
    tenant-id: ${{ secrets.AZURE_TENANT_ID }}
    subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- uses: azure/k8s-deploy@v5
  with:
    namespace: web-prod
    manifests: |
      manifests/deployment.yaml
    images: |
      registry.example.com/app:sha-${{ github.sha }}
    strategy: canary
    percentage: 20
维度            推送式(CI kubectl)       拉取式(GitOps 控制器)
事实来源        集群(Git 只是输入)         Git
漂移纠正        无                          自动
凭证位置        CI secret 持有 kubeconfig    控制器在集群内
集群暴露面      CI 需要可达集群 API          集群主动出站拉取
回滚            重跑旧 workflow               git revert
多集群         每个集群各配一份凭证         一份 Git 管全部
适用            一次性运维脚本、集群引导      长期运行的集群

推送式并非一无是处:集群引导、GitOps 控制器升级前的手工介入都适合直接推送。真正的坑是两者长期并存——CI 用 kubectl set image 改了 Deployment,五分钟后 selfHeal: true 把它当成漂移改回旧 tag,服务出现「发布成功又自动回退」的诡异现象。要么彻底走 GitOps 删掉 CI 里的 kubectl 步骤,要么临时禁用 selfHeal 并标注原因。


九、漂移检测与回滚

9.1 漂移检测

# ArgoCD 查询所有 OutOfSync 的应用
argocd app list -o json | jq -r '.[] | select(.status.sync.status=="OutOfSync") | .metadata.name'
argocd app diff web-prod

# Flux 检查 Kustomization 状态
flux get kustomizations --status-selector ready=false
flux reconcile kustomization web-prod --with-source
常见漂移来源
  1) 有人 kubectl edit 手工改了副本数或镜像
  2) HPA 动态改了 replicas
  3) MutatingWebhook 注入了 sidecar
  4) 其他控制器改了 Service 的 clusterIP

排除 HPA 与 webhook 造成的「假漂移」:

spec:
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers: [/spec/replicas]
    - group: ""
      kind: Service
      jsonPointers: [/spec/clusterIP]

9.2 回滚

argocd app history web-prod
argocd app rollback web-prod 12

# Flux 没有内置 history,回滚即 git revert
git revert <bad-commit-sha> && git push
回滚策略选择
  代码问题       git revert 部署仓库,控制器自动同步
  镜像问题       改 newTag 或 digest 回上一个版本
  配置漂移       删除手工改动,让控制器拉回
  CRD/数据迁移   不能靠回滚解决,需要前滚修复

生产发布前后各确认一次状态:发布前确保镜像用 digest 锁定、部署仓库 PR 通过 CODEOWNERS、selfHeal 与 prune 语义已确认;发布后确认状态为 Synced 且 Healthy、无 OutOfSync 残留,并记录本次的 commit 与镜像 digest。


总结

GitHub Actions 与 Kubernetes 的 GitOps 协同,本质是把流水线从「一串命令」变成「两个职责清晰的系统」。CI 侧只负责把源码变成不可变镜像,产出短哈希标签或 digest,再通过直接提交、argocd-image-updater 或 PR 化三种方式之一更新部署仓库;CD 侧交给 ArgoCD 或 Flux 控制器,用 Application、ApplicationSet、GitRepository、Kustomization 这些声明式资源把 Git 状态同步到集群。生产发布再叠加 Argo Rollouts 的金丝雀与分析指标,让失败自动止损。推送式部署在引导和一次性脚本里仍有位置,但绝不该与拉取式长期并存,否则 selfHeal 会与 kubectl 互相打架。最后用漂移检测与 git revert 兜住回滚,集群的每一个状态就都能追溯到一次提交。

延伸阅读:

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「github-actions」更多文章

  1. 多云部署编排与基础设施漂移检测
  2. 文档站与静态站点发布流水线
  3. AI 代码审查与 PR 助手集成