Лучший способ предоставления ресурсов Kubernetes с помощью Terraform

Terraform обладает огромными возможностями, когда речь идет об определении и поддержке инфраструктуры в виде кода. В сочетании с декларативным API, например, API облачного провайдера, он может определять, предварительно просматривать и применять изменения к кодифицированной инфраструктуре.

Поэтому часто команды используют Terraform для определения инфраструктуры своих кластеров Kubernetes. И как платформа для создания платформ, Kubernetes обычно требует ряд дополнительных сервисов перед развертыванием рабочих нагрузок. Подумайте о контроллерах входа, агентах регистрации и мониторинга и так далее. Но, несмотря на декларативный API Kubernetes и очевидные преимущества поддержки инфраструктуры и сервисов кластера из той же инфраструктуры, что и репозиторий кода, Terraform – далеко не первый выбор для предоставления ресурсов Kubernetes.

С Kubestack, фреймворком Terraform с открытым исходным кодом, который я поддерживаю, я выполняю миссию по обеспечению наилучшего опыта разработчиков для команд, работающих с Terraform и Kubernetes. И унифицированное предоставление всех компонентов платформы, от инфраструктуры кластера до кластерных сервисов, – это то, что я считаю критически важным в моем неустанном стремлении к обеспечению такого опыта для разработчиков.

Поэтому два распространенных подхода к обеспечению ресурсов Kubernetes с помощью Terraform никогда меня не привлекали.

С одной стороны, есть провайдер Kubernetes. И хотя он интегрирует ресурсы Kubernetes в Terraform, поддержка ресурсов Kubernetes в HCL требует больших усилий. Особенно для Kubernetes YAML, который вы потребляете из upstream. С другой стороны, есть провайдер Helm и провайдер Kubectl. Эти два используют родной YAML вместо HCL, но не интегрируют ресурсы Kubernetes в состояние Terraform и, как следствие, жизненный цикл.

Я считаю, что мои модули на основе провайдера Kustomize являются лучшей альтернативой из-за трех очевидных преимуществ:

  1. Как и в случае с Kustomize, YAML восходящего потока остается нетронутым, что означает, что обновления восходящего потока требуют минимальных усилий по обслуживанию.
  2. Благодаря определению оверлея Kustomize в HCL, все ресурсы Kubernetes полностью настраиваются с помощью значений из Terraform.
  3. Каждый ресурс Kubernetes отслеживается индивидуально в состоянии Terraform, поэтому дифы и планы показывают изменения в фактических ресурсах Kubernetes.

Чтобы сделать эти преимущества менее абстрактными, давайте сравним мой модуль Nginx ingress с модулем, использующим провайдер Helm для обеспечения Nginx ingress.

Конфигурация 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. План не говорит вам, какие ресурсы Kubernetes будут созданы для ингресс-контроллера Nginx. И ресурсы Kubernetes также не отслеживаются в состоянии Terraform, как показано в выводе apply.

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.

Так, если вы увеличите значение replicaCount графика Helm, план терраформирования просто покажет изменение ресурса 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 в первую очередь, а webhooks – в последнюю, ресурсы надежно применяются в правильном порядке.

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
}]
Войти в полноэкранный режим Выход из полноэкранного режима

…план терраформирования показывает, какие ресурсы 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 также правильно покажет, можно ли изменить ресурс с помощью обновления на месте. Или если требуется уничтожение и воссоздание, например, из-за изменения неизменяемого поля.

Это результат двух вещей:

  1. Как вы только что видели, каждый ресурс Kubernetes обрабатывается индивидуально в Terraform state, и
  2. что провайдер Kustomization использует сухие прогоны Kubernetes на стороне сервера для определения diff каждого ресурса.

Основываясь на результатах этого сухого прогона, провайдер дает Terraform указание создать план in-place или destroy-and-recreate.

Итак, в качестве примера такого изменения представьте, что вам нужно изменить 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 самостоятельно, вы можете использовать один из модулей из каталога, которые связывают upstream YAML, например, оператор Prometheus, Cert-Manager, Sealed secrets или Tekton.

Но это работает не только для сервисов восходящего потока. Существует также модуль, который можно использовать для обеспечения любого YAML Kubernetes точно таким же образом, как и модули каталога – он называется модулем пользовательского манифеста.

Принять участие

В настоящее время количество сервисов, доступных из каталога, все еще ограничено.

Если вы хотите принять участие, вы также можете найти исходники каталога на GitHub.

Фото Владислава Бабиенко на Unsplash

Оцените статью
Procodings.ru
Добавить комментарий