如何在 terraform 中运行 kubectl apply 命令

Posted

技术标签:

【中文标题】如何在 terraform 中运行 kubectl apply 命令【英文标题】:How To Run kubectl apply commands in terraform 【发布时间】:2019-06-03 07:25:40 【问题描述】:

我开发了一个 terraform 脚本来在 GKE 上创建一个 k8 集群。

成功创建集群后,我有一组要应用于 k8 集群的 yaml 文件。

如何在我的 terraform 脚本中调用以下命令?

kubectl create <.yaml>

【问题讨论】:

【参考方案1】:

您可以使用 Terraform kubectl 第三方提供商。按照此处的安装说明进行操作:Kubectl Terraform Provider

然后简单地定义一个 kubectl_manifest 指向您的 YAML 文件,例如:

# Get your cluster-info
data "google_container_cluster" "my_cluster" 
  name     = "my-cluster"
  location = "us-east1-a"


# Same parameters as kubernetes provider
provider "kubectl" 
  load_config_file       = false
  host                   = "https://$data.google_container_cluster.my_cluster.endpoint"
  token                  = "$data.google_container_cluster.my_cluster.access_token"
  cluster_ca_certificate = "$base64decode(data.google_container_cluster.my_cluster.master_auth.0.cluster_ca_certificate)"


resource "kubectl_manifest" "my_service" 
    yaml_body = file("$path.module/my_service.yaml")

这种方法的一大优势是所有内容都是动态获取的,不依赖于任何本地配置文件(如果您在 CI/CD 服务器中运行 Terraform 或管理多集群环境,这非常重要)。

多对象清单文件

kubectl 提供程序还提供有助于轻松处理具有多个对象的文件的数据源。来自文档kubectl_filename_list:

data "kubectl_filename_list" "manifests" 
    pattern = "./manifests/*.yaml"


resource "kubectl_manifest" "test" 
    count = length(data.kubectl_filename_list.manifests.matches)
    yaml_body = file(element(data.kubectl_filename_list.manifests.matches, count.index))

加分项:您可以模板化您的yaml 文件。我在多资源自动扩缩器 yaml 文件中插入集群名称,如下所示:

resource "kubectl_manifest" "autoscaler" 
  yaml_body = templatefile("$path.module/autoscaler.yaml", cluster_name = var.cluster_name )

【讨论】:

我想在 terraform 云中使用它,但 kubectl 提供程序未发布到注册表,因此它失败了。我该如何克服这个问题? 当前版本使用token = data.google_client_config.default.access_token - 见terraform.io/docs/providers/google/d/client_config.html @Shawlz 只需添加提供者定义:``` terraform required_version = ">= 0.13.3" required_providers kubectl = source = "gavinbunney/kubectl" version = ">= 1.7.0" ``` 这似乎只适用于单对象清单。被---分割时如果有多个对象事件将不起作用。 @ChristopherMarkieta,提供程序正确处理多个对象,但您必须先使用 kubectl_filename_list 数据拆分它们。请参见上面的示例。我还更新了链接。【参考方案2】:

有几种方法可以实现您想做的事情。

您可以使用 Terraform 资源 template_filenull_resource。 请注意,我总是使用 trigger 来运行 kubectl 命令,因为您总是修改模板(您可能希望将 create 替换为 apply)。

data "template_file" "your_template" 
  template = "$file("$path.module/templates/<.yaml>")"


resource "null_resource" "your_deployment" 
  triggers = 
    manifest_sha1 = "$sha1("$data.template_file.your_template.rendered")"
  

  provisioner "local-exec" 
    command = "kubectl create -f -<<EOF\n$data.template_file.your_template.rendered\nEOF"
  

但也许最好的方法是使用Kubernetes provider。 有两种配置方式:

默认情况下,您的清单将部署在您当前的上下文中 (kubectl config current-context) 第二种方式是静态定义TLS证书凭证:
provider "kubernetes" 
  host = "https://104.196.242.174"

  client_certificate     = "$file("~/.kube/client-cert.pem")"
  client_key             = "$file("~/.kube/client-key.pem")"
  cluster_ca_certificate = "$file("~/.kube/cluster-ca-cert.pem")"

完成后,您可以非常轻松地创建自己的部署。对于一个基本的 pod,它会很简单:

resource "kubernetes_pod" "hello_world" 
  metadata 
    name = "hello-world"
  

  spec 
    container 
      image = "my_account/hello-world:1.0.0"
      name  = "hello-world"
    

    image_pull_secrets  
      name = "docker-hub"
    
  

【讨论】:

不适用于 null_resource 和本地 exec。 exit status 1. Output: &lt;&lt; was unexpected at this time. Christopher,我需要查看您的代码才能告诉您问题所在。 我的代码就是你的代码。唯一的区别是我填写了命名的 yaml 文件,并且该文件肯定存在并且可以工作 data "template_file" "your_template" template = "$file("$path.module/templates/ingress.yaml")" 我可以在 terraform 和本地运行 kubectl apply -f filepath/templates/ingress.yml 没有问题。问题在于 EOF 和渲染文件业务。 复制你的 ingress.yaml 文件的内容,打开一个终端,写 kubectl apply -f - 【参考方案3】:

如果托管 yaml 文件的远程 url,并且 yaml 文件包含多个配置/对象,则可以这样做。

resource "null_resource" "controller_rancher_installation" 

  provisioner "local-exec" 
    command = <<EOT
      echo "Downloading rancher config"
      curl -L https://some-url.yaml -o rancherconfig.yaml
    EOT
  


data "kubectl_path_documents" "rancher_manifests" 
    pattern = "./rancherconfig.yaml"
    depends_on = [null_resource.controller_rancher_installation]


resource "kubectl_manifest" "spot_cluster_controller" 
    count     = length(data.kubectl_path_documents.spot_controller_manifests.documents)
    yaml_body = element(data.kubectl_path_documents.spot_controller_manifests.documents, count.index)


这个想法是先下载它,然后再应用它。这是基于观察:

    pattern = "./rancherconfig.yaml" 不支持远程 url,只支持 本地文件。 "kubectl_manifest" 默认只应用第一个 yaml 文件中的配置/对象。

【讨论】:

如果您在 eks 模块中包含 kubectl,这将不起作用【参考方案4】:

您可以使用 terraform local-exec 来执行此操作。

   resource "aws_instance" "web" 
     # ...
     provisioner "local-exec" 
      command = "echo $aws_instance.web.private_ip >> private_ips.txt"
     
   

参考:https://www.terraform.io/docs/provisioners/local-exec.html

【讨论】:

如果您实际上包含问题包含的命令而不是文档中示例的复制粘贴,这将是一个更好的示例。此外,如果您使用terraform fmt 格式化它。【参考方案5】:

这里的答案很棒。一个建议是,随着您的需求从初始清单演变而来,您可能希望考虑从清单(或者可能已经存在)创建一个 helm 图表,并使用 terraform helm 提供程序来为您的环境设置值。

https://tech.paulcz.net/blog/getting-started-with-helm/

您会注意到 helm provider 的一个优势是易于通过 terraform 环境覆盖和管理值的更改,而不是将其嵌入到清单中。

https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release

【讨论】:

【参考方案6】:

当创建多个 Terraform 资源时,使用命名资源实例通常比使用列表更好 (count)。如果源文件更新,Kubernetes 资源的顺序发生变化,可能会导致 Terraform 仅仅因为索引的变化而删除/创建资源。此代码通过连接 kindmetadata.name 字段来创建一个键:

data "kubectl_file_documents" "myk8s" 
  content = file("./myk8s.yaml")


resource "kubectl_manifest" "myk8s" 
  # Create a map of  "kind--name" => raw_yaml 
  for_each  = 
    for value in [
      for v in data.kubectl_file_documents.myk8s.documents : [yamldecode(v), v]
    ] : "$value.0["kind"]--$value.0["metadata"]["name"]" => value.1
  
  yaml_body = each.value

将来您可能希望改用 Hashicorp 的官方 kubernetes_manifest resource(从 2.5.0 开始——处于测试阶段,有问题):

resource "kubernetes_manifest" "default" 
  for_each = 
    for value in [
      for yaml in split(
        "\n---\n",
        "\n$replace(file("./myk8s.yaml"), "/(?m)^---[[:blank:]]*(#.*)?$/", "---")\n"
      ) :
      yamldecode(yaml)
      if trimspace(replace(yaml, "/(?m)(^[[:blank:]]*(#.*)?$)+/", "")) != ""
    ] : "$value["kind"]--$value["metadata"]["name"]" => value
  
  manifest = each.value

【讨论】:

【参考方案7】:

最好的方法是使用 Terraform 的 Kubernetes 提供者

【讨论】:

如果您提供一个简单、有效的示例,而不是仅仅链接到 Kubernetes 提供者文档,这个答案会更好。 使用当前版本的 kubernetes 提供程序无法应用 .yaml 文件。见github.com/terraform-providers/terraform-provider-kubernetes/…

以上是关于如何在 terraform 中运行 kubectl apply 命令的主要内容,如果未能解决你的问题,请参考以下文章

在具有私有 GKE 集群的 Terraform 上使用 Kubernetes 提供程序

terraform-kubernetes-provider 如何从文件中创建秘密?

如何在容器内运行 kubectl 命令?

如何将 kubectl 命令用于在 Rancher 中使用 docker 运行的 kubernetes?

如何在脚本开始时在 terraform 中运行 null_resource

如何使用 CMD 在 docker 容器中运行 terraform apply --auto-approve