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 FREEFrequently Asked Questions
Configuration & Feature Types
multiclusteringress, multiclusterservicediscovery, servicemesh, fleetobservability, configmanagement, policycontroller, clusterupgrade, and rbacrolebindingactuation. Each feature type uses different configuration options in spec or fleetDefaultMemberConfig.name to multiclusteringress, location to global, and configure spec.multiclusteringress.configMembership with a membership ID that references your GKE cluster.COPY duplicates logs to the destination, while MOVE relocates them. Configure these modes in spec.fleetobservability.loggingConfig for either defaultConfig or fleetScopeLogsConfig.Immutability & Lifecycle
location, name, and project properties are immutable. Changing any of these will force recreation of the feature resource.configMembership reference to specify which cluster handles ingress configuration. Other features like Multi-Cluster Service Discovery don’t require membership references.Labels & Metadata
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.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: