使用 Terraform 配置 Kubernetes 资源的更好方法
在定义和维护基础设施即代码方面,Terraform 非常强大。结合声明式 API(如云提供商 API),它可以确定、预览和应用对编码基础设施的更改。
因此,团队通常使用 Terraform 来定义其 Kubernetes 集群的基础设施。而作为构建平台的平台,Kubernetes 通常需要一些额外的服务才能部署工作负载。想想入口控制器或日志记录和监控代理等。但是,尽管 Kubernetes 有自己的声明式 API,并且从与代码存储库相同的基础设施维护集群的基础设施和服务有明显的好处,但 Terraform 远不是供应 Kubernetes 资源的首选。
使用我维护的开源 Terraform 框架Kubestack,我的使命是为使用 Terraform 和 Kubernetes 的团队提供最佳的开发人员体验。从集群基础设施到集群服务,所有平台组件的统一配置是我不懈追求上述开发人员体验的关键。
正因为如此,使用 Terraform 提供 Kubernetes 资源的两种常用方法从来没有真正吸引过我。
一方面,有 Kubernetes 提供者。虽然它将 Kubernetes 资源集成到 Terraform 中,但在 HCL 中维护 Kubernetes 资源需要付出很多努力。特别是对于您从上游消费的 Kubernetes YAML。另一方面,有 Helm 提供者和 Kubectl 提供者。这两个使用原生 YAML 而不是 HCL,但没有将 Kubernetes 资源集成到 Terraform 状态,因此也没有集成到生命周期中。
我相信基于 Kustomization 提供程序的模块是更好的选择,因为它具有三个明显的好处:
-
与 Kustomize 一样,上游 YAML 保持不变,这意味着上游更新需要最少的维护工作。
-
通过在 HCL 中定义 Kustomize 覆盖,所有 Kubernetes 资源都可以使用 Terraform 中的值完全自定义。
-
每个 Kubernetes 资源都在 Terraform 状态下单独跟踪,因此差异和计划显示了对实际 Kubernetes 资源的更改。
为了使这些好处不那么抽象,让我们将我的 Nginx 入口模块与使用 Helm 提供程序来配置 Nginx 入口的模块进行比较。
这两个示例的 Terraform 配置在这个存储库中可用。我们先来看看 Helm 模块。
基于 Helm 的模块
该模块的使用很简单。首先,配置 Kubernetes 和 Helm 提供程序。
provider "kubernetes" {
config_path = "~/.kube/config"
}
provider "helm" {
kubernetes {
config_path = "~/.kube/config"
}
}
进入全屏模式 退出全屏模式
然后定义一个 kubernetes_namespace 并调用 release/helm 模块。
resource "kubernetes_namespace" "nginx_ingress" {
metadata {
name = "ingress-nginx"
}
}
module "nginx_ingress" {
source = "terraform-module/release/helm"
version = "2.7.0"
namespace = kubernetes_namespace.nginx_ingress.metadata.0.name
repository = "https://kubernetes.github.io/ingress-nginx"
app = {
name = "ingress-nginx"
version = "4.1.0"
chart = "ingress-nginx"
force_update = true
wait = false
recreate_pods = false
deploy = 1
}
set = [
{
name = "replicaCount"
value = 2
}
]
}
进入全屏模式 退出全屏模式
如果您现在为此配置运行 terraform 计划,您会看到要创建的资源。
Terraform will perform the following actions:
# kubernetes_namespace.nginx_ingress will be created
+ resource "kubernetes_namespace" "nginx_ingress" {
+ id = (known after apply)
+ metadata {
+ generation = (known after apply)
+ name = "ingress-nginx"
+ resource_version = (known after apply)
+ uid = (known after apply)
}
}
# module.nginx_ingress.helm_release.this[0] will be created
+ resource "helm_release" "this" {
+ atomic = false
+ chart = "ingress-nginx"
+ cleanup_on_fail = false
+ create_namespace = false
+ dependency_update = false
+ disable_crd_hooks = false
+ disable_openapi_validation = false
+ disable_webhooks = false
+ force_update = true
+ id = (known after apply)
+ lint = true
+ manifest = (known after apply)
+ max_history = 0
+ metadata = (known after apply)
+ name = "ingress-nginx"
+ namespace = "ingress-nginx"
+ recreate_pods = false
+ render_subchart_notes = true
+ replace = false
+ repository = "https://kubernetes.github.io/ingress-nginx"
+ reset_values = false
+ reuse_values = false
+ skip_crds = false
+ status = "deployed"
+ timeout = 300
+ values = []
+ verify = false
+ version = "4.1.0"
+ wait = false
+ wait_for_jobs = false
+ set {
+ name = "replicaCount"
+ value = "2"
}
}
Plan: 2 to add, 0 to change, 0 to destroy.
进入全屏模式 退出全屏模式
这是 Helm 如何集成到 Terraform 工作流程中的关键问题。该计划并未告诉您将为 Nginx 入口控制器创建哪些 Kubernetes 资源。 Kubernetes 资源也不会在 Terraform 状态下被跟踪,如应用输出所示。
kubernetes_namespace.nginx_ingress: Creating...
kubernetes_namespace.nginx_ingress: Creation complete after 0s [id=ingress-nginx]
module.nginx_ingress.helm_release.this[0]: Creating...
module.nginx_ingress.helm_release.this[0]: Creation complete after 3s [id=ingress-nginx]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
进入全屏模式 退出全屏模式
同样,如果计划进行更改,也无法确定 Kubernetes 资源的更改内容。
因此,如果您增加 Helm 图表的replicaCount值,则地形计划将仅显示对helm_release资源的更改。
set = [
{
name = "replicaCount"
value = 3
}
]
进入全屏模式 退出全屏模式
Kubernetes 资源会发生什么变化?更重要的是,它是简单的就地更新,还是需要销毁并重新创建?看计划,你无从知晓。
Terraform will perform the following actions:
# module.nginx_ingress.helm_release.this[0] will be updated in-place
~ resource "helm_release" "this" {
id = "ingress-nginx"
name = "ingress-nginx"
# (27 unchanged attributes hidden)
- set {
- name = "replicaCount" -> null
- value = "2" -> null
}
+ set {
+ name = "replicaCount"
+ value = "3"
}
}
Plan: 0 to add, 1 to change, 0 to destroy.
进入全屏模式 退出全屏模式
基于 Kustomize 的模块
现在,让我们看看基于 Kustomize 的模块的相同步骤。用法类似。首先需要 kbst/kustomization 提供程序并对其进行配置。
terraform {
required_providers {
kustomization = {
source = "kbst/kustomization"
}
}
}
provider "kustomization" {
kubeconfig_path = "~/.kube/config"
}
进入全屏模式 退出全屏模式
然后调用 nginx/kustomization 模块。
module "nginx_ingress" {
source = "kbst.xyz/catalog/nginx/kustomization"
version = "1.1.3-kbst.1"
configuration_base_key = "default"
configuration = {
default = {
replicas = [{
name = "ingress-nginx-controller"
count = 2
}]
}
}
}
进入全屏模式 退出全屏模式
不过,与基于 Helm 的模块不同,当您现在运行 terraform plan 时,您将单独看到每个 Kubernetes 资源及其实际配置。为了让这篇博文更受欢迎,我只展示了命名空间的详细信息。
Terraform will perform the following actions:
# module.nginx_ingress.kustomization_resource.p0["_/Namespace/_/ingress-nginx"] will be created
+ resource "kustomization_resource" "p0" {
+ id = (known after apply)
+ manifest = jsonencode(
{
+ apiVersion = "v1"
+ kind = "Namespace"
+ metadata = {
+ annotations = {
+ "app.kubernetes.io/version" = "v0.46.0"
+ "catalog.kubestack.com/heritage" = "kubestack.com/catalog/nginx"
+ "catalog.kubestack.com/variant" = "base"
}
+ labels = {
+ "app.kubernetes.io/component" = "ingress-controller"
+ "app.kubernetes.io/instance" = "ingress-nginx"
+ "app.kubernetes.io/managed-by" = "kubestack"
+ "app.kubernetes.io/name" = "nginx"
}
+ name = "ingress-nginx"
}
}
)
}
# module.nginx_ingress.kustomization_resource.p1["_/ConfigMap/ingress-nginx/ingress-nginx-controller"] will be created
# module.nginx_ingress.kustomization_resource.p1["_/Service/ingress-nginx/ingress-nginx-controller"] will be created
# module.nginx_ingress.kustomization_resource.p1["_/Service/ingress-nginx/ingress-nginx-controller-admission"] will be created
# module.nginx_ingress.kustomization_resource.p1["_/ServiceAccount/ingress-nginx/ingress-nginx"] will be created
# module.nginx_ingress.kustomization_resource.p1["_/ServiceAccount/ingress-nginx/ingress-nginx-admission"] will be created
# module.nginx_ingress.kustomization_resource.p1["apps/Deployment/ingress-nginx/ingress-nginx-controller"] will be created
# module.nginx_ingress.kustomization_resource.p1["batch/Job/ingress-nginx/ingress-nginx-admission-create"] will be created
# module.nginx_ingress.kustomization_resource.p1["batch/Job/ingress-nginx/ingress-nginx-admission-patch"] will be created
# module.nginx_ingress.kustomization_resource.p1["networking.k8s.io/IngressClass/_/nginx"] will be created
# module.nginx_ingress.kustomization_resource.p1["rbac.authorization.k8s.io/ClusterRole/_/ingress-nginx"] will be created
# module.nginx_ingress.kustomization_resource.p1["rbac.authorization.k8s.io/ClusterRole/_/ingress-nginx-admission"] will be created
# module.nginx_ingress.kustomization_resource.p1["rbac.authorization.k8s.io/ClusterRoleBinding/_/ingress-nginx"] will be created
# module.nginx_ingress.kustomization_resource.p1["rbac.authorization.k8s.io/ClusterRoleBinding/_/ingress-nginx-admission"] will be created
# module.nginx_ingress.kustomization_resource.p1["rbac.authorization.k8s.io/Role/ingress-nginx/ingress-nginx"] will be created
# module.nginx_ingress.kustomization_resource.p1["rbac.authorization.k8s.io/Role/ingress-nginx/ingress-nginx-admission"] will be created
# module.nginx_ingress.kustomization_resource.p1["rbac.authorization.k8s.io/RoleBinding/ingress-nginx/ingress-nginx"] will be created
# module.nginx_ingress.kustomization_resource.p1["rbac.authorization.k8s.io/RoleBinding/ingress-nginx/ingress-nginx-admission"] will be created
# module.nginx_ingress.kustomization_resource.p2["admissionregistration.k8s.io/ValidatingWebhookConfiguration/_/ingress-nginx-admission"] will be created
Plan: 19 to add, 0 to change, 0 to destroy.
进入全屏模式 退出全屏模式
再次应用,拥有所有单独的 Kubernetes 资源。并且由于模块使用显式depends_on首先处理命名空间和 CRD,最后处理 webhook,因此资源以正确的顺序可靠地应用。
module.nginx_ingress.kustomization_resource.p0["_/Namespace/_/ingress-nginx"]: Creating...
module.nginx_ingress.kustomization_resource.p0["_/Namespace/_/ingress-nginx"]: Creation complete after 0s [id=369e8643-ad33-4eb4-95dc-f506cef4a198]
module.nginx_ingress.kustomization_resource.p1["rbac.authorization.k8s.io/RoleBinding/ingress-nginx/ingress-nginx"]: Creating...
module.nginx_ingress.kustomization_resource.p1["batch/Job/ingress-nginx/ingress-nginx-admission-create"]: Creating...
...
module.nginx_ingress.kustomization_resource.p1["batch/Job/ingress-nginx/ingress-nginx-admission-patch"]: Creation complete after 1s [id=58346878-70bd-42f2-af61-2730e3435ca7]
module.nginx_ingress.kustomization_resource.p1["_/ServiceAccount/ingress-nginx/ingress-nginx"]: Creation complete after 0s [id=f009bbb7-7d2e-4f28-a826-ce133c91cc15]
module.nginx_ingress.kustomization_resource.p2["admissionregistration.k8s.io/ValidatingWebhookConfiguration/_/ingress-nginx-admission"]: Creating...
module.nginx_ingress.kustomization_resource.p2["admissionregistration.k8s.io/ValidatingWebhookConfiguration/_/ingress-nginx-admission"]: Creation complete after 1s [id=3185b09f-1f67-4079-b44f-de01bff44bd2]
Apply complete! Resources: 19 added, 0 changed, 0 destroyed.
进入全屏模式 退出全屏模式
当然,这也意味着如果你像这样增加副本数......
replicas = [{
name = "ingress-nginx-controller"
count = 3
}]
进入全屏模式 退出全屏模式
... terraform 计划显示了哪些 Kubernetes 资源将发生变化以及差异是什么。
Terraform will perform the following actions:
# module.nginx_ingress.kustomization_resource.p1["apps/Deployment/ingress-nginx/ingress-nginx-controller"] will be updated in-place
~ resource "kustomization_resource" "p1" {
id = "81e8ff18-6c6c-440d-bd8b-bf5f0d016953"
~ manifest = jsonencode(
~ {
~ spec = {
~ replicas = 2 -> 3
# (4 unchanged elements hidden)
}
# (3 unchanged elements hidden)
}
)
}
Plan: 0 to add, 1 to change, 0 to destroy.
进入全屏模式 退出全屏模式
甚至更重要的是,Kustomization 提供程序也会正确显示资源是否可以使用就地更新进行更改。或者,例如,如果由于不可变字段发生更改而需要销毁并重新创建。
这是两件事的结果:
-
如您所见,每个 Kubernetes 资源都在 Terraform 状态下单独处理,并且
-
Kustomization 提供者使用 Kubernetes 的服务器端 dry-runs 来确定每个资源的 diff。
根据试运行的结果,提供者指示 Terraform 创建就地或销毁并重新创建计划。
因此,作为此类更改的一个示例,假设您需要更改spec.selector.matchLabels。由于matchLabels是一个不可变字段,您将看到一个说明必须替换 Deployment 资源的计划。您将在计划摘要中看到 1 个要添加和 1 个要销毁。
Terraform will perform the following actions:
# module.nginx_ingress.kustomization_resource.p1["apps/Deployment/ingress-nginx/ingress-nginx-controller"] must be replaced
-/+ resource "kustomization_resource" "p1" {
~ id = "81e8ff18-6c6c-440d-bd8b-bf5f0d016953" -> (known after apply)
~ manifest = jsonencode(
~ {
~ metadata = {
~ labels = {
+ example-selector = "example"
# (6 unchanged elements hidden)
}
name = "ingress-nginx-controller"
# (2 unchanged elements hidden)
}
~ spec = {
~ replicas = 2 -> 3
~ selector = {
~ matchLabels = {
+ example-selector = "example"
# (4 unchanged elements hidden)
}
}
~ template = {
~ metadata = {
~ labels = {
+ example-selector = "example"
# (4 unchanged elements hidden)
}
# (1 unchanged element hidden)
}
# (1 unchanged element hidden)
}
# (2 unchanged elements hidden)
}
# (2 unchanged elements hidden)
} # forces replacement
)
}
Plan: 1 to add, 0 to change, 1 to destroy.
进入全屏模式 退出全屏模式
自己试试
如果您想自己试验差异,您可以在 GitHub 上找到源代码进行比较。
如果您想自己尝试 Kustomize 模块,您可以使用目录中捆绑上游 YAML 的模块之一,例如Prometheus operator、Cert-Manager、Sealed secrets或Tekton例如。
但这不仅适用于上游服务。还有一个模块可用于以与目录模块完全相同的方式配置任何 Kubernetes YAML - 称为自定义清单模块。
参与
目前,目录中提供的服务数量仍然有限。
如果您想参与其中,您还可以在 GitHub](https://github.com/kbst/catalog)上找到[目录源。
照片由Vladislav Babienko 拍摄onUnsplash
更多推荐

所有评论(0)