Deploy GCP Anthos Attached Clusters

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

Attached clusters must already exist on their respective platforms (EKS, AKS, or generic CNCF-conformant) and have accessible OIDC issuer URLs. The examples are intentionally small. Combine them with your own cluster infrastructure and access policies.

Register an external cluster with minimal configuration

Teams running Kubernetes on AWS EKS, Azure AKS, or other platforms can register those clusters with Anthos to gain unified management and observability across environments.

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 (
	"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). The oidcConfig provides the issuer URL that GCP uses to validate Kubernetes Service Account tokens, enabling system workloads to authenticate back to Google Cloud. The platformVersion specifies the Anthos version, and fleet connects the cluster to a Fleet project for centralized management.

Configure RBAC, logging, and monitoring for production

Production clusters typically need access controls, observability, and security policies 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 (
	"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 cluster-admin permissions to specific users and groups. The loggingConfig enables system component and workload logs, while monitoringConfig activates managed Prometheus for metrics collection. The binaryAuthorization property enforces image signing policies. For clusters with private OIDC issuers, the jwks field provides the JSON Web Key Set data that GCP uses to validate tokens. The proxyConfig references a Kubernetes secret in the attached cluster that contains proxy settings.

Handle deletion when cluster is already removed

When the underlying Kubernetes cluster has been deleted outside of Pulumi, attempting to clean up the Anthos registration can fail with errors.

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 (
	"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 how Pulumi handles deletion failures. Setting it to DELETE_IGNORE_ERRORS allows the resource to be removed from Pulumi state even if the cluster is unreachable or already deleted, preventing stuck deployments during cleanup.

Beyond these examples

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

The examples assume pre-existing infrastructure such as Kubernetes clusters running on EKS, AKS, or other platforms, OIDC issuer URLs accessible from the cluster, and a GCP project with Fleet API enabled. 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
  • Workload Identity setup and configuration

These omissions are intentional: the goal is to illustrate how each attached cluster feature is wired, not provide drop-in multi-cloud 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 & Setup
What Kubernetes distributions are supported, and can I change them later?
Attached clusters support three distributions: eks (Amazon EKS), aks (Azure AKS), and generic (any CNCF conformant cluster). The distribution property is immutable, so changing it requires recreating the cluster.
How do I find valid platform versions for my cluster?
Use the gcp.container.getAttachedVersions data source with your location and project to retrieve available versions. The examples show accessing validVersions[0] for the latest version.
What properties can't be changed after cluster creation?
Four properties are immutable: distribution, location, name, and project. Modifying any of these requires recreating the cluster.
Authentication & Authorization
How do I configure OIDC for clusters with private issuers?
Clusters with public issuers only need issuerUrl in oidcConfig. Clusters with private issuers must provide both issuerUrl and jwks (base64-encoded JWKS JSON data), as shown in the full example.
How do I configure admin access to the cluster?
Use the authorization block to specify adminUsers (email addresses) and adminGroups (group email addresses) who can administer the cluster.
Resource Management
Why aren't all my annotations showing up in the annotations field?
The annotations field is non-authoritative and only manages annotations defined in your configuration. To see all annotations on the resource (including those set by other clients), use the effectiveAnnotations output property.
How can I ignore errors during cluster deletion?
Set deletionPolicy to DELETE_IGNORE_ERRORS instead of the default DELETE. This is useful when the underlying cluster may already be deleted or unreachable.
Deprecations & Warnings
Should I use securityPostureConfig for new clusters?
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: