The gcp:gkehub/membership:Membership resource, part of the Pulumi GCP provider, registers Kubernetes clusters with GKE Hub for centralized management and policy enforcement. This guide focuses on three capabilities: GKE cluster registration, regional metadata placement, and Workload Identity configuration.
Memberships reference existing GKE clusters and require the GKE Hub API enabled in your project. The examples are intentionally small. Combine them with your own cluster infrastructure and fleet policies.
Register a GKE cluster with labels
Teams managing multiple clusters use GKE Hub to register clusters for centralized visibility and policy enforcement. Labels organize clusters by environment or team.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const primary = new gcp.container.Cluster("primary", {
name: "basic-cluster",
location: "us-central1-a",
initialNodeCount: 1,
deletionProtection: true,
network: "default",
subnetwork: "default",
});
const membership = new gcp.gkehub.Membership("membership", {
membershipId: "basic",
endpoint: {
gkeCluster: {
resourceLink: pulumi.interpolate`//container.googleapis.com/${primary.id}`,
},
},
labels: {
env: "test",
},
});
import pulumi
import pulumi_gcp as gcp
primary = gcp.container.Cluster("primary",
name="basic-cluster",
location="us-central1-a",
initial_node_count=1,
deletion_protection=True,
network="default",
subnetwork="default")
membership = gcp.gkehub.Membership("membership",
membership_id="basic",
endpoint={
"gke_cluster": {
"resource_link": primary.id.apply(lambda id: f"//container.googleapis.com/{id}"),
},
},
labels={
"env": "test",
})
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 {
primary, err := container.NewCluster(ctx, "primary", &container.ClusterArgs{
Name: pulumi.String("basic-cluster"),
Location: pulumi.String("us-central1-a"),
InitialNodeCount: pulumi.Int(1),
DeletionProtection: pulumi.Bool(true),
Network: pulumi.String("default"),
Subnetwork: pulumi.String("default"),
})
if err != nil {
return err
}
_, err = gkehub.NewMembership(ctx, "membership", &gkehub.MembershipArgs{
MembershipId: pulumi.String("basic"),
Endpoint: &gkehub.MembershipEndpointArgs{
GkeCluster: &gkehub.MembershipEndpointGkeClusterArgs{
ResourceLink: primary.ID().ApplyT(func(id string) (string, error) {
return fmt.Sprintf("//container.googleapis.com/%v", id), nil
}).(pulumi.StringOutput),
},
},
Labels: pulumi.StringMap{
"env": pulumi.String("test"),
},
})
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 primary = new Gcp.Container.Cluster("primary", new()
{
Name = "basic-cluster",
Location = "us-central1-a",
InitialNodeCount = 1,
DeletionProtection = true,
Network = "default",
Subnetwork = "default",
});
var membership = new Gcp.GkeHub.Membership("membership", new()
{
MembershipId = "basic",
Endpoint = new Gcp.GkeHub.Inputs.MembershipEndpointArgs
{
GkeCluster = new Gcp.GkeHub.Inputs.MembershipEndpointGkeClusterArgs
{
ResourceLink = primary.Id.Apply(id => $"//container.googleapis.com/{id}"),
},
},
Labels =
{
{ "env", "test" },
},
});
});
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 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 primary = new Cluster("primary", ClusterArgs.builder()
.name("basic-cluster")
.location("us-central1-a")
.initialNodeCount(1)
.deletionProtection(true)
.network("default")
.subnetwork("default")
.build());
var membership = new Membership("membership", MembershipArgs.builder()
.membershipId("basic")
.endpoint(MembershipEndpointArgs.builder()
.gkeCluster(MembershipEndpointGkeClusterArgs.builder()
.resourceLink(primary.id().applyValue(_id -> String.format("//container.googleapis.com/%s", _id)))
.build())
.build())
.labels(Map.of("env", "test"))
.build());
}
}
resources:
primary:
type: gcp:container:Cluster
properties:
name: basic-cluster
location: us-central1-a
initialNodeCount: 1
deletionProtection: true
network: default
subnetwork: default
membership:
type: gcp:gkehub:Membership
properties:
membershipId: basic
endpoint:
gkeCluster:
resourceLink: //container.googleapis.com/${primary.id}
labels:
env: test
The membershipId provides a stable identifier for the cluster within the fleet. The endpoint.gkeCluster.resourceLink connects the membership to the actual GKE cluster using its resource path. Labels add organizational metadata that fleet policies can reference.
Register a cluster in a specific region
Some organizations need to control where membership metadata is stored for compliance or latency reasons.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const primary = new gcp.container.Cluster("primary", {
name: "basic-cluster",
location: "us-central1-a",
initialNodeCount: 1,
deletionProtection: false,
network: "default",
subnetwork: "default",
});
const membership = new gcp.gkehub.Membership("membership", {
membershipId: "basic",
location: "us-west1",
endpoint: {
gkeCluster: {
resourceLink: pulumi.interpolate`//container.googleapis.com/${primary.id}`,
},
},
});
import pulumi
import pulumi_gcp as gcp
primary = gcp.container.Cluster("primary",
name="basic-cluster",
location="us-central1-a",
initial_node_count=1,
deletion_protection=False,
network="default",
subnetwork="default")
membership = gcp.gkehub.Membership("membership",
membership_id="basic",
location="us-west1",
endpoint={
"gke_cluster": {
"resource_link": primary.id.apply(lambda id: f"//container.googleapis.com/{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 {
primary, err := container.NewCluster(ctx, "primary", &container.ClusterArgs{
Name: pulumi.String("basic-cluster"),
Location: pulumi.String("us-central1-a"),
InitialNodeCount: pulumi.Int(1),
DeletionProtection: pulumi.Bool(false),
Network: pulumi.String("default"),
Subnetwork: pulumi.String("default"),
})
if err != nil {
return err
}
_, err = gkehub.NewMembership(ctx, "membership", &gkehub.MembershipArgs{
MembershipId: pulumi.String("basic"),
Location: pulumi.String("us-west1"),
Endpoint: &gkehub.MembershipEndpointArgs{
GkeCluster: &gkehub.MembershipEndpointGkeClusterArgs{
ResourceLink: primary.ID().ApplyT(func(id string) (string, error) {
return fmt.Sprintf("//container.googleapis.com/%v", id), nil
}).(pulumi.StringOutput),
},
},
})
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 primary = new Gcp.Container.Cluster("primary", new()
{
Name = "basic-cluster",
Location = "us-central1-a",
InitialNodeCount = 1,
DeletionProtection = false,
Network = "default",
Subnetwork = "default",
});
var membership = new Gcp.GkeHub.Membership("membership", new()
{
MembershipId = "basic",
Location = "us-west1",
Endpoint = new Gcp.GkeHub.Inputs.MembershipEndpointArgs
{
GkeCluster = new Gcp.GkeHub.Inputs.MembershipEndpointGkeClusterArgs
{
ResourceLink = primary.Id.Apply(id => $"//container.googleapis.com/{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 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 primary = new Cluster("primary", ClusterArgs.builder()
.name("basic-cluster")
.location("us-central1-a")
.initialNodeCount(1)
.deletionProtection(false)
.network("default")
.subnetwork("default")
.build());
var membership = new Membership("membership", MembershipArgs.builder()
.membershipId("basic")
.location("us-west1")
.endpoint(MembershipEndpointArgs.builder()
.gkeCluster(MembershipEndpointGkeClusterArgs.builder()
.resourceLink(primary.id().applyValue(_id -> String.format("//container.googleapis.com/%s", _id)))
.build())
.build())
.build());
}
}
resources:
primary:
type: gcp:container:Cluster
properties:
name: basic-cluster
location: us-central1-a
initialNodeCount: 1
deletionProtection: false
network: default
subnetwork: default
membership:
type: gcp:gkehub:Membership
properties:
membershipId: basic
location: us-west1
endpoint:
gkeCluster:
resourceLink: //container.googleapis.com/${primary.id}
The location property places membership metadata in a specific GCP region rather than the default global location. This extends basic registration with explicit regional control while maintaining the same endpoint configuration pattern.
Configure workload identity federation
Workload Identity allows Kubernetes service accounts to authenticate as Google service accounts without managing keys.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const primary = new gcp.container.Cluster("primary", {
name: "basic-cluster",
location: "us-central1-a",
initialNodeCount: 1,
workloadIdentityConfig: {
workloadPool: "my-project-name.svc.id.goog",
},
deletionProtection: true,
network: "default",
subnetwork: "default",
});
const membership = new gcp.gkehub.Membership("membership", {
membershipId: "basic",
endpoint: {
gkeCluster: {
resourceLink: primary.id,
},
},
authority: {
issuer: pulumi.interpolate`https://container.googleapis.com/v1/${primary.id}`,
},
});
import pulumi
import pulumi_gcp as gcp
primary = gcp.container.Cluster("primary",
name="basic-cluster",
location="us-central1-a",
initial_node_count=1,
workload_identity_config={
"workload_pool": "my-project-name.svc.id.goog",
},
deletion_protection=True,
network="default",
subnetwork="default")
membership = gcp.gkehub.Membership("membership",
membership_id="basic",
endpoint={
"gke_cluster": {
"resource_link": primary.id,
},
},
authority={
"issuer": primary.id.apply(lambda id: f"https://container.googleapis.com/v1/{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 {
primary, err := container.NewCluster(ctx, "primary", &container.ClusterArgs{
Name: pulumi.String("basic-cluster"),
Location: pulumi.String("us-central1-a"),
InitialNodeCount: pulumi.Int(1),
WorkloadIdentityConfig: &container.ClusterWorkloadIdentityConfigArgs{
WorkloadPool: pulumi.String("my-project-name.svc.id.goog"),
},
DeletionProtection: pulumi.Bool(true),
Network: pulumi.String("default"),
Subnetwork: pulumi.String("default"),
})
if err != nil {
return err
}
_, err = gkehub.NewMembership(ctx, "membership", &gkehub.MembershipArgs{
MembershipId: pulumi.String("basic"),
Endpoint: &gkehub.MembershipEndpointArgs{
GkeCluster: &gkehub.MembershipEndpointGkeClusterArgs{
ResourceLink: primary.ID(),
},
},
Authority: &gkehub.MembershipAuthorityArgs{
Issuer: primary.ID().ApplyT(func(id string) (string, error) {
return fmt.Sprintf("https://container.googleapis.com/v1/%v", id), nil
}).(pulumi.StringOutput),
},
})
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 primary = new Gcp.Container.Cluster("primary", new()
{
Name = "basic-cluster",
Location = "us-central1-a",
InitialNodeCount = 1,
WorkloadIdentityConfig = new Gcp.Container.Inputs.ClusterWorkloadIdentityConfigArgs
{
WorkloadPool = "my-project-name.svc.id.goog",
},
DeletionProtection = true,
Network = "default",
Subnetwork = "default",
});
var membership = new Gcp.GkeHub.Membership("membership", new()
{
MembershipId = "basic",
Endpoint = new Gcp.GkeHub.Inputs.MembershipEndpointArgs
{
GkeCluster = new Gcp.GkeHub.Inputs.MembershipEndpointGkeClusterArgs
{
ResourceLink = primary.Id,
},
},
Authority = new Gcp.GkeHub.Inputs.MembershipAuthorityArgs
{
Issuer = primary.Id.Apply(id => $"https://container.googleapis.com/v1/{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.container.inputs.ClusterWorkloadIdentityConfigArgs;
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.inputs.MembershipAuthorityArgs;
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 primary = new Cluster("primary", ClusterArgs.builder()
.name("basic-cluster")
.location("us-central1-a")
.initialNodeCount(1)
.workloadIdentityConfig(ClusterWorkloadIdentityConfigArgs.builder()
.workloadPool("my-project-name.svc.id.goog")
.build())
.deletionProtection(true)
.network("default")
.subnetwork("default")
.build());
var membership = new Membership("membership", MembershipArgs.builder()
.membershipId("basic")
.endpoint(MembershipEndpointArgs.builder()
.gkeCluster(MembershipEndpointGkeClusterArgs.builder()
.resourceLink(primary.id())
.build())
.build())
.authority(MembershipAuthorityArgs.builder()
.issuer(primary.id().applyValue(_id -> String.format("https://container.googleapis.com/v1/%s", _id)))
.build())
.build());
}
}
resources:
primary:
type: gcp:container:Cluster
properties:
name: basic-cluster
location: us-central1-a
initialNodeCount: 1
workloadIdentityConfig:
workloadPool: my-project-name.svc.id.goog
deletionProtection: true
network: default
subnetwork: default
membership:
type: gcp:gkehub:Membership
properties:
membershipId: basic
endpoint:
gkeCluster:
resourceLink: ${primary.id}
authority:
issuer: https://container.googleapis.com/v1/${primary.id}
The authority.issuer property establishes the trust relationship between the cluster and Google’s identity system. The cluster must have workloadIdentityConfig.workloadPool configured to enable this federation. This builds on basic registration by adding secure service-to-service authentication.
Beyond these examples
These snippets focus on specific membership-level features: cluster registration and metadata, regional placement, and workload identity federation. They’re intentionally minimal rather than full fleet management solutions.
The examples reference pre-existing infrastructure such as GKE clusters with appropriate configuration, and a GCP project with GKE Hub API enabled. They focus on registering clusters rather than provisioning the clusters themselves.
To keep things focused, common membership patterns are omitted, including:
- Non-GKE cluster registration (external clusters)
- Multi-cluster service mesh configuration
- Fleet-level policy management
- Cluster upgrade coordination
These omissions are intentional: the goal is to illustrate how each membership feature is wired, not provide drop-in fleet modules. See the GKE Hub Membership resource reference for all available configuration options.
Let's register GCP GKE Hub Cluster Memberships
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Immutability & Updates
membershipId, project, endpoint, and location properties are immutable and require resource recreation if changed.labels field is non-authoritative and only manages labels present in your configuration. Use effectiveLabels to see all labels present on the resource in GCP, including those set by other clients and services.Configuration & Setup
endpoint.gkeCluster.resourceLink with the cluster’s resource link in the format //container.googleapis.com/${clusterId}.authority.issuer to the cluster’s OIDC issuer URL in the format https://container.googleapis.com/v1/${clusterId}. The cluster must have workloadIdentityConfig enabled.location can be set independently from the cluster location. For example, you can create a membership in us-west1 for a cluster running in us-central1-a.global if not specified.project property is not provided.Using a different cloud?
Explore containers guides for other cloud providers: