Announcement

👇Official Account👇

图片

Welcome to join the group & private message

Article first/tail QR code

Skip to content

GitOps实战:从应用部署到全生命周期管理

去年我们团队从传统的Jenkins + kubectl apply模式迁移到GitOps,踩了不少坑,也收获了很多。这篇文章分享我们在生产环境摸爬滚打一年的经验,包括ArgoCD的各种坑、Flux的选型纠结,以及那些官方文档不会告诉你的实战技巧。

🎯 为什么我们需要GitOps?

说实话,在用GitOps之前,我们的部署流程挺混乱的。

那些年踩过的坑

记得有一次,一个新来的同事为了修一个紧急bug,直接在生产环境用kubectl edit改了Deployment的镜像标签。当时确实解决问题了,但两周后另一次部署时,这个修改被覆盖了,导致服务异常,我们花了大半夜排查原因。

类似的情况还有很多:

  • 配置漂移防不胜防:有人直接在集群里改配置,Git里的配置和实际运行状态对不上。最尴尬的是,有时候「我机器上能跑」变成了「只有生产环境能跑」,因为不知道谁偷偷改了什么。
  • 权限管理一团糟:开发、测试、运维都在用同一个kubeconfig,出了事根本不知道是谁干的。有一次误删了namespace,查了半天日志才找到元凶。
  • 回滚像赌博:用脚本部署,回滚的时候发现脚本依赖当时的执行环境,根本跑不通。最后只能手动一个一个改,心惊胆战的。
  • 协作基本靠吼:开发说"我代码提交了",运维说"我没收到部署请求",测试说"环境不对啊"。每个人都在自己的世界里,信息完全不透明。

GitOps到底解决了什么

后来我们引入了GitOps,情况好了很多。

GitOps的核心思想很简单:Git仓库就是唯一的事实来源(Single Source of Truth)。所有配置都放到Git里,然后通过ArgoCD这样的工具自动同步到集群。

这样做的好处很明显:

  1. 变更可追溯:谁改了什么、什么时候改的、为什么改,Git history里一目了然。再也不用猜"这个配置是谁改的"。
  2. 回滚就是git revert:出问题的时候,不用手忙脚乱地改配置文件,直接git revert然后等ArgoCD同步就行。我们有一次回滚只花了3分钟。
  3. 集群状态和Git保持一致:如果有人偷偷kubectl edit,ArgoCD会自动把它改回来(如果你开了self-heal)。配置漂移基本被根治了。
  4. 部署自动化:代码合并到main分支后,CI打包镜像,然后自动更新GitOps仓库里的镜像标签。剩下的交给ArgoCD,不需要人工干预。
  5. 多环境管理简单了:我们用的是Kustomize的overlay模式,base放通用配置,overlays/devoverlays/prod放环境特定的配置。环境之间的差异清清楚楚。
┌─────────────────────────────────────────────────────────────┐
│                       GitOps 工作流                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────┐      ┌──────────┐      ┌──────────────┐      │
│  │  开发分支  │ ───→ │  Git仓库  │ ←──→ │  声明式控制器   │      │
│  │ (Feature)│      │(Source of│      │(ArgoCD/Flux) │      │
│  └──────────┘      │ Truth)   │      └──────┬───────┘      │
│         ↓          └──────────┘             │              │
│  ┌──────────┐            ↑                  ↓              │
│  │  Code    │            │           ┌──────────┐          │
│  │ Review   │────────────┘           │ K8s集群  │          │
│  └──────────┘              自动同步    │ (实际状态)│          │
│                                        └──────────┘          │
└─────────────────────────────────────────────────────────────┘

📚 GitOps核心概念

声明式 vs 命令式

刚开始接触GitOps的时候,我对「声明式」这个概念有点懵。后来想明白了,其实很简单:

  • 命令式:你告诉系统「一步一步怎么做」。就像教别人做菜:"先放油,再放菜,然后翻炒5分钟..."
  • 声明式:你告诉系统「我想要什么结果」。就像点菜:"我要一份宫保鸡丁,微辣"。

Kubernetes本身就是声明式的。你不是ssh到机器上启动进程,而是写一个YAML说"我要3个副本",然后K8s自己去搞定。

GitOps把这套理念扩展到了部署流程。你只需要在Git里声明「我想要集群长这样」,ArgoCD会自动让集群变成那个样子。

yaml
# 声明式:描述期望状态
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: app
        image: my-app:v1.2.3
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"

对比命令式操作:

bash
# 命令式:一步步执行(容易出错且难以复现)
kubectl run my-app --image=my-app:v1.0 --replicas=3
kubectl set resources deployment my-app --requests=cpu=100m,memory=128Mi
# ... 更多命令

说实话,一开始我觉得声明式不够灵活。但用了半年后,真香。最明显的好处是:幂等性。你执行一次和执行十次结果是一样的,不会搞出「重复创建」之类的问题。

Git是唯一的事实来源

这一点看起来简单,但实际执行起来有不少细节要注意。

我们在实践中把仓库分成了几类:

  • 代码仓库:应用源码、Dockerfile,这个和原来一样
  • 配置仓库:专门放K8s manifests、Helm charts、Kustomize配置
  • 策略仓库:网络策略、RBAC配置等安全相关的东西

一开始我们想把所有东西放在一个仓库里,后来发现这样权限不好控制。让开发有权限改代码,但不一定应该让他们改生产环境的ingress配置。所以拆成了多个仓库。

下面是我们现在的目录结构,供参考:

project-gitops/
├── apps/                          # 应用配置
│   ├── frontend/
│   │   ├── base/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   └── kustomization.yaml
│   │   └── overlays/
│   │       ├── dev/
│   │       ├── staging/
│   │       └── production/
│   └── backend/
├── infrastructure/                # 基础设施配置
│   ├── nginx-ingress/
│   ├── cert-manager/
│   └── monitoring/
├── policies/                      # 安全策略
└── docs/                          # 文档

有一个坑要注意:别把敏感信息明文提交到Git。后面会讲怎么用Sealed Secrets或者External Secrets来解决这个问题。

拉取模式 vs 推送模式

┌────────────────────────────────────────────────────────────────┐
│                     推送模式 (Push)                              │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  CI/CD Pipeline ──→ kubectl apply ──→ Kubernetes Cluster      │
│                                                                │
│  ❌ 需要集群访问凭证                                            │
│  ❌ 难以保证最终一致性                                          │
│  ❌ 审计和回滚困难                                              │
│                                                                │
└────────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────────┐
│                     拉取模式 (Pull)                              │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  Git Repository ←──── 监控变更 ←──── GitOps Controller         │
│       │                                            │           │
│       └──────────────── 拉取配置 ──────────────────→│           │
│                                                    ↓           │
│                                            Kubernetes Cluster │
│                                                                │
│  ✅ 控制器在集群内,无需外部访问                                  │
│  ✅ 持续协调,保证状态一致                                        │
│  ✅ Git历史即审计日志                                            │
│                                                                │
└────────────────────────────────────────────────────────────────┘

🛠️ GitOps工具链实战

ArgoCD:我们最终的选择

我们对比了ArgoCD和Flux,最后选了ArgoCD。主要原因是:

  1. 有UI:Flux没有官方UI,虽然可以用Weave GitOps,但还是不如ArgoCD的原生UI直观
  2. 上手快:Dashboard里点点就能创建应用,对刚开始接触GitOps的同事友好
  3. 社区大:遇到问题Stack Overflow上基本都能找到答案

但ArgoCD也有缺点,比如资源占用比Flux高,而且配置复杂应用的时候YAML会写得比较多。

总之,如果你想要一个开箱即用、有图形界面的方案,ArgoCD是个不错的选择。如果你更喜欢轻量级、纯声明式的,可以考虑Flux。

安装部署

安装很简单,但有几个坑要注意:

bash
# 创建命名空间
kubectl create namespace argocd

# 安装ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

第一个坑:默认安装用的是NodePort或者ClusterIP,你要么配Ingress,要么用port-forward。我们生产环境是用Ingress + cert-manager管理证书的。

bash
# 开发环境可以用port-forward
kubectl port-forward svc/argocd-server -n argocd 8080:443

第二个坑:初始密码。2.0版本以后变了,用下面这个命令:

bash
# 2.0+版本用这个
argocd admin initial-password -n argocd

# 老版本用这个(现在基本不用了)
kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-server -o name | cut -d'/' -f 2

登录之后记得改密码,还要配RBAC。默认的admin用户权限太大,生产环境建议对接SSO(我们对接的是GitLab OAuth)。

应用定义示例

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: production-frontend
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/gitops-repo.git
    targetRevision: main
    path: apps/frontend/overlays/production
    # Helm示例
    # helm:
    #   valueFiles:
    #     - values-production.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true              # 自动删除Git中不存在的资源
      selfHeal: true           # 自动修复配置漂移
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true   # 自动创建命名空间
      - PrunePropagationPolicy=foreground
      - PruneLast=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
  revisionHistoryLimit: 10

同步顺序与 Sync Wave

当存在 Namespace → CRD → Application 等依赖时,需要控制同步顺序,避免「应用先于依赖」的竞态。ArgoCD 通过 Sync Wave 实现:数字小的先执行,未标注的默认为 0。

yaml
# Namespace 最先创建(wave 最小)
apiVersion: v1
kind: Namespace
metadata:
  name: production
  annotations:
    argocd.argoproj.io/sync-wave: "-1"

---
# CRD 其次
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: mycrds.example.com
  annotations:
    argocd.argoproj.io/sync-wave: "0"

---
# 应用最后
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  annotations:
    argocd.argoproj.io/sync-wave: "1"

多环境管理策略

说明:App of Apps 中 path 需指向包含多个 Application 清单或 Kustomization 的目录(如 apps 下每子目录一个 Application.yamlkustomization.yaml),directory.recurse: true 会递归发现并同步这些资源。

yaml
# 使用App of Apps模式管理多个应用
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: production-apps
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/gitops-repo.git
    targetRevision: main
    directory:
      recurse: true
      jsonnet: {}
    path: apps
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

FluxCD:云原生GitOps方案

FluxCD是CNCF孵化项目,与GitHub生态深度集成。

核心组件架构

┌─────────────────────────────────────────────────────────────┐
│                      FluxCD 架构                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │  Source      │  │  Kustomize   │  │  Helm        │      │
│  │  Controller  │  │  Controller  │  │  Controller  │      │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘      │
│         │                 │                  │             │
│         └─────────────────┼──────────────────┘             │
│                           ↓                                │
│                    ┌──────────────┐                        │
│                    │  Git Repo    │                        │
│                    └──────┬───────┘                        │
│                           │                                │
│         ┌─────────────────┼─────────────────┐              │
│         ↓                 ↓                 ↓              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │  Image       │  │  Notification│  │  Alert       │      │
│  │  Automation  │  │  Controller  │  │  Controller  │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

安装与配置

bash
# 安装Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash

# 引导Flux到集群
flux bootstrap github \
  --owner=$GITHUB_USER \
  --repository=gitops-repo \
  --branch=main \
  --path=./clusters/production \
  --personal

# 验证安装
flux check

自动化镜像更新

说明:Flux 的 ImageRepositoryImagePolicyImageUpdateAutomation 等 API 版本会随版本迭代更新,示例以撰写时为准,请以 Flux 官方文档 为准。

yaml
# 配置镜像仓库扫描
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: frontend-app
  namespace: flux-system
spec:
  image: ghcr.io/your-org/frontend
  interval: 1m0s
  secretRef:
    name: ghcr-auth
---
# 定义镜像策略
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: frontend-app
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: frontend-app
  policy:
    semver:
      range: '>=1.0.0 <2.0.0'
  filterTags:
    pattern: '^v(?P<version>.*)$'
    extract: '$version'
---
# 自动更新Git仓库
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: frontend-app
  namespace: flux-system
spec:
  interval: 1m0s
  sourceRef:
    kind: GitRepository
    name: flux-system
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        name: Flux Bot
        email: flux@example.com
      messageTemplate: |
        Automated image update
        
        Images:
        {{ range .Updated.Images -}}
        - {{.}}
        {{ end }}
      signingKey:
        secretRef:
          name: flux-gpg-signing-key
    push:
      branch: main
  policy:
    semver:
      range: '>=1.0.0'

ArgoCD 与 Flux 选型对比

维度ArgoCDFlux
安装单 YAML 部署,相对简单CLI bootstrap,与 Git 仓库强绑定
UI自带 Web UI,开箱即用无官方 UI,可搭配 Weave GitOps UI
多集群原生多集群、多租户(Project)每集群独立 bootstrap,多集群靠多 repo/path
Helm/Kustomize均支持,且支持 Raw/Plugin独立 Controller,Kustomize/Helm 分离
镜像自动更新需配合 Argo CD Image Updater 或 CI内置 Image Automation(ImageRepository/ImageUpdateAutomation)
与 GitHub/GitLab通用 Git,无深度绑定与 GitHub/GitLab 集成好,PR 驱动可选
声明式配置Application CRGitRepository + Kustomization/HelmRelease
社区与生态使用广泛,企业案例多CNCF 孵化,云原生生态一致

我的建议:如果你像我一样喜欢有图形界面、能快速查看应用状态,选 ArgoCD;如果你更在意资源占用、喜欢一切用 YAML 声明,或者团队里有人熟悉 Flux 那套模式,Flux 也不错。

🏗️ 生产级GitOps架构设计

多集群管理策略

┌─────────────────────────────────────────────────────────────────┐
│                   多集群GitOps架构                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                        Git Repository                           │
│              (github.com/your-org/gitops)                       │
│                              │                                  │
│          ┌───────────────────┼───────────────────┐              │
│          ↓                   ↓                   ↓              │
│   ┌──────────────┐   ┌──────────────┐   ┌──────────────┐       │
│   │   开发集群    │   │   测试集群    │   │   生产集群    │       │
│   │   (Dev)      │   │   (Staging)  │   │   (Prod)     │       │
│   └──────┬───────┘   └──────┬───────┘   └──────┬───────┘       │
│          │                  │                  │               │
│   ┌──────▼───────┐   ┌──────▼───────┐   ┌──────▼───────┐       │
│   │  ArgoCD/Flux │   │  ArgoCD/Flux │   │  ArgoCD/Flux │       │
│   └──────────────┘   └──────────────┘   └──────────────┘       │
│                                                                 │
│  配置策略:                                                       │
│  • Dev: 自动同步 + 自动镜像更新                                  │
│  • Staging: 自动同步 + 人工审批镜像                              │
│  • Prod: 人工触发同步 + 强制审批流程                             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Git 分支策略与同步策略

GitOps 不要求「只用单分支」,分支策略决定了谁审批、谁触发同步

策略做法适用场景
main 即生产生产用 main,其他环境用目录(如 overlays/dev)区分小团队、环境少,希望简单
branch-per-envmainstagingproduction 各分支,每环境盯各自分支环境隔离强、发布节奏不同
trunk-based + 目录单分支,用 apps/*/overlays/{dev,staging,prod} 区分环境配置复用高、希望 diff 清晰

无论哪种方式,都建议:生产环境的同步由人工触发或需审批(ArgoCD 可关掉该应用的 automated,或使用 Sync Window);开发/测试可开自动同步。

分层配置管理

yaml
# 基础层:通用配置(base/kustomization.yaml)
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml
  - configmap.yaml

commonLabels:
  app.kubernetes.io/part-of: ecommerce-platform
  app.kubernetes.io/managed-by: argocd

images:
  - name: frontend-app
    newName: ghcr.io/your-org/frontend
yaml
# 环境层:生产环境覆盖(overlays/production/kustomization.yaml)
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: production

resources:
  - ../../base
  - ingress.yaml
  - hpa.yaml                    # 生产环境启用HPA
  - pdb.yaml                    # Pod Disruption Budget

namePrefix: prod-

commonLabels:
  environment: production

patchesStrategicMerge:
  - deployment-patch.yaml       # 副本数、资源限制
  - configmap-patch.yaml        # 生产环境配置

configMapGenerator:
  - name: app-config
    behavior: merge
    literals:
      - LOG_LEVEL=warn
      - CACHE_TTL=3600
      - FEATURE_FLAG_NEW_UI=true

secretGenerator:
  - name: app-secrets
    envs:
      - secrets.env             # 加密存储,使用Sealed Secrets或SOPS

replicas:
  - name: frontend-app
    count: 5
yaml
# 部署补丁:资源限制(overlays/production/deployment-patch.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-app
spec:
  template:
    spec:
      containers:
      - name: frontend
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - frontend-app
              topologyKey: kubernetes.io/hostname

安全最佳实践

1. 密钥管理

bash
# 使用Sealed Secrets加密敏感数据
# 1. 安装Sealed Secrets控制器
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml

# 2. 安装kubeseal CLI
brew install kubeseal

# 3. 加密Secret
cat <<EOF > secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
stringData:
  username: admin
  password: super-secret-password
EOF

kubeseal --format yaml < secret.yaml > sealed-secret.yaml

# 4. 将sealed-secret.yaml提交到Git
# 只有Sealed Secrets控制器能够解密

密钥管理扩展:若希望密钥不落 Git,可由集群内组件从外部拉取:

  • External Secrets Operator (ESO):从 AWS Secrets Manager、GCP Secret Manager、HashiCorp Vault、Azure Key Vault 等同步到 K8s Secret,Git 中只存 ExternalSecret 声明(不含密文)。
  • HashiCorp Vault:通过 Vault Agent Injector 或 ESO 的 Vault 后端,在 Pod 启动时注入环境变量或文件;GitOps 只管理应用清单,密钥由 Vault 统一发放与轮转。

这样 Git 仓库只保留「要什么 Secret、从哪来」,不存敏感内容,更利于合规与审计。

2. RBAC策略

yaml
# ArgoCD项目级隔离
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: production
  namespace: argocd
spec:
  description: Production Environment
  
  # 允许的源仓库
  sourceRepos:
    - 'https://github.com/your-org/gitops-repo.git'
  
  # 允许部署的目标集群和命名空间
  destinations:
    - namespace: production
      server: https://kubernetes.default.svc
    - namespace: monitoring
      server: https://kubernetes.default.svc
  
  # 允许的资源类型
  clusterResourceWhitelist:
    - group: ''
      kind: Namespace
    - group: 'rbac.authorization.k8s.io'
      kind: ClusterRole
  
  # 命名空间级资源白名单
  namespaceResourceWhitelist:
    - group: 'apps'
      kind: Deployment
    - group: ''
      kind: Service
    - group: 'networking.k8s.io'
      kind: Ingress
  
  # 禁止的资源
  namespaceResourceBlacklist:
    - group: ''
      kind: ResourceQuota
  
  # 同步窗口(维护窗口限制)
  syncWindows:
    - kind: allow
      schedule: '0 2 * * *'      # 每天凌晨2点允许同步
      duration: 4h
      namespaces:
        - production
    - kind: deny
      schedule: '* * * * *'      # 其他时间禁止
      duration: 20h
      namespaces:
        - production

3. 策略即代码(OPA/Gatekeeper)

注意:PSP(Pod Security Policy)相关 Constraint(如 K8sPSPPrivilegedContainer)在 Kubernetes 1.21 弃用、1.25 移除。若集群 ≥ 1.25,请改用 Pod Security Standard (PSS)Kyverno 或 Gatekeeper 的 non-PSP 约束。

yaml
# 禁止特权容器(仅适用于仍支持 PSP 的旧集群)
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
  name: psp-privileged-container
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    excludedNamespaces: ["kube-system", "argocd"]
---
# 强制资源限制
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResources
metadata:
  name: require-resources
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
  parameters:
    limits:
      - cpu
      - memory
    requests:
      - cpu
      - memory

灾难恢复:如果集群挂了怎么办?

这是我们最初担心的问题:如果整个集群挂了,或者ArgoCD本身出问题,我们怎么恢复?

好消息是,因为所有配置都在Git里,恢复其实不难。

场景1:ArgoCD挂了,但集群还在

  • 直接重新安装ArgoCD
  • 如果你的Application定义也在Git里(App of Apps模式),重新apply一下就行
  • 如果Application只在集群里... 这就是为什么我建议用App of Apps

场景2:整个集群挂了

  • 新建一个集群
  • 安装ArgoCD
  • 从Git恢复配置

关键是:Git仓库里要有所有东西。K8s manifests、Helm values、Application定义,都要在Git里。密钥用Sealed Secrets,这样也能存Git。

备份策略

  • Git仓库本身要备份(GitLab/GitHub都有备份机制)
  • 定期导出ArgoCD配置(虽然我们基本没用到过)
  • 测试过恢复流程吗?我们每季度会做一次灾难恢复演练

📊 可观测性与监控

GitOps监控体系

yaml
# Prometheus监控ArgoCD
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: argocd-metrics
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app.kubernetes.io/part-of: argocd
  endpoints:
    - port: metrics
      interval: 30s
      path: /metrics
---
# ArgoCD关键告警规则
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: argocd-alerts
  namespace: monitoring
spec:
  groups:
    - name: argocd
      rules:
        - alert: ArgoCDApplicationNotSynced
          expr: |
            argocd_app_info{sync_status!="Synced"} == 1
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "ArgoCD应用 {{ $labels.name }} 未同步"
            description: "应用 {{ $labels.name }} 处于 {{ $labels.sync_status }} 状态超过5分钟"
        
        - alert: ArgoCDApplicationUnhealthy
          expr: |
            argocd_app_info{health_status!="Healthy"} == 1
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "ArgoCD应用 {{ $labels.name }} 不健康"
            description: "应用 {{ $labels.name }} 健康状态为 {{ $labels.health_status }}"
        
        - alert: ArgoCDSyncFailed
          expr: |
            increase(argocd_app_sync_total{phase!="Succeeded"}[1h]) > 3
          labels:
            severity: warning
          annotations:
            summary: "ArgoCD应用同步频繁失败"
            description: "应用 {{ $labels.name }} 在过去1小时内同步失败超过3次"

通知集成

yaml
# ArgoCD通知配置
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  service.slack: |
    token: $slack-token
  
  template.app-sync-succeeded: |
    message: |
      ✅ *应用同步成功*
      
      应用: {{.app.metadata.name}}
      环境: {{.app.spec.destination.namespace}}
      版本: {{.app.status.sync.revision}}
      同步时间: {{.app.status.operationState.finishedAt}}
      
      {{if .app.status.operationState.syncResult.resources}}
      更新资源:
      {{range .app.status.operationState.syncResult.resources}}
      • {{.kind}}/{{.name}} ({{.status}})
      {{end}}
      {{end}}
    slack:
      attachments: |
        [{
          "title": "{{.app.metadata.name}}",
          "title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
          "color": "#18be18",
          "fields": [
            {"title": "环境", "value": "{{.app.spec.destination.namespace}}", "short": true},
            {"title": "版本", "value": "{{.app.status.sync.revision}}", "short": true}
          ]
        }]
  
  trigger.on-sync-succeeded: |
    - description: 应用同步成功
      oncePer: app.status.operationState.syncResult.revision
      send:
        - app-sync-succeeded
      when: app.status.operationState.phase in ['Succeeded']

🚀 CI/CD与GitOps集成

完整交付流水线

yaml
# .github/workflows/gitops-pipeline.yaml
name: GitOps Delivery Pipeline

on:
  push:
    branches: [main]
    tags: ['v*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    outputs:
      image_tag: ${{ steps.meta.outputs.tags }}
      image_digest: ${{ steps.build.outputs.digest }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix=,suffix=,format=short
      
      - name: Build and push
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64,linux/arm64
      
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: ${{ steps.meta.outputs.tags }}
          format: spdx-json
          output-file: sbom.spdx.json
      
      - name: Upload SBOM
        uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.spdx.json

  security-scan:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ needs.build-and-push.outputs.image_tag }}
          format: 'sarif'
          output: 'trivy-results.sarif'
      
      - name: Upload scan results
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'

  update-gitops:
    needs: [build-and-push, security-scan]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Checkout GitOps repo
        uses: actions/checkout@v4
        with:
          repository: your-org/gitops-repo
          token: ${{ secrets.GITOPS_TOKEN }}
      
      - name: Install yq
        run: sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 && sudo chmod +x /usr/local/bin/yq
      
      - name: Update image tag
        run: |
          yq eval ".images[0].newTag = \"${{ github.sha }}\"" \
            -i apps/frontend/overlays/dev/kustomization.yaml
      
      - name: Commit and push
        run: |
          git config user.name "GitOps Bot"
          git config user.email "gitops@example.com"
          git add .
          git commit -m "ci: update frontend image to ${{ github.sha }}"
          git push

  deploy-staging:
    needs: update-gitops
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Create deployment PR
        uses: peter-evans/create-pull-request@v5
        with:
          token: ${{ secrets.GITOPS_TOKEN }}
          repository: your-org/gitops-repo
          title: 'Deploy ${{ github.sha }} to Staging'
          body: |
            ## Deployment Request
            
            **Image:** `${{ needs.build-and-push.outputs.image_tag }}`
            **Commit:** ${{ github.sha }}
            **Author:** ${{ github.actor }}
            
            ### Changes
            ${{ github.event.head_commit.message }}
            
            ### Checklist
            - [ ] Code review completed
            - [ ] Tests passed
            - [ ] Security scan clean
          
      - name: Login to ArgoCD (需在 repo 中配置 ARGOCD_SERVER、ARGOCD_AUTH_TOKEN 等)
        run: |
          argocd login ${{ secrets.ARGOCD_SERVER }} \
            --auth-token ${{ secrets.ARGOCD_AUTH_TOKEN }} \
            --grpc-web
      
      - name: Wait for ArgoCD sync
        run: |
          argocd app wait staging-frontend \
            --health \
            --timeout 600

📈 GitOps成熟度模型

级别名称特征关键指标
L1基础• 手动部署
• 配置分散
• 缺乏版本控制
部署频率: 月/季度
恢复时间: 天/周
L2标准化• Git管理配置
• 基础CI/CD
• 单一环境
部署频率: 周
恢复时间: 小时
L3自动化• GitOps工具引入
• 多环境管理
• 自动同步
部署频率: 天
恢复时间: 分钟
L4优化• 金丝雀/蓝绿部署
• 自动回滚
• 策略即代码
部署频率: 小时
恢复时间: <5分钟
L5智能化• AIOps集成
• 预测性运维
• 全自动化
部署频率: 按需
恢复时间: 自动

关于成本:很多人问我上GitOps会不会增加成本。说实话,会有一点:

  • ArgoCD本身要占资源(我们生产环境给了2核4G)
  • CI跑得更频繁了(每次提交都触发)
  • 团队需要时间学习

但这些都是前期投入。长期看,因为配置漂移少了、故障恢复快了,反而节省了成本。我们统计过,上了GitOps之后的半年,凌晨on-call的次数减少了60%。

🎯 实施路线图

第一阶段:基础建设(1-2周)

第二阶段:流程完善(3-4周)

第三阶段:规模化推广(1-2月)

第四阶段:持续优化(持续)

🔧 常见故障排查

现象可能原因排查思路
Sync 失败manifest 不合法、RBAC 不足、目标命名空间不存在在 ArgoCD UI 看 Sync 详情与错误信息;argocd app logs <app>;检查 Application 的 destination.namespace 与 Project 的 destinations 是否允许
Health 一直 Progressing/Degraded探针配置不当、依赖服务未就绪、资源不足查 Pod 的 kubectl describe pod、应用日志;核对 Deployment 的 livenessProbe/readinessProbe 路径与端口;确认依赖(如 DB、缓存)已就绪
ImagePullBackOff镜像不存在、私有仓库未配置 pull secret确认镜像 tag 存在、命名空间下是否有 imagePullSecrets;ArgoCD 可配置 repo 的 pull secret 或使用 default service account 的 imagePullSecrets
Sync 成功但无变更未触发同步、缓存未刷新、path/revision 错误使用「Hard Refresh」或勾选「Replace」;确认 targetRevisionpath 指向预期分支与目录
Prune 误删资源资源未在 Git 中声明却被 ArgoCD 管理检查是否被同一 Application 的 selector 包含;必要时为不应被 prune 的资源加 argocd.argoproj.io/sync-options: Prune=false

排查的时候有个经验:先看ArgoCD UI,再看kubectl。ArgoCD UI里能看到同步状态和错误信息,但有时候不够详细,还得kubectl describe pod看具体原因。另外建议开启ArgoCD的通知,同步失败的时候直接推送到Slack,不用一直盯着UI看。

💡 常见问题与解决方案

Q1: GitOps适合我们团队吗?

A: 这个问题我被问过好多次。坦白说,不是所有团队都适合。

比较适合的情况:

  • 已经在用Kubernetes了(这是前提)
  • 部署比较频繁,每周至少好几次
  • 有多个环境(开发、测试、生产)需要管理
  • 对变更有审计要求(比如金融、医疗行业)

不太适合的情况:

  • 还在用传统虚拟机部署,没有上K8s的计划
  • 就一个单体应用,部署也很简单
  • 团队里没人懂K8s,想学GitOps但是连Pod是什么都不知道

我个人的经验是:如果你已经在用K8s了,GitOps值得尝试。如果你还在VM时代,先把K8s搞定再说。

Q2: 数据库Schema变更怎么处理?

A: 这是个老大难问题。我们用的是Flyway,配合ArgoCD的Hook来做。

原理很简单:在部署应用之前,先跑一个Job执行数据库迁移。ArgoCD支持PreSync Hook,就是在正式同步之前执行一些操作。

yaml
# 使用Flyway/liquibase Job处理Schema
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
  annotations:
    argocd.argoproj.io/hook: PreSync      # ArgoCD同步前执行
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  template:
    spec:
      containers:
      - name: flyway
        image: flyway/flyway:latest
        command:
          - flyway
          - migrate
        env:
          - name: FLYWAY_URL
            valueFrom:
              secretKeyRef:
                name: db-credentials
                key: url
      restartPolicy: OnFailure

Q3: 私有仓库如何配置?

A: 使用SSH密钥或Token:

bash
# 创建SSH密钥
ssh-keygen -t ed25519 -C "argocd@example.com"

# 在ArgoCD中配置
argocd repo add git@github.com:your-org/private-repo.git \
  --ssh-private-key-path ~/.ssh/id_ed25519 \
  --insecure-ignore-host-key

# 或使用HTTPS Token
argocd repo add https://github.com/your-org/private-repo.git \
  --username x-access-token \
  --password $GITHUB_TOKEN

Q4: 如何实现蓝绿部署?

A: 使用ArgoCD Rollouts:

yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: frontend
spec:
  replicas: 5
  strategy:
    blueGreen:
      activeService: frontend-active
      previewService: frontend-preview
      autoPromotionEnabled: false    # 人工审批切流
      prePromotionAnalysis:
        templates:
        - templateName: smoke-test
        args:
        - name: service-name
          value: frontend-preview
  selector:
    matchLabels:
      app: frontend
  template:
    # ... Pod模板

🔮 GitOps的未来趋势

  1. AI驱动的GitOps:智能异常检测、自动修复、预测性扩展
  2. 多云GitOps:跨云平台的统一配置管理
  3. 边缘GitOps:IoT和边缘计算的声明式管理
  4. FinOps集成:成本可见性和优化
  5. Policy as Code进化:更智能的合规和治理

📝 写在最后

写到这里,我想说几句心里话。

GitOps确实帮了我们大忙,但它不是银弹,更不是什么"颠覆性的革命"。它只是一种更好的工作方式,解决了我们之前遇到的很多痛点。

回顾这一年多的实践,我觉得GitOps最大的价值不是那些花里胡哨的功能,而是让基础设施管理变得像代码管理一样简单。版本控制、Code Review、CI/CD,这些软件开发里的最佳实践,现在也能用在运维上了。

当然,代价也是有的:

  • 学习成本:团队成员需要理解声明式配置、Kustomize这些概念
  • 工具复杂度:ArgoCD本身就需要维护,升级的时候也要小心
  • 不适用于所有场景:传统VM部署、简单的单应用架构,强行上GitOps可能得不偿失

我的建议

如果你正在考虑要不要上GitOps,我的建议是:

  1. 先小规模试点:找一两个非核心应用试试水,别一上来就全迁
  2. 团队先统一认识:确保大家都理解GitOps的理念,不然很容易用回老方法
  3. 别追求完美:刚开始目录结构可能不够优雅,配置可能有点乱,没关系,先跑起来再优化
  4. 保留逃生通道:万一GitOps出问题了,要有人能手动kubectl救急

最后送大家一句话:工具是为人服务的,别让工具绑架了工作方式。GitOps很好,但如果它不适合你的场景,也不用强求。


这篇文章是基于我们团队的真实实践写的,有些地方可能不够完美,有些方案可能不是最优解,但都是我们在生产环境踩过坑、流过汗总结出来的。希望能对你有所帮助。

如果有任何问题,欢迎在评论区留言讨论。也欢迎你分享自己的GitOps实践经验,让我们一起进步。


相关标签: #GitOps #DevOps #Kubernetes #ArgoCD #Flux #云原生 #持续交付

延伸阅读: 如果你准备在生产环境落地GitOps,建议看看这几个资源:

另外推荐几篇我觉得写得不错的中文博客( Google 搜一下标题就能找到):

  • 《我们团队落地GitOps的18个月》- 另一篇实战经验分享
  • 《ArgoCD生产环境配置指南》- 讲了很多细节配置
  • 《从Jenkins迁移到GitOps踩坑实录》- 如果你正在做迁移

写这篇文章花了两个周末,主要是整理这一年多的实践笔记。如果觉得有用,欢迎点赞收藏。如果有不同意见或者更好的实践方式,也欢迎在评论区交流。

上次更新于: