2. Terraform基础回顾

在正式用 Terraform 交付 Kubernetes 基础设施之前,需要先把 Terraform 的四个核心概念重新对齐:Provider 负责与目标平台对话,Resource 描述期望的基础设施对象,State 记录 Terraform 眼中的真实世界,Plan/Apply 则是把“期望”逐步收敛到“现实”的工作流。理解这四者的分工,才能写出可维护、可复现的 Kubernetes 交付配置。

Provider:Terraform 与平台的适配层

Provider 是 Terraform 与外部 API 之间的插件。声明一个 Provider,本质上是告诉 Terraform:我要管理哪个平台、用哪个版本、从哪里获取凭据。以 Kubernetes 为例,最常用的 Provider 是 hashicorp/kubernetes,它通过 kubeconfig 或显式的主机、证书、Token 连接集群 API Server。

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    kubernetes = {
      source  = "hashicorp/kubernetes"
      version = "~> 2.30"
    }
  }
}

provider "kubernetes" {
  config_path    = "~/.kube/config"
  config_context = "dev-cluster"
}

这里有两个关键点:第一,required_providers 锁定 Provider 来源与版本范围,避免不同机器拉到不同版本导致行为漂移;第二,config_context 明确指定使用 kubeconfig 中的哪个上下文,防止误操作到生产集群。Provider 的配置属于“连接层”,它本身不创建资源,但所有 Resource 都依赖它完成认证与请求发送。

Resource:声明期望状态的最小单元

Resource 是 Terraform 管理的基础设施对象。在 Kubernetes 场景中,一个 Namespace、一个 Deployment、一个 Service、一个 ConfigMap 都可以是一个 Resource。Terraform 不关心你“怎么创建”,只关心你“声明了什么”。

resource "kubernetes_namespace" "app" {
  metadata {
    name = "demo-app"
    labels = {
      "managed-by" = "terraform"
    }
  }
}

resource "kubernetes_deployment" "web" {
  metadata {
    name      = "web"
    namespace = kubernetes_namespace.app.metadata[0].name
  }
  spec {
    replicas = 2
    selector {
      match_labels = { app = "web" }
    }
    template {
      metadata {
        labels = { app = "web" }
      }
      spec {
        container {
          name  = "web"
          image = "nginx:1.25-alpine"
          port { container_port = 80 }
        }
      }
    }
  }
}

注意 namespace = kubernetes_namespace.app.metadata[0].name 这一行:它体现了 Resource 之间的隐式依赖。Terraform 会据此构建依赖图,先创建 Namespace,再创建 Deployment。这种引用关系比手写 depends_on 更自然,也更不容易出错。

State:Terraform 的“记忆”

State 是 Terraform 记录“我管理了哪些资源、它们的 ID 是什么、当前属性是什么”的本地或远端文件。默认情况下,执行 terraform apply 后会在当前目录生成 terraform.tfstate。它承担三件事:映射配置中的资源地址到真实对象、缓存属性供后续 Plan 比较、保存依赖关系。

对于 Kubernetes 交付,State 的位置非常关键。如果多人协作,本地 State 会导致冲突和覆盖。推荐做法是使用远端后端,例如 S3 + DynamoDB 锁,或 Terraform Cloud。一个最小可用的后端配置如下:

terraform {
  backend "s3" {
    bucket         = "tf-state-demo"
    key            = "k8s/dev/terraform.tfstate"
    region         = "ap-southeast-1"
    dynamodb_table = "tf-lock"
    encrypt        = true
  }
}

需要强调的是:State 中可能包含敏感信息(如 Secret 的明文、Token),因此后端必须开启加密,且不要将 terraform.tfstate 提交到 Git。

Plan/Apply:从期望到现实的收敛过程

Terraform 的工作流可以概括为四步:init 初始化 Provider 与后端,plan 计算差异,apply 执行变更,destroy 回收资源。其中 plan 是最值得认真阅读的一步,它会输出 + create、~ update、- destroy 三类符号,分别表示新增、原地修改和删除。

一个典型的首次执行过程如下:

terraform init
terraform plan -out=tfplan
terraform apply tfplan

使用 -out 保存 Plan 文件再 Apply,可以保证“看到的计划”和“执行的计划”完全一致,避免在 Plan 与 Apply 之间配置被改动。执行完成后,可以用 kubectl get ns demo-app 和 kubectl get deploy -n demo-app 验证资源是否真实存在,再用 terraform state list 查看 State 中登记的资源地址。

Terraform 交付 Kubernetes 的最小可用配置 Provider 连接 → Resource 声明 → State 记录 → Plan/Apply 收敛 Provider hashicorp/kubernetes config_path / context 认证与 API 适配 Resource kubernetes_namespace kubernetes_deployment 隐式依赖:namespace 引用 State 资源地址 ↔ 真实 ID 属性缓存 / 依赖图 远端后端 + 锁 Plan / Apply 工作流 terraform init terraform plan terraform apply kubectl 验证 状态漂移 → 重新 plan,收敛到期望状态 最小可用配置 = 锁定 Provider 版本 + 声明 Namespace/Deployment + 远端 State 后端 + 保存 Plan 文件后 Apply 验证依据:kubectl get ns/deploy 能看到对象,terraform state list 能列出资源地址

为 Kubernetes 交付建立最小可用配置

所谓“最小可用”,不是功能最少,而是结构完整、可执行、可验证。一个合格的起点至少应包含:版本约束、Provider 配置、一个 Namespace、一个工作负载、一个 Service,以及远端 State 后端。下面给出一个可直接参考的目录结构:

k8s-delivery/
├── versions.tf      # required_version + required_providers + backend
├── providers.tf     # kubernetes provider 配置
├── main.tf          # namespace / deployment / service
├── variables.tf     # 集群上下文、镜像、副本数
└── outputs.tf       # namespace 名称、service 名称

把变量抽出来,可以让同一份配置在 dev/staging/prod 之间复用:

variable "kube_context" {
  type        = string
  description = "kubeconfig 中的上下文名称"
  default     = "dev-cluster"
}

variable "app_replicas" {
  type        = number
  default     = 2
  validation {
    condition     = var.app_replicas >= 1 && var.app_replicas <= 10
    error_message = "副本数必须在 1 到 10 之间。"
  }
}

执行时通过 -var 或 terraform.tfvars 覆盖默认值。例如生产环境使用 terraform apply -var="kube_context=prod-cluster" -var="app_replicas=4"。这样既保留了默认可用性,又允许按环境调整。

建议在首次交付时先执行 terraform plan -out=tfplan,人工确认 Plan 中只有预期的 + create,没有意外的 - destroy,再执行 terraform apply tfplan。这是防止误删资源最有效的一道闸门。

常见误区与判断依据

第一,把 State 放在本地并多人共用,导致后执行者覆盖前者。判断依据是 terraform plan 频繁出现“资源已存在但 State 中没有”的报错。第二,Provider 不锁版本,某次 init 后行为突变。判断依据是 terraform version 显示的 Provider 版本与预期不符。第三,Resource 之间用 depends_on 硬编码依赖,而不是用属性引用。判断依据是 Plan 中资源创建顺序不稳定,或出现循环依赖报错。

最后,验证一次交付是否成功,可以按以下顺序检查:terraform state list 是否列出了全部资源地址;kubectl get all -n demo-app 是否能看到 Deployment 和 Service;kubectl rollout status deploy/web -n demo-app 是否返回成功。三者一致,才说明 Terraform 的期望状态、State 记录和集群现实三者对齐。

记住这条主线:Provider 决定“连得上”,Resource 决定“要什么”,State 决定“记得住”,Plan/Apply 决定“改得对”。四者缺一,Kubernetes 交付就无法稳定复现。