元数据卡
- 前置知识:第7章(K8s 基础)、第9章(SRE 基础)
- 预计时间:40 分钟
- 阅读模式:高度专注
- 完成标志:理解 IaC 的核心价值,能用 Terraform 管理基础设施,理解 GitOps 的 reconciliation 模式及其与 CI/CD 的关系
你的进度
前线指挥系统运行在 K8s 上,但你发现另一个问题:所有底层基础设施——虚拟机、负载均衡器、DNS 记录、数据库——都是手动创建的。
有人 SSH 进去装软件,有人通过网页控制台点按钮创建资源,有人直接在云厂商的命令行里敲了一行就跑了。结果是:团队里没人知道"现在线上到底有哪些资源"。一台叫 outpost-alpha-nginx 的虚拟机,是半年前谁建的、配了什么、为什么还在运行——没人说得清楚。
更可怕的是,你要建一个跟生产环境一样的环境跑新版本测试。你问林将军:"生产环境的配置清单有吗?"
林将军说:"找老周——但老周上周休假了。你 SSH 上去 cat 一下 nginx 配置不就完了。"
你 SSH 上去,发现这台机器上装的东西和旁边那台一模一样但路径不同。这就是经典的"雪花服务器"问题——每台服务器都是独一无二的雪花,没有两台是完全一致的。
你的任务
掌握 IaC(基础设施即代码)和 GitOps 的核心实践。你将学会用 Terraform 声明式地管理云资源,用 Git 作为基础设施的唯一真相来源,用 Argo CD 或 Flux 的 reconciliation 机制让集群状态始终与 Git 仓库保持一致。
破局 · 溯源
为什么 IaC:从 SSH 灾难到声明式管理
手动管理基础设施的问题:
操作 | 风险 | 可复现性
SSH 进去改 nginx 配置 | 改错了没有回退按钮 | 不可复现
在云控制台点按钮创建数据库 | 点了什么忘了 | 不可复现
运维小张的笔记本上有所有密码 | 小张离职了 | 不可复现每次手动操作都是一次"事故"的种子。手动操作越频繁,种子发芽的概率越高。
IaC 把基础设施的管理变成一个代码工程问题:
- 可版本控制:所有配置在 Git 里,看到每一次变更
- 可评审:修改基础设施要走 PR 流程,有 Code Review
- 可复现:一份配置可以反复部署到不同环境
- 可审计:谁、什么时间、改了什么,有完整的 Git 日志
你不再 SSH 进去改东西。你改代码,提交 PR,等 CI 通过,merge 到 main 分支——然后自动化工具帮你把变更应用到环境上。
Terraform:基础设施的声明式编排器
Terraform 是 HashiCorp 的 IaC 工具。它由三个核心概念组成:Provider(提供商)、Resource(资源)、State(状态)。
Provider
Provider 是你想要管理的目标系统。AWS、Azure、GCP、Kubernetes、GitHub、Cloudflare——Terraform 有数百个 provider。
# main.tf
# Terraform 配置入口
# 运行: terraform init && terraform plan && terraform apply
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.0"
}
}
}
provider "aws" {
region = "ap-east-1"
}
provider "kubernetes" {
config_path = "~/.kube/config"
}Resource
Resource 是你声明要创建的资源。你描述"我想要什么",Terraform 负责把它变成现实。
# resources/network.tf
# 声明网络基础设施
# terraform plan 预览变更,terraform apply 执行
resource "aws_vpc" "frontline_vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "atlas-frontline-vpc"
Env = "production"
}
}
resource "aws_subnet" "frontline_subnet_a" {
vpc_id = aws_vpc.frontline_vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-east-1a"
tags = {
Name = "frontline-subnet-a"
}
}
resource "aws_eks_cluster" "frontline_eks" {
name = "atlas-frontline-cluster"
role_arn = aws_iam_role.eks_role.arn
vpc_config {
subnet_ids = [
aws_subnet.frontline_subnet_a.id,
aws_subnet.frontline_subnet_b.id
]
}
}State
Terraform 维护一个状态文件(terraform.tfstate),记录当前环境中每个资源的状态和 Terraform 配置之间的映射关系。这是 IaC 的心脏——没有状态文件,Terraform 不知道"这个 VPC 是它创建的还是别人创建的"。
状态文件不能放在本地。多人协作时,必须存在远程后端——就像军令不能只保存在一个人的脑子里,必须存档在指挥部且上锁:
# backend.tf
# 状态文件存放在 S3(AWS)或类似的远程存储中
# 用 DynamoDB 做状态锁,防止多人同时 apply
terraform {
backend "s3" {
bucket = "atlas-terraform-state"
key = "prod/frontline/terraform.tfstate"
region = "ap-east-1"
dynamodb_table = "terraform-state-lock"
encrypt = true
}
}Plan/Apply 工作流
编写 .tf 文件 → terraform init(初始化 provider)
→ terraform plan(预览变更,不执行)
→ 人工 review plan 输出
→ terraform apply(执行变更)
→ terraform destroy(销毁资源)terraform plan 的输出是关键的审计步骤。它告诉你:
Terraform will perform the following actions:
# aws_eks_cluster.frontline_eks will be created
+ resource "aws_eks_cluster" "frontline_eks" {
+ name = "atlas-frontline-cluster"
+ version = "1.28"
+ endpoint = (known after apply)
+ ...
}
# aws_subnet.frontline_subnet_a will be created
+ resource "aws_subnet" "frontline_subnet_a" {
+ cidr_block = "10.0.1.0/24"
+ ...
}
Plan: 5 to add, 0 to change, 0 to destroy.Module:复用基础设施代码
当你在多个环境部署同样的资源时,Terraform Module 让你复用代码。
# modules/eks-cluster/main.tf
# 一个可复用的 EKS 集群模块
variable "cluster_name" {
description = "EKS cluster name"
type = string
}
variable "subnet_cidrs" {
description = "Subnet CIDR blocks"
type = list(string)
}
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
}
resource "aws_eks_cluster" "this" {
name = var.cluster_name
# ...
}
output "cluster_endpoint" {
value = aws_eks_cluster.this.endpoint
}使用模块:
# envs/prod/main.tf
module "eks_cluster" {
source = "../../modules/eks-cluster"
cluster_name = "atlas-prod"
subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
}环境隔离:Dev、Staging、Prod
不同环境需要隔离——不仅是网络层面,还有 IaC 层面。目录结构决定了隔离策略:
terraform/
├── modules/ # 共享模块
│ ├── eks-cluster/
│ └── rds-postgres/
├── envs/
│ ├── dev/
│ │ ├── main.tf # 环境特有资源
│ │ ├── backend.tf # dev 状态文件
│ │ └── terraform.tfvars # dev 变量值
│ ├── staging/
│ │ ├── main.tf
│ │ ├── backend.tf
│ │ └── terraform.tfvars
│ └── prod/
│ ├── main.tf
│ ├── backend.tf
│ └── terraform.tfvars # prod 用更强的实例规格
└── global/
├── iam.tf # IAM 角色(全局)
└── dns.tf # DNS 记录每个环境使用独立的状态文件,互不干扰。部署流程:
# 部署到 staging 环境
cd terraform/envs/staging
terraform init
terraform plan -out=staging.tfplan
terraform apply staging.tfplan
# 验证通过后,再用同样的模块部署到 prod
cd terraform/envs/prod
terraform init
terraform plan -out=prod.tfplan
terraform apply prod.tfplan环境隔离的关键原则:
- 不要手工改 dev 来"模拟 prod"——dev 的环境、配置、数据量应该和 prod 有差异但结构一致
- 多部署一套新环境比调试环境差异更容易
- 所有环境入口配置在 Git 中,不做"这个环境额外手动改了"的事情
Secrets 管理:不要放进 Git
.tfvars 文件里有数据库密码、API key、证书——这些不能放在 Git 里。
常见 secrets 管理方案:
# 方案 1:Terraform + Vault
# HashiCorp Vault 动态生成数据库凭据
provider "vault" {
address = "https://vault.atlas.local:8200"
}
data "vault_generic_secret" "db_password" {
path = "secret/atlas/frontline/database"
}
resource "aws_db_instance" "postgres" {
# ...
password = data.vault_generic_secret.db_password.data["password"]
}# 方案 2:Terraform + AWS Secrets Manager / GCP Secret Manager
# 用云厂商的托管的秘密存储
data "aws_secretsmanager_secret_version" "db_creds" {
secret_id = "atlas/prod/database"
}
locals {
db_creds = jsondecode(data.aws_secretsmanager_secret_version.db_creds.secret_string)
}
resource "aws_db_instance" "postgres" {
username = local.db_creds.username
password = local.db_creds.password
}# 方案 3:sops 加密文件
# 用 sops(Mozilla 的加密工具)加密 .tfvars 文件
# 加密的文件可以安全地提交到 Git
sops --encrypt --kms arn:aws:kms:... terraform.tfvars > terraform.tfvars.sops
# 解密使用
sops --decrypt terraform.tfvars.sops核心原则:把秘密放在密钥管理服务中,不要让秘密出现在代码仓库、日志、或环境变量中明文暴露。
GitOps:Git 作为单一真相来源
IaC 解决了"基础设施代码化"的问题。但下一个问题出现了:你写好了 Terraform 配置,谁来执行 terraform apply?如果每个人都在本地执行,环境状态还是会漂移。
GitOps 的回答是:Git 仓库里的状态才是系统的期望状态。集群会自动朝这个状态靠拢。
GitOps 模式:
+-----------+
| Git Repo | ← 开发者提交 PR → review → merge
+-----+-----+
|
| (Argo CD / Flux 持续拉取)
v
+-----------+
| K8s 集群 |
| | ← 自动对比 Git 中的期望状态和集群实际状态
| +-------+ |
| | App | | ← 发现差异则自动纠正
| +-------+ |
| | App | |
| +-------+ |
+-----------+Argo CD / Flux:Reconciliation 和 Drift Detection
Argo CD
Argo CD 是 CNCF 毕业的 GitOps 工具,专门为 K8s 设计。想象你在指挥部挂了一张标准化的阵地部署图——每个哨位的位置、每个兵种的配置都画好了。部队必须严格按照这张图布阵,有人擅自移动岗哨位置,自动修正回来:
# application.yaml
# 告诉 Argo CD:这个应用的期望状态在这个 Git 路径
# kubectl apply -f application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: atlas-frontline
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/atlas/frontline-k8s-manifests
targetRevision: main
path: k8s/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: frontline
syncPolicy:
automated:
prune: true # 自动删除 Git 中不存在的资源
selfHeal: true # 手动改了集群资源会被自动还原
syncOptions:
- CreateNamespace=true当 Argo CD 的 Application 配置好后,它的工作原理是:
- 每 3 分钟(可配)拉取 Git 仓库
- 对比 Git 中的 YAML 与 K8s 集群中的实际状态
- 如果发现有差异(Drift),标记为"OutOfSync"
- 如果开启了自动同步,自动把集群状态修正为 Git 中的状态
Argo CD UI 状态标识:
Synced(已同步) → 集群状态与 Git 一致
OutOfSync(漂移) → 有人手动改了集群资源,或者 Git 有新的提交Flux
Flux 是另一个 CNCF 毕业的 GitOps 工具,定位类似但架构略有不同。Flux 走的是 "Source 拉取配置,Kustomization 应用配置" 两步流水线:
# flux-source.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: atlas-frontline
namespace: flux-system
spec:
interval: 1m
url: https://github.com/atlas/frontline-k8s-manifests
ref:
branch: main
---
# flux-kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: atlas-frontline
namespace: flux-system
spec:
interval: 5m
path: ./k8s/overlays/production
prune: true
sourceRef:
kind: GitRepository
name: atlas-frontlineFlux 的核心概念是 "Source → Kustomization" 的流水线:
GitRepository(Git 源)
↓ 定时拉取
Kustomization(应用到集群)
↓ reconciliation
K8s 资源GitOps vs CI/CD
这不是一个替代关系,它们解决不同层面的问题。
传统 CI/CD 管道:
开发者提交代码 → CI 构建和测试 → CD 将构建产物部署到目标环境
↓ ↓
(代码质量保证) (部署编排)
GitOps 管道:
开发者提交配置 → CI 验证配置和 lint → 合并到 main
↓
GitOps 工具(Argo CD / Flux)
检测到 Git 变更 → reconcile 到集群| 维度 | CI/CD | GitOps |
|---|---|---|
| 触发方式 | Push 触发 | Reconciliation 循环(持续对比) |
| 状态保证 | 部署时保证一次 | 持续保证(检测 drift) |
| 回滚 | 执行 rollback pipeline | Git revert → 自动修复 |
| 权限控制 | CI 系统 token | Git 仓库权限 + 代码评审 |
| 谁操作目标环境 | CD agent | GitOps operator(运行在集群内) |
| 适用场景 | 应用构建、测试、打包 | 基础设施、配置、部署到 K8s |
最佳实践是用 CI/CD 做构建和测试,用 GitOps 做部署:
CI/CD (Jenkins / GitHub Actions):
代码 → 构建 → 单元测试 → 集成测试 → 生成 Docker 镜像
↓
推送到镜像仓库
↓
更新 K8s YAML 中的镜像标签
提交到 Git(main 分支)
↓
GitOps (Argo CD):
Git 变更触发 reconciliation
拉取新 YAML
更新集群中的 Deployment
等待 rollout 完成
报告同步状态常见陷阱
把敏感信息直接写在 IaC 配置里。 Terraform 配置文件可能被提交到 Git、在 CI 日志中暴露、或通过状态文件泄露。始终使用密钥管理服务引用秘密。
多人直接对同一个状态文件执行 terraform apply。 没有状态锁,两个人同时 apply 导致状态损坏。使用远程后端(S3 + DynamoDB 锁)或 Terraform Cloud。
手动修改云资源后不同步 IaC 配置。 你在 AWS 控制台点了一下改 VPC 的路由表,Terraform 不知道。下次
terraform apply会把这个改动覆盖——或者更糟,plan 报错说状态不一致。原则:任何基础设施变更都通过 IaC,不做游离于代码之外的"手术"。GitOps 开启了自动同步却没有 Prune。 不从 Git 中删除资源,而是手工在集群里删除。下一次 Argo CD reconcile 时又会重建。为了删除资源,应该从 Git 中删除它的 YAML 并确认
prune: true。分支策略混乱。 所有环境都用一个分支部署,无法控制发布的顺序。常见策略:
dev分支对应开发环境,staging分支预发布验证,main分支对应生产环境。每个环境有独立的 Kustomization overlay。
通关挑战
- 热身:用 Terraform 在本地创建一个模拟环境:定义一个
local_file资源写入一个配置文件,然后运行plan和apply。
# demo.tf
resource "local_file" "frontline_config" {
filename = "${path.module}/frontline-config.yaml"
content = <<-EOF
environment: dev
service: outpost-alpha
version: 1.0.0
EOF
}
output "config_path" {
value = resource.local_file.frontline_config.filename
}- 挑战:在本地用 kind 创建一个 K8s 集群,然后部署 Argo CD。写一个 Application YAML 从 Git 仓库中同步一个 Deployment(上传你的 Deployment YAML 到一个测试仓库)。
# 安装 Argo CD
kubectl create namespace argocd
kubectl apply -n argocd -f \
https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# 暴露 Argo CD UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# 登录 Argo CD
argocd admin initial-password -n argocd
argocd login localhost:8080- 观察:在 K8s 集群中手工修改一个由 Argo CD 管理的 Deployment 的副本数。观察 Argo CD 的自动修复机制将副本数恢复为 Git 中的期望值。查看 Argo CD UI 中的 Sync Status 变化。
验收标准
- 能用 Terraform 声明式地创建一组基础设施资源
- 理解 Terraform state 的作用和远程存储的必要性
- 知道三种以上的 secrets 管理方式及其适用场景
- 理解 GitOps 的 reconciliation 模式及其与 CI/CD 的区别
- 能在 K8s 集群中部署并配置 Argo CD 或 Flux
常见卡点
Terraform state 文件冲突了怎么办? 如果本地 state 和远程 state 不一致,先
terraform refresh同步实际资源状态。如果锁定,手动释放锁(terraform force-unlock <lock_id>)但要确保没有其他人在同时 apply。Argo CD 和 Flux 选哪个? Argo CD 的 UI 更成熟,多人协作场景更友好。Flux 更轻量,和 Kustomize 集成度更高。两者都是 CNCF 毕业项目,选择取决于团队的运维习惯。
GitOps 适合所有资源吗? 不是。Secrets(除非用 Sealed Secrets / External Secrets Operator)、数据库 schema 变更、需要人为确认的破坏性操作——这些不适合完全走自动同步。
分支策略怎么跟环境对应? 最小化方案:main 分支对应 prod,feature 分支 PR 合并到 main 前自动部署到 dev 验证。PR 验证通过后合并到 main,GitOps 自动同步到 prod。
现在不需要理解
- Terraform Cloud / Enterprise 的完整工作流——开源版本足够处理中小规模的基础设施管理
- Crossplane(另一种"用 K8s 管理基础设施"的方案)——概念更激进,适合以 K8s 为中心的组织
- 多集群 GitOps 的复杂拓扑(Hub-and-Spoke)——先掌握单集群的 GitOps 流程
旅人笔记
IaC 解决的是"雪花服务器"——让每台机器不再是独一无二的、学完就忘的手工艺品。GitOps 走得更远:它不只是把配置写成代码,而是让集群持续地、自动地向 Git 中的期望状态收敛。你不 SSH 进去、不点控制台按钮、不记小本本——你改代码、提交、review、merge。剩下的交给 reconciliation 循环。这不是运维方式的改善,是从"运维"到"工程"的转变。
下一站预告
IaC 和 GitOps 让你的基础设施有了代码化的管理方式。接下来你需要在 K8s 上运行真正的生产工作负载——有状态的服务、精细的网络策略、资源约束。基础概念上一章讲过,现在该讨论生产环境下那些"坑"了。下一章,我们进入 K8s 的生产实践。