Deploy Kong Mesh using Terraform and Konnect

TL;DR

Use the konnect and konnect-beta Terraform providers to create a global control plane, a mesh, and a MeshTrafficPermission policy in Konnect, then deploy a Kubernetes zone control plane that authenticates with a system account token.

Prerequisites

If you don’t have a Konnect account, you can get started quickly with our onboarding wizard.

  1. The following Konnect items are required to complete this tutorial:
    • Personal access token (PAT): Create a new personal access token by opening the Konnect PAT page and selecting Generate Token.
  2. Set the personal access token as an environment variable:

    export KONNECT_TOKEN='YOUR KONNECT TOKEN'

This how-to requires you to install Terraform.

Install k3d to run a local Kubernetes cluster (tested on k3d v5.8.1, k3s v1.31.4-k3s1).

This guide uses Terraform to provision a Konnect-managed global control plane, a mesh with mTLS, and a MeshTrafficPermission policy, and then deploys a Kubernetes zone control plane that connects to it. To learn how Kong Mesh works with Konnect, see Kong Mesh in Konnect.

Set up the Terraform variables

  1. Create and enter a working directory for this guide:

    mkdir -p ~/mesh-konnect && cd ~/mesh-konnect
  2. Create a variables.tf file:

    cat <<'EOF' > variables.tf
    variable "konnect_personal_access_token" {
        type    = string
    }
    
    variable "region" {
        type    = string
    }
    EOF
  3. Provide these variables at runtime, or set them as environment variables:

    export TF_VAR_konnect_personal_access_token=$KONNECT_TOKEN
    export TF_VAR_region="us"

Configure the providers

This guide uses the konnect and konnect-beta Terraform providers. The konnect provider is the general availability version, and konnect-beta is the beta version with the latest features. Features move from the beta provider to the GA provider once they are stable and fully tested.

Mesh resources are currently available only in the beta provider.

  1. Create a providers.tf file:

    cat <<'EOF' > providers.tf
    terraform {
      required_providers {
        konnect = {
          source = "kong/konnect"
        }
        konnect-beta = {
          source  = "kong/konnect-beta"
        }
      }
    }
    
    provider "konnect" {
        personal_access_token = var.konnect_personal_access_token
        server_url            = "https://${var.region}.api.konghq.com"
    }
    
    provider "konnect-beta" {
        personal_access_token = var.konnect_personal_access_token
        server_url            = "https://${var.region}.api.konghq.com"
    }
    EOF
  2. Download the providers:

    terraform init

    You should see the following message:

    Terraform has been successfully initialized!

Create a global control plane in Konnect

  1. Create a main.tf file that defines a global control plane in Konnect:

    cat <<'EOF' > main.tf
    resource "konnect_mesh_control_plane" "my_meshcontrolplane" {
      provider    = konnect-beta
      name        = "tf-cp"
      description = "A control plane created using terraform"
      labels = {
        "terraform" = "true"
      }
    }
    EOF
  2. Apply the changes to create the resource:

    terraform apply -auto-approve

    You should see:

    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:
       
      # konnect_mesh_control_plane.my_meshcontrolplane will be created
      + resource "konnect_mesh_control_plane" "my_meshcontrolplane" {
          + created_at  = (known after apply)
          + description = "A control plane created using terraform"
          + features    = (known after apply)
          + id          = (known after apply)
          + labels      = {
              + "terraform" = "true"
            }
          + name        = "tf-cp"
          + updated_at  = (known after apply)
        }
       
    Plan: 1 to add, 0 to change, 0 to destroy.
    konnect_mesh_control_plane.my_meshcontrolplane: Creating...
    konnect_mesh_control_plane.my_meshcontrolplane: Creation complete after 1s
       
    Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Create a mesh

Now that a control plane exists, create a mesh with mTLS enabled. The cp_id property is set to the ID of the control plane created in the previous step, and skip_creating_initial_policies is set to ["*"] to skip creating the default policies so that all resources in the mesh are tracked by Terraform.

  1. Create a mesh.tf file:

    cat <<'EOF' > mesh.tf
    resource "konnect_mesh" "my_mesh" {
      provider = konnect-beta
    
      name     = "my-mesh"
      type     = "Mesh"
      skip_creating_initial_policies = [ "*" ]
    
      mtls = {
        "backends" = [
          {
            "name" = "ca-1"
            "type" = "builtin"
          }
        ]
        "mode"           = "permissive"
        "enabledBackend" = "ca-1"
      }
    
      cp_id    = konnect_mesh_control_plane.my_meshcontrolplane.id
    }
    EOF
  2. Apply the changes to create the mesh:

    terraform apply -auto-approve

For the full schema of the mesh resource, see the konnect-beta provider documentation.

Add a traffic permission policy

Each Kong Mesh policy example includes a Terraform tab showing a Terraform representation of the policy. This step uses the allow-all example from the MeshTrafficPermission page.

Kong Mesh manages reserved kuma.io/* labels, such as kuma.io/mesh and kuma.io/origin, automatically. Don’t set them in your Terraform configuration: the mesh attribute already associates the policy with its mesh, and setting a reserved label causes a provider plan error.

  1. Create a traffic-permission.tf file:

    cat <<'EOF' > traffic-permission.tf
    resource "konnect_mesh_traffic_permission" "allow_all" {
     provider = konnect-beta
    
     type = "MeshTrafficPermission"
     name = "allow-all"
     spec = {
       from = [
         {
           target_ref = {
             kind = "Mesh"
           }
           default = {
             action = "Allow"
           }
         }
       ]
     }
    
     cp_id    = konnect_mesh_control_plane.my_meshcontrolplane.id
     mesh     = konnect_mesh.my_mesh.name
    }
    EOF
  2. Apply the changes to create the policy:

    terraform apply -auto-approve

Deploy a Kubernetes zone

You can deploy a Kubernetes zone to any Kubernetes cluster. This guide uses k3d to create a local cluster.

Create a cluster

  1. Create a new k3d cluster:

    k3d cluster create tfmink
  2. Store the tfmink cluster configuration in $KUBECONFIG:

    export KUBECONFIG=$(k3d kubeconfig write tfmink)
  3. Add a variable pointing to the kubeconfig file and a variable for the zone name to variables.tf:

    cat <<'EOF' >> variables.tf
    variable "k8s_cluster_config_path" {
      type        = string
      description = "The location where this cluster's kubeconfig will be saved to."
    }
    
    variable "zone_name" {
        type    = string
        default = "tfzone1"
    }
    EOF
  4. Set TF_VAR_k8s_cluster_config_path to your kubeconfig value:

    export TF_VAR_k8s_cluster_config_path=$KUBECONFIG

Configure the Kubernetes and Helm providers

  1. Update providers.tf to add the time, kubernetes, and helm providers:

    cat <<'EOF' > providers.tf
    terraform {
      required_providers {
        konnect = {
          source = "kong/konnect"
        }
        konnect-beta = {
          source  = "kong/konnect-beta"
        }
        time = {
          source  = "hashicorp/time"
        }
        kubernetes = {
          source  = "hashicorp/kubernetes"
        }
        helm = {
          source  = "hashicorp/helm"
        }
      }
    }
    
    provider "konnect" {
        personal_access_token = var.konnect_personal_access_token
        server_url            = "https://${var.region}.api.konghq.com"
    }
    
    provider "konnect-beta" {
        personal_access_token = var.konnect_personal_access_token
        server_url            = "https://${var.region}.api.konghq.com"
    }
    
    provider "helm" {
        kubernetes = {
            config_path = pathexpand(var.k8s_cluster_config_path)
        }
    }
    
    provider "kubernetes" {
        config_path = pathexpand(var.k8s_cluster_config_path)
    }
    EOF
  2. Download the new providers:

    terraform init -upgrade

Create a system account

  1. Create a system-account.tf file that defines a system account and a token to authenticate the zone:

    cat <<'EOF' > system-account.tf
    resource "konnect_system_account" "zone_system_account" {
      name            = "mesh_${konnect_mesh_control_plane.my_meshcontrolplane.id}_${var.zone_name}"
      description     = "Terraform generated system account for authentication zone ${var.zone_name} in ${konnect_mesh_control_plane.my_meshcontrolplane.id} control plane."
      konnect_managed = false
    }
    
    resource "konnect_system_account_role" "zone_system_account_role" {
      account_id       = konnect_system_account.zone_system_account.id
      entity_id        = konnect_mesh_control_plane.my_meshcontrolplane.id
      entity_region    = var.region
      entity_type_name = "Mesh Control Planes"
      role_name        = "Connector"
    }
    
    resource "time_offset" "one_year_from_now" {
      offset_years = 1
    }
    
    resource "konnect_system_account_access_token" "zone_system_account_token" {
      account_id = konnect_system_account.zone_system_account.id
      expires_at = time_offset.one_year_from_now.rfc3339
      name       = konnect_system_account.zone_system_account.name
    }
    EOF
  2. Store the token in Kubernetes by creating a k8s.tf file:

    cat <<'EOF' > k8s.tf
    resource "kubernetes_namespace" "kong_mesh_system" {
      metadata {
        name = "kong-mesh-system"
        labels = {
          "kuma.io/system-namespace" = "true"
        }
      }
    }
    
    resource "kubernetes_secret" "mesh_cp_token" {
      metadata {
        name = "cp-token"
        namespace = kubernetes_namespace.kong_mesh_system.metadata.0.name
      }
    
      data = {
        token = konnect_system_account_access_token.zone_system_account_token.token
      }
    
      type = "opaque"
    }
    EOF

Create the zone

  1. Create a values.tftpl file with templated values for the zone, address, and control plane ID:

    cat <<'EOF' > values.tftpl
    kuma:
      controlPlane:
        mode: zone
        zone: ${zone_name}
        kdsGlobalAddress: grpcs://${region}.mesh.sync.konghq.com:443
        konnect:
          cpId: ${cp_id}
        secrets:
          - Env: KMESH_MULTIZONE_ZONE_KDS_AUTH_CP_TOKEN_INLINE
            Secret: cp-token
            Key: token
      ingress:
        enabled: true
      egress:
        enabled: true
    EOF
  2. Create a zone.tf file that defines the zone:

    cat <<'EOF' > zone.tf
    resource "helm_release" "kong_mesh" {
      name       = "kong-mesh"
      repository = "https://kong.github.io/kong-mesh-charts"
      chart      = "kong-mesh"
    
      namespace = kubernetes_namespace.kong_mesh_system.metadata.0.name
      upgrade_install = true
    
      values = [templatefile("values.tftpl", {
        zone_name = var.zone_name,
        region    = var.region,
        cp_id     = konnect_mesh_control_plane.my_meshcontrolplane.id
      })]
    }
    EOF
  3. Apply the changes:

    terraform apply -auto-approve

    You should see 7 resources created. The helm_release can take some time to create:

    helm_release.kong_mesh: Creation complete after 54s [id=kong-mesh]

Validate

  1. Confirm that the MeshTrafficPermission you created earlier is now available in the zone:

    kubectl get meshtrafficpermissions.kuma.io -A

    You should see:

    NAMESPACE          NAME
    kong-mesh-system   allow-all-wd5xx76vc44b498c
  2. In the Konnect sidebar, click Service Mesh.
  3. Click tf-cp. You should see the zone and the mesh you created.

Cleanup

From the ~/mesh-konnect working directory, remove all the resources created by this guide:

terraform destroy

Return to the parent directory, then delete the working directory and the files created during this guide:

cd ../
rm -rf ~/mesh-konnect

FAQs

Certain properties, such as the mesh name or policy name, are used as identifiers. Changing them results in a new resource being created and all dependent resources being recreated.

For example, changing the mesh name to another-name:

resource "konnect_mesh" "my_mesh" {
  # ...
  name = "another-name"
  # ...
}

forces replacement of both the konnect_mesh and konnect_mesh_traffic_permission resources:

    # konnect_mesh.my_mesh must be replaced
-/+ resource "konnect_mesh" "my_mesh" {
      ~ name                           = "mesh1" -> "another-name" # forces replacement
        # (4 unchanged attributes hidden)
    }

  # konnect_mesh_traffic_permission.allow_all must be replaced
-/+ resource "konnect_mesh_traffic_permission" "allow_all" {
      ~ creation_time     = "2025-03-13T09:53:00.606442Z" -> (known after apply)
      ~ mesh              = "mesh1" -> "another-name" # forces replacement

Help us make these docs great!

Kong Developer docs are open source. If you find these useful and want to make them better, contribute today!