Deploy GCP Anthos Attached Clusters

The gcp:container/attachedCluster:AttachedCluster resource, part of the Pulumi GCP provider, registers external Kubernetes clusters with Google Cloud for Anthos Fleet management. This guide focuses on three capabilities: basic cluster registration with OIDC authentication, RBAC and observability configuration, and deletion policy handling.

Attached clusters connect existing Kubernetes infrastructure (EKS, AKS, or generic CNCF-conformant clusters) to Google Cloud. The examples are intentionally small. Combine them with your own cluster infrastructure, OIDC providers, and Fleet projects.

Register an external cluster with minimal configuration

Teams running Kubernetes on AWS EKS, Azure AKS, or other platforms often need to connect those clusters to Google Cloud for unified management through Anthos Fleet.

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

const project = gcp.organizations.getProject({});
const versions = project.then(project => gcp.container.getAttachedVersions({
    location: "us-west1",
    project: project.projectId,
}));
const primary = new gcp.container.AttachedCluster("primary", {
    name: "basic",
    location: "us-west1",
    project: project.then(project => project.projectId),
    description: "Test cluster",
    distribution: "aks",
    oidcConfig: {
        issuerUrl: "https://oidc.issuer.url",
    },
    platformVersion: versions.then(versions => versions.validVersions?.[0]),
    fleet: {
        project: project.then(project => `projects/${project.number}`),
    },
});
import pulumi
import pulumi_gcp as gcp

project = gcp.organizations.get_project()
versions = gcp.container.get_attached_versions(location="us-west1",
    project=project.project_id)
primary = gcp.container.AttachedCluster("primary",
    name="basic",
    location="us-west1",
    project=project.project_id,
    description="Test cluster",
    distribution="aks",
    oidc_config={
        "issuer_url": "https://oidc.issuer.url",
    },
    platform_version=versions.valid_versions[0],
    fleet={
        "project": f"projects/{project.number}",
    })
package main

import (
	"fmt"

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		project, err := organizations.LookupProject(ctx, &organizations.LookupProjectArgs{}, nil)
		if err != nil {
			return err
		}
		versions, err := container.GetAttachedVersions(ctx, &container.GetAttachedVersionsArgs{
			Location: "us-west1",
			Project:  project.ProjectId,
		}, nil)
		if err != nil {
			return err
		}
		_, err = container.NewAttachedCluster(ctx, "primary", &container.AttachedClusterArgs{
			Name:         pulumi.String("basic"),
			Location:     pulumi.String("us-west1"),
			Project:      pulumi.String(project.ProjectId),
			Description:  pulumi.String("Test cluster"),
			Distribution: pulumi.String("aks"),
			OidcConfig: &container.AttachedClusterOidcConfigArgs{
				IssuerUrl: pulumi.String("https://oidc.issuer.url"),
			},
			PlatformVersion: pulumi.String(versions.ValidVersions[0]),
			Fleet: &container.AttachedClusterFleetArgs{
				Project: pulumi.Sprintf("projects/%v", project.Number),
			},
		})
		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 project = Gcp.Organizations.GetProject.Invoke();

    var versions = Gcp.Container.GetAttachedVersions.Invoke(new()
    {
        Location = "us-west1",
        Project = project.Apply(getProjectResult => getProjectResult.ProjectId),
    });

    var primary = new Gcp.Container.AttachedCluster("primary", new()
    {
        Name = "basic",
        Location = "us-west1",
        Project = project.Apply(getProjectResult => getProjectResult.ProjectId),
        Description = "Test cluster",
        Distribution = "aks",
        OidcConfig = new Gcp.Container.Inputs.AttachedClusterOidcConfigArgs
        {
            IssuerUrl = "https://oidc.issuer.url",
        },
        PlatformVersion = versions.Apply(getAttachedVersionsResult => getAttachedVersionsResult.ValidVersions[0]),
        Fleet = new Gcp.Container.Inputs.AttachedClusterFleetArgs
        {
            Project = $"projects/{project.Apply(getProjectResult => getProjectResult.Number)}",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
import com.pulumi.gcp.container.ContainerFunctions;
import com.pulumi.gcp.container.inputs.GetAttachedVersionsArgs;
import com.pulumi.gcp.container.AttachedCluster;
import com.pulumi.gcp.container.AttachedClusterArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterOidcConfigArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterFleetArgs;
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) {
        final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
            .build());

        final var versions = ContainerFunctions.getAttachedVersions(GetAttachedVersionsArgs.builder()
            .location("us-west1")
            .project(project.projectId())
            .build());

        var primary = new AttachedCluster("primary", AttachedClusterArgs.builder()
            .name("basic")
            .location("us-west1")
            .project(project.projectId())
            .description("Test cluster")
            .distribution("aks")
            .oidcConfig(AttachedClusterOidcConfigArgs.builder()
                .issuerUrl("https://oidc.issuer.url")
                .build())
            .platformVersion(versions.validVersions()[0])
            .fleet(AttachedClusterFleetArgs.builder()
                .project(String.format("projects/%s", project.number()))
                .build())
            .build());

    }
}
resources:
  primary:
    type: gcp:container:AttachedCluster
    properties:
      name: basic
      location: us-west1
      project: ${project.projectId}
      description: Test cluster
      distribution: aks
      oidcConfig:
        issuerUrl: https://oidc.issuer.url
      platformVersion: ${versions.validVersions[0]}
      fleet:
        project: projects/${project.number}
variables:
  project:
    fn::invoke:
      function: gcp:organizations:getProject
      arguments: {}
  versions:
    fn::invoke:
      function: gcp:container:getAttachedVersions
      arguments:
        location: us-west1
        project: ${project.projectId}

The distribution property identifies the cluster type (eks, aks, or generic for CNCF-conformant clusters). The oidcConfig provides the issuer URL that Google Cloud uses to validate Kubernetes Service Account tokens. The platformVersion specifies the Anthos version, and fleet connects the cluster to a Google Cloud project for centralized management.

Configure RBAC, logging, monitoring, and security policies

Production clusters typically require access controls, observability, and security enforcement beyond basic registration.

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

const project = gcp.organizations.getProject({});
const versions = project.then(project => gcp.container.getAttachedVersions({
    location: "us-west1",
    project: project.projectId,
}));
const primary = new gcp.container.AttachedCluster("primary", {
    name: "basic",
    project: project.then(project => project.projectId),
    location: "us-west1",
    description: "Test cluster",
    distribution: "aks",
    annotations: {
        "label-one": "value-one",
    },
    authorization: {
        adminUsers: [
            "user1@example.com",
            "user2@example.com",
        ],
        adminGroups: [
            "group1@example.com",
            "group2@example.com",
        ],
    },
    oidcConfig: {
        issuerUrl: "https://oidc.issuer.url",
        jwks: std.base64encode({
            input: "{\"keys\":[{\"use\":\"sig\",\"kty\":\"RSA\",\"kid\":\"testid\",\"alg\":\"RS256\",\"n\":\"somedata\",\"e\":\"AQAB\"}]}",
        }).then(invoke => invoke.result),
    },
    platformVersion: versions.then(versions => versions.validVersions?.[0]),
    fleet: {
        project: project.then(project => `projects/${project.number}`),
    },
    loggingConfig: {
        componentConfig: {
            enableComponents: [
                "SYSTEM_COMPONENTS",
                "WORKLOADS",
            ],
        },
    },
    monitoringConfig: {
        managedPrometheusConfig: {
            enabled: true,
        },
    },
    binaryAuthorization: {
        evaluationMode: "PROJECT_SINGLETON_POLICY_ENFORCE",
    },
    proxyConfig: {
        kubernetesSecret: {
            name: "proxy-config",
            namespace: "default",
        },
    },
});
import pulumi
import pulumi_gcp as gcp
import pulumi_std as std

project = gcp.organizations.get_project()
versions = gcp.container.get_attached_versions(location="us-west1",
    project=project.project_id)
primary = gcp.container.AttachedCluster("primary",
    name="basic",
    project=project.project_id,
    location="us-west1",
    description="Test cluster",
    distribution="aks",
    annotations={
        "label-one": "value-one",
    },
    authorization={
        "admin_users": [
            "user1@example.com",
            "user2@example.com",
        ],
        "admin_groups": [
            "group1@example.com",
            "group2@example.com",
        ],
    },
    oidc_config={
        "issuer_url": "https://oidc.issuer.url",
        "jwks": std.base64encode(input="{\"keys\":[{\"use\":\"sig\",\"kty\":\"RSA\",\"kid\":\"testid\",\"alg\":\"RS256\",\"n\":\"somedata\",\"e\":\"AQAB\"}]}").result,
    },
    platform_version=versions.valid_versions[0],
    fleet={
        "project": f"projects/{project.number}",
    },
    logging_config={
        "component_config": {
            "enable_components": [
                "SYSTEM_COMPONENTS",
                "WORKLOADS",
            ],
        },
    },
    monitoring_config={
        "managed_prometheus_config": {
            "enabled": True,
        },
    },
    binary_authorization={
        "evaluation_mode": "PROJECT_SINGLETON_POLICY_ENFORCE",
    },
    proxy_config={
        "kubernetes_secret": {
            "name": "proxy-config",
            "namespace": "default",
        },
    })
package main

import (
	"fmt"

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		project, err := organizations.LookupProject(ctx, &organizations.LookupProjectArgs{}, nil)
		if err != nil {
			return err
		}
		versions, err := container.GetAttachedVersions(ctx, &container.GetAttachedVersionsArgs{
			Location: "us-west1",
			Project:  project.ProjectId,
		}, nil)
		if err != nil {
			return err
		}
		invokeBase64encode, err := std.Base64encode(ctx, &std.Base64encodeArgs{
			Input: "{\"keys\":[{\"use\":\"sig\",\"kty\":\"RSA\",\"kid\":\"testid\",\"alg\":\"RS256\",\"n\":\"somedata\",\"e\":\"AQAB\"}]}",
		}, nil)
		if err != nil {
			return err
		}
		_, err = container.NewAttachedCluster(ctx, "primary", &container.AttachedClusterArgs{
			Name:         pulumi.String("basic"),
			Project:      pulumi.String(project.ProjectId),
			Location:     pulumi.String("us-west1"),
			Description:  pulumi.String("Test cluster"),
			Distribution: pulumi.String("aks"),
			Annotations: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
			Authorization: &container.AttachedClusterAuthorizationArgs{
				AdminUsers: pulumi.StringArray{
					pulumi.String("user1@example.com"),
					pulumi.String("user2@example.com"),
				},
				AdminGroups: pulumi.StringArray{
					pulumi.String("group1@example.com"),
					pulumi.String("group2@example.com"),
				},
			},
			OidcConfig: &container.AttachedClusterOidcConfigArgs{
				IssuerUrl: pulumi.String("https://oidc.issuer.url"),
				Jwks:      pulumi.String(invokeBase64encode.Result),
			},
			PlatformVersion: pulumi.String(versions.ValidVersions[0]),
			Fleet: &container.AttachedClusterFleetArgs{
				Project: pulumi.Sprintf("projects/%v", project.Number),
			},
			LoggingConfig: &container.AttachedClusterLoggingConfigArgs{
				ComponentConfig: &container.AttachedClusterLoggingConfigComponentConfigArgs{
					EnableComponents: pulumi.StringArray{
						pulumi.String("SYSTEM_COMPONENTS"),
						pulumi.String("WORKLOADS"),
					},
				},
			},
			MonitoringConfig: &container.AttachedClusterMonitoringConfigArgs{
				ManagedPrometheusConfig: &container.AttachedClusterMonitoringConfigManagedPrometheusConfigArgs{
					Enabled: pulumi.Bool(true),
				},
			},
			BinaryAuthorization: &container.AttachedClusterBinaryAuthorizationArgs{
				EvaluationMode: pulumi.String("PROJECT_SINGLETON_POLICY_ENFORCE"),
			},
			ProxyConfig: &container.AttachedClusterProxyConfigArgs{
				KubernetesSecret: &container.AttachedClusterProxyConfigKubernetesSecretArgs{
					Name:      pulumi.String("proxy-config"),
					Namespace: pulumi.String("default"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
using Std = Pulumi.Std;

return await Deployment.RunAsync(() => 
{
    var project = Gcp.Organizations.GetProject.Invoke();

    var versions = Gcp.Container.GetAttachedVersions.Invoke(new()
    {
        Location = "us-west1",
        Project = project.Apply(getProjectResult => getProjectResult.ProjectId),
    });

    var primary = new Gcp.Container.AttachedCluster("primary", new()
    {
        Name = "basic",
        Project = project.Apply(getProjectResult => getProjectResult.ProjectId),
        Location = "us-west1",
        Description = "Test cluster",
        Distribution = "aks",
        Annotations = 
        {
            { "label-one", "value-one" },
        },
        Authorization = new Gcp.Container.Inputs.AttachedClusterAuthorizationArgs
        {
            AdminUsers = new[]
            {
                "user1@example.com",
                "user2@example.com",
            },
            AdminGroups = new[]
            {
                "group1@example.com",
                "group2@example.com",
            },
        },
        OidcConfig = new Gcp.Container.Inputs.AttachedClusterOidcConfigArgs
        {
            IssuerUrl = "https://oidc.issuer.url",
            Jwks = Std.Base64encode.Invoke(new()
            {
                Input = "{\"keys\":[{\"use\":\"sig\",\"kty\":\"RSA\",\"kid\":\"testid\",\"alg\":\"RS256\",\"n\":\"somedata\",\"e\":\"AQAB\"}]}",
            }).Apply(invoke => invoke.Result),
        },
        PlatformVersion = versions.Apply(getAttachedVersionsResult => getAttachedVersionsResult.ValidVersions[0]),
        Fleet = new Gcp.Container.Inputs.AttachedClusterFleetArgs
        {
            Project = $"projects/{project.Apply(getProjectResult => getProjectResult.Number)}",
        },
        LoggingConfig = new Gcp.Container.Inputs.AttachedClusterLoggingConfigArgs
        {
            ComponentConfig = new Gcp.Container.Inputs.AttachedClusterLoggingConfigComponentConfigArgs
            {
                EnableComponents = new[]
                {
                    "SYSTEM_COMPONENTS",
                    "WORKLOADS",
                },
            },
        },
        MonitoringConfig = new Gcp.Container.Inputs.AttachedClusterMonitoringConfigArgs
        {
            ManagedPrometheusConfig = new Gcp.Container.Inputs.AttachedClusterMonitoringConfigManagedPrometheusConfigArgs
            {
                Enabled = true,
            },
        },
        BinaryAuthorization = new Gcp.Container.Inputs.AttachedClusterBinaryAuthorizationArgs
        {
            EvaluationMode = "PROJECT_SINGLETON_POLICY_ENFORCE",
        },
        ProxyConfig = new Gcp.Container.Inputs.AttachedClusterProxyConfigArgs
        {
            KubernetesSecret = new Gcp.Container.Inputs.AttachedClusterProxyConfigKubernetesSecretArgs
            {
                Name = "proxy-config",
                Namespace = "default",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
import com.pulumi.gcp.container.ContainerFunctions;
import com.pulumi.gcp.container.inputs.GetAttachedVersionsArgs;
import com.pulumi.gcp.container.AttachedCluster;
import com.pulumi.gcp.container.AttachedClusterArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterAuthorizationArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterOidcConfigArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterFleetArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterLoggingConfigArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterLoggingConfigComponentConfigArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterMonitoringConfigArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterMonitoringConfigManagedPrometheusConfigArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterBinaryAuthorizationArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterProxyConfigArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterProxyConfigKubernetesSecretArgs;
import com.pulumi.std.StdFunctions;
import com.pulumi.std.inputs.Base64encodeArgs;
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) {
        final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
            .build());

        final var versions = ContainerFunctions.getAttachedVersions(GetAttachedVersionsArgs.builder()
            .location("us-west1")
            .project(project.projectId())
            .build());

        var primary = new AttachedCluster("primary", AttachedClusterArgs.builder()
            .name("basic")
            .project(project.projectId())
            .location("us-west1")
            .description("Test cluster")
            .distribution("aks")
            .annotations(Map.of("label-one", "value-one"))
            .authorization(AttachedClusterAuthorizationArgs.builder()
                .adminUsers(                
                    "user1@example.com",
                    "user2@example.com")
                .adminGroups(                
                    "group1@example.com",
                    "group2@example.com")
                .build())
            .oidcConfig(AttachedClusterOidcConfigArgs.builder()
                .issuerUrl("https://oidc.issuer.url")
                .jwks(StdFunctions.base64encode(Base64encodeArgs.builder()
                    .input("{\"keys\":[{\"use\":\"sig\",\"kty\":\"RSA\",\"kid\":\"testid\",\"alg\":\"RS256\",\"n\":\"somedata\",\"e\":\"AQAB\"}]}")
                    .build()).result())
                .build())
            .platformVersion(versions.validVersions()[0])
            .fleet(AttachedClusterFleetArgs.builder()
                .project(String.format("projects/%s", project.number()))
                .build())
            .loggingConfig(AttachedClusterLoggingConfigArgs.builder()
                .componentConfig(AttachedClusterLoggingConfigComponentConfigArgs.builder()
                    .enableComponents(                    
                        "SYSTEM_COMPONENTS",
                        "WORKLOADS")
                    .build())
                .build())
            .monitoringConfig(AttachedClusterMonitoringConfigArgs.builder()
                .managedPrometheusConfig(AttachedClusterMonitoringConfigManagedPrometheusConfigArgs.builder()
                    .enabled(true)
                    .build())
                .build())
            .binaryAuthorization(AttachedClusterBinaryAuthorizationArgs.builder()
                .evaluationMode("PROJECT_SINGLETON_POLICY_ENFORCE")
                .build())
            .proxyConfig(AttachedClusterProxyConfigArgs.builder()
                .kubernetesSecret(AttachedClusterProxyConfigKubernetesSecretArgs.builder()
                    .name("proxy-config")
                    .namespace("default")
                    .build())
                .build())
            .build());

    }
}
resources:
  primary:
    type: gcp:container:AttachedCluster
    properties:
      name: basic
      project: ${project.projectId}
      location: us-west1
      description: Test cluster
      distribution: aks
      annotations:
        label-one: value-one
      authorization:
        adminUsers:
          - user1@example.com
          - user2@example.com
        adminGroups:
          - group1@example.com
          - group2@example.com
      oidcConfig:
        issuerUrl: https://oidc.issuer.url
        jwks:
          fn::invoke:
            function: std:base64encode
            arguments:
              input: '{"keys":[{"use":"sig","kty":"RSA","kid":"testid","alg":"RS256","n":"somedata","e":"AQAB"}]}'
            return: result
      platformVersion: ${versions.validVersions[0]}
      fleet:
        project: projects/${project.number}
      loggingConfig:
        componentConfig:
          enableComponents:
            - SYSTEM_COMPONENTS
            - WORKLOADS
      monitoringConfig:
        managedPrometheusConfig:
          enabled: true
      binaryAuthorization:
        evaluationMode: PROJECT_SINGLETON_POLICY_ENFORCE
      proxyConfig:
        kubernetesSecret:
          name: proxy-config
          namespace: default
variables:
  project:
    fn::invoke:
      function: gcp:organizations:getProject
      arguments: {}
  versions:
    fn::invoke:
      function: gcp:container:getAttachedVersions
      arguments:
        location: us-west1
        project: ${project.projectId}

The authorization block grants admin access to specific users and groups. The loggingConfig enables component and workload logging, while monitoringConfig activates managed Prometheus. The binaryAuthorization property enforces image signing policies. For clusters with private OIDC issuers, the jwks field provides the JSON Web Key Set data that Google Cloud uses to validate tokens. The proxyConfig references a Kubernetes secret containing proxy settings.

Handle deletion with error suppression

When decommissioning clusters, deletion operations may encounter errors if the underlying cluster is unreachable.

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

const project = gcp.organizations.getProject({});
const versions = project.then(project => gcp.container.getAttachedVersions({
    location: "us-west1",
    project: project.projectId,
}));
const primary = new gcp.container.AttachedCluster("primary", {
    name: "basic",
    location: "us-west1",
    project: project.then(project => project.projectId),
    description: "Test cluster",
    distribution: "aks",
    oidcConfig: {
        issuerUrl: "https://oidc.issuer.url",
    },
    platformVersion: versions.then(versions => versions.validVersions?.[0]),
    fleet: {
        project: project.then(project => `projects/${project.number}`),
    },
    deletionPolicy: "DELETE_IGNORE_ERRORS",
});
import pulumi
import pulumi_gcp as gcp

project = gcp.organizations.get_project()
versions = gcp.container.get_attached_versions(location="us-west1",
    project=project.project_id)
primary = gcp.container.AttachedCluster("primary",
    name="basic",
    location="us-west1",
    project=project.project_id,
    description="Test cluster",
    distribution="aks",
    oidc_config={
        "issuer_url": "https://oidc.issuer.url",
    },
    platform_version=versions.valid_versions[0],
    fleet={
        "project": f"projects/{project.number}",
    },
    deletion_policy="DELETE_IGNORE_ERRORS")
package main

import (
	"fmt"

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		project, err := organizations.LookupProject(ctx, &organizations.LookupProjectArgs{}, nil)
		if err != nil {
			return err
		}
		versions, err := container.GetAttachedVersions(ctx, &container.GetAttachedVersionsArgs{
			Location: "us-west1",
			Project:  project.ProjectId,
		}, nil)
		if err != nil {
			return err
		}
		_, err = container.NewAttachedCluster(ctx, "primary", &container.AttachedClusterArgs{
			Name:         pulumi.String("basic"),
			Location:     pulumi.String("us-west1"),
			Project:      pulumi.String(project.ProjectId),
			Description:  pulumi.String("Test cluster"),
			Distribution: pulumi.String("aks"),
			OidcConfig: &container.AttachedClusterOidcConfigArgs{
				IssuerUrl: pulumi.String("https://oidc.issuer.url"),
			},
			PlatformVersion: pulumi.String(versions.ValidVersions[0]),
			Fleet: &container.AttachedClusterFleetArgs{
				Project: pulumi.Sprintf("projects/%v", project.Number),
			},
			DeletionPolicy: pulumi.String("DELETE_IGNORE_ERRORS"),
		})
		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 project = Gcp.Organizations.GetProject.Invoke();

    var versions = Gcp.Container.GetAttachedVersions.Invoke(new()
    {
        Location = "us-west1",
        Project = project.Apply(getProjectResult => getProjectResult.ProjectId),
    });

    var primary = new Gcp.Container.AttachedCluster("primary", new()
    {
        Name = "basic",
        Location = "us-west1",
        Project = project.Apply(getProjectResult => getProjectResult.ProjectId),
        Description = "Test cluster",
        Distribution = "aks",
        OidcConfig = new Gcp.Container.Inputs.AttachedClusterOidcConfigArgs
        {
            IssuerUrl = "https://oidc.issuer.url",
        },
        PlatformVersion = versions.Apply(getAttachedVersionsResult => getAttachedVersionsResult.ValidVersions[0]),
        Fleet = new Gcp.Container.Inputs.AttachedClusterFleetArgs
        {
            Project = $"projects/{project.Apply(getProjectResult => getProjectResult.Number)}",
        },
        DeletionPolicy = "DELETE_IGNORE_ERRORS",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
import com.pulumi.gcp.container.ContainerFunctions;
import com.pulumi.gcp.container.inputs.GetAttachedVersionsArgs;
import com.pulumi.gcp.container.AttachedCluster;
import com.pulumi.gcp.container.AttachedClusterArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterOidcConfigArgs;
import com.pulumi.gcp.container.inputs.AttachedClusterFleetArgs;
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) {
        final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
            .build());

        final var versions = ContainerFunctions.getAttachedVersions(GetAttachedVersionsArgs.builder()
            .location("us-west1")
            .project(project.projectId())
            .build());

        var primary = new AttachedCluster("primary", AttachedClusterArgs.builder()
            .name("basic")
            .location("us-west1")
            .project(project.projectId())
            .description("Test cluster")
            .distribution("aks")
            .oidcConfig(AttachedClusterOidcConfigArgs.builder()
                .issuerUrl("https://oidc.issuer.url")
                .build())
            .platformVersion(versions.validVersions()[0])
            .fleet(AttachedClusterFleetArgs.builder()
                .project(String.format("projects/%s", project.number()))
                .build())
            .deletionPolicy("DELETE_IGNORE_ERRORS")
            .build());

    }
}
resources:
  primary:
    type: gcp:container:AttachedCluster
    properties:
      name: basic
      location: us-west1
      project: ${project.projectId}
      description: Test cluster
      distribution: aks
      oidcConfig:
        issuerUrl: https://oidc.issuer.url
      platformVersion: ${versions.validVersions[0]}
      fleet:
        project: projects/${project.number}
      deletionPolicy: DELETE_IGNORE_ERRORS
variables:
  project:
    fn::invoke:
      function: gcp:organizations:getProject
      arguments: {}
  versions:
    fn::invoke:
      function: gcp:container:getAttachedVersions
      arguments:
        location: us-west1
        project: ${project.projectId}

The deletionPolicy property controls cleanup behavior. Setting it to DELETE_IGNORE_ERRORS allows Pulumi to complete the deletion even if the cluster is already gone or network issues prevent normal cleanup operations.

Beyond these examples

These snippets focus on specific attached cluster features: cluster registration and Fleet integration, RBAC and observability configuration, and deletion policy handling. They’re intentionally minimal rather than full cluster deployments.

The examples reference pre-existing infrastructure such as existing Kubernetes clusters (EKS, AKS, or generic CNCF-conformant), OIDC issuer endpoints with optional JWKS data, GCP projects for Fleet registration, and Kubernetes secrets for proxy configuration (when used). They focus on registering the cluster rather than provisioning the underlying Kubernetes infrastructure.

To keep things focused, common attached cluster patterns are omitted, including:

  • Annotations for metadata and labels
  • Security posture configuration (deprecated)
  • Custom proxy configurations beyond basic secret reference
  • Error handling and reconciliation monitoring

These omissions are intentional: the goal is to illustrate how each attached cluster feature is wired, not provide drop-in cluster management modules. See the AttachedCluster resource reference for all available configuration options.

Let's deploy GCP Anthos Attached Clusters

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Cluster Configuration & Immutability
What properties can't I change after creating the cluster?
The distribution, location, name, and project properties are immutable and cannot be changed after cluster creation. Changing these will force recreation of the cluster.
What Kubernetes distributions can I attach?
You can attach three distribution types: eks (AWS EKS), aks (Azure AKS), or generic (any CNCF conformant cluster).
OIDC & Authentication
What's the difference between public and private OIDC issuers?
Clusters with public issuers only need to specify issuerUrl. Clusters with private issuers must provide both issuerUrl and jwks (the JWKS JSON data, typically base64-encoded).
Annotations & Metadata
Why doesn't my annotations field show all annotations on the cluster?
The annotations field is non-authoritative and only manages annotations present in your configuration. To see all annotations on the resource, use the effectiveAnnotations output property.
Deletion & Lifecycle
How do I delete a cluster even if there are errors?
Set deletionPolicy to DELETE_IGNORE_ERRORS. This tells the API to proceed with deletion despite any errors encountered.
Deprecated Features
Should I use securityPostureConfig?
No, securityPostureConfig is deprecated and will be removed in a future major release. Avoid using it in new configurations.

Using a different cloud?

Explore containers guides for other cloud providers: