Configure GCP GKE Hub Feature Memberships

The gcp:gkehub/featureMembership:FeatureMembership resource, part of the Pulumi GCP provider, configures GKEHub features for specific cluster memberships. This guide focuses on three capabilities: Config Management with Git and OCI sources, Service Mesh automatic management, and Policy Controller enforcement and tuning.

Feature memberships bind GKE clusters to GKEHub features. You must create the cluster, register it as a membership, and enable the feature before configuring the feature membership. The examples are intentionally small. Combine them with your own cluster infrastructure and feature definitions.

Sync cluster configuration from a Git repository

Teams managing Kubernetes configuration as code store manifests in Git repositories. Config Management syncs these manifests to clusters automatically.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const cluster = new gcp.container.Cluster("cluster", {
    name: "my-cluster",
    location: "us-central1-a",
    initialNodeCount: 1,
});
const membership = new gcp.gkehub.Membership("membership", {
    membershipId: "my-membership",
    endpoint: {
        gkeCluster: {
            resourceLink: pulumi.interpolate`//container.googleapis.com/${cluster.id}`,
        },
    },
});
const feature = new gcp.gkehub.Feature("feature", {
    name: "configmanagement",
    location: "global",
    labels: {
        foo: "bar",
    },
});
const featureMember = new gcp.gkehub.FeatureMembership("feature_member", {
    location: "global",
    feature: feature.name,
    membership: membership.membershipId,
    configmanagement: {
        version: "1.19.0",
        configSync: {
            enabled: true,
            git: {
                syncRepo: "https://github.com/hashicorp/terraform",
            },
        },
    },
});
import pulumi
import pulumi_gcp as gcp

cluster = gcp.container.Cluster("cluster",
    name="my-cluster",
    location="us-central1-a",
    initial_node_count=1)
membership = gcp.gkehub.Membership("membership",
    membership_id="my-membership",
    endpoint={
        "gke_cluster": {
            "resource_link": cluster.id.apply(lambda id: f"//container.googleapis.com/{id}"),
        },
    })
feature = gcp.gkehub.Feature("feature",
    name="configmanagement",
    location="global",
    labels={
        "foo": "bar",
    })
feature_member = gcp.gkehub.FeatureMembership("feature_member",
    location="global",
    feature=feature.name,
    membership=membership.membership_id,
    configmanagement={
        "version": "1.19.0",
        "config_sync": {
            "enabled": True,
            "git": {
                "sync_repo": "https://github.com/hashicorp/terraform",
            },
        },
    })
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/gkehub"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		cluster, err := container.NewCluster(ctx, "cluster", &container.ClusterArgs{
			Name:             pulumi.String("my-cluster"),
			Location:         pulumi.String("us-central1-a"),
			InitialNodeCount: pulumi.Int(1),
		})
		if err != nil {
			return err
		}
		membership, err := gkehub.NewMembership(ctx, "membership", &gkehub.MembershipArgs{
			MembershipId: pulumi.String("my-membership"),
			Endpoint: &gkehub.MembershipEndpointArgs{
				GkeCluster: &gkehub.MembershipEndpointGkeClusterArgs{
					ResourceLink: cluster.ID().ApplyT(func(id string) (string, error) {
						return fmt.Sprintf("//container.googleapis.com/%v", id), nil
					}).(pulumi.StringOutput),
				},
			},
		})
		if err != nil {
			return err
		}
		feature, err := gkehub.NewFeature(ctx, "feature", &gkehub.FeatureArgs{
			Name:     pulumi.String("configmanagement"),
			Location: pulumi.String("global"),
			Labels: pulumi.StringMap{
				"foo": pulumi.String("bar"),
			},
		})
		if err != nil {
			return err
		}
		_, err = gkehub.NewFeatureMembership(ctx, "feature_member", &gkehub.FeatureMembershipArgs{
			Location:   pulumi.String("global"),
			Feature:    feature.Name,
			Membership: membership.MembershipId,
			Configmanagement: &gkehub.FeatureMembershipConfigmanagementArgs{
				Version: pulumi.String("1.19.0"),
				ConfigSync: &gkehub.FeatureMembershipConfigmanagementConfigSyncArgs{
					Enabled: pulumi.Bool(true),
					Git: &gkehub.FeatureMembershipConfigmanagementConfigSyncGitArgs{
						SyncRepo: pulumi.String("https://github.com/hashicorp/terraform"),
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var cluster = new Gcp.Container.Cluster("cluster", new()
    {
        Name = "my-cluster",
        Location = "us-central1-a",
        InitialNodeCount = 1,
    });

    var membership = new Gcp.GkeHub.Membership("membership", new()
    {
        MembershipId = "my-membership",
        Endpoint = new Gcp.GkeHub.Inputs.MembershipEndpointArgs
        {
            GkeCluster = new Gcp.GkeHub.Inputs.MembershipEndpointGkeClusterArgs
            {
                ResourceLink = cluster.Id.Apply(id => $"//container.googleapis.com/{id}"),
            },
        },
    });

    var feature = new Gcp.GkeHub.Feature("feature", new()
    {
        Name = "configmanagement",
        Location = "global",
        Labels = 
        {
            { "foo", "bar" },
        },
    });

    var featureMember = new Gcp.GkeHub.FeatureMembership("feature_member", new()
    {
        Location = "global",
        Feature = feature.Name,
        Membership = membership.MembershipId,
        Configmanagement = new Gcp.GkeHub.Inputs.FeatureMembershipConfigmanagementArgs
        {
            Version = "1.19.0",
            ConfigSync = new Gcp.GkeHub.Inputs.FeatureMembershipConfigmanagementConfigSyncArgs
            {
                Enabled = true,
                Git = new Gcp.GkeHub.Inputs.FeatureMembershipConfigmanagementConfigSyncGitArgs
                {
                    SyncRepo = "https://github.com/hashicorp/terraform",
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.gkehub.Membership;
import com.pulumi.gcp.gkehub.MembershipArgs;
import com.pulumi.gcp.gkehub.inputs.MembershipEndpointArgs;
import com.pulumi.gcp.gkehub.inputs.MembershipEndpointGkeClusterArgs;
import com.pulumi.gcp.gkehub.Feature;
import com.pulumi.gcp.gkehub.FeatureArgs;
import com.pulumi.gcp.gkehub.FeatureMembership;
import com.pulumi.gcp.gkehub.FeatureMembershipArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipConfigmanagementArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipConfigmanagementConfigSyncArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipConfigmanagementConfigSyncGitArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var cluster = new Cluster("cluster", ClusterArgs.builder()
            .name("my-cluster")
            .location("us-central1-a")
            .initialNodeCount(1)
            .build());

        var membership = new Membership("membership", MembershipArgs.builder()
            .membershipId("my-membership")
            .endpoint(MembershipEndpointArgs.builder()
                .gkeCluster(MembershipEndpointGkeClusterArgs.builder()
                    .resourceLink(cluster.id().applyValue(_id -> String.format("//container.googleapis.com/%s", _id)))
                    .build())
                .build())
            .build());

        var feature = new Feature("feature", FeatureArgs.builder()
            .name("configmanagement")
            .location("global")
            .labels(Map.of("foo", "bar"))
            .build());

        var featureMember = new FeatureMembership("featureMember", FeatureMembershipArgs.builder()
            .location("global")
            .feature(feature.name())
            .membership(membership.membershipId())
            .configmanagement(FeatureMembershipConfigmanagementArgs.builder()
                .version("1.19.0")
                .configSync(FeatureMembershipConfigmanagementConfigSyncArgs.builder()
                    .enabled(true)
                    .git(FeatureMembershipConfigmanagementConfigSyncGitArgs.builder()
                        .syncRepo("https://github.com/hashicorp/terraform")
                        .build())
                    .build())
                .build())
            .build());

    }
}
resources:
  cluster:
    type: gcp:container:Cluster
    properties:
      name: my-cluster
      location: us-central1-a
      initialNodeCount: 1
  membership:
    type: gcp:gkehub:Membership
    properties:
      membershipId: my-membership
      endpoint:
        gkeCluster:
          resourceLink: //container.googleapis.com/${cluster.id}
  feature:
    type: gcp:gkehub:Feature
    properties:
      name: configmanagement
      location: global
      labels:
        foo: bar
  featureMember:
    type: gcp:gkehub:FeatureMembership
    name: feature_member
    properties:
      location: global
      feature: ${feature.name}
      membership: ${membership.membershipId}
      configmanagement:
        version: 1.19.0
        configSync:
          enabled: true
          git:
            syncRepo: https://github.com/hashicorp/terraform

When Config Sync is enabled, the controller pulls from syncRepo at regular intervals and applies manifests to the cluster. The version property controls which Config Management release to install. The git block specifies the repository URL; Config Sync handles authentication and sync scheduling.

Sync configuration from an OCI artifact registry

Organizations using container registries can store Kubernetes configuration as OCI images alongside application containers.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const cluster = new gcp.container.Cluster("cluster", {
    name: "my-cluster",
    location: "us-central1-a",
    initialNodeCount: 1,
});
const membership = new gcp.gkehub.Membership("membership", {
    membershipId: "my-membership",
    endpoint: {
        gkeCluster: {
            resourceLink: pulumi.interpolate`//container.googleapis.com/${cluster.id}`,
        },
    },
});
const feature = new gcp.gkehub.Feature("feature", {
    name: "configmanagement",
    location: "global",
    labels: {
        foo: "bar",
    },
});
const featureMember = new gcp.gkehub.FeatureMembership("feature_member", {
    location: "global",
    feature: feature.name,
    membership: membership.membershipId,
    configmanagement: {
        version: "1.19.0",
        configSync: {
            enabled: true,
            oci: {
                syncRepo: "us-central1-docker.pkg.dev/sample-project/config-repo/config-sync-gke:latest",
                policyDir: "config-connector",
                syncWaitSecs: "20",
                secretType: "gcpserviceaccount",
                gcpServiceAccountEmail: "sa@project-id.iam.gserviceaccount.com",
            },
        },
    },
});
import pulumi
import pulumi_gcp as gcp

cluster = gcp.container.Cluster("cluster",
    name="my-cluster",
    location="us-central1-a",
    initial_node_count=1)
membership = gcp.gkehub.Membership("membership",
    membership_id="my-membership",
    endpoint={
        "gke_cluster": {
            "resource_link": cluster.id.apply(lambda id: f"//container.googleapis.com/{id}"),
        },
    })
feature = gcp.gkehub.Feature("feature",
    name="configmanagement",
    location="global",
    labels={
        "foo": "bar",
    })
feature_member = gcp.gkehub.FeatureMembership("feature_member",
    location="global",
    feature=feature.name,
    membership=membership.membership_id,
    configmanagement={
        "version": "1.19.0",
        "config_sync": {
            "enabled": True,
            "oci": {
                "sync_repo": "us-central1-docker.pkg.dev/sample-project/config-repo/config-sync-gke:latest",
                "policy_dir": "config-connector",
                "sync_wait_secs": "20",
                "secret_type": "gcpserviceaccount",
                "gcp_service_account_email": "sa@project-id.iam.gserviceaccount.com",
            },
        },
    })
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/gkehub"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		cluster, err := container.NewCluster(ctx, "cluster", &container.ClusterArgs{
			Name:             pulumi.String("my-cluster"),
			Location:         pulumi.String("us-central1-a"),
			InitialNodeCount: pulumi.Int(1),
		})
		if err != nil {
			return err
		}
		membership, err := gkehub.NewMembership(ctx, "membership", &gkehub.MembershipArgs{
			MembershipId: pulumi.String("my-membership"),
			Endpoint: &gkehub.MembershipEndpointArgs{
				GkeCluster: &gkehub.MembershipEndpointGkeClusterArgs{
					ResourceLink: cluster.ID().ApplyT(func(id string) (string, error) {
						return fmt.Sprintf("//container.googleapis.com/%v", id), nil
					}).(pulumi.StringOutput),
				},
			},
		})
		if err != nil {
			return err
		}
		feature, err := gkehub.NewFeature(ctx, "feature", &gkehub.FeatureArgs{
			Name:     pulumi.String("configmanagement"),
			Location: pulumi.String("global"),
			Labels: pulumi.StringMap{
				"foo": pulumi.String("bar"),
			},
		})
		if err != nil {
			return err
		}
		_, err = gkehub.NewFeatureMembership(ctx, "feature_member", &gkehub.FeatureMembershipArgs{
			Location:   pulumi.String("global"),
			Feature:    feature.Name,
			Membership: membership.MembershipId,
			Configmanagement: &gkehub.FeatureMembershipConfigmanagementArgs{
				Version: pulumi.String("1.19.0"),
				ConfigSync: &gkehub.FeatureMembershipConfigmanagementConfigSyncArgs{
					Enabled: pulumi.Bool(true),
					Oci: &gkehub.FeatureMembershipConfigmanagementConfigSyncOciArgs{
						SyncRepo:               pulumi.String("us-central1-docker.pkg.dev/sample-project/config-repo/config-sync-gke:latest"),
						PolicyDir:              pulumi.String("config-connector"),
						SyncWaitSecs:           pulumi.String("20"),
						SecretType:             pulumi.String("gcpserviceaccount"),
						GcpServiceAccountEmail: pulumi.String("sa@project-id.iam.gserviceaccount.com"),
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var cluster = new Gcp.Container.Cluster("cluster", new()
    {
        Name = "my-cluster",
        Location = "us-central1-a",
        InitialNodeCount = 1,
    });

    var membership = new Gcp.GkeHub.Membership("membership", new()
    {
        MembershipId = "my-membership",
        Endpoint = new Gcp.GkeHub.Inputs.MembershipEndpointArgs
        {
            GkeCluster = new Gcp.GkeHub.Inputs.MembershipEndpointGkeClusterArgs
            {
                ResourceLink = cluster.Id.Apply(id => $"//container.googleapis.com/{id}"),
            },
        },
    });

    var feature = new Gcp.GkeHub.Feature("feature", new()
    {
        Name = "configmanagement",
        Location = "global",
        Labels = 
        {
            { "foo", "bar" },
        },
    });

    var featureMember = new Gcp.GkeHub.FeatureMembership("feature_member", new()
    {
        Location = "global",
        Feature = feature.Name,
        Membership = membership.MembershipId,
        Configmanagement = new Gcp.GkeHub.Inputs.FeatureMembershipConfigmanagementArgs
        {
            Version = "1.19.0",
            ConfigSync = new Gcp.GkeHub.Inputs.FeatureMembershipConfigmanagementConfigSyncArgs
            {
                Enabled = true,
                Oci = new Gcp.GkeHub.Inputs.FeatureMembershipConfigmanagementConfigSyncOciArgs
                {
                    SyncRepo = "us-central1-docker.pkg.dev/sample-project/config-repo/config-sync-gke:latest",
                    PolicyDir = "config-connector",
                    SyncWaitSecs = "20",
                    SecretType = "gcpserviceaccount",
                    GcpServiceAccountEmail = "sa@project-id.iam.gserviceaccount.com",
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.gkehub.Membership;
import com.pulumi.gcp.gkehub.MembershipArgs;
import com.pulumi.gcp.gkehub.inputs.MembershipEndpointArgs;
import com.pulumi.gcp.gkehub.inputs.MembershipEndpointGkeClusterArgs;
import com.pulumi.gcp.gkehub.Feature;
import com.pulumi.gcp.gkehub.FeatureArgs;
import com.pulumi.gcp.gkehub.FeatureMembership;
import com.pulumi.gcp.gkehub.FeatureMembershipArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipConfigmanagementArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipConfigmanagementConfigSyncArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipConfigmanagementConfigSyncOciArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var cluster = new Cluster("cluster", ClusterArgs.builder()
            .name("my-cluster")
            .location("us-central1-a")
            .initialNodeCount(1)
            .build());

        var membership = new Membership("membership", MembershipArgs.builder()
            .membershipId("my-membership")
            .endpoint(MembershipEndpointArgs.builder()
                .gkeCluster(MembershipEndpointGkeClusterArgs.builder()
                    .resourceLink(cluster.id().applyValue(_id -> String.format("//container.googleapis.com/%s", _id)))
                    .build())
                .build())
            .build());

        var feature = new Feature("feature", FeatureArgs.builder()
            .name("configmanagement")
            .location("global")
            .labels(Map.of("foo", "bar"))
            .build());

        var featureMember = new FeatureMembership("featureMember", FeatureMembershipArgs.builder()
            .location("global")
            .feature(feature.name())
            .membership(membership.membershipId())
            .configmanagement(FeatureMembershipConfigmanagementArgs.builder()
                .version("1.19.0")
                .configSync(FeatureMembershipConfigmanagementConfigSyncArgs.builder()
                    .enabled(true)
                    .oci(FeatureMembershipConfigmanagementConfigSyncOciArgs.builder()
                        .syncRepo("us-central1-docker.pkg.dev/sample-project/config-repo/config-sync-gke:latest")
                        .policyDir("config-connector")
                        .syncWaitSecs("20")
                        .secretType("gcpserviceaccount")
                        .gcpServiceAccountEmail("sa@project-id.iam.gserviceaccount.com")
                        .build())
                    .build())
                .build())
            .build());

    }
}
resources:
  cluster:
    type: gcp:container:Cluster
    properties:
      name: my-cluster
      location: us-central1-a
      initialNodeCount: 1
  membership:
    type: gcp:gkehub:Membership
    properties:
      membershipId: my-membership
      endpoint:
        gkeCluster:
          resourceLink: //container.googleapis.com/${cluster.id}
  feature:
    type: gcp:gkehub:Feature
    properties:
      name: configmanagement
      location: global
      labels:
        foo: bar
  featureMember:
    type: gcp:gkehub:FeatureMembership
    name: feature_member
    properties:
      location: global
      feature: ${feature.name}
      membership: ${membership.membershipId}
      configmanagement:
        version: 1.19.0
        configSync:
          enabled: true
          oci:
            syncRepo: us-central1-docker.pkg.dev/sample-project/config-repo/config-sync-gke:latest
            policyDir: config-connector
            syncWaitSecs: '20'
            secretType: gcpserviceaccount
            gcpServiceAccountEmail: sa@project-id.iam.gserviceaccount.com

The oci block replaces git for registry-based sync. The syncRepo points to an OCI artifact URL, and policyDir specifies which directory within the artifact contains manifests. The secretType and gcpServiceAccountEmail properties configure authentication to the registry. The syncWaitSecs property controls how long Config Sync waits between sync attempts.

Tune Config Sync resource limits and requests

Large clusters or high-frequency sync operations may need custom resource allocation for Config Sync components.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const cluster = new gcp.container.Cluster("cluster", {
    name: "my-cluster",
    location: "us-central1-a",
    initialNodeCount: 1,
});
const membership = new gcp.gkehub.Membership("membership", {
    membershipId: "my-membership",
    endpoint: {
        gkeCluster: {
            resourceLink: pulumi.interpolate`//container.googleapis.com/${cluster.id}`,
        },
    },
});
const feature = new gcp.gkehub.Feature("feature", {
    name: "configmanagement",
    location: "global",
    labels: {
        foo: "bar",
    },
});
const featureMember = new gcp.gkehub.FeatureMembership("feature_member", {
    location: "global",
    feature: feature.name,
    membership: membership.membershipId,
    configmanagement: {
        version: "1.20.1",
        configSync: {
            enabled: true,
            deploymentOverrides: [{
                deploymentName: "reconciler-manager",
                deploymentNamespace: "config-management-system",
                containers: [{
                    containerName: "reconciler-manager",
                    cpuRequest: "100m",
                    memoryRequest: "64Mi",
                    cpuLimit: "250m",
                    memoryLimit: "128Mi",
                }],
            }],
        },
    },
});
import pulumi
import pulumi_gcp as gcp

cluster = gcp.container.Cluster("cluster",
    name="my-cluster",
    location="us-central1-a",
    initial_node_count=1)
membership = gcp.gkehub.Membership("membership",
    membership_id="my-membership",
    endpoint={
        "gke_cluster": {
            "resource_link": cluster.id.apply(lambda id: f"//container.googleapis.com/{id}"),
        },
    })
feature = gcp.gkehub.Feature("feature",
    name="configmanagement",
    location="global",
    labels={
        "foo": "bar",
    })
feature_member = gcp.gkehub.FeatureMembership("feature_member",
    location="global",
    feature=feature.name,
    membership=membership.membership_id,
    configmanagement={
        "version": "1.20.1",
        "config_sync": {
            "enabled": True,
            "deployment_overrides": [{
                "deployment_name": "reconciler-manager",
                "deployment_namespace": "config-management-system",
                "containers": [{
                    "container_name": "reconciler-manager",
                    "cpu_request": "100m",
                    "memory_request": "64Mi",
                    "cpu_limit": "250m",
                    "memory_limit": "128Mi",
                }],
            }],
        },
    })
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/gkehub"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		cluster, err := container.NewCluster(ctx, "cluster", &container.ClusterArgs{
			Name:             pulumi.String("my-cluster"),
			Location:         pulumi.String("us-central1-a"),
			InitialNodeCount: pulumi.Int(1),
		})
		if err != nil {
			return err
		}
		membership, err := gkehub.NewMembership(ctx, "membership", &gkehub.MembershipArgs{
			MembershipId: pulumi.String("my-membership"),
			Endpoint: &gkehub.MembershipEndpointArgs{
				GkeCluster: &gkehub.MembershipEndpointGkeClusterArgs{
					ResourceLink: cluster.ID().ApplyT(func(id string) (string, error) {
						return fmt.Sprintf("//container.googleapis.com/%v", id), nil
					}).(pulumi.StringOutput),
				},
			},
		})
		if err != nil {
			return err
		}
		feature, err := gkehub.NewFeature(ctx, "feature", &gkehub.FeatureArgs{
			Name:     pulumi.String("configmanagement"),
			Location: pulumi.String("global"),
			Labels: pulumi.StringMap{
				"foo": pulumi.String("bar"),
			},
		})
		if err != nil {
			return err
		}
		_, err = gkehub.NewFeatureMembership(ctx, "feature_member", &gkehub.FeatureMembershipArgs{
			Location:   pulumi.String("global"),
			Feature:    feature.Name,
			Membership: membership.MembershipId,
			Configmanagement: &gkehub.FeatureMembershipConfigmanagementArgs{
				Version: pulumi.String("1.20.1"),
				ConfigSync: &gkehub.FeatureMembershipConfigmanagementConfigSyncArgs{
					Enabled: pulumi.Bool(true),
					DeploymentOverrides: gkehub.FeatureMembershipConfigmanagementConfigSyncDeploymentOverrideArray{
						&gkehub.FeatureMembershipConfigmanagementConfigSyncDeploymentOverrideArgs{
							DeploymentName:      pulumi.String("reconciler-manager"),
							DeploymentNamespace: pulumi.String("config-management-system"),
							Containers: gkehub.FeatureMembershipConfigmanagementConfigSyncDeploymentOverrideContainerArray{
								&gkehub.FeatureMembershipConfigmanagementConfigSyncDeploymentOverrideContainerArgs{
									ContainerName: pulumi.String("reconciler-manager"),
									CpuRequest:    pulumi.String("100m"),
									MemoryRequest: pulumi.String("64Mi"),
									CpuLimit:      pulumi.String("250m"),
									MemoryLimit:   pulumi.String("128Mi"),
								},
							},
						},
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var cluster = new Gcp.Container.Cluster("cluster", new()
    {
        Name = "my-cluster",
        Location = "us-central1-a",
        InitialNodeCount = 1,
    });

    var membership = new Gcp.GkeHub.Membership("membership", new()
    {
        MembershipId = "my-membership",
        Endpoint = new Gcp.GkeHub.Inputs.MembershipEndpointArgs
        {
            GkeCluster = new Gcp.GkeHub.Inputs.MembershipEndpointGkeClusterArgs
            {
                ResourceLink = cluster.Id.Apply(id => $"//container.googleapis.com/{id}"),
            },
        },
    });

    var feature = new Gcp.GkeHub.Feature("feature", new()
    {
        Name = "configmanagement",
        Location = "global",
        Labels = 
        {
            { "foo", "bar" },
        },
    });

    var featureMember = new Gcp.GkeHub.FeatureMembership("feature_member", new()
    {
        Location = "global",
        Feature = feature.Name,
        Membership = membership.MembershipId,
        Configmanagement = new Gcp.GkeHub.Inputs.FeatureMembershipConfigmanagementArgs
        {
            Version = "1.20.1",
            ConfigSync = new Gcp.GkeHub.Inputs.FeatureMembershipConfigmanagementConfigSyncArgs
            {
                Enabled = true,
                DeploymentOverrides = new[]
                {
                    new Gcp.GkeHub.Inputs.FeatureMembershipConfigmanagementConfigSyncDeploymentOverrideArgs
                    {
                        DeploymentName = "reconciler-manager",
                        DeploymentNamespace = "config-management-system",
                        Containers = new[]
                        {
                            new Gcp.GkeHub.Inputs.FeatureMembershipConfigmanagementConfigSyncDeploymentOverrideContainerArgs
                            {
                                ContainerName = "reconciler-manager",
                                CpuRequest = "100m",
                                MemoryRequest = "64Mi",
                                CpuLimit = "250m",
                                MemoryLimit = "128Mi",
                            },
                        },
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.gkehub.Membership;
import com.pulumi.gcp.gkehub.MembershipArgs;
import com.pulumi.gcp.gkehub.inputs.MembershipEndpointArgs;
import com.pulumi.gcp.gkehub.inputs.MembershipEndpointGkeClusterArgs;
import com.pulumi.gcp.gkehub.Feature;
import com.pulumi.gcp.gkehub.FeatureArgs;
import com.pulumi.gcp.gkehub.FeatureMembership;
import com.pulumi.gcp.gkehub.FeatureMembershipArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipConfigmanagementArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipConfigmanagementConfigSyncArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var cluster = new Cluster("cluster", ClusterArgs.builder()
            .name("my-cluster")
            .location("us-central1-a")
            .initialNodeCount(1)
            .build());

        var membership = new Membership("membership", MembershipArgs.builder()
            .membershipId("my-membership")
            .endpoint(MembershipEndpointArgs.builder()
                .gkeCluster(MembershipEndpointGkeClusterArgs.builder()
                    .resourceLink(cluster.id().applyValue(_id -> String.format("//container.googleapis.com/%s", _id)))
                    .build())
                .build())
            .build());

        var feature = new Feature("feature", FeatureArgs.builder()
            .name("configmanagement")
            .location("global")
            .labels(Map.of("foo", "bar"))
            .build());

        var featureMember = new FeatureMembership("featureMember", FeatureMembershipArgs.builder()
            .location("global")
            .feature(feature.name())
            .membership(membership.membershipId())
            .configmanagement(FeatureMembershipConfigmanagementArgs.builder()
                .version("1.20.1")
                .configSync(FeatureMembershipConfigmanagementConfigSyncArgs.builder()
                    .enabled(true)
                    .deploymentOverrides(FeatureMembershipConfigmanagementConfigSyncDeploymentOverrideArgs.builder()
                        .deploymentName("reconciler-manager")
                        .deploymentNamespace("config-management-system")
                        .containers(FeatureMembershipConfigmanagementConfigSyncDeploymentOverrideContainerArgs.builder()
                            .containerName("reconciler-manager")
                            .cpuRequest("100m")
                            .memoryRequest("64Mi")
                            .cpuLimit("250m")
                            .memoryLimit("128Mi")
                            .build())
                        .build())
                    .build())
                .build())
            .build());

    }
}
resources:
  cluster:
    type: gcp:container:Cluster
    properties:
      name: my-cluster
      location: us-central1-a
      initialNodeCount: 1
  membership:
    type: gcp:gkehub:Membership
    properties:
      membershipId: my-membership
      endpoint:
        gkeCluster:
          resourceLink: //container.googleapis.com/${cluster.id}
  feature:
    type: gcp:gkehub:Feature
    properties:
      name: configmanagement
      location: global
      labels:
        foo: bar
  featureMember:
    type: gcp:gkehub:FeatureMembership
    name: feature_member
    properties:
      location: global
      feature: ${feature.name}
      membership: ${membership.membershipId}
      configmanagement:
        version: 1.20.1
        configSync:
          enabled: true
          deploymentOverrides:
            - deploymentName: reconciler-manager
              deploymentNamespace: config-management-system
              containers:
                - containerName: reconciler-manager
                  cpuRequest: 100m
                  memoryRequest: 64Mi
                  cpuLimit: 250m
                  memoryLimit: 128Mi

The deploymentOverrides array targets specific Config Sync deployments by name and namespace. Each override specifies containers with custom CPU and memory settings. This prevents throttling or out-of-memory conditions in clusters with many resources or frequent updates.

Enable managed service mesh for a cluster

Applications requiring service-to-service encryption or traffic management can use GKE’s managed service mesh without manual Istio installation.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const cluster = new gcp.container.Cluster("cluster", {
    name: "my-cluster",
    location: "us-central1-a",
    initialNodeCount: 1,
});
const membership = new gcp.gkehub.Membership("membership", {
    membershipId: "my-membership",
    endpoint: {
        gkeCluster: {
            resourceLink: pulumi.interpolate`//container.googleapis.com/${cluster.id}`,
        },
    },
});
const feature = new gcp.gkehub.Feature("feature", {
    name: "servicemesh",
    location: "global",
});
const featureMember = new gcp.gkehub.FeatureMembership("feature_member", {
    location: "global",
    feature: feature.name,
    membership: membership.membershipId,
    mesh: {
        management: "MANAGEMENT_AUTOMATIC",
    },
});
import pulumi
import pulumi_gcp as gcp

cluster = gcp.container.Cluster("cluster",
    name="my-cluster",
    location="us-central1-a",
    initial_node_count=1)
membership = gcp.gkehub.Membership("membership",
    membership_id="my-membership",
    endpoint={
        "gke_cluster": {
            "resource_link": cluster.id.apply(lambda id: f"//container.googleapis.com/{id}"),
        },
    })
feature = gcp.gkehub.Feature("feature",
    name="servicemesh",
    location="global")
feature_member = gcp.gkehub.FeatureMembership("feature_member",
    location="global",
    feature=feature.name,
    membership=membership.membership_id,
    mesh={
        "management": "MANAGEMENT_AUTOMATIC",
    })
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/gkehub"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		cluster, err := container.NewCluster(ctx, "cluster", &container.ClusterArgs{
			Name:             pulumi.String("my-cluster"),
			Location:         pulumi.String("us-central1-a"),
			InitialNodeCount: pulumi.Int(1),
		})
		if err != nil {
			return err
		}
		membership, err := gkehub.NewMembership(ctx, "membership", &gkehub.MembershipArgs{
			MembershipId: pulumi.String("my-membership"),
			Endpoint: &gkehub.MembershipEndpointArgs{
				GkeCluster: &gkehub.MembershipEndpointGkeClusterArgs{
					ResourceLink: cluster.ID().ApplyT(func(id string) (string, error) {
						return fmt.Sprintf("//container.googleapis.com/%v", id), nil
					}).(pulumi.StringOutput),
				},
			},
		})
		if err != nil {
			return err
		}
		feature, err := gkehub.NewFeature(ctx, "feature", &gkehub.FeatureArgs{
			Name:     pulumi.String("servicemesh"),
			Location: pulumi.String("global"),
		})
		if err != nil {
			return err
		}
		_, err = gkehub.NewFeatureMembership(ctx, "feature_member", &gkehub.FeatureMembershipArgs{
			Location:   pulumi.String("global"),
			Feature:    feature.Name,
			Membership: membership.MembershipId,
			Mesh: &gkehub.FeatureMembershipMeshArgs{
				Management: pulumi.String("MANAGEMENT_AUTOMATIC"),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var cluster = new Gcp.Container.Cluster("cluster", new()
    {
        Name = "my-cluster",
        Location = "us-central1-a",
        InitialNodeCount = 1,
    });

    var membership = new Gcp.GkeHub.Membership("membership", new()
    {
        MembershipId = "my-membership",
        Endpoint = new Gcp.GkeHub.Inputs.MembershipEndpointArgs
        {
            GkeCluster = new Gcp.GkeHub.Inputs.MembershipEndpointGkeClusterArgs
            {
                ResourceLink = cluster.Id.Apply(id => $"//container.googleapis.com/{id}"),
            },
        },
    });

    var feature = new Gcp.GkeHub.Feature("feature", new()
    {
        Name = "servicemesh",
        Location = "global",
    });

    var featureMember = new Gcp.GkeHub.FeatureMembership("feature_member", new()
    {
        Location = "global",
        Feature = feature.Name,
        Membership = membership.MembershipId,
        Mesh = new Gcp.GkeHub.Inputs.FeatureMembershipMeshArgs
        {
            Management = "MANAGEMENT_AUTOMATIC",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.gkehub.Membership;
import com.pulumi.gcp.gkehub.MembershipArgs;
import com.pulumi.gcp.gkehub.inputs.MembershipEndpointArgs;
import com.pulumi.gcp.gkehub.inputs.MembershipEndpointGkeClusterArgs;
import com.pulumi.gcp.gkehub.Feature;
import com.pulumi.gcp.gkehub.FeatureArgs;
import com.pulumi.gcp.gkehub.FeatureMembership;
import com.pulumi.gcp.gkehub.FeatureMembershipArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipMeshArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var cluster = new Cluster("cluster", ClusterArgs.builder()
            .name("my-cluster")
            .location("us-central1-a")
            .initialNodeCount(1)
            .build());

        var membership = new Membership("membership", MembershipArgs.builder()
            .membershipId("my-membership")
            .endpoint(MembershipEndpointArgs.builder()
                .gkeCluster(MembershipEndpointGkeClusterArgs.builder()
                    .resourceLink(cluster.id().applyValue(_id -> String.format("//container.googleapis.com/%s", _id)))
                    .build())
                .build())
            .build());

        var feature = new Feature("feature", FeatureArgs.builder()
            .name("servicemesh")
            .location("global")
            .build());

        var featureMember = new FeatureMembership("featureMember", FeatureMembershipArgs.builder()
            .location("global")
            .feature(feature.name())
            .membership(membership.membershipId())
            .mesh(FeatureMembershipMeshArgs.builder()
                .management("MANAGEMENT_AUTOMATIC")
                .build())
            .build());

    }
}
resources:
  cluster:
    type: gcp:container:Cluster
    properties:
      name: my-cluster
      location: us-central1-a
      initialNodeCount: 1
  membership:
    type: gcp:gkehub:Membership
    properties:
      membershipId: my-membership
      endpoint:
        gkeCluster:
          resourceLink: //container.googleapis.com/${cluster.id}
  feature:
    type: gcp:gkehub:Feature
    properties:
      name: servicemesh
      location: global
  featureMember:
    type: gcp:gkehub:FeatureMembership
    name: feature_member
    properties:
      location: global
      feature: ${feature.name}
      membership: ${membership.membershipId}
      mesh:
        management: MANAGEMENT_AUTOMATIC

The mesh block enables service mesh features for the membership. Setting management to “MANAGEMENT_AUTOMATIC” delegates control plane installation and upgrades to Google, eliminating manual Istio lifecycle management.

Enable policy enforcement with default settings

Teams adopting policy-as-code start by enabling Policy Controller with default constraints.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const cluster = new gcp.container.Cluster("cluster", {
    name: "my-cluster",
    location: "us-central1-a",
    initialNodeCount: 1,
});
const membership = new gcp.gkehub.Membership("membership", {
    membershipId: "my-membership",
    endpoint: {
        gkeCluster: {
            resourceLink: pulumi.interpolate`//container.googleapis.com/${cluster.id}`,
        },
    },
});
const feature = new gcp.gkehub.Feature("feature", {
    name: "policycontroller",
    location: "global",
});
const featureMember = new gcp.gkehub.FeatureMembership("feature_member", {
    location: "global",
    feature: feature.name,
    membership: membership.membershipId,
    policycontroller: {
        policyControllerHubConfig: {
            installSpec: "INSTALL_SPEC_ENABLED",
        },
    },
});
import pulumi
import pulumi_gcp as gcp

cluster = gcp.container.Cluster("cluster",
    name="my-cluster",
    location="us-central1-a",
    initial_node_count=1)
membership = gcp.gkehub.Membership("membership",
    membership_id="my-membership",
    endpoint={
        "gke_cluster": {
            "resource_link": cluster.id.apply(lambda id: f"//container.googleapis.com/{id}"),
        },
    })
feature = gcp.gkehub.Feature("feature",
    name="policycontroller",
    location="global")
feature_member = gcp.gkehub.FeatureMembership("feature_member",
    location="global",
    feature=feature.name,
    membership=membership.membership_id,
    policycontroller={
        "policy_controller_hub_config": {
            "install_spec": "INSTALL_SPEC_ENABLED",
        },
    })
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/gkehub"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		cluster, err := container.NewCluster(ctx, "cluster", &container.ClusterArgs{
			Name:             pulumi.String("my-cluster"),
			Location:         pulumi.String("us-central1-a"),
			InitialNodeCount: pulumi.Int(1),
		})
		if err != nil {
			return err
		}
		membership, err := gkehub.NewMembership(ctx, "membership", &gkehub.MembershipArgs{
			MembershipId: pulumi.String("my-membership"),
			Endpoint: &gkehub.MembershipEndpointArgs{
				GkeCluster: &gkehub.MembershipEndpointGkeClusterArgs{
					ResourceLink: cluster.ID().ApplyT(func(id string) (string, error) {
						return fmt.Sprintf("//container.googleapis.com/%v", id), nil
					}).(pulumi.StringOutput),
				},
			},
		})
		if err != nil {
			return err
		}
		feature, err := gkehub.NewFeature(ctx, "feature", &gkehub.FeatureArgs{
			Name:     pulumi.String("policycontroller"),
			Location: pulumi.String("global"),
		})
		if err != nil {
			return err
		}
		_, err = gkehub.NewFeatureMembership(ctx, "feature_member", &gkehub.FeatureMembershipArgs{
			Location:   pulumi.String("global"),
			Feature:    feature.Name,
			Membership: membership.MembershipId,
			Policycontroller: &gkehub.FeatureMembershipPolicycontrollerArgs{
				PolicyControllerHubConfig: &gkehub.FeatureMembershipPolicycontrollerPolicyControllerHubConfigArgs{
					InstallSpec: pulumi.String("INSTALL_SPEC_ENABLED"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var cluster = new Gcp.Container.Cluster("cluster", new()
    {
        Name = "my-cluster",
        Location = "us-central1-a",
        InitialNodeCount = 1,
    });

    var membership = new Gcp.GkeHub.Membership("membership", new()
    {
        MembershipId = "my-membership",
        Endpoint = new Gcp.GkeHub.Inputs.MembershipEndpointArgs
        {
            GkeCluster = new Gcp.GkeHub.Inputs.MembershipEndpointGkeClusterArgs
            {
                ResourceLink = cluster.Id.Apply(id => $"//container.googleapis.com/{id}"),
            },
        },
    });

    var feature = new Gcp.GkeHub.Feature("feature", new()
    {
        Name = "policycontroller",
        Location = "global",
    });

    var featureMember = new Gcp.GkeHub.FeatureMembership("feature_member", new()
    {
        Location = "global",
        Feature = feature.Name,
        Membership = membership.MembershipId,
        Policycontroller = new Gcp.GkeHub.Inputs.FeatureMembershipPolicycontrollerArgs
        {
            PolicyControllerHubConfig = new Gcp.GkeHub.Inputs.FeatureMembershipPolicycontrollerPolicyControllerHubConfigArgs
            {
                InstallSpec = "INSTALL_SPEC_ENABLED",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.gkehub.Membership;
import com.pulumi.gcp.gkehub.MembershipArgs;
import com.pulumi.gcp.gkehub.inputs.MembershipEndpointArgs;
import com.pulumi.gcp.gkehub.inputs.MembershipEndpointGkeClusterArgs;
import com.pulumi.gcp.gkehub.Feature;
import com.pulumi.gcp.gkehub.FeatureArgs;
import com.pulumi.gcp.gkehub.FeatureMembership;
import com.pulumi.gcp.gkehub.FeatureMembershipArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipPolicycontrollerArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipPolicycontrollerPolicyControllerHubConfigArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var cluster = new Cluster("cluster", ClusterArgs.builder()
            .name("my-cluster")
            .location("us-central1-a")
            .initialNodeCount(1)
            .build());

        var membership = new Membership("membership", MembershipArgs.builder()
            .membershipId("my-membership")
            .endpoint(MembershipEndpointArgs.builder()
                .gkeCluster(MembershipEndpointGkeClusterArgs.builder()
                    .resourceLink(cluster.id().applyValue(_id -> String.format("//container.googleapis.com/%s", _id)))
                    .build())
                .build())
            .build());

        var feature = new Feature("feature", FeatureArgs.builder()
            .name("policycontroller")
            .location("global")
            .build());

        var featureMember = new FeatureMembership("featureMember", FeatureMembershipArgs.builder()
            .location("global")
            .feature(feature.name())
            .membership(membership.membershipId())
            .policycontroller(FeatureMembershipPolicycontrollerArgs.builder()
                .policyControllerHubConfig(FeatureMembershipPolicycontrollerPolicyControllerHubConfigArgs.builder()
                    .installSpec("INSTALL_SPEC_ENABLED")
                    .build())
                .build())
            .build());

    }
}
resources:
  cluster:
    type: gcp:container:Cluster
    properties:
      name: my-cluster
      location: us-central1-a
      initialNodeCount: 1
  membership:
    type: gcp:gkehub:Membership
    properties:
      membershipId: my-membership
      endpoint:
        gkeCluster:
          resourceLink: //container.googleapis.com/${cluster.id}
  feature:
    type: gcp:gkehub:Feature
    properties:
      name: policycontroller
      location: global
  featureMember:
    type: gcp:gkehub:FeatureMembership
    name: feature_member
    properties:
      location: global
      feature: ${feature.name}
      membership: ${membership.membershipId}
      policycontroller:
        policyControllerHubConfig:
          installSpec: INSTALL_SPEC_ENABLED

The policyControllerHubConfig block controls Policy Controller installation. Setting installSpec to “INSTALL_SPEC_ENABLED” deploys the admission webhook with default settings. Policy Controller validates resource configurations against constraints at admission time.

Customize policy enforcement behavior and limits

Production environments often need fine-tuned policy settings: custom audit intervals, violation limits, or mutation capabilities.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const cluster = new gcp.container.Cluster("cluster", {
    name: "my-cluster",
    location: "us-central1-a",
    initialNodeCount: 1,
});
const membership = new gcp.gkehub.Membership("membership", {
    membershipId: "my-membership",
    endpoint: {
        gkeCluster: {
            resourceLink: pulumi.interpolate`//container.googleapis.com/${cluster.id}`,
        },
    },
});
const feature = new gcp.gkehub.Feature("feature", {
    name: "policycontroller",
    location: "global",
});
const featureMember = new gcp.gkehub.FeatureMembership("feature_member", {
    location: "global",
    feature: feature.name,
    membership: membership.membershipId,
    policycontroller: {
        policyControllerHubConfig: {
            installSpec: "INSTALL_SPEC_SUSPENDED",
            policyContent: {
                templateLibrary: {
                    installation: "NOT_INSTALLED",
                },
            },
            constraintViolationLimit: 50,
            auditIntervalSeconds: 120,
            referentialRulesEnabled: true,
            logDeniesEnabled: true,
            mutationEnabled: true,
        },
        version: "1.17.0",
    },
});
import pulumi
import pulumi_gcp as gcp

cluster = gcp.container.Cluster("cluster",
    name="my-cluster",
    location="us-central1-a",
    initial_node_count=1)
membership = gcp.gkehub.Membership("membership",
    membership_id="my-membership",
    endpoint={
        "gke_cluster": {
            "resource_link": cluster.id.apply(lambda id: f"//container.googleapis.com/{id}"),
        },
    })
feature = gcp.gkehub.Feature("feature",
    name="policycontroller",
    location="global")
feature_member = gcp.gkehub.FeatureMembership("feature_member",
    location="global",
    feature=feature.name,
    membership=membership.membership_id,
    policycontroller={
        "policy_controller_hub_config": {
            "install_spec": "INSTALL_SPEC_SUSPENDED",
            "policy_content": {
                "template_library": {
                    "installation": "NOT_INSTALLED",
                },
            },
            "constraint_violation_limit": 50,
            "audit_interval_seconds": 120,
            "referential_rules_enabled": True,
            "log_denies_enabled": True,
            "mutation_enabled": True,
        },
        "version": "1.17.0",
    })
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/gkehub"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		cluster, err := container.NewCluster(ctx, "cluster", &container.ClusterArgs{
			Name:             pulumi.String("my-cluster"),
			Location:         pulumi.String("us-central1-a"),
			InitialNodeCount: pulumi.Int(1),
		})
		if err != nil {
			return err
		}
		membership, err := gkehub.NewMembership(ctx, "membership", &gkehub.MembershipArgs{
			MembershipId: pulumi.String("my-membership"),
			Endpoint: &gkehub.MembershipEndpointArgs{
				GkeCluster: &gkehub.MembershipEndpointGkeClusterArgs{
					ResourceLink: cluster.ID().ApplyT(func(id string) (string, error) {
						return fmt.Sprintf("//container.googleapis.com/%v", id), nil
					}).(pulumi.StringOutput),
				},
			},
		})
		if err != nil {
			return err
		}
		feature, err := gkehub.NewFeature(ctx, "feature", &gkehub.FeatureArgs{
			Name:     pulumi.String("policycontroller"),
			Location: pulumi.String("global"),
		})
		if err != nil {
			return err
		}
		_, err = gkehub.NewFeatureMembership(ctx, "feature_member", &gkehub.FeatureMembershipArgs{
			Location:   pulumi.String("global"),
			Feature:    feature.Name,
			Membership: membership.MembershipId,
			Policycontroller: &gkehub.FeatureMembershipPolicycontrollerArgs{
				PolicyControllerHubConfig: &gkehub.FeatureMembershipPolicycontrollerPolicyControllerHubConfigArgs{
					InstallSpec: pulumi.String("INSTALL_SPEC_SUSPENDED"),
					PolicyContent: &gkehub.FeatureMembershipPolicycontrollerPolicyControllerHubConfigPolicyContentArgs{
						TemplateLibrary: &gkehub.FeatureMembershipPolicycontrollerPolicyControllerHubConfigPolicyContentTemplateLibraryArgs{
							Installation: pulumi.String("NOT_INSTALLED"),
						},
					},
					ConstraintViolationLimit: pulumi.Int(50),
					AuditIntervalSeconds:     pulumi.Int(120),
					ReferentialRulesEnabled:  pulumi.Bool(true),
					LogDeniesEnabled:         pulumi.Bool(true),
					MutationEnabled:          pulumi.Bool(true),
				},
				Version: pulumi.String("1.17.0"),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var cluster = new Gcp.Container.Cluster("cluster", new()
    {
        Name = "my-cluster",
        Location = "us-central1-a",
        InitialNodeCount = 1,
    });

    var membership = new Gcp.GkeHub.Membership("membership", new()
    {
        MembershipId = "my-membership",
        Endpoint = new Gcp.GkeHub.Inputs.MembershipEndpointArgs
        {
            GkeCluster = new Gcp.GkeHub.Inputs.MembershipEndpointGkeClusterArgs
            {
                ResourceLink = cluster.Id.Apply(id => $"//container.googleapis.com/{id}"),
            },
        },
    });

    var feature = new Gcp.GkeHub.Feature("feature", new()
    {
        Name = "policycontroller",
        Location = "global",
    });

    var featureMember = new Gcp.GkeHub.FeatureMembership("feature_member", new()
    {
        Location = "global",
        Feature = feature.Name,
        Membership = membership.MembershipId,
        Policycontroller = new Gcp.GkeHub.Inputs.FeatureMembershipPolicycontrollerArgs
        {
            PolicyControllerHubConfig = new Gcp.GkeHub.Inputs.FeatureMembershipPolicycontrollerPolicyControllerHubConfigArgs
            {
                InstallSpec = "INSTALL_SPEC_SUSPENDED",
                PolicyContent = new Gcp.GkeHub.Inputs.FeatureMembershipPolicycontrollerPolicyControllerHubConfigPolicyContentArgs
                {
                    TemplateLibrary = new Gcp.GkeHub.Inputs.FeatureMembershipPolicycontrollerPolicyControllerHubConfigPolicyContentTemplateLibraryArgs
                    {
                        Installation = "NOT_INSTALLED",
                    },
                },
                ConstraintViolationLimit = 50,
                AuditIntervalSeconds = 120,
                ReferentialRulesEnabled = true,
                LogDeniesEnabled = true,
                MutationEnabled = true,
            },
            Version = "1.17.0",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.gkehub.Membership;
import com.pulumi.gcp.gkehub.MembershipArgs;
import com.pulumi.gcp.gkehub.inputs.MembershipEndpointArgs;
import com.pulumi.gcp.gkehub.inputs.MembershipEndpointGkeClusterArgs;
import com.pulumi.gcp.gkehub.Feature;
import com.pulumi.gcp.gkehub.FeatureArgs;
import com.pulumi.gcp.gkehub.FeatureMembership;
import com.pulumi.gcp.gkehub.FeatureMembershipArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipPolicycontrollerArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipPolicycontrollerPolicyControllerHubConfigArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipPolicycontrollerPolicyControllerHubConfigPolicyContentArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureMembershipPolicycontrollerPolicyControllerHubConfigPolicyContentTemplateLibraryArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var cluster = new Cluster("cluster", ClusterArgs.builder()
            .name("my-cluster")
            .location("us-central1-a")
            .initialNodeCount(1)
            .build());

        var membership = new Membership("membership", MembershipArgs.builder()
            .membershipId("my-membership")
            .endpoint(MembershipEndpointArgs.builder()
                .gkeCluster(MembershipEndpointGkeClusterArgs.builder()
                    .resourceLink(cluster.id().applyValue(_id -> String.format("//container.googleapis.com/%s", _id)))
                    .build())
                .build())
            .build());

        var feature = new Feature("feature", FeatureArgs.builder()
            .name("policycontroller")
            .location("global")
            .build());

        var featureMember = new FeatureMembership("featureMember", FeatureMembershipArgs.builder()
            .location("global")
            .feature(feature.name())
            .membership(membership.membershipId())
            .policycontroller(FeatureMembershipPolicycontrollerArgs.builder()
                .policyControllerHubConfig(FeatureMembershipPolicycontrollerPolicyControllerHubConfigArgs.builder()
                    .installSpec("INSTALL_SPEC_SUSPENDED")
                    .policyContent(FeatureMembershipPolicycontrollerPolicyControllerHubConfigPolicyContentArgs.builder()
                        .templateLibrary(FeatureMembershipPolicycontrollerPolicyControllerHubConfigPolicyContentTemplateLibraryArgs.builder()
                            .installation("NOT_INSTALLED")
                            .build())
                        .build())
                    .constraintViolationLimit(50)
                    .auditIntervalSeconds(120)
                    .referentialRulesEnabled(true)
                    .logDeniesEnabled(true)
                    .mutationEnabled(true)
                    .build())
                .version("1.17.0")
                .build())
            .build());

    }
}
resources:
  cluster:
    type: gcp:container:Cluster
    properties:
      name: my-cluster
      location: us-central1-a
      initialNodeCount: 1
  membership:
    type: gcp:gkehub:Membership
    properties:
      membershipId: my-membership
      endpoint:
        gkeCluster:
          resourceLink: //container.googleapis.com/${cluster.id}
  feature:
    type: gcp:gkehub:Feature
    properties:
      name: policycontroller
      location: global
  featureMember:
    type: gcp:gkehub:FeatureMembership
    name: feature_member
    properties:
      location: global
      feature: ${feature.name}
      membership: ${membership.membershipId}
      policycontroller:
        policyControllerHubConfig:
          installSpec: INSTALL_SPEC_SUSPENDED
          policyContent:
            templateLibrary:
              installation: NOT_INSTALLED
          constraintViolationLimit: 50
          auditIntervalSeconds: 120
          referentialRulesEnabled: true
          logDeniesEnabled: true
          mutationEnabled: true
        version: 1.17.0

The installSpec “INSTALL_SPEC_SUSPENDED” disables admission enforcement while keeping audit mode active. The policyContent block controls template library installation. The constraintViolationLimit caps how many violations are reported per constraint. The auditIntervalSeconds property controls how often Policy Controller scans existing resources. Setting mutationEnabled to true allows Policy Controller to automatically remediate violations.

Beyond these examples

These snippets focus on specific feature membership capabilities: Config Management (Git, OCI, resource tuning), Service Mesh with automatic management, and Policy Controller enforcement and customization. They’re intentionally minimal rather than full fleet management solutions.

The examples require pre-existing infrastructure such as GKE clusters, GKEHub memberships, GKEHub features (configmanagement, servicemesh, policycontroller), and OCI registries and service accounts for OCI sync. They focus on feature membership configuration rather than provisioning the underlying clusters and features.

To keep things focused, common feature membership patterns are omitted, including:

  • Regional membership configuration (membershipLocation)
  • Multi Cluster Service Discovery setup
  • Policy template library installation options
  • Config Sync source-of-truth selection (git vs oci)

These omissions are intentional: the goal is to illustrate how each feature membership capability is wired, not provide drop-in fleet management modules. See the GKEHub FeatureMembership resource reference for all available configuration options.

Let's configure GCP GKE Hub Feature Memberships

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Immutability & Limitations
What properties can't I change after creating a FeatureMembership?
The feature, location, membership, project, and membershipLocation properties are immutable. Changing any of these requires recreating the resource.
Configuration & Setup
What features can I configure with FeatureMembership?
You can configure configmanagement (Config Management), mesh (Service Mesh), and policycontroller (Policy Controller) features for specific memberships.
What's the default location for memberships?
The membershipLocation defaults to global if not specified.
How do I import an existing FeatureMembership?

Use one of these formats:

  • projects/{{project}}/locations/{{location}}/features/{{feature}}/membershipId/{{membership}}
  • {{project}}/{{location}}/{{feature}}/{{membership}}
  • {{location}}/{{feature}}/{{membership}}
Config Management
What's the difference between Git and OCI sync for Config Management?
Git sync uses configSync.git.syncRepo with a Git repository URL, while OCI sync uses configSync.oci.syncRepo pointing to a container image in a registry like Artifact Registry.
How do I configure Config Management for a regional membership?
Set membershipLocation to match your membership’s region (e.g., us-central1) instead of using the default global location.
Can I customize resource limits for Config Sync components?
Yes, use configSync.deploymentOverrides to specify cpuRequest, memoryRequest, cpuLimit, and memoryLimit for specific containers like reconciler-manager.
Policy Controller
What's the minimal configuration needed to enable Policy Controller?
Set policycontroller.policyControllerHubConfig.installSpec to INSTALL_SPEC_ENABLED.
What Policy Controller settings can I customize?
You can configure installSpec, policyContent.templateLibrary, constraintViolationLimit, auditIntervalSeconds, referentialRulesEnabled, logDeniesEnabled, mutationEnabled, and version.

Using a different cloud?

Explore containers guides for other cloud providers: