GitOps:这次怎么落地的
这次我以 ArgoCD 为例讲,但概念是通用的。
它建立在几个基本原则上:
- 声明式配置:用 YAML 或类似格式描述你想要的状态,而不是用脚本描述怎么达到这个状态
- 版本化:所有配置都放在 Git 里,每次变更都有记录
- 自动化:有进程自动对比期望状态和实际状态,并做同步
- 持续协调:这个同步过程是持续运行的,不是一次性任务
这套思路跟 Kubernetes 的设计哲学很契合。
为什么要折腾 GitOps
说实话,刚开始接触 GitOps 的时候,我持怀疑态度。以前的做法很传统:CI/CD 流水线跑完,最后一步用 kubectl 或者 Helm 命令把新版本部署上去。这套流程跑了好几年,除了偶尔有人手滑敲错命令,倒也没出过大问题。
直到有一次,线上某个服务突然挂了,大家开始排查问题。我们发现环境的实际状态跟任何人记忆里的都不一样:有人直接在服务器上改过配置,有人用 kubectl apply 覆盖过某些资源,还有一份 Helm values 文件在本地却没提交到仓库。最要命的是,没人记得这些操作具体是在哪天做的,也就没法准确定位问题根源。
从那之后,我开始认真考虑 GitOps。它的核心想法其实很简单:用 Git 仓库来存储目标状态,让系统自动把实际状态同步到目标状态。任何变更都必须通过 Git 的 Pull Request 流程,这样就有完整的审计历史,也方便回滚。
GitOps 到底是什么
GitOps 不是一个具体工具,而是一套方法论。它建立在几个基本原则上:
- 声明式配置:用 YAML 或类似格式描述你想要的状态,而不是用脚本描述怎么达到这个状态
- 版本化:所有配置都放在 Git 里,每次变更都有记录
- 自动化:有进程自动对比期望状态和实际状态,并做同步
- 持续协调:这个同步过程是持续运行的,不是一次性任务
这套思路跟 Kubernetes 的设计哲学很契合。Kubernetes 本身就是声明式的:你告诉它你想要三个 Pod,它就去弄三个出来,中间挂了就自动拉起新的。GitOps 把这个思想延伸到整个集群的配置管理上。
ArgoCD vs Flux:工具选择
市面上最常用的两个 GitOps 工具是 ArgoCD 和 Flux。我用过两个,简单说下感受。
ArgoCD 有个很友好的 Web UI,能看到集群当前状态、同步历史、资源依赖关系图。这对刚开始上手的人很友好,也能给不直接操作集群的同事一个视图。它的功能比较丰富,支持多集群、应用健康检查、自动回滚等。但功能多也意味着配置选项多,学习曲线陡一些。
Flux 相对更轻量,没有 UI,主要靠 CLI 和 CRD 操作。它的设计更贴近 Kubernetes 原生体验,如果你已经习惯了 kubectl 和 YAML,上手会快一些。Flux 2 之后重构了架构,变得更模块化,可以根据需要只启用部分组件。
如果你有可视化需求或者团队里有人不熟悉 kubectl,ArgoCD 可能更合适。如果追求轻量和简洁,或者已经在重度使用 GitOps 的各种高级功能,Flux 可能更对味。这次我以 ArgoCD 为例讲,但概念是通用的。
实战 ArgoCD
安装 ArgoCD
假设你已经有一个 Kubernetes 集群(本机 Minikube、云厂商托管都行),先安装 ArgoCD:
# 创建 namespace
kubectl create namespace argocd
# 安装 ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# 查看 pod 状态
kubectl get pods -n argocd
安装完成后,可以把 ArgoCD 的 API Server 暴露出来。生产环境建议用 Ingress,本地测试可以直接用 port-forward:
kubectl port-forward svc/argocd-server -n argocd 8080:443
然后访问 https://localhost:8080,默认用户名是 admin,初始密码需要从 secret 里获取:
# 获取初始密码
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
# 登录后记得改密码
argocd account update-password
准备 Git 仓库
GitOps 的第一步是建一个 Git 仓库来存放你的应用配置。仓库结构可以按应用分,也可以按环境分,这个看你习惯。我习惯这样组织:
gitops-repo/
├── apps/
│ ├── app1/
│ │ ├── base/
│ │ │ ├── deployment.yaml
│ │ │ ├── service.yaml
│ │ │ └── kustomization.yaml
│ │ └── overlays/
│ │ ├── dev/
│ │ │ ├── kustomization.yaml
│ │ │ └── patch.yaml
│ │ └── prod/
│ │ ├── kustomization.yaml
│ │ └── patch.yaml
│ └── app2/
│ └── ...
├── clusters/
│ ├── dev/
│ │ └── argocd/
│ │ ├── app1.yaml
│ │ └── app2.yaml
│ └── prod/
│ └── argocd/
│ ├── app1.yaml
│ └── app2.yaml
这个结构用到了 Kustomize 来管理不同环境的差异。base/ 存放通用配置,overlays/ 下按环境放覆盖配置。clusters/ 目录里定义每个集群要部署哪些应用。
创建第一个应用
假设有一个简单的 Web 应用,准备一个 deployment 和 service:
# apps/webapp/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
labels:
app: webapp
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: nginx:1.24
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
# apps/webapp/base/service.yaml
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
selector:
app: webapp
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
# apps/webapp/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
commonLabels:
app: webapp
然后创建一个 Application 对象告诉 ArgoCD 去同步这个应用:
# clusters/dev/argocd/webapp.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: webapp
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-org/gitops-repo.git
targetRevision: HEAD
path: apps/webapp/overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
把这个文件 apply 到集群:
kubectl apply -f clusters/dev/argocd/webapp.yaml
如果一切正常,ArgoCD 会开始同步你的应用。在 UI 里应该能看到应用状态变成 “Synced”,集群里也能看到对应的 Pod 跑起来了。
实战踩坑
权限问题
刚开始的时候,我遇到过 ArgoCD 报权限错误:它试图创建某些资源但被 RBAC 拒绝。这是因为 ArgoCD 的 ServiceAccount 默认没有足够的权限。
解决办法是给 ArgoCD 的 ServiceAccount 绑定合适的 ClusterRole:
apiVersion: v1
kind: ServiceAccount
metadata:
name: argocd-application-controller
namespace: argocd
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argocd-application-controller
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
- nonResourceURLs:
- '*'
verbs:
- '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argocd-application-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: argocd-application-controller
subjects:
- kind: ServiceAccount
name: argocd-application-controller
namespace: argocd
这个配置给了 ArgoCD 全局权限,生产环境里应该收紧一些,只给它需要的权限。
Git 仓库访问问题
ArgoCD 需要访问你的 Git 仓库。如果是公开仓库,直接用 HTTPS URL 就行。如果是私有仓库,需要配置访问凭据。
最简单的方式是用 SSH 密钥。先生成密钥对:
ssh-keygen -t ed25519 -N '' -f ~/.ssh/argocd -C "argocd"
把公钥加到你的 GitHub/GitLab 账户里,然后在 ArgoCD 里创建 secret:
# 从私钥创建 secret
kubectl create secret generic argocd-repo-creds \
--from-file=sshPrivateKey=~/.ssh/argocd \
--type=kubernetes.io/ssh-auth \
-n argocd
然后在 Application 里把 repoURL 改成 SSH 格式:
spec:
source:
repoURL: [email protected]:your-org/gitops-repo.git
如果你用 HTTPS + Personal Access Token,可以这样配置:
kubectl create secret generic argocd-repo-creds \
--from-literal=username=your-username \
--from-literal=password=your-token \
-n argocd
然后在 Application 里引用这个 secret:
spec:
source:
repoURL: https://github.com/your-org/gitops-repo.git
repoCredsSecret:
name: argocd-repo-creds
同步频率问题
ArgoCD 默认每 3 分钟同步一次状态。有时候你觉得太慢,希望改动更快生效。可以在 Application 里调整:
spec:
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
# 添加这个字段
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
这个例子告诉 ArgoCD 忽略 Deployment 副本数的变化,避免某些自动扩缩容操作导致的同步冲突。如果你只是想加快同步频率,可以在 ArgoCD 的 ConfigMap 里调整:
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
repo.server: "github.com"
timeout.reconciliation: "30s"
resource.exclusion: |
- apiGroups:
- cilium.io
kinds:
- CiliumIdentity
clusters:
- "*"
timeout.reconciliation 控制同步操作的超时时间,默认是 180 秒。这个值调小一些,失败的时候能更快发现。
资源依赖问题
有些资源之间有依赖关系,比如 Deployment 依赖 ConfigMap,Service 依赖 Pod。ArgoCD 默认的同步策略可能会导致顺序不对,先创建了 Deployment 但 ConfigMap 还没准备好,导致 Pod 启动失败。
ArgoCD 提供了几个 syncOptions 来处理这个问题:
spec:
syncPolicy:
syncOptions:
- CreateNamespace=true
- RespectIgnoreDifferences=true
- ApplyOutOfSyncOnly=true
# 按依赖顺序同步
- ServerSideApply=true
# 跳过不健康资源
- SkipDryRunOnMissingResource=true
ServerSideApply=true 会用 Kubernetes 的 Server-Side Apply 特性,能更好地处理多字段修改冲突。SkipDryRunOnMissingResource=true 会在资源不存在时跳过预检,加快同步速度。
如果你需要明确控制同步顺序,可以在 Application 的 syncPolicy 里设置 syncOptions:
spec:
syncPolicy:
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- PruneLast=true
PruneLast=true 会先创建新资源,再删除旧资源,减少服务中断。PrunePropagationPolicy=foreground 会确保被删除资源(如 Pod)先被清理,再删除依赖它们的资源(如 Deployment)。
进阶用法
多集群管理
ArgoCD 可以管理多个 Kubernetes 集群。先添加远程集群的凭据:
# 获取远程集群的 kubeconfig
argocd cluster add <context-name>
# 查看已添加的集群
argocd cluster list
然后在 Application 里指定目标集群:
spec:
destination:
server: https://your-production-cluster.example.com
namespace: default
这样就可以用一个 ArgoCD 实例管理多个集群了。需要注意网络连通性,ArgoCD 所在的集群要能访问到目标集群的 API Server。
Secret 管理
把敏感信息直接放进 Git 仓库是个坏习惯。ArgoCD 支持多种 Secret 管理方案,比如:
- Sealed Secrets:用公钥加密 Secret,只有集群里的私钥能解密
- External Secrets Operator:从外部系统(AWS Secrets Manager、Azure Key Vault 等)同步 Secret
- Vault Agent:从 HashiCorp Vault 读取 Secret
以 Sealed Secrets 为例,先安装:
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
然后加密你的 secret:
# 创建原始 secret
echo -n "my-password" > password.txt
# 用公钥加密
kubeseal -f password.txt -w sealed-secret.yaml --scope strict
得到的 sealed-secret.yaml 可以安全地放进 Git 仓库。Sealed Secrets Controller 会自动解密并创建真正的 Secret。
CI/CD 集成
GitOps 不是要替代 CI/CD,而是跟它配合。典型的流程是:CI 流水线测试代码、构建镜像、更新 Git 仓库里的镜像版本,然后 GitOps 工具自动把新版本部署上去。
比如用 GitHub Actions:
name: CI/CD
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build and push Docker image
run: |
docker build -t your-registry/webapp:${{ github.sha }} .
docker push your-registry/webapp:${{ github.sha }}
- name: Update GitOps repository
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "[email protected]"
git clone https://${{ secrets.GH_TOKEN }}@github.com/your-org/gitops-repo.git
cd gitops-repo
# 用 yq 更新镜像版本
yq e '.spec.template.spec.containers[0].image = "your-registry/webapp:${{ github.sha }}"' \
-i apps/webapp/overlays/dev/deployment.yaml
git add apps/webapp/overlays/dev/deployment.yaml
git commit -m "Update webapp image to ${{ github.sha }}"
git push
这个流水线构建新镜像后,自动更新 GitOps 仓库里的镜像标签。ArgoCD 检测到变化后会自动同步新版本。
不是银弹
GitOps 好是好,但不是所有场景都适合。如果你在部署单体应用,或者环境很少、团队不大,传统的 CI/CD 可能更简单。GitOps 的价值在复杂场景下更明显:多环境、多集群、多团队、需要强审计的场景。
还有一个现实问题是学习曲线。团队成员需要理解 GitOps 的工作方式,知道所有变更都要通过 Git,不能随手 kubectl apply。这需要一定的文化改变。
另外,GitOps 工具本身也是个要维护的组件。ArgoCD 挂了怎么办?它的存储是 etcd,要有备份策略。权限管理要规划好,不然给了太大权限又是一把双刃剑。
写在最后
GitOps 不是什么革命性技术,它只是把"配置即代码"这个思想贯彻到底。用 Git 作为单一事实来源,让部署过程可审计、可回滚、可自动化。听起来简单,但真正做起来需要不少调整:工具、流程、甚至团队文化。
从我的实践看,GitOps 最核心的价值不在于它多酷,而在于它减少了手动操作的不确定性。半夜排障时能准确知道环境是什么时候变成现在这个样子的,这种感觉挺踏实。
当然,它解决不了所有问题。代码写得烂、架构设计有问题,GitOps 再高级也救不了。但它至少让部署和配置管理这件事变得可控、可追踪。在复杂系统的维护上,这点确定性挺重要的。
版权声明: 本文首发于 指尖魔法屋-GitOps:这次怎么落地的(https://blog.thinkmoon.cn/post/135-gitops-deep-guide-concept-to-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。