Configure GCP GKE Hub Feature Memberships

The gcp:gkehub/featureMembership:FeatureMembership resource, part of the Pulumi GCP provider, configures GKE Hub (Fleet) features for specific cluster memberships. This guide focuses on three capabilities: Config Sync with Git and OCI sources, managed service mesh enablement, and Policy Controller enforcement.

Feature memberships bind Fleet features to individual clusters. They require existing GKE clusters, Fleet memberships, and Fleet features to be created first. The examples are intentionally small. Combine them with your own cluster infrastructure and feature definitions.

Enable Config Sync with automatic version management

Teams managing multiple clusters often start with Config Sync auto-upgrades, where Google handles version updates and component lifecycle 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: {
        management: "MANAGEMENT_AUTOMATIC",
        configSync: {
            enabled: true,
        },
    },
});
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={
        "management": "MANAGEMENT_AUTOMATIC",
        "config_sync": {
            "enabled": True,
        },
    })
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{
				Management: pulumi.String("MANAGEMENT_AUTOMATIC"),
				ConfigSync: &gkehub.FeatureMembershipConfigmanagementConfigSyncArgs{
					Enabled: pulumi.Bool(true),
				},
			},
		})
		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
        {
            Management = "MANAGEMENT_AUTOMATIC",
            ConfigSync = new Gcp.GkeHub.Inputs.FeatureMembershipConfigmanagementConfigSyncArgs
            {
                Enabled = true,
            },
        },
    });

});
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()
                .management("MANAGEMENT_AUTOMATIC")
                .configSync(FeatureMembershipConfigmanagementConfigSyncArgs.builder()
                    .enabled(true)
                    .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:
        management: MANAGEMENT_AUTOMATIC
        configSync:
          enabled: true

When management is set to MANAGEMENT_AUTOMATIC, Google controls Config Sync versions and upgrades. The configSync block enables the feature without specifying a Git or OCI source, allowing you to configure sources later or use other sync methods.

Sync cluster configuration from a Git repository

Most Config Management deployments sync Kubernetes manifests from a Git repository, enabling GitOps workflows where infrastructure changes follow code review processes.

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

Config Sync pulls manifests from the specified Git repository and applies them to the cluster. The version property pins Config Sync to a specific release, and syncRepo points to your Git repository URL. The cluster continuously reconciles its state against the repository contents.

Sync cluster configuration from an OCI artifact

Organizations using Artifact Registry or other OCI registries can store configuration as container images, providing versioning and access control through existing registry infrastructure.

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 configures Config Sync to pull from an OCI registry instead of Git. The syncRepo specifies the full image path, policyDir identifies the directory within the artifact containing manifests, and gcpServiceAccountEmail provides authentication. The secretType of “gcpserviceaccount” uses Workload Identity for secure access.

Enable managed service mesh for traffic control

Service mesh provides traffic management, security, and observability for microservices without requiring application code changes.

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 Anthos Service Mesh on the cluster. Setting management to MANAGEMENT_AUTOMATIC lets Google handle mesh version upgrades and component lifecycle, similar to Config Sync auto-upgrades.

Enable policy enforcement with default settings

Policy Controller enforces organizational policies across clusters, validating resources against constraints before they’re admitted to the cluster.

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 configures Policy Controller behavior. Setting installSpec to INSTALL_SPEC_ENABLED activates the controller with default settings, which include standard audit intervals and violation limits.

Configure policy enforcement with custom settings

Production deployments often need fine-grained control over policy behavior, including audit frequency, violation limits, and 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

This configuration extends basic Policy Controller setup with custom tuning. The auditIntervalSeconds controls how often the controller scans existing resources, constraintViolationLimit caps the number of violations reported per constraint, and mutationEnabled allows the controller to modify resources to meet policy requirements. Setting installSpec to INSTALL_SPEC_SUSPENDED disables enforcement while keeping the controller installed for testing.

Beyond these examples

These snippets focus on specific feature membership capabilities: Config Sync with Git and OCI sources, service mesh management, and Policy Controller enforcement. They’re intentionally minimal rather than full Fleet configurations.

The examples require pre-existing infrastructure such as GKE clusters, Fleet memberships, Fleet features, and Git repositories or OCI registries for Config Sync. They focus on binding features to memberships rather than provisioning the underlying infrastructure.

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

  • Deployment resource overrides (CPU/memory limits)
  • Regional membership configuration
  • Template library installation options
  • Multi-cluster service discovery setup

These omissions are intentional: the goal is to illustrate how each feature is wired to a membership, not provide drop-in Fleet modules. See the GKE Hub 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

Resource Configuration & Immutability
What properties can't I change after creating a FeatureMembership?
The properties feature, location, membership, project, and membershipLocation are all immutable. Changing any of these requires destroying and recreating the resource.
How do I configure a regional membership instead of global?
Set membershipLocation to match your membership’s location. For example, if your membership is in us-central1, set membershipLocation: "us-central1". The default is global.
Config Management & Sync
Should I use Config Sync auto-upgrades or specify a version?
Use management: "MANAGEMENT_AUTOMATIC" for auto-upgrades, where Google manages Config Sync versions. For manual control, omit management and specify a version like 1.19.0. Don’t specify both.
What's the difference between Git and OCI sync sources for Config Sync?
Git sync uses git.syncRepo with a Git repository URL. OCI sync uses oci.syncRepo with a container registry path and requires additional configuration like policyDir, syncWaitSecs, secretType, and gcpServiceAccountEmail.
Feature Types & Configuration
What features can I configure with FeatureMembership?
You can configure configmanagement (Config Sync), mesh (Service Mesh), policycontroller (Policy Controller), and multiclusterservicediscovery. Each feature has its own configuration block.
What's the minimal configuration needed for Policy Controller?
Set policycontroller.policyControllerHubConfig.installSpec to INSTALL_SPEC_ENABLED. This enables Policy Controller with default settings.

Using a different cloud?

Explore containers guides for other cloud providers: