「Kubernetes Provider 编排」

讲解 Terraform Kubernetes Provider:集群认证与 Provider 配置、kubectl/helm 资源编排、ingress 与证书管理、operator 与 CRD 的声明式落地,以及与 k8s 控制器配合的边界与坑。

1. Kubernetes Provider 与认证

一句话总结: Kubernetes Provider 通过 kubeconfig 或 Token 完成认证,Provider 版本选型要在 kubernetes/helm/kubectl 三个之间按需组合。

Terraform 管理 K8s 资源有三类 Provider:kubernetes(原生 API 资源)、helm(Helm Chart 发布)、kubectl(通用 YAML 应用)。它们各司其职,组合使用覆盖「声明原生资源 + 发布 Chart + 应用清单」。

terraform {
  required_providers {
    kubernetes = {
      source  = "hashicorp/kubernetes"
      version = "~> 2.0"
    }
    helm = {
      source  = "hashicorp/helm"
      version = "~> 2.0"
    }
  }
}

1.1 认证方式

Provider 读取 kubeconfig 与上下文,或显式指定 host/token/cluster_ca_certificate。云上集群通常用「云 Provider 获取凭据 + 传给 k8s Provider」。

provider "kubernetes" {
  host                   = data.aws_eks_cluster.cluster.endpoint
  cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority[0].data)
  token                  = data.aws_eks_cluster_auth.cluster.token
}

provider "helm" {
  kubernetes {
    host                   = data.aws_eks_cluster.cluster.endpoint
    cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority[0].data)
    token                  = data.aws_eks_cluster_auth.cluster.token
  }
}

一句话:认证链通常是「云 Provider 建集群 → data 读取凭据 → 两个 K8s Provider 复用同一组 endpoint/token」。

1.2 三个 Provider 的边界

Provider管什么用在哪
kubernetesdeployment/service/configmap/secret 等原生资源内联声明小资源
helmrelease 的安装/升级/回滚发布第三方 Chart
kubectl任意 YAML 清单、自定义资源的直接应用现有 YAML 复用

2. kubectl / helm 资源编排

一句话总结: helm Provider 发布 Chart 并管理 release 生命周期,kubectl Provider 把任意 YAML 当资源管理,两者把「手敲 kubectl apply」变成声明式。

2.1 helm release

resource "helm_release" "nginx_ingress" {
  name             = "nginx-ingress"
  repository       = "https://kubernetes.github.io/ingress-nginx"
  chart            = "ingress-nginx"
  namespace        = "ingress-nginx"
  create_namespace = true
  version          = "4.10.0"

  set {
    name  = "controller.replicaCount"
    value = "2"
  }
  set {
    name  = "controller.service.type"
    value = "LoadBalancer"
  }
}

set 覆盖 values,复杂覆盖建议用 values 字段读取独立 values 文件,便于评审与复用。

resource "helm_release" "prometheus" {
  name       = "prometheus"
  repository = "https://prometheus-community.github.io/helm-charts"
  chart      = "prometheus"
  values     = [file("${path.module}/values/prometheus-values.yaml")]
}

2.2 kubectl 应用清单

kubectl Provider 通过 apply 语义把 YAML 作为资源管理,删除时自动清理。

resource "kubectl_manifest" "example" {
  yaml = <<-YAML
    apiVersion: v1
    kind: Namespace
    metadata:
      name: app-team
  YAML
}

resource "kubectl_manifest" "deploy" {
  yaml = file("${path.module}/manifests/deployment.yaml")
  depends_on = [helm_release.nginx_ingress]
}

2.3 优先级建议

能用 kubernetes Provider 原生声明的,不绕道 kubectl;能用 helm 发布 Chart 的,不手工 apply。原生 Provider 有 schema 校验,kubectl 是「直接透传 YAML」,越直接越容易出隐藏错误。

3. Ingress 与证书

一句话总结: Ingress 用 annotation 绑定 ingress-nginx 控制器,TLS 证书用 cert-manager 自动签发,Terraform 只需声明 Ingress 与 Certificate 资源。

3.1 Ingress 资源

resource "kubernetes_ingress_v1" "web" {
  metadata {
    name      = "web-ingress"
    namespace = "app-team"
    annotations = {
      "nginx.ingress.kubernetes.io/rewrite-target" = "/"
    }
  }
  spec {
    ingress_class_name = "nginx"
    rule {
      host = "app.example.com"
      http {
        path {
          path      = "/"
          path_type = "Prefix"
          backend {
            service {
              name = "web"
              port {
                number = 80
              }
            }
          }
        }
      }
    }
  }
}

3.2 证书:cert-manager 声明式签发

证书不在 Terraform 里生成,而是声明 Certificate 自定义资源,由 cert-manager 控制器自动签发并写回 Secret。

resource "kubectl_manifest" "cert" {
  yaml = <<-YAML
    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: app-tls
      namespace: app-team
    spec:
      secretName: app-tls-secret
      issuerRef:
        name: letsencrypt-prod
        kind: ClusterIssuer
      dnsNames:
        - app.example.com
  YAML
}

随后 Ingress 引用该 Secret 提供 TLS:

spec {
  tls {
    hosts       = ["app.example.com"]
    secret_name = "app-tls-secret"
  }
}

一句话:Terraform 只声明「我要一个证书」,签发的执行者是集群里的 cert-manager,这正是「声明式 + 控制器收敛」的典型。

4. Operator 与 CRD

一句话总结: Operator 把复杂应用封装成 CRD,Terraform 通过 kubectl/kubernetes Provider 声明 CRD 实例,由 Operator 控制器收敛到期望状态。

4.1 为什么需要 CRD

数据库、消息队列等有状态组件,原生 Kubernetes 资源表达不了「备份、主从、扩容」语义。Operator 引入自定义资源(CRD),把运维逻辑下沉到控制器。

resource "kubectl_manifest" "postgres_cluster" {
  yaml = <<-YAML
    apiVersion: postgres-operator.crunchydata.com/v1beta1
    kind: PostgresCluster
    metadata:
      name: hippo
      namespace: app-team
    spec:
      postgresVersion: 15
      instances:
        - name: instance1
          replicas: 3
      backups:
        pgbackrest:
          repos:
            - name: repo1
              volume:
                volumeClaimSpec:
                  accessModes: ["ReadWriteOnce"]
                  resources:
                    requests:
                      storage: 1Gi
  YAML
}

4.2 谁先装 CRD

CRD 由 Operator 安装(通常 helm release 同时带上 CRD),Terraform 声明实例时要用 depends_on 保证 Operator 已就绪。

resource "helm_release" "pg_operator" {
  name       = "postgres-operator"
  repository = "https://crunchydata.github.io/charts"
  chart      = "postgres-operator"
}

resource "kubectl_manifest" "postgres_cluster" {
  yaml = file("${path.module}/manifests/postgres.yaml")
  depends_on = [helm_release.pg_operator]
}

一句话:CRD 实例由 Terraform 声明,收敛由 Operator 负责,Terraform 只关心「清单应用成功」,不关心控制器内部怎么达成。

5. 与 k8s 控制器配合

一句话总结: Terraform 负责「应用清单」,K8s 控制器负责「收敛现状」,两者之间用 depends_on、annotations 与现状读取协作,避免 Terraform 重复造轮子。

5.1 现状读取与等待

用 kubernetes_* 的 data source 读取控制器已生成的现状(如 Service 的 LoadBalancer IP),把集群内产生的外部值带回 Terraform 供其他资源使用。

data "kubernetes_service" "nginx" {
  metadata {
    name      = "ingress-nginx-controller"
    namespace = "ingress-nginx"
  }
}

output "lb_address" {
  value = data.kubernetes_service.nginx.status[0].load_balancer[0].ingress[0].hostname
}

5.2 时间与依赖的边界

场景谁负责
清单应用成功Terraform 的 apply 完成
Pod 真正 RunningDeployment 控制器收敛
证书签发完成cert-manager 控制器收敛
LoadBalancer 分配云负载均衡异步分配

Terraform 的 apply 返回不等于集群「真正就绪」。需要等待时,可用 kubernetes_manifest 的 wait 字段或外部的就绪探测脚本。

5.3 避免与控制器抢资源

Helm 注释管理(app.kubernetes.io/managed-by: Helm)标注的资源被 Terraform 直接改会冲突。要么全交给 Helm,要么全交给 Terraform,不要两边同时改同一资源。跨工具交接用 import 或移除注释后再接管。

6. 权限与安全

一句话总结: Terraform 用的 K8s 凭据应是最小权限的 ServiceAccount,敏感值进 Secret 资源时走敏感字段标记与加密,避免明文入库。

6.1 最小权限的 ServiceAccount

Terraform 与 CI 使用的 K8s 凭据遵循最小权限:只能创建它需要的命名空间与资源类型。

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: terraform-ops
  namespace: app-team
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "create", "update", "patch", "delete"]
  - apiGroups: [""]
    resources: ["services", "configmaps", "secrets"]
    verbs: ["get", "list", "create", "update", "patch", "delete"]

6.2 敏感值处理

Secret 内容用 sensitive = true 变量传入,不要以字面量出现在 tfvars 中。

resource "kubernetes_secret_v1" "app" {
  metadata {
    name      = "app-secret"
    namespace = "app-team"
  }
  data = {
    "password" = var.app_password
  }
}

variable "app_password" {
  type      = string
  sensitive = true
}

一句话:K8s 侧的权限与敏感值治理,与 AWS 侧的最小权限 IAM 是同一套安全哲学,只是落在 Role/RoleBinding 与 Secret 上。

7. 常见坑

一句话总结: K8s Provider 高发坑集中在 namespace 不存在、Helm 与原生资源冲突、证书签发时序、CRD 顺序,前三个几乎每天都会遇到。

坑现象正确做法
namespace 缺失资源创建报「namespace not found」先声明 namespace,或 create_namespace
双工具改一资源每次 plan 都出 diff统一管理方,跨工具用 import 交接
证书未就绪Ingress 引用不存在的 Secret用 depends_on 或 wait 就绪探测
CRD 未安装manifest 应用失败CRD 随 Operator 先装,depends_on
Provider 版本漂移行为不一致required_providers 锁版本

7.1 一个稳妥的编排顺序

1. kubernetes_namespace 声明 namespace
2. helm_release 安装基础组件(ingress、cert-manager、operator)
3. kubectl_manifest 应用 CRD 实例(Certificate、PostgresCluster 等)
4. kubernetes_* 声明应用原生资源(deployment/service/ingress)
5. data 读取控制器回写的现状,输出给其他系统

8. 总结

Kubernetes Provider 把「kubectl 手工操作」变成可审查的声明式配置:

环节要点
Providerkubernetes/helm/kubectl 三件套按需组合
认证云 Provider 建集群,data 取凭据复用
编排helm 管 Chart,kubectl 管 YAML,原生管资源
Ingressannotation 绑定控制器,证书交给 cert-manager
CRDTerraform 声明实例,Operator 控制器收敛
协作apply 完成 ≠ 集群就绪,必要时 wait
安全ServiceAccount 最小权限,敏感值不进仓库
顺序namespace → helm → CRD → 原生资源

一句话收尾:Terraform 在 K8s 世界的角色是「声明期望清单」,把收敛的执行权交给集群内的控制器,二者各司其职,才能把声明式 IaC 的优势延伸到云原生。下一篇「数据源与远程数据」将回到 Terraform 本身,讲 data source 与远程 state 的读取机制。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「terraform」更多文章

  1. 「漂移检测与收敛」
  2. 「资源重构与迁移」
  3. 「数据源与远程数据读取」