使用Terraform创建或修改ASM自定义资源

服务网格 ASM(Service Mesh)1.22.6.109版本开始支持使用Terraform Kubernetes ProviderASM中的自定义资源进行操作,或是对网格功能特性进行修改。本文将通过两个示例介绍如何使用Terraform创建或修改ASM自定义资源。

前提条件

准备工作

在开始演示流程前,建议您新建一个空目录作为Terraform项目目录,同时在目录中新建一个包含以下内容的provider.tf文件。

provider "kubernetes" {
  config_path = "~/.kube/config"
}

该配置文件指定了Terraform Kubernetes Provider所使用的kubeconfig。

本文完整的目录结构如下:

terraform-Project         # Terraform项目目录
├── asmmeshconfig.tf      # 场景二tf文件
├── virtualservice.tf     # 场景一tf文件
├── provider.tf           # provider文件
└── resources             # 用于存放资源文件,例如yaml、json等
    └── demo.yaml         # 场景一资源文件

provider.tf文件创建完成后,开始初始化Terraform项目。

terraform init

场景一:使用Terraform创建VirtualService资源

  1. resources目录下创建名称为demo.yamlVirtualService资源文件。

    apiVersion: networking.istio.io/v1
    kind: VirtualService
    metadata:
      name: my-productpage-rule
      namespace: istio-system
    spec:
      hosts:
      - productpage.prod.svc.cluster.local # ignores rule namespace
      http:
      - timeout: 5s
        route:
        - destination:
            host: productpage.prod.svc.cluster.local
  2. 创建virtualservice.tf。

    resource "kubernetes_manifest" "virtualservice_demo" {
      manifest = yamldecode(file("./resources/demo.yaml"))
    }
  3. 查看Terraform资源的变更情况。

    terraform plan

    预期输出:

    展开查看terraform plan输出

    Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
    following symbols:
      + create
    
    Terraform will perform the following actions:
    
      # kubernetes_manifest.virtualservice_demo will be created
      + resource "kubernetes_manifest" "virtualservice_demo" {
          + manifest = {
              + apiVersion = "networking.istio.io/v1"
              + kind       = "VirtualService"
              + metadata   = {
                  + name      = "my-productpage-rule"
                  + namespace = "istio-system"
                }
              + spec       = {
                  + hosts = [
                      + "productpage.prod.svc.cluster.local",
                    ]
                  + http  = [
                      + {
                          + route   = [
                              + {
                                  + destination = {
                                      + host = "productpage.prod.svc.cluster.local"
                                    }
                                },
                            ]
                          + timeout = "5s"
                        },
                    ]
                }
            }
          + object   = {
              + apiVersion = "networking.istio.io/v1"
              + kind       = "VirtualService"
              + metadata   = {
                  + annotations                = (known after apply)
                  + creationTimestamp          = (known after apply)
                  + deletionGracePeriodSeconds = (known after apply)
                  + deletionTimestamp          = (known after apply)
                  + finalizers                 = (known after apply)
                  + generateName               = (known after apply)
                  + generation                 = (known after apply)
                  + labels                     = (known after apply)
                  + managedFields              = (known after apply)
                  + name                       = "my-productpage-rule"
                  + namespace                  = "istio-system"
                  + ownerReferences            = (known after apply)
                  + resourceVersion            = (known after apply)
                  + selfLink                   = (known after apply)
                  + uid                        = (known after apply)
                }
              + spec       = {
                  + exportTo = (known after apply)
                  + gateways = (known after apply)
                  + hosts    = [
                      + "productpage.prod.svc.cluster.local",
                    ]
                  + http     = [
                      + {
                          + corsPolicy               = {
                              + allowCredentials    = (known after apply)
                              + allowHeaders        = (known after apply)
                              + allowMethods        = (known after apply)
                              + allowOrigin         = (known after apply)
                              + allowOrigins        = (known after apply)
                              + exposeHeaders       = (known after apply)
                              + maxAge              = (known after apply)
                              + unmatchedPreflights = (known after apply)
                            }
                          + delegate                 = {
                              + name      = (known after apply)
                              + namespace = (known after apply)
                            }
                          + directResponse           = {
                              + body   = {
                                  + bytes  = (known after apply)
                                  + string = (known after apply)
                                }
                              + status = (known after apply)
                            }
                          + fault                    = {
                              + abort = {
                                  + grpcStatus = (known after apply)
                                  + http2Error = (known after apply)
                                  + httpStatus = (known after apply)
                                  + percentage = {
                                      + value = (known after apply)
                                    }
                                }
                              + delay = {
                                  + exponentialDelay = (known after apply)
                                  + fixedDelay       = (known after apply)
                                  + percent          = (known after apply)
                                  + percentage       = {
                                      + value = (known after apply)
                                    }
                                }
                            }
                          + headerToDynamicSubsetKey = (known after apply)
                          + headers                  = {
                              + request  = {
                                  + add    = (known after apply)
                                  + remove = (known after apply)
                                  + set    = (known after apply)
                                }
                              + response = {
                                  + add    = (known after apply)
                                  + remove = (known after apply)
                                  + set    = (known after apply)
                                }
                            }
                          + match                    = (known after apply)
                          + mirror                   = {
                              + host   = (known after apply)
                              + port   = {
                                  + number = (known after apply)
                                }
                              + subset = (known after apply)
                            }
                          + mirrorPercent            = (known after apply)
                          + mirrorPercentage         = {
                              + value = (known after apply)
                            }
                          + mirror_percent           = (known after apply)
                          + mirrors                  = (known after apply)
                          + name                     = (known after apply)
                          + redirect                 = {
                              + authority    = (known after apply)
                              + derivePort   = (known after apply)
                              + port         = (known after apply)
                              + redirectCode = (known after apply)
                              + scheme       = (known after apply)
                              + uri          = (known after apply)
                            }
                          + retries                  = {
                              + attempts              = (known after apply)
                              + perTryTimeout         = (known after apply)
                              + retryOn               = (known after apply)
                              + retryRemoteLocalities = (known after apply)
                            }
                          + rewrite                  = {
                              + authority       = (known after apply)
                              + uri             = (known after apply)
                              + uriRegexRewrite = {
                                  + match   = (known after apply)
                                  + rewrite = (known after apply)
                                }
                            }
                          + route                    = [
                              + {
                                  + destination = {
                                      + host   = "productpage.prod.svc.cluster.local"
                                      + port   = {
                                          + number = (known after apply)
                                        }
                                      + subset = (known after apply)
                                    }
                                  + fallback    = {
                                      + case    = (known after apply)
                                      + target  = {
                                          + host   = (known after apply)
                                          + port   = {
                                              + number = (known after apply)
                                            }
                                          + subset = (known after apply)
                                        }
                                      + targets = (known after apply)
                                    }
                                  + headers     = {
                                      + request  = {
                                          + add    = (known after apply)
                                          + remove = (known after apply)
                                          + set    = (known after apply)
                                        }
                                      + response = {
                                          + add    = (known after apply)
                                          + remove = (known after apply)
                                          + set    = (known after apply)
                                        }
                                    }
                                  + weight      = (known after apply)
                                },
                            ]
                          + timeout                  = "5s"
                        },
                    ]
                  + tcp      = (known after apply)
                  + tls      = (known after apply)
                }
            }
        }
    
    Plan: 1 to add, 0 to change, 0 to destroy.
  4. 创建并验证资源。

    1. 创建资源。

      terraform apply --auto-approve
    2. 验证资源。

      kubectl get VirtualService -n istio-system

      预期输出:

      NAME                  GATEWAYS   HOSTS                                    AGE
      my-productpage-rule              ["productpage.prod.svc.cluster.local"]   77s
  5. (可选)清理资源。

    terraform destroy -target=kubernetes_manifest.virtualservice_demo --auto-approve

场景二:修改ASMMeshConfig资源控制网格功能特性

本场景演示如何修改已有的ASMMeshConfig资源,关闭服务网格默认的为Pod重写健康检查的功能。

  1. 导入ASMMeshConfig资源。

    使用tfk8s工具

    1. 生成asmmeshconfig.tf。

      kubectl get asmmeshconfig default -o yaml | tfk8s --strip -o asmmeshconfig.tf

      上述命令会直接生成asmmeshconfig.tf文件,预期内容如下。

      resource "kubernetes_manifest" "asmmeshconfig_default" {
        manifest = {
          "apiVersion" = "istio.alibabacloud.com/v1beta1"
          "kind" = "ASMMeshConfig"
          "metadata" = {
            "name" = "default"
          }
          "spec" = {
            "accessLogConfiguration" = {}
            "ambientConfiguration" = {
              "enabled" = false
              "redirectMode" = ""
              "waypoint" = {}
              "ztunnel" = {}
            }
            "cniConfiguration" = {
              "enabled" = true
              "excludeNamespaces" = "istio-system,kube-system"
              "repair" = {}
            }
            "enableGatewayAPI" = true
            "gatewayAPIInferenceExtension" = {}
            "ingressControllerMode" = "OFF"
            "ingressSelector" = "ingressgateway1"
            "ingressService" = "istio-ingressgateway1"
            "sidecarInjectorWebhookConfiguration" = {}
            "smcEnabled" = false
          }
        }
      }
    2. ASMMeshConfig资源导入到terraform state。

      terraform import kubernetes_manifest.asmmeshconfig_default "apiVersion=istio.alibabacloud.com/v1beta1,name=default,kind=ASMMeshConfig"

      预期输出:

      Import successful!
      
      The resources that were imported are shown above. These resources are now in
      your Terraform state and will henceforth be managed by Terraform.
      
      ╷
      │ Warning: Apply needed after 'import'
      │ 
      │ Please run apply after a successful import to realign the resource state to the configuration in Terraform.
      ╵
    3. 执行terraform apply命令,以对齐TerraformKubernetes资源状态。

      terraform apply --auto-approve

      部分预期输出:

      ...
      # kubernetes_manifest.asmmeshconfig_default will be updated in-place
      ~ resource "kubernetes_manifest" "asmmeshconfig_default" {
            + manifest = {
                + apiVersion = "istio.alibabacloud.com/v1beta1"
                + kind       = "ASMMeshConfig"
                + metadata   = {
                    + annotations                = null
                    + creationTimestamp          = null
      ...
      

    使用terraform命令

    1. ASMMeshConfig资源导入state。

      terraform import kubernetes_manifest.asmmeshconfig_default "apiVersion=istio.alibabacloud.com/v1beta1,name=default,kind=ASMMeshConfig"

      预期输出:

      Import successful!
      
      The resources that were imported are shown above. These resources are now in
      your Terraform state and will henceforth be managed by Terraform.
      
      ╷
      │ Warning: Apply needed after 'import'
      │ 
      │ Please run apply after a successful import to realign the resource state to the configuration in Terraform.
      ╵
    2. 生成asmmeshconfig.tf。

      terraform show -no-color > asmmeshconfig.tf

      预期文件内容:

      resource "kubernetes_manifest" "asmmeshconfig_default" {
          object = {
              apiVersion = "istio.alibabacloud.com/v1beta1"
              kind       = "ASMMeshConfig"
              metadata   = {
                  annotations                = null
                  creationTimestamp          = null
                  deletionGracePeriodSeconds = null
                  deletionTimestamp          = null
                  finalizers                 = null
                  generateName               = null
                  generation                 = null
                  labels                     = null
                  managedFields              = null
                  name                       = "default"
                  namespace                  = null
                  ownerReferences            = null
                  resourceVersion            = null
                  selfLink                   = null
                  uid                        = null
              }
              spec       = {
                  accessLogConfiguration         = {}
                  adaptiveSchedulerConfiguration = {}
                  ambientConfiguration           = {
                      redirectMode = ""
                      waypoint     = {}
                      ztunnel      = {}
                  }
                  cniConfiguration               = {
                      enabled = true
                      repair  = {}
                  }
                  localityLbSetting              = {
                      enabled = true
                  }
              }
          }
      }
    3. asmmeshconfig.tf文件中的object改为manifest,移除值为null的参数。以下为调整后的asmmeshconfig.tf内容。

      resource "kubernetes_manifest" "asmmeshconfig_default" {
          manifest = {
              apiVersion = "istio.alibabacloud.com/v1beta1"
              kind       = "ASMMeshConfig"
              metadata   = {
                  name                       = "default"
              }
              spec       = {
                  accessLogConfiguration         = {}
                  adaptiveSchedulerConfiguration = {}
                  ambientConfiguration           = {
                      redirectMode = ""
                      waypoint     = {}
                      ztunnel      = {}
                  }
                  cniConfiguration               = {
                      enabled = true
                      repair  = {}
                  }
                  localityLbSetting              = {
                      enabled = true
                  }
              }
          }
      }
    4. 执行terraform apply命令,以对齐TerraformKubernetes资源状态。

      terraform apply --auto-approve

      部分预期输出:

      ...
      # kubernetes_manifest.asmmeshconfig_default will be updated in-place
      ~ resource "kubernetes_manifest" "asmmeshconfig_default" {
            + manifest = {
                + apiVersion = "istio.alibabacloud.com/v1beta1"
                + kind       = "ASMMeshConfig"
                + metadata   = {
                    + annotations                = null
                    + creationTimestamp          = null
      ...
      

    上述步骤中,terraform import的参数说明如下:

    参数

    说明

    kubernetes_manifest

    Terraform resource类型,与asmmeshconfig.tf中的resource类型相对应。

    asmmeshconfig_default

    Terraform resource名称,与asmmeshconfig.tf中的resource名称相对应。

    apiVersion

    Kubernetes CRD中注册的API版本。您可以通过执行 kubectl get ${资源类型} ${资源名称}查看资源的apiVersion。

    kind

    Kubernetes CRD中注册的资源类型。您可以通过执行 kubectl get ${资源类型} ${资源名称}查看资源的kind。

    name

    需要导入的ASMMeshConfig资源名称,在这里为default。由于ASMMeshConfig资源是一个集群级别的Kubernetes资源,所以在此并未指定命名空间。对于命名空间级别的资源,您可以通过namespace=${资源命名空间}指定。

  2. 编辑asmmeshconfig.tf,增加spec.sidecarInjectorWebhookConfiguration.rewriteAppHTTPProbe字段的值为false,以关闭服务网格默认的为Pod重写健康检查的功能。

    resource "kubernetes_manifest" "asmmeshconfig_default" {
      manifest = {
        "apiVersion" = "istio.alibabacloud.com/v1beta1"
        "kind" = "ASMMeshConfig"
        "metadata" = {
          "name" = "default"
        }
        "spec" = {
          "accessLogConfiguration" = {}
          "ambientConfiguration" = {
            "enabled" = false 
            "redirectMode" = ""
            "waypoint" = {}
            "ztunnel" = {}
          }
          "cniConfiguration" = {
            "enabled" = true
            "excludeNamespaces" = "istio-system,kube-system"
            "repair" = {}
          }
          "enableGatewayAPI" = true
          "gatewayAPIInferenceExtension" = {}
          "ingressControllerMode" = "OFF"
          "ingressSelector" = "ingressgateway1"
          "ingressService" = "istio-ingressgateway1"
          "sidecarInjectorWebhookConfiguration" = {
            "rewriteAppHTTPProbe" = false
          }
          "smcEnabled" = false
        }
      }
    }
  3. 查看变更情况。

    terraform plan

    预期输出:

    kubernetes_manifest.asmmeshconfig_default: Refreshing state...
    
    Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
    following symbols:
      ~ update in-place
    
    Terraform will perform the following actions:
    
      # kubernetes_manifest.asmmeshconfig_default will be updated in-place
      ~ resource "kubernetes_manifest" "asmmeshconfig_default" {
          ~ manifest = {
              ~ spec       = {
                  ~ sidecarInjectorWebhookConfiguration = {
                      + rewriteAppHTTPProbe = false
                    }
                    # (9 unchanged attributes hidden)
                }
                # (3 unchanged attributes hidden)
            }
          ~ object   = {
              ~ spec       = {
                  ~ sidecarInjectorWebhookConfiguration = {
                      + rewriteAppHTTPProbe = false
                    }
                    # (9 unchanged attributes hidden)
                }
                # (3 unchanged attributes hidden)
            }
        }
    Plan: 0 to add, 1 to change, 0 to destroy.
    说明

    如果manifestobject中的变更内容不一致,说明通过其他方式对Kubernetes资源进行了修改,Kubernetes资源的实际状态与Terraform记录的状态不同。您可以执行terraform refresh更新Terraform的状态。

  4. 应用变更。

    terraform apply --auto-approve

    预期输出:

    kubernetes_manifest.asmmeshconfig_default: Modifying...
    kubernetes_manifest.asmmeshconfig_default: Modifications complete after 1s
    ...
    Apply complete! Resources: 0 added, 1 changed, 0 destroyed.