跳到内容

元数据卡

  • 前置知识:第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 把基础设施的管理变成一个代码工程问题:

  1. 可版本控制:所有配置在 Git 里,看到每一次变更
  2. 可评审:修改基础设施要走 PR 流程,有 Code Review
  3. 可复现:一份配置可以反复部署到不同环境
  4. 可审计:谁、什么时间、改了什么,有完整的 Git 日志

你不再 SSH 进去改东西。你改代码,提交 PR,等 CI 通过,merge 到 main 分支——然后自动化工具帮你把变更应用到环境上。


Terraform:基础设施的声明式编排器

Terraform 是 HashiCorp 的 IaC 工具。它由三个核心概念组成:Provider(提供商)、Resource(资源)、State(状态)。

Provider

Provider 是你想要管理的目标系统。AWS、Azure、GCP、Kubernetes、GitHub、Cloudflare——Terraform 有数百个 provider。

hcl
# 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 负责把它变成现实。

hcl
# 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 是它创建的还是别人创建的"。

状态文件不能放在本地。多人协作时,必须存在远程后端——就像军令不能只保存在一个人的脑子里,必须存档在指挥部且上锁:

hcl
# 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 让你复用代码。

hcl
# 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
}

使用模块:

hcl
# 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 记录

每个环境使用独立的状态文件,互不干扰。部署流程:

bash
# 部署到 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 管理方案:

bash
# 方案 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"]
}
bash
# 方案 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
}
bash
# 方案 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 设计。想象你在指挥部挂了一张标准化的阵地部署图——每个哨位的位置、每个兵种的配置都画好了。部队必须严格按照这张图布阵,有人擅自移动岗哨位置,自动修正回来:

yaml
# 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 配置好后,它的工作原理是:

  1. 每 3 分钟(可配)拉取 Git 仓库
  2. 对比 Git 中的 YAML 与 K8s 集群中的实际状态
  3. 如果发现有差异(Drift),标记为"OutOfSync"
  4. 如果开启了自动同步,自动把集群状态修正为 Git 中的状态
Argo CD UI 状态标识:

Synced(已同步)  → 集群状态与 Git 一致
OutOfSync(漂移) → 有人手动改了集群资源,或者 Git 有新的提交

Flux

Flux 是另一个 CNCF 毕业的 GitOps 工具,定位类似但架构略有不同。Flux 走的是 "Source 拉取配置,Kustomization 应用配置" 两步流水线:

yaml
# 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-frontline

Flux 的核心概念是 "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/CDGitOps
触发方式Push 触发Reconciliation 循环(持续对比)
状态保证部署时保证一次持续保证(检测 drift)
回滚执行 rollback pipelineGit revert → 自动修复
权限控制CI 系统 tokenGit 仓库权限 + 代码评审
谁操作目标环境CD agentGitOps operator(运行在集群内)
适用场景应用构建、测试、打包基础设施、配置、部署到 K8s

最佳实践是用 CI/CD 做构建和测试,用 GitOps 做部署:

CI/CD (Jenkins / GitHub Actions):
  代码 → 构建 → 单元测试 → 集成测试 → 生成 Docker 镜像

                                  推送到镜像仓库

                                  更新 K8s YAML 中的镜像标签
                                  提交到 Git(main 分支)

GitOps (Argo CD):
                              Git 变更触发 reconciliation
                                  拉取新 YAML
                                  更新集群中的 Deployment
                                  等待 rollout 完成
                                  报告同步状态

常见陷阱

  1. 把敏感信息直接写在 IaC 配置里。 Terraform 配置文件可能被提交到 Git、在 CI 日志中暴露、或通过状态文件泄露。始终使用密钥管理服务引用秘密。

  2. 多人直接对同一个状态文件执行 terraform apply。 没有状态锁,两个人同时 apply 导致状态损坏。使用远程后端(S3 + DynamoDB 锁)或 Terraform Cloud。

  3. 手动修改云资源后不同步 IaC 配置。 你在 AWS 控制台点了一下改 VPC 的路由表,Terraform 不知道。下次 terraform apply 会把这个改动覆盖——或者更糟,plan 报错说状态不一致。原则:任何基础设施变更都通过 IaC,不做游离于代码之外的"手术"。

  4. GitOps 开启了自动同步却没有 Prune。 不从 Git 中删除资源,而是手工在集群里删除。下一次 Argo CD reconcile 时又会重建。为了删除资源,应该从 Git 中删除它的 YAML 并确认 prune: true

  5. 分支策略混乱。 所有环境都用一个分支部署,无法控制发布的顺序。常见策略:dev 分支对应开发环境,staging 分支预发布验证,main 分支对应生产环境。每个环境有独立的 Kustomization overlay。


通关挑战

  • 热身:用 Terraform 在本地创建一个模拟环境:定义一个 local_file 资源写入一个配置文件,然后运行 planapply
hcl
# 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 到一个测试仓库)。
bash
# 安装 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

常见卡点

  1. Terraform state 文件冲突了怎么办? 如果本地 state 和远程 state 不一致,先 terraform refresh 同步实际资源状态。如果锁定,手动释放锁(terraform force-unlock <lock_id>)但要确保没有其他人在同时 apply。

  2. Argo CD 和 Flux 选哪个? Argo CD 的 UI 更成熟,多人协作场景更友好。Flux 更轻量,和 Kustomize 集成度更高。两者都是 CNCF 毕业项目,选择取决于团队的运维习惯。

  3. GitOps 适合所有资源吗? 不是。Secrets(除非用 Sealed Secrets / External Secrets Operator)、数据库 schema 变更、需要人为确认的破坏性操作——这些不适合完全走自动同步。

  4. 分支策略怎么跟环境对应? 最小化方案: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 的生产实践。

Built with VitePress | Software Systems Atlas