Configure GCP GKE Hub Features

The gcp:gkehub/feature:Feature resource, part of the Pulumi GCP provider, enables and configures fleet-wide capabilities for GKE clusters registered in a fleet. This guide focuses on three capabilities: multi-cluster networking, fleet observability and log collection, and policy controller with fleet-wide defaults.

Features operate on GKE clusters that must already be registered as fleet memberships. The examples are intentionally small. Combine them with your own cluster registrations and fleet configuration.

Enable multi-cluster ingress with a config cluster

Teams running workloads across multiple GKE clusters often need a single ingress controller that routes traffic to services in any 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: "multiclusteringress",
    location: "global",
    spec: {
        multiclusteringress: {
            configMembership: membership.id,
        },
    },
});
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="multiclusteringress",
    location="global",
    spec={
        "multiclusteringress": {
            "config_membership": membership.id,
        },
    })
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
		}
		_, err = gkehub.NewFeature(ctx, "feature", &gkehub.FeatureArgs{
			Name:     pulumi.String("multiclusteringress"),
			Location: pulumi.String("global"),
			Spec: &gkehub.FeatureSpecArgs{
				Multiclusteringress: &gkehub.FeatureSpecMulticlusteringressArgs{
					ConfigMembership: membership.ID(),
				},
			},
		})
		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 = "multiclusteringress",
        Location = "global",
        Spec = new Gcp.GkeHub.Inputs.FeatureSpecArgs
        {
            Multiclusteringress = new Gcp.GkeHub.Inputs.FeatureSpecMulticlusteringressArgs
            {
                ConfigMembership = membership.Id,
            },
        },
    });

});
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.inputs.FeatureSpecArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureSpecMulticlusteringressArgs;
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("multiclusteringress")
            .location("global")
            .spec(FeatureSpecArgs.builder()
                .multiclusteringress(FeatureSpecMulticlusteringressArgs.builder()
                    .configMembership(membership.id())
                    .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: multiclusteringress
      location: global
      spec:
        multiclusteringress:
          configMembership: ${membership.id}

The multiclusteringress configuration designates one cluster as the configuration source through configMembership. Traffic flows to services across all fleet members, but ingress rules are managed from the config cluster. The name property must be “multiclusteringress” (immutable), and location is typically “global” for fleet-wide features.

Enable multi-cluster service discovery

Applications spanning multiple clusters need DNS-based service discovery to locate endpoints across the fleet without hardcoding addresses.

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

const feature = new gcp.gkehub.Feature("feature", {
    name: "multiclusterservicediscovery",
    location: "global",
    labels: {
        foo: "bar",
    },
});
import pulumi
import pulumi_gcp as gcp

feature = gcp.gkehub.Feature("feature",
    name="multiclusterservicediscovery",
    location="global",
    labels={
        "foo": "bar",
    })
package main

import (
	"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 {
		_, err := gkehub.NewFeature(ctx, "feature", &gkehub.FeatureArgs{
			Name:     pulumi.String("multiclusterservicediscovery"),
			Location: pulumi.String("global"),
			Labels: pulumi.StringMap{
				"foo": pulumi.String("bar"),
			},
		})
		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 feature = new Gcp.GkeHub.Feature("feature", new()
    {
        Name = "multiclusterservicediscovery",
        Location = "global",
        Labels = 
        {
            { "foo", "bar" },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.gkehub.Feature;
import com.pulumi.gcp.gkehub.FeatureArgs;
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 feature = new Feature("feature", FeatureArgs.builder()
            .name("multiclusterservicediscovery")
            .location("global")
            .labels(Map.of("foo", "bar"))
            .build());

    }
}
resources:
  feature:
    type: gcp:gkehub:Feature
    properties:
      name: multiclusterservicediscovery
      location: global
      labels:
        foo: bar

The name “multiclusterservicediscovery” enables cross-cluster DNS resolution. Services in any fleet member become discoverable via DNS queries. The labels property adds metadata for organization and filtering.

Collect default logs with copy mode

Fleet observability aggregates logs from all clusters into a central location for monitoring and troubleshooting.

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

const feature = new gcp.gkehub.Feature("feature", {
    name: "fleetobservability",
    location: "global",
    spec: {
        fleetobservability: {
            loggingConfig: {
                defaultConfig: {
                    mode: "COPY",
                },
            },
        },
    },
});
import pulumi
import pulumi_gcp as gcp

feature = gcp.gkehub.Feature("feature",
    name="fleetobservability",
    location="global",
    spec={
        "fleetobservability": {
            "logging_config": {
                "default_config": {
                    "mode": "COPY",
                },
            },
        },
    })
package main

import (
	"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 {
		_, err := gkehub.NewFeature(ctx, "feature", &gkehub.FeatureArgs{
			Name:     pulumi.String("fleetobservability"),
			Location: pulumi.String("global"),
			Spec: &gkehub.FeatureSpecArgs{
				Fleetobservability: &gkehub.FeatureSpecFleetobservabilityArgs{
					LoggingConfig: &gkehub.FeatureSpecFleetobservabilityLoggingConfigArgs{
						DefaultConfig: &gkehub.FeatureSpecFleetobservabilityLoggingConfigDefaultConfigArgs{
							Mode: pulumi.String("COPY"),
						},
					},
				},
			},
		})
		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 feature = new Gcp.GkeHub.Feature("feature", new()
    {
        Name = "fleetobservability",
        Location = "global",
        Spec = new Gcp.GkeHub.Inputs.FeatureSpecArgs
        {
            Fleetobservability = new Gcp.GkeHub.Inputs.FeatureSpecFleetobservabilityArgs
            {
                LoggingConfig = new Gcp.GkeHub.Inputs.FeatureSpecFleetobservabilityLoggingConfigArgs
                {
                    DefaultConfig = new Gcp.GkeHub.Inputs.FeatureSpecFleetobservabilityLoggingConfigDefaultConfigArgs
                    {
                        Mode = "COPY",
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.gkehub.Feature;
import com.pulumi.gcp.gkehub.FeatureArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureSpecArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureSpecFleetobservabilityArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureSpecFleetobservabilityLoggingConfigArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureSpecFleetobservabilityLoggingConfigDefaultConfigArgs;
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 feature = new Feature("feature", FeatureArgs.builder()
            .name("fleetobservability")
            .location("global")
            .spec(FeatureSpecArgs.builder()
                .fleetobservability(FeatureSpecFleetobservabilityArgs.builder()
                    .loggingConfig(FeatureSpecFleetobservabilityLoggingConfigArgs.builder()
                        .defaultConfig(FeatureSpecFleetobservabilityLoggingConfigDefaultConfigArgs.builder()
                            .mode("COPY")
                            .build())
                        .build())
                    .build())
                .build())
            .build());

    }
}
resources:
  feature:
    type: gcp:gkehub:Feature
    properties:
      name: fleetobservability
      location: global
      spec:
        fleetobservability:
          loggingConfig:
            defaultConfig:
              mode: COPY

Copy mode duplicates logs to both cluster-local and fleet-wide destinations. The loggingConfig.defaultConfig.mode property controls how logs flow: “COPY” preserves local logs while sending copies to the fleet project. This balances centralized visibility with cluster-local access.

Configure both default and scope-level logging

Some deployments need different log handling for cluster-wide logs versus scope-specific logs.

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

const feature = new gcp.gkehub.Feature("feature", {
    name: "fleetobservability",
    location: "global",
    spec: {
        fleetobservability: {
            loggingConfig: {
                defaultConfig: {
                    mode: "COPY",
                },
                fleetScopeLogsConfig: {
                    mode: "MOVE",
                },
            },
        },
    },
});
import pulumi
import pulumi_gcp as gcp

feature = gcp.gkehub.Feature("feature",
    name="fleetobservability",
    location="global",
    spec={
        "fleetobservability": {
            "logging_config": {
                "default_config": {
                    "mode": "COPY",
                },
                "fleet_scope_logs_config": {
                    "mode": "MOVE",
                },
            },
        },
    })
package main

import (
	"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 {
		_, err := gkehub.NewFeature(ctx, "feature", &gkehub.FeatureArgs{
			Name:     pulumi.String("fleetobservability"),
			Location: pulumi.String("global"),
			Spec: &gkehub.FeatureSpecArgs{
				Fleetobservability: &gkehub.FeatureSpecFleetobservabilityArgs{
					LoggingConfig: &gkehub.FeatureSpecFleetobservabilityLoggingConfigArgs{
						DefaultConfig: &gkehub.FeatureSpecFleetobservabilityLoggingConfigDefaultConfigArgs{
							Mode: pulumi.String("COPY"),
						},
						FleetScopeLogsConfig: &gkehub.FeatureSpecFleetobservabilityLoggingConfigFleetScopeLogsConfigArgs{
							Mode: pulumi.String("MOVE"),
						},
					},
				},
			},
		})
		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 feature = new Gcp.GkeHub.Feature("feature", new()
    {
        Name = "fleetobservability",
        Location = "global",
        Spec = new Gcp.GkeHub.Inputs.FeatureSpecArgs
        {
            Fleetobservability = new Gcp.GkeHub.Inputs.FeatureSpecFleetobservabilityArgs
            {
                LoggingConfig = new Gcp.GkeHub.Inputs.FeatureSpecFleetobservabilityLoggingConfigArgs
                {
                    DefaultConfig = new Gcp.GkeHub.Inputs.FeatureSpecFleetobservabilityLoggingConfigDefaultConfigArgs
                    {
                        Mode = "COPY",
                    },
                    FleetScopeLogsConfig = new Gcp.GkeHub.Inputs.FeatureSpecFleetobservabilityLoggingConfigFleetScopeLogsConfigArgs
                    {
                        Mode = "MOVE",
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.gkehub.Feature;
import com.pulumi.gcp.gkehub.FeatureArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureSpecArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureSpecFleetobservabilityArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureSpecFleetobservabilityLoggingConfigArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureSpecFleetobservabilityLoggingConfigDefaultConfigArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureSpecFleetobservabilityLoggingConfigFleetScopeLogsConfigArgs;
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 feature = new Feature("feature", FeatureArgs.builder()
            .name("fleetobservability")
            .location("global")
            .spec(FeatureSpecArgs.builder()
                .fleetobservability(FeatureSpecFleetobservabilityArgs.builder()
                    .loggingConfig(FeatureSpecFleetobservabilityLoggingConfigArgs.builder()
                        .defaultConfig(FeatureSpecFleetobservabilityLoggingConfigDefaultConfigArgs.builder()
                            .mode("COPY")
                            .build())
                        .fleetScopeLogsConfig(FeatureSpecFleetobservabilityLoggingConfigFleetScopeLogsConfigArgs.builder()
                            .mode("MOVE")
                            .build())
                        .build())
                    .build())
                .build())
            .build());

    }
}
resources:
  feature:
    type: gcp:gkehub:Feature
    properties:
      name: fleetobservability
      location: global
      spec:
        fleetobservability:
          loggingConfig:
            defaultConfig:
              mode: COPY
            fleetScopeLogsConfig:
              mode: MOVE

This configuration sets copy mode for default logs and move mode for scope logs. The fleetScopeLogsConfig property applies to logs tagged with specific scopes, while defaultConfig handles everything else. Move mode transfers logs without keeping local copies, reducing storage costs for scope-specific data.

Apply policy controller defaults across the fleet

Organizations enforcing compliance policies across many clusters use fleet-wide defaults to ensure consistent policy enforcement.

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

const feature = new gcp.gkehub.Feature("feature", {
    name: "policycontroller",
    location: "global",
    fleetDefaultMemberConfig: {
        policycontroller: {
            policyControllerHubConfig: {
                installSpec: "INSTALL_SPEC_ENABLED",
                exemptableNamespaces: ["foo"],
                policyContent: {
                    bundles: [{
                        bundle: "policy-essentials-v2022",
                        exemptedNamespaces: [
                            "foo",
                            "bar",
                        ],
                    }],
                    templateLibrary: {
                        installation: "ALL",
                    },
                },
                auditIntervalSeconds: 30,
                referentialRulesEnabled: true,
            },
        },
    },
});
import pulumi
import pulumi_gcp as gcp

feature = gcp.gkehub.Feature("feature",
    name="policycontroller",
    location="global",
    fleet_default_member_config={
        "policycontroller": {
            "policy_controller_hub_config": {
                "install_spec": "INSTALL_SPEC_ENABLED",
                "exemptable_namespaces": ["foo"],
                "policy_content": {
                    "bundles": [{
                        "bundle": "policy-essentials-v2022",
                        "exempted_namespaces": [
                            "foo",
                            "bar",
                        ],
                    }],
                    "template_library": {
                        "installation": "ALL",
                    },
                },
                "audit_interval_seconds": 30,
                "referential_rules_enabled": True,
            },
        },
    })
package main

import (
	"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 {
		_, err := gkehub.NewFeature(ctx, "feature", &gkehub.FeatureArgs{
			Name:     pulumi.String("policycontroller"),
			Location: pulumi.String("global"),
			FleetDefaultMemberConfig: &gkehub.FeatureFleetDefaultMemberConfigArgs{
				Policycontroller: &gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerArgs{
					PolicyControllerHubConfig: &gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigArgs{
						InstallSpec: pulumi.String("INSTALL_SPEC_ENABLED"),
						ExemptableNamespaces: pulumi.StringArray{
							pulumi.String("foo"),
						},
						PolicyContent: &gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentArgs{
							Bundles: gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentBundleArray{
								&gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentBundleArgs{
									Bundle: pulumi.String("policy-essentials-v2022"),
									ExemptedNamespaces: pulumi.StringArray{
										pulumi.String("foo"),
										pulumi.String("bar"),
									},
								},
							},
							TemplateLibrary: &gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentTemplateLibraryArgs{
								Installation: pulumi.String("ALL"),
							},
						},
						AuditIntervalSeconds:    pulumi.Int(30),
						ReferentialRulesEnabled: 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 feature = new Gcp.GkeHub.Feature("feature", new()
    {
        Name = "policycontroller",
        Location = "global",
        FleetDefaultMemberConfig = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigArgs
        {
            Policycontroller = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerArgs
            {
                PolicyControllerHubConfig = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigArgs
                {
                    InstallSpec = "INSTALL_SPEC_ENABLED",
                    ExemptableNamespaces = new[]
                    {
                        "foo",
                    },
                    PolicyContent = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentArgs
                    {
                        Bundles = new[]
                        {
                            new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentBundleArgs
                            {
                                Bundle = "policy-essentials-v2022",
                                ExemptedNamespaces = new[]
                                {
                                    "foo",
                                    "bar",
                                },
                            },
                        },
                        TemplateLibrary = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentTemplateLibraryArgs
                        {
                            Installation = "ALL",
                        },
                    },
                    AuditIntervalSeconds = 30,
                    ReferentialRulesEnabled = true,
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.gkehub.Feature;
import com.pulumi.gcp.gkehub.FeatureArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureFleetDefaultMemberConfigArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureFleetDefaultMemberConfigPolicycontrollerArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentTemplateLibraryArgs;
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 feature = new Feature("feature", FeatureArgs.builder()
            .name("policycontroller")
            .location("global")
            .fleetDefaultMemberConfig(FeatureFleetDefaultMemberConfigArgs.builder()
                .policycontroller(FeatureFleetDefaultMemberConfigPolicycontrollerArgs.builder()
                    .policyControllerHubConfig(FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigArgs.builder()
                        .installSpec("INSTALL_SPEC_ENABLED")
                        .exemptableNamespaces("foo")
                        .policyContent(FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentArgs.builder()
                            .bundles(FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentBundleArgs.builder()
                                .bundle("policy-essentials-v2022")
                                .exemptedNamespaces(                                
                                    "foo",
                                    "bar")
                                .build())
                            .templateLibrary(FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentTemplateLibraryArgs.builder()
                                .installation("ALL")
                                .build())
                            .build())
                        .auditIntervalSeconds(30)
                        .referentialRulesEnabled(true)
                        .build())
                    .build())
                .build())
            .build());

    }
}
resources:
  feature:
    type: gcp:gkehub:Feature
    properties:
      name: policycontroller
      location: global
      fleetDefaultMemberConfig:
        policycontroller:
          policyControllerHubConfig:
            installSpec: INSTALL_SPEC_ENABLED
            exemptableNamespaces:
              - foo
            policyContent:
              bundles:
                - bundle: policy-essentials-v2022
                  exemptedNamespaces:
                    - foo
                    - bar
              templateLibrary:
                installation: ALL
            auditIntervalSeconds: 30
            referentialRulesEnabled: true

The fleetDefaultMemberConfig applies to all clusters in the fleet automatically. The policyControllerHubConfig defines which policy bundles to enforce (like “policy-essentials-v2022”) and which namespaces to exempt. The installSpec property controls whether the controller is enabled, and exemptableNamespaces lists namespaces that can opt out of specific policies.

Configure policy controller with resource limits and monitoring

Production policy controller deployments often need custom resource allocation and monitoring integration to handle large fleets reliably.

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

const feature = new gcp.gkehub.Feature("feature", {
    name: "policycontroller",
    location: "global",
    fleetDefaultMemberConfig: {
        policycontroller: {
            policyControllerHubConfig: {
                installSpec: "INSTALL_SPEC_SUSPENDED",
                policyContent: {
                    bundles: [
                        {
                            bundle: "pci-dss-v3.2.1",
                            exemptedNamespaces: [
                                "baz",
                                "bar",
                            ],
                        },
                        {
                            bundle: "nist-sp-800-190",
                            exemptedNamespaces: [],
                        },
                    ],
                    templateLibrary: {
                        installation: "ALL",
                    },
                },
                constraintViolationLimit: 50,
                referentialRulesEnabled: true,
                logDeniesEnabled: true,
                mutationEnabled: true,
                deploymentConfigs: [
                    {
                        component: "admission",
                        replicaCount: 2,
                        podAffinity: "ANTI_AFFINITY",
                    },
                    {
                        component: "audit",
                        containerResources: {
                            limits: {
                                memory: "1Gi",
                                cpu: "1.5",
                            },
                            requests: {
                                memory: "500Mi",
                                cpu: "150m",
                            },
                        },
                        podTolerations: [{
                            key: "key1",
                            operator: "Equal",
                            value: "value1",
                            effect: "NoSchedule",
                        }],
                    },
                ],
                monitoring: {
                    backends: ["PROMETHEUS"],
                },
            },
        },
    },
});
import pulumi
import pulumi_gcp as gcp

feature = gcp.gkehub.Feature("feature",
    name="policycontroller",
    location="global",
    fleet_default_member_config={
        "policycontroller": {
            "policy_controller_hub_config": {
                "install_spec": "INSTALL_SPEC_SUSPENDED",
                "policy_content": {
                    "bundles": [
                        {
                            "bundle": "pci-dss-v3.2.1",
                            "exempted_namespaces": [
                                "baz",
                                "bar",
                            ],
                        },
                        {
                            "bundle": "nist-sp-800-190",
                            "exempted_namespaces": [],
                        },
                    ],
                    "template_library": {
                        "installation": "ALL",
                    },
                },
                "constraint_violation_limit": 50,
                "referential_rules_enabled": True,
                "log_denies_enabled": True,
                "mutation_enabled": True,
                "deployment_configs": [
                    {
                        "component": "admission",
                        "replica_count": 2,
                        "pod_affinity": "ANTI_AFFINITY",
                    },
                    {
                        "component": "audit",
                        "container_resources": {
                            "limits": {
                                "memory": "1Gi",
                                "cpu": "1.5",
                            },
                            "requests": {
                                "memory": "500Mi",
                                "cpu": "150m",
                            },
                        },
                        "pod_tolerations": [{
                            "key": "key1",
                            "operator": "Equal",
                            "value": "value1",
                            "effect": "NoSchedule",
                        }],
                    },
                ],
                "monitoring": {
                    "backends": ["PROMETHEUS"],
                },
            },
        },
    })
package main

import (
	"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 {
		_, err := gkehub.NewFeature(ctx, "feature", &gkehub.FeatureArgs{
			Name:     pulumi.String("policycontroller"),
			Location: pulumi.String("global"),
			FleetDefaultMemberConfig: &gkehub.FeatureFleetDefaultMemberConfigArgs{
				Policycontroller: &gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerArgs{
					PolicyControllerHubConfig: &gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigArgs{
						InstallSpec: pulumi.String("INSTALL_SPEC_SUSPENDED"),
						PolicyContent: &gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentArgs{
							Bundles: gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentBundleArray{
								&gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentBundleArgs{
									Bundle: pulumi.String("pci-dss-v3.2.1"),
									ExemptedNamespaces: pulumi.StringArray{
										pulumi.String("baz"),
										pulumi.String("bar"),
									},
								},
								&gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentBundleArgs{
									Bundle:             pulumi.String("nist-sp-800-190"),
									ExemptedNamespaces: pulumi.StringArray{},
								},
							},
							TemplateLibrary: &gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentTemplateLibraryArgs{
								Installation: pulumi.String("ALL"),
							},
						},
						ConstraintViolationLimit: pulumi.Int(50),
						ReferentialRulesEnabled:  pulumi.Bool(true),
						LogDeniesEnabled:         pulumi.Bool(true),
						MutationEnabled:          pulumi.Bool(true),
						DeploymentConfigs: gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigArray{
							&gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigArgs{
								Component:    pulumi.String("admission"),
								ReplicaCount: pulumi.Int(2),
								PodAffinity:  pulumi.String("ANTI_AFFINITY"),
							},
							&gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigArgs{
								Component: pulumi.String("audit"),
								ContainerResources: &gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigContainerResourcesArgs{
									Limits: &gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigContainerResourcesLimitsArgs{
										Memory: pulumi.String("1Gi"),
										Cpu:    pulumi.String("1.5"),
									},
									Requests: &gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigContainerResourcesRequestsArgs{
										Memory: pulumi.String("500Mi"),
										Cpu:    pulumi.String("150m"),
									},
								},
								PodTolerations: gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigPodTolerationArray{
									&gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigPodTolerationArgs{
										Key:      pulumi.String("key1"),
										Operator: pulumi.String("Equal"),
										Value:    pulumi.String("value1"),
										Effect:   pulumi.String("NoSchedule"),
									},
								},
							},
						},
						Monitoring: &gkehub.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigMonitoringArgs{
							Backends: pulumi.StringArray{
								pulumi.String("PROMETHEUS"),
							},
						},
					},
				},
			},
		})
		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 feature = new Gcp.GkeHub.Feature("feature", new()
    {
        Name = "policycontroller",
        Location = "global",
        FleetDefaultMemberConfig = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigArgs
        {
            Policycontroller = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerArgs
            {
                PolicyControllerHubConfig = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigArgs
                {
                    InstallSpec = "INSTALL_SPEC_SUSPENDED",
                    PolicyContent = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentArgs
                    {
                        Bundles = new[]
                        {
                            new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentBundleArgs
                            {
                                Bundle = "pci-dss-v3.2.1",
                                ExemptedNamespaces = new[]
                                {
                                    "baz",
                                    "bar",
                                },
                            },
                            new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentBundleArgs
                            {
                                Bundle = "nist-sp-800-190",
                                ExemptedNamespaces = new() { },
                            },
                        },
                        TemplateLibrary = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentTemplateLibraryArgs
                        {
                            Installation = "ALL",
                        },
                    },
                    ConstraintViolationLimit = 50,
                    ReferentialRulesEnabled = true,
                    LogDeniesEnabled = true,
                    MutationEnabled = true,
                    DeploymentConfigs = new[]
                    {
                        new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigArgs
                        {
                            Component = "admission",
                            ReplicaCount = 2,
                            PodAffinity = "ANTI_AFFINITY",
                        },
                        new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigArgs
                        {
                            Component = "audit",
                            ContainerResources = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigContainerResourcesArgs
                            {
                                Limits = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigContainerResourcesLimitsArgs
                                {
                                    Memory = "1Gi",
                                    Cpu = "1.5",
                                },
                                Requests = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigContainerResourcesRequestsArgs
                                {
                                    Memory = "500Mi",
                                    Cpu = "150m",
                                },
                            },
                            PodTolerations = new[]
                            {
                                new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigPodTolerationArgs
                                {
                                    Key = "key1",
                                    Operator = "Equal",
                                    Value = "value1",
                                    Effect = "NoSchedule",
                                },
                            },
                        },
                    },
                    Monitoring = new Gcp.GkeHub.Inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigMonitoringArgs
                    {
                        Backends = new[]
                        {
                            "PROMETHEUS",
                        },
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.gkehub.Feature;
import com.pulumi.gcp.gkehub.FeatureArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureFleetDefaultMemberConfigArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureFleetDefaultMemberConfigPolicycontrollerArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentTemplateLibraryArgs;
import com.pulumi.gcp.gkehub.inputs.FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigMonitoringArgs;
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 feature = new Feature("feature", FeatureArgs.builder()
            .name("policycontroller")
            .location("global")
            .fleetDefaultMemberConfig(FeatureFleetDefaultMemberConfigArgs.builder()
                .policycontroller(FeatureFleetDefaultMemberConfigPolicycontrollerArgs.builder()
                    .policyControllerHubConfig(FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigArgs.builder()
                        .installSpec("INSTALL_SPEC_SUSPENDED")
                        .policyContent(FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentArgs.builder()
                            .bundles(                            
                                FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentBundleArgs.builder()
                                    .bundle("pci-dss-v3.2.1")
                                    .exemptedNamespaces(                                    
                                        "baz",
                                        "bar")
                                    .build(),
                                FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentBundleArgs.builder()
                                    .bundle("nist-sp-800-190")
                                    .exemptedNamespaces()
                                    .build())
                            .templateLibrary(FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigPolicyContentTemplateLibraryArgs.builder()
                                .installation("ALL")
                                .build())
                            .build())
                        .constraintViolationLimit(50)
                        .referentialRulesEnabled(true)
                        .logDeniesEnabled(true)
                        .mutationEnabled(true)
                        .deploymentConfigs(                        
                            FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigArgs.builder()
                                .component("admission")
                                .replicaCount(2)
                                .podAffinity("ANTI_AFFINITY")
                                .build(),
                            FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigArgs.builder()
                                .component("audit")
                                .containerResources(FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigContainerResourcesArgs.builder()
                                    .limits(FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigContainerResourcesLimitsArgs.builder()
                                        .memory("1Gi")
                                        .cpu("1.5")
                                        .build())
                                    .requests(FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigContainerResourcesRequestsArgs.builder()
                                        .memory("500Mi")
                                        .cpu("150m")
                                        .build())
                                    .build())
                                .podTolerations(FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigDeploymentConfigPodTolerationArgs.builder()
                                    .key("key1")
                                    .operator("Equal")
                                    .value("value1")
                                    .effect("NoSchedule")
                                    .build())
                                .build())
                        .monitoring(FeatureFleetDefaultMemberConfigPolicycontrollerPolicyControllerHubConfigMonitoringArgs.builder()
                            .backends("PROMETHEUS")
                            .build())
                        .build())
                    .build())
                .build())
            .build());

    }
}
resources:
  feature:
    type: gcp:gkehub:Feature
    properties:
      name: policycontroller
      location: global
      fleetDefaultMemberConfig:
        policycontroller:
          policyControllerHubConfig:
            installSpec: INSTALL_SPEC_SUSPENDED
            policyContent:
              bundles:
                - bundle: pci-dss-v3.2.1
                  exemptedNamespaces:
                    - baz
                    - bar
                - bundle: nist-sp-800-190
                  exemptedNamespaces: []
              templateLibrary:
                installation: ALL
            constraintViolationLimit: 50
            referentialRulesEnabled: true
            logDeniesEnabled: true
            mutationEnabled: true
            deploymentConfigs:
              - component: admission
                replicaCount: 2
                podAffinity: ANTI_AFFINITY
              - component: audit
                containerResources:
                  limits:
                    memory: 1Gi
                    cpu: '1.5'
                  requests:
                    memory: 500Mi
                    cpu: 150m
                podTolerations:
                  - key: key1
                    operator: Equal
                    value: value1
                    effect: NoSchedule
            monitoring:
              backends:
                - PROMETHEUS

The deploymentConfigs property tunes resource allocation for admission and audit components. You can set CPU and memory limits, configure pod affinity rules, and add tolerations for specialized node pools. The monitoring.backends property enables Prometheus integration for observability. This configuration extends basic policy enforcement with production-grade resource management.

Beyond these examples

These snippets focus on specific fleet feature capabilities: multi-cluster ingress and service discovery, fleet observability and log aggregation, and policy controller and compliance enforcement. They’re intentionally minimal rather than full fleet management solutions.

The examples assume pre-existing infrastructure such as GKE clusters registered as fleet memberships, and fleet project and location configuration. They focus on enabling features rather than provisioning the underlying clusters.

To keep things focused, common fleet features are omitted, including:

  • Service mesh configuration (servicemesh feature)
  • Config management and GitOps (configmanagement feature)
  • Cluster upgrade orchestration (clusterupgrade feature)
  • RBAC role binding actuation (rbacrolebindingactuation feature)

These omissions are intentional: the goal is to illustrate how each fleet feature is wired, not provide drop-in fleet modules. See the GKE Hub Feature resource reference for all available configuration options.

Let's configure GCP GKE Hub Features

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Configuration & Feature Types
What types of Hub features can I create?
You can create features like multiclusteringress, multiclusterservicediscovery, servicemesh, fleetobservability, configmanagement, policycontroller, clusterupgrade, and rbacrolebindingactuation. Each feature type uses different configuration options in spec or fleetDefaultMemberConfig.
How do I configure Multi-Cluster Ingress?
Set name to multiclusteringress, location to global, and configure spec.multiclusteringress.configMembership with a membership ID that references your GKE cluster.
What's the difference between COPY and MOVE logging modes in Fleet Observability?
COPY duplicates logs to the destination, while MOVE relocates them. Configure these modes in spec.fleetobservability.loggingConfig for either defaultConfig or fleetScopeLogsConfig.
Immutability & Lifecycle
What properties can't I change after creating a feature?
The location, name, and project properties are immutable. Changing any of these will force recreation of the feature resource.
When do I need to reference a GKE cluster membership?
Multi-Cluster Ingress requires a configMembership reference to specify which cluster handles ingress configuration. Other features like Multi-Cluster Service Discovery don’t require membership references.
Labels & Metadata
Why aren't all my labels showing up in the labels field?
The labels field is non-authoritative and only manages labels present in your configuration. Use effectiveLabels to see all labels on the resource, including those set by other clients or services.
What's the difference between labels, effectiveLabels, and pulumiLabels?
labels contains only the labels you configure. pulumiLabels combines your labels with provider defaults. effectiveLabels shows all labels present on the resource in GCP, including those set outside Pulumi.

Using a different cloud?

Explore containers guides for other cloud providers: