Run an opinionated Google Kubernetes Engine cluster with Pulumi

Switch variant

Choose a different cloud.

Provision an opinionated Google Kubernetes Engine cluster on the Pulumi landing-zone network, preinstall External Secrets Operator plus GKE Gateway API and Node Auto Provisioning (NAP) through a reusable component, and export a kubeconfig downstream workloads can consume.

Before you deploy: deploy the GCP landing zone first.

This blueprint consumes shared network, identity, and secret-store outputs from the GCP landing-zone stack in the same cloud account. If you haven't deployed one yet, follow Build a GCP landing zone and come back with the stack name.

Download blueprint

Get this GCP blueprint project as a zip. Switch Pulumi language here to keep the download aligned with the install commands and blueprint program on the page.

Download the TypeScript blueprint with the matching Pulumi program, dependency files, and README.

Download TypeScript blueprint

Download the Python blueprint with the matching Pulumi program, dependency files, and README.

Download Python blueprint

Download the Go blueprint with the matching Pulumi program, dependency files, and README.

Download Go blueprint

What this guide covers

A production-shaped managed Kubernetes blueprint that consumes the Pulumi landing-zone stack and ships with the controllers most teams install by hand on day one. One Pulumi stack provisions the cluster, the add-ons workloads expect, the workload-identity wiring downstream stacks need, and outputs they can consume by name.

The blueprint covers:

  • one Pulumi stack that provisions a managed Google Kubernetes Engine cluster inside the landing-zone VPC (VPC-native)
  • a small system node pool sized for the in-cluster controllers, with Node Auto Provisioning (NAP) handling every workload node on demand
  • pinned installs of External Secrets Operator and GKE Gateway API through Helm, plus the cloud-side data-plane resources Pulumi provisions as part of the same stack
  • Workload Identity Federation for GKE wired end-to-end for every service account the add-ons use, so pods call cloud APIs with short-lived tokens only
  • restricted Pod Security Admission labels on the add-on namespaces from the first deploy
  • a reusable Cluster component so other Pulumi projects can provision the same cluster shape in their own stacks
  • a Pulumi ESC environment and StackReference snippets every workload stack can import by name

Everything the blueprint creates is additive, so you can bring your own add-ons, node pools, or workloads on top without touching the module.

What gets deployed

In one Pulumi stack on GCP this blueprint provisions:

  • Cluster control plane: a managed Google Kubernetes Engine cluster at Kubernetes version 1.35 (Regular channel) with Workload Identity Federation for GKE turned on so pods call cloud APIs with short-lived tokens instead of long-lived credentials.
  • System node pool: 1 e2-standard-4 instance(s) sized for the in-cluster controllers (External Secrets Operator, the ingress controller, and the cloud-native autoscaler itself). Every additional workload node is launched by Node Auto Provisioning (NAP) on demand.
  • Add-ons:
    • External Secrets Operator chart v2.3.0 installed in the external-secrets namespace with workload-identity-backed access to Google Secret Manager.
    • GKE Gateway API wired for Layer-7 ingress: the in-cluster controller is installed through Helm and the cloud-side data-plane service is provisioned by Pulumi so workload stacks can drop Ingress / Gateway / HTTPRoute resources on the first deploy.
    • Node Auto Provisioning (NAP) configured to launch workload nodes on demand with scoped IAM/identity and the landing-zone network.
  • Workload Identity: one identity per controller service account, scoped to a single namespace + service-account pair so no other pod can assume it.
  • Pod Security Admission: the restricted profile is enforced on the external-secrets and ingress-controller namespaces so privileged containers cannot land there by default.

Every resource is annotated with pulumi-stack, landing-zone, solution-family, cloud, and language labels/tags so workload stacks can filter them later. Cluster control-plane logs ship to the cloud-native audit destination the landing-zone stack already wires up (CloudWatch on AWS, Log Analytics on Azure, Cloud Logging on GCP).

On GCP

The blueprint uses Google Kubernetes Engine in Standard mode on the Regular release channel, the landing-zone VPC (VPC-native) with secondary IP ranges the landing-zone stack already carved out for pods and services, and GKE Standard node pools with Node Auto Provisioning so a small system pool handles the controllers and Node Auto Provisioning handles every workload node afterwards. Workload Identity Federation for GKE is turned on so pods receive short-lived tokens for the scoped Google service accounts each controller needs.

The first deployment creates:

  • one GKE Standard cluster at Kubernetes 1.35 (Regular channel) with Workload Identity Federation enabled (<project>.svc.id.goog), Gateway API turned on (CHANNEL_STANDARD), shielded nodes, and Node Auto Provisioning with OPTIMIZE_UTILIZATION
  • one system node pool of 1 e2-standard-4 VMs with workloadMetadataConfig.mode = GKE_METADATA so pods on those nodes pick up Workload Identity
  • dedicated Google service accounts for External Secrets Operator and the GKE Gateway controller, each bound to their Kubernetes service account via roles/iam.workloadIdentityUser with a narrow serviceAccount:<PROJECT>.svc.id.goog[<ns>/<sa>] member
  • a Pod Security Admission restricted label on the add-on namespaces (external-secrets, gateway-system) so privileged workloads cannot land there
  • the External Secrets Operator Helm release wired to Google Secret Manager through the landing-zone secret store, with iam.gke.io/gcp-service-account annotated on the service account
  • a gateway-controller service account in gateway-system already annotated for Workload Identity so workload stacks can drop Gateway + HTTPRoute resources using the built-in gke-l7-global-external-managed class without extra setup

Quickstart

Deploy the landing-zone stack first, then point this stack at it. The landing-zone stack owns the shared primitives this cluster plugs into: the landing-zone VPC (VPC-native) the nodes run on, the customer-managed encryption key the control plane uses, the deployer identity that needs kubectl access, and the Google Secret Manager instance External Secrets Operator reads from. Keeping those in a separate stack lets one team own the account foundation while many teams stand up their own GKE clusters against it, and destroying a cluster never tears down the network other stacks depend on. This stack reads those outputs over a StackReference and fails fast if any are missing, so a missing landing-zone stack is the first thing pulumi up complains about.

  1. Make sure the Pulumi landing-zone stack for this cloud is already up. If not, follow the GCP landing-zone guide before coming back.
  2. Download the example zip at the top of the page and unzip it.
  3. Open a terminal in the extracted project root.
  4. Install the Pulumi dependencies for the language you want to use:
npm install
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
go mod tidy
  1. For a first local test, keep using whichever GCP credentials already work in your shell. If you want a shared or repeatable setup, use the Pulumi ESC section below before continuing.
  2. Create the stack, tell it which landing-zone stack to consume, and deploy:
pulumi login
pulumi stack init dev
pulumi config set gcp:project my-project-id
pulumi config set gcp:region us-central1
pulumi config set landingZoneStack <your-org>/landing-zone/dev
pulumi up
  1. When the update finishes, export the kubeconfig and verify the cluster:
pulumi stack output kubeconfig --show-secrets > kubeconfig.yaml
KUBECONFIG=./kubeconfig.yaml kubectl get nodes
KUBECONFIG=./kubeconfig.yaml kubectl get pods -A

You should see the system nodes Ready and all three controllers (external-secrets, aws-load-balancer-controller, karpenter) running.

Prerequisites

  • a Pulumi account and the Pulumi CLI installed
  • the Pulumi landing-zone stack already deployed in this GCP account
  • kubectl on your path
  • Helm 3.14 or newer (the blueprint uses the Pulumi Helm Release resource; Helm on your machine is only required if you later want to helm into the cluster by hand)
  • a Google Cloud project where the Pulumi landing-zone stack is already deployed and you have permission to create GKE clusters, service accounts, IAM bindings, and Secret Manager resources
  • Node.js 20 or newer and npm

Consume the landing-zone stack

This stack reads the outputs it needs from the landing-zone stack through a StackReference. For GCP:

  • networkName, clusterSubnetName, podsRangeName, servicesRangeName - the VPC-native network plumbing
  • deployerServiceAccountEmail - impersonated by Node Auto Provisioning for new nodes
  • secretsStore - the Google Secret Manager store External Secrets Operator will read from

Set which landing-zone stack to read:

pulumi config set landingZoneStack <your-org>/landing-zone/dev

The blueprint resolves that config value into a pulumi.StackReference at runtime and fails fast if any output it needs is missing. If you want to run this blueprint against a network you already manage, replace the StackReference block in the entrypoint with the ids you already have - the Cluster component does not care where those values come from.

Set up credentials with Pulumi ESC

Before you run pulumi up, configure Pulumi ESC so your stack receives short-lived GCP credentials through dynamic login credentials.

If you already have working GCP credentials in your shell and only want a quick local test, you can skip this section. The landing-zone family has a longer walkthrough that applies here verbatim; reuse the same ESC environment between landing-zone and Google Kubernetes Engine stacks so cluster upgrades run with the same deployer identity that created the network.

Step 1: Create or update an ESC environment

imports:
  - <your-org>/base
values:
  gcp:
    login:
      fn::open::gcp-login:
        project: 123456789012
        oidc:
          workloadPoolId: pulumi-esc
          providerId: pulumi-esc
          serviceAccount: pulumi-esc@example-project.iam.gserviceaccount.com
  environmentVariables:
    GOOGLE_CLOUD_PROJECT: ${gcp.login.project}
    GOOGLE_OAUTH_ACCESS_TOKEN: ${gcp.login.accessToken}
  pulumiConfig:
    gcp:project: my-project-id

Step 2: Attach the environment to your stack

In Pulumi.dev.yaml, add:

environment:
  - <your-org>/<your-environment>

Pulumi picks up the environment automatically on pulumi preview, pulumi up, and pulumi destroy. You do not need to run esc open <your-org>/<your-environment> first.

What you get in the download

The downloadable example zip includes:

  • index.ts as the Pulumi entrypoint
  • components/cluster.ts as the reusable Cluster module
  • package.json and tsconfig.json for the root Pulumi project
  • README.md with the same commands you will see on this page
  • index.ts as the Pulumi entrypoint
  • components/cluster.ts as the reusable Cluster module
  • package.json and tsconfig.json for the root Pulumi project
  • __main__.py as the Pulumi entrypoint
  • components/cluster.py as the reusable Cluster module
  • requirements.txt for the root Pulumi project
  • main.go as the Pulumi entrypoint
  • cluster/cluster.go as the reusable Cluster module
  • go.mod for the root Pulumi project

The entrypoint stays small: it loads the landing-zone outputs, reads a handful of config values, and instantiates the reusable Cluster component. The component file is where the cluster shape, add-on installs, and IRSA bindings live.

Deploy with Pulumi

Run these from the extracted project root.

Step 1: Install the root Pulumi dependencies for the language you want to use

npm install
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
go mod tidy

Step 2: Create a Pulumi stack and point it at your landing-zone stack

pulumi login
pulumi stack init dev
pulumi config set gcp:project my-project-id
pulumi config set gcp:region us-central1
pulumi config set landingZoneStack <your-org>/landing-zone/dev

If you already created the stack, pulumi stack select dev instead.

Step 3: Deploy

pulumi up

Approve the preview when Pulumi asks.

The first run creates the GKE Standard cluster with Workload Identity Federation enabled, the system node pool with GKE_METADATA, dedicated Google service accounts for External Secrets Operator and the Gateway controller, their roles/iam.workloadIdentityUser bindings, and the External Secrets Operator Helm release. Gateway API is enabled at the cluster level and the default gke-l7-global-external-managed class is ready for workload stacks to use. Expect 10-15 minutes on a cold project.

Pulumi imports the ESC environment automatically through the environment: reference in your stack config. You do not need esc open <your-org>/<your-environment> before pulumi up.

Step 4: Verify the cluster

pulumi stack output kubeconfig --show-secrets > kubeconfig.yaml
KUBECONFIG=./kubeconfig.yaml kubectl get nodes
KUBECONFIG=./kubeconfig.yaml kubectl -n external-secrets rollout status deploy/external-secrets
KUBECONFIG=./kubeconfig.yaml kubectl get gatewayclass gke-l7-global-external-managed

Controllers should report successfully rolled out (or Established for the GKE gateway class) once healthy.

Stack outputs

Every variant exports the same top-level shape so downstream Pulumi projects can consume the cluster the same way regardless of cloud. Run pulumi stack output --show-secrets after pulumi up to see values.

Common across AWS, Azure, and GCP:

  • kubeconfig (Pulumi secret) - authenticated kubeconfig you can feed into new pulumi.Provider("kubernetes", { kubeconfig })
  • clusterName - the provider-assigned cluster name
  • clusterEndpoint - the control-plane API endpoint
  • clusterCertificateAuthority - base64 CA cert, useful when the downstream stack builds its own kubeconfig
  • escEnvironment - the Pulumi ESC environment name workload stacks import by reference

Google Kubernetes Engine-specific:

  • workloadIdentityPool - <project>.svc.id.goog, the pool downstream Workload Identity bindings target
  • externalSecretsServiceAccountEmail - the Google service account the ESO Kubernetes service account maps to
  • gatewayServiceAccountEmail - the Google service account the Gateway API controller maps to

Add-ons

What is installed

Every variant installs the same three things, with cloud-appropriate wiring:

  • External Secrets Operator (chart v2.3.0, namespace external-secrets) syncs secrets from Google Secret Manager into Kubernetes Secret objects. Its service account uses Workload Identity Federation for GKE so the operator authenticates with short-lived tokens.
  • GKE Gateway API is the Layer-7 entry point this cluster will answer Ingress / Gateway / HTTPRoute resources on.
  • Node Auto Provisioning (NAP) handles workload-node launches. The system pool stays small; every additional node is launched by the autoscaler when a pending pod cannot fit.

The GCP variant turns on Node Auto Provisioning on the cluster itself (clusterAutoscaling.enabled = true, autoProvisioningDefaults carries the shielded-node + auto-repair defaults), so there is no separate Karpenter Helm release to install. Gateway API is enabled at the cluster level (gatewayApiConfig.channel: CHANNEL_STANDARD), the built-in gke-l7-global-external-managed gateway class is available immediately, and Pulumi pre-creates a gateway-controller service account in gateway-system annotated with a dedicated Google service account through Workload Identity.

Pod Security Admission

The add-on namespaces (external-secrets, plus the ingress-controller namespace for this cloud) are labelled with pod-security.kubernetes.io/enforce: restricted from the first deploy, matching the Kubernetes project’s recommended baseline for platform add-ons. Drop application workloads in new namespaces with your own PSA labels so the cluster never starts with a “default is permissive” story.

Add-on controls

Each add-on has a config flag. Disable any of them at pulumi up time:

pulumi config set enableExternalSecrets false
pulumi config set enableIngressGateway false

Node Auto Provisioning is controlled by the cluster’s clusterAutoscaling block, not by a Pulumi config flag.

Keeping an add-on disabled skips the Helm release and the identity resources that support it, so nothing orphans in your account. You can re-enable later and pulumi up again.

Add another add-on

The Cluster component exposes the in-cluster Kubernetes provider as an output. From the same program you can drop in additional kubernetes.helm.v3.Release resources against that provider and they will install on the same cluster alongside the blueprint add-ons. Keep workload-identity bindings inside the component if they need access to cloud APIs so the audit story stays consistent.

What the blueprint does NOT install

Intentionally out of scope for the first deploy: a full observability stack (Prometheus / Grafana / Loki) and a GitOps controller (Flux / Argo CD). Both are worth adding early - follow the pattern above or add them as dedicated families later.

Consume the cluster from workload stacks

Once the stack is up, every Pulumi workload project in the same GCP account can deploy into the cluster. Two patterns, pick whichever fits your team.

Pattern 1: Pulumi ESC environment

The stack attaches a Pulumi ESC environment (escEnvironment output). Downstream projects import it with one line in their stack config:

environment:
  - your-org/gcp-kubernetes-dev

After that, a kubernetes.Provider instantiated from pulumi.Config().requireSecret("kubeconfig") talks directly to this cluster.

Pattern 2: StackReference

If you prefer explicit wiring, use a StackReference:

import * as pulumi from "@pulumi/pulumi";
import * as k8s from "@pulumi/kubernetes";

const cluster = new pulumi.StackReference("your-org/kubernetes/dev");
const kubeconfig = cluster.requireOutput("kubeconfig") as pulumi.Output<string>;

const provider = new k8s.Provider("workload", { kubeconfig });
import pulumi
import pulumi_kubernetes as k8s

cluster = pulumi.StackReference("your-org/kubernetes/dev")
kubeconfig = cluster.require_output("kubeconfig")

provider = k8s.Provider("workload", kubeconfig=kubeconfig)
cluster, err := pulumi.NewStackReference(ctx, "your-org/kubernetes/dev", nil)
if err != nil {
    return err
}
kubeconfig := cluster.GetStringOutput(pulumi.String("kubeconfig"))

provider, err := kubernetes.NewProvider(ctx, "workload", &kubernetes.ProviderArgs{
    Kubeconfig: kubeconfig,
})
if err != nil {
    return err
}

Running workloads on Node Auto Provisioning (NAP)

Every variant launches nodes on demand; you do not need to manage node pools manually for application workloads.

Node Auto Provisioning reads pending pods directly. Use standard nodeSelector / nodeAffinity (including cloud.google.com/gke-spot: "true" for spot) to influence placement. Node pools are created on demand per pod profile.

Using External Secrets

Create SecretStore (or ClusterSecretStore) and ExternalSecret resources that point at Google Secret Manager.

Provider: gcpsm. Authenticate with workloadIdentity - the external-secrets service account is already annotated with iam.gke.io/gcp-service-account. Project defaults to the stack’s gcp:project.

Set up CI/CD with Pulumi Deployments

A managed Google Kubernetes Engine cluster is something you want updated from a tracked source, not from a laptop. Pulumi Deployments runs pulumi up from your GitHub repository whenever you merge to a branch.

What you will configure in Pulumi Deployments for this project:

  • the Git repository and branch holding the unzipped blueprint
  • the stack name (for example your-org/gcp-kubernetes/dev)
  • the root dependency command for the language you picked (npm install)
  • the Pulumi ESC environment reference, so Deployments receives the same short-lived credentials as your local run
  • the landingZoneStack config value so Deployments knows which landing-zone stack to consume

Once Deployments is wired up, land add-on upgrades, Kubernetes version bumps, and node-pool changes through PRs. Workload stacks that consume this cluster pick up the new outputs automatically on their next pulumi up.

Blueprint Pulumi program

The blueprint keeps the entrypoint tight: it reads landing-zone outputs, configures the cluster, and instantiates the reusable Cluster component.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
import { Cluster } from "./components/cluster";

const config = new pulumi.Config();
const landingZoneStackName = config.require("landingZoneStack");
const clusterVersion = config.get("clusterVersion") ?? "1.35 (Regular channel)";
const releaseChannel = config.get("releaseChannel") ?? "REGULAR";
const enableExternalSecrets = config.getBoolean("enableExternalSecrets") ?? true;
const enableIngressGateway = config.getBoolean("enableIngressGateway") ?? true;

const gcpConfig = new pulumi.Config("gcp");
const project = gcpConfig.require("project");
const region = gcpConfig.require("region");

const landingZone = new pulumi.StackReference(landingZoneStackName);
const networkName = landingZone.requireOutput("networkName") as pulumi.Output<string>;
const subnetName = landingZone.requireOutput("clusterSubnetName") as pulumi.Output<string>;
const podsRangeName = landingZone.requireOutput("podsRangeName") as pulumi.Output<string>;
const servicesRangeName = landingZone.requireOutput("servicesRangeName") as pulumi.Output<string>;
const deployerServiceAccountEmail = landingZone.requireOutput("deployerServiceAccountEmail") as pulumi.Output<string>;
const secretName = landingZone.requireOutput("secretsStore") as pulumi.Output<string>;

const clusterName = `${pulumi.getStack()}-gke`;

const cluster = new Cluster("platform", {
    clusterName,
    project,
    region,
    network: networkName,
    subnetwork: subnetName,
    podsRangeName,
    servicesRangeName,
    deployerServiceAccountEmail,
    secretName,
    version: clusterVersion,
    releaseChannel,
    enableExternalSecrets,
    enableIngressGateway,
    externalSecretsChartVersion: "2.3.0",
    labels: {
        environment: pulumi.getStack(),
        "solution-family": "kubernetes",
        cloud: "gcp",
        language: "typescript",
    },
});

export const kubeconfig = cluster.kubeconfig;
export const clusterNameOut = clusterName;
export const clusterEndpoint = cluster.clusterEndpoint;
export const clusterCertificateAuthority = cluster.clusterCertificateAuthority;
export const workloadIdentityPool = cluster.workloadIdentityPool;
export const externalSecretsServiceAccountEmail = cluster.externalSecretsServiceAccountEmail;
export const gatewayServiceAccountEmail = cluster.gatewayServiceAccountEmail;
export const escEnvironment = `${pulumi.getStack()}-gke`;
import pulumi
from components import Cluster, ClusterArgs

config = pulumi.Config()
landing_zone_stack_name = config.require("landingZoneStack")
cluster_version = config.get("clusterVersion") or "1.35 (Regular channel)"
release_channel = config.get("releaseChannel") or "REGULAR"
enable_external_secrets = config.get_bool("enableExternalSecrets")
if enable_external_secrets is None:
    enable_external_secrets = True
enable_ingress_gateway = config.get_bool("enableIngressGateway")
if enable_ingress_gateway is None:
    enable_ingress_gateway = True

gcp_config = pulumi.Config("gcp")
project = gcp_config.require("project")
region = gcp_config.require("region")

landing_zone = pulumi.StackReference(landing_zone_stack_name)
network = landing_zone.require_output("networkName")
subnet = landing_zone.require_output("clusterSubnetName")
pods_range_name = landing_zone.require_output("podsRangeName")
services_range_name = landing_zone.require_output("servicesRangeName")
deployer_service_account_email = landing_zone.require_output("deployerServiceAccountEmail")
secret_name = landing_zone.require_output("secretsStore")

cluster_name = f"{pulumi.get_stack()}-gke"

cluster = Cluster(
    "platform",
    ClusterArgs(
        cluster_name=cluster_name,
        project=project,
        region=region,
        network=network,
        subnetwork=subnet,
        pods_range_name=pods_range_name,
        services_range_name=services_range_name,
        deployer_service_account_email=deployer_service_account_email,
        secret_name=secret_name,
        version=cluster_version,
        release_channel=release_channel,
        enable_external_secrets=enable_external_secrets,
        enable_ingress_gateway=enable_ingress_gateway,
        external_secrets_chart_version="2.3.0",
        labels={
            "environment": pulumi.get_stack(),
            "solution-family": "kubernetes",
            "cloud": "gcp",
            "language": "python",
        },
    ),
)

pulumi.export("kubeconfig", cluster.kubeconfig)
pulumi.export("clusterName", cluster_name)
pulumi.export("clusterEndpoint", cluster.cluster_endpoint)
pulumi.export("clusterCertificateAuthority", cluster.cluster_certificate_authority)
pulumi.export("workloadIdentityPool", cluster.workload_identity_pool)
pulumi.export("externalSecretsServiceAccountEmail", cluster.external_secrets_service_account_email)
pulumi.export("gatewayServiceAccountEmail", cluster.gateway_service_account_email)
pulumi.export("escEnvironment", f"{pulumi.get_stack()}-gke")
package main

import (
	"fmt"

	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"

	"kubernetes-gcp/cluster"
)

func main() {
	pulumi.Run(Program)
}

func Program(ctx *pulumi.Context) error {
	cfg := config.New(ctx, "")
	landingZoneStackName := cfg.Require("landingZoneStack")
	clusterVersion := cfg.Get("clusterVersion")
	if clusterVersion == "" {
		clusterVersion = "1.35 (Regular channel)"
	}
	releaseChannel := cfg.Get("releaseChannel")
	if releaseChannel == "" {
		releaseChannel = "REGULAR"
	}
	enableExternalSecrets := true
	if v, err := cfg.TryBool("enableExternalSecrets"); err == nil {
		enableExternalSecrets = v
	}
	enableIngressGateway := true
	if v, err := cfg.TryBool("enableIngressGateway"); err == nil {
		enableIngressGateway = v
	}

	gcpConfig := config.New(ctx, "gcp")
	project := gcpConfig.Require("project")
	region := gcpConfig.Require("region")

	landingZone, err := pulumi.NewStackReference(ctx, landingZoneStackName, nil)
	if err != nil {
		return err
	}
	network := landingZone.GetStringOutput(pulumi.String("networkName"))
	subnetwork := landingZone.GetStringOutput(pulumi.String("clusterSubnetName"))
	podsRangeName := landingZone.GetStringOutput(pulumi.String("podsRangeName"))
	servicesRangeName := landingZone.GetStringOutput(pulumi.String("servicesRangeName"))
	deployerServiceAccountEmail := landingZone.GetStringOutput(pulumi.String("deployerServiceAccountEmail"))
	secretName := landingZone.GetStringOutput(pulumi.String("secretsStore"))

	clusterName := fmt.Sprintf("%s-gke", ctx.Stack())

	c, err := cluster.New(ctx, "platform", &cluster.Args{
		ClusterName:                 pulumi.String(clusterName),
		Project:                     pulumi.String(project),
		Region:                      pulumi.String(region),
		Network:                     network,
		Subnetwork:                  subnetwork,
		PodsRangeName:               podsRangeName,
		ServicesRangeName:           servicesRangeName,
		DeployerServiceAccountEmail: deployerServiceAccountEmail,
		SecretName:                  secretName,
		Version:                     pulumi.String(clusterVersion),
		ReleaseChannel:              pulumi.String(releaseChannel),
		EnableExternalSecrets:       enableExternalSecrets,
		EnableIngressGateway:        enableIngressGateway,
		ExternalSecretsChartVersion: "2.3.0",
		Labels: pulumi.StringMap{
			"environment":     pulumi.String(ctx.Stack()),
			"solution-family": pulumi.String("kubernetes"),
			"cloud":           pulumi.String("gcp"),
			"language":        pulumi.String("go"),
		},
	})
	if err != nil {
		return err
	}

	ctx.Export("kubeconfig", c.Kubeconfig)
	ctx.Export("clusterName", pulumi.String(clusterName))
	ctx.Export("clusterEndpoint", c.ClusterEndpoint)
	ctx.Export("clusterCertificateAuthority", c.ClusterCertificateAuthority)
	ctx.Export("workloadIdentityPool", c.WorkloadIdentityPool)
	ctx.Export("externalSecretsServiceAccountEmail", c.ExternalSecretsServiceAccountEmail)
	ctx.Export("gatewayServiceAccountEmail", c.GatewayServiceAccountEmail)
	ctx.Export("escEnvironment", pulumi.Sprintf("%s-gke", ctx.Stack()))
	return nil
}

Reusable components

The cluster wiring and add-on installs live in a reusable module so you can import it from other Pulumi projects or adapt it per team.

components/cluster.ts

Provisions the GKE cluster, a system node pool sized for the controllers, workload-identity wiring (Workload Identity Federation for GKE), and the Helm releases for External Secrets Operator and the ingress controller for this cloud.

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

export interface ClusterArgs {
    clusterName: pulumi.Input<string>;
    project: pulumi.Input<string>;
    region: pulumi.Input<string>;
    network: pulumi.Input<string>;
    subnetwork: pulumi.Input<string>;
    podsRangeName: pulumi.Input<string>;
    servicesRangeName: pulumi.Input<string>;
    deployerServiceAccountEmail: pulumi.Input<string>;
    secretName: pulumi.Input<string>;
    version: pulumi.Input<string>;
    releaseChannel: pulumi.Input<string>;
    enableExternalSecrets: boolean;
    enableIngressGateway: boolean;
    externalSecretsChartVersion: string;
    labels?: pulumi.Input<{ [key: string]: pulumi.Input<string> }>;
}

/**
 * Opinionated GKE Standard cluster with Workload Identity Federation, Node Auto
 * Provisioning, Gateway API enabled, and External Secrets Operator wired to
 * Google Secret Manager through a dedicated service account binding.
 */
export class Cluster extends pulumi.ComponentResource {
    public readonly cluster: gcp.container.Cluster;
    public readonly kubeconfig: pulumi.Output<string>;
    public readonly clusterEndpoint: pulumi.Output<string>;
    public readonly clusterCertificateAuthority: pulumi.Output<string>;
    public readonly workloadIdentityPool: pulumi.Output<string>;
    public readonly externalSecretsServiceAccountEmail: pulumi.Output<string>;
    public readonly gatewayServiceAccountEmail: pulumi.Output<string>;

    constructor(name: string, args: ClusterArgs, opts?: pulumi.ComponentResourceOptions) {
        super("kubernetes:gcp:Cluster", name, {}, opts);
        const labels = args.labels ?? {};

        const cluster = new gcp.container.Cluster(
            `${name}-gke`,
            {
                name: args.clusterName,
                project: args.project,
                location: args.region,
                network: args.network,
                subnetwork: args.subnetwork,
                // Autopilot would be a valid alternative; Standard gives us explicit NAP control.
                removeDefaultNodePool: true,
                initialNodeCount: 1,
                deletionProtection: false,
                releaseChannel: { channel: args.releaseChannel },
                minMasterVersion: args.version,
                ipAllocationPolicy: {
                    clusterSecondaryRangeName: args.podsRangeName,
                    servicesSecondaryRangeName: args.servicesRangeName,
                },
                workloadIdentityConfig: {
                    workloadPool: pulumi.interpolate`${args.project}.svc.id.goog`,
                },
                // Enable Gateway API - the default 2026 ingress story on GKE.
                gatewayApiConfig: { channel: "CHANNEL_STANDARD" },
                clusterAutoscaling: {
                    enabled: true,
                    autoscalingProfile: "OPTIMIZE_UTILIZATION",
                    resourceLimits: [
                        { resourceType: "cpu", minimum: 1, maximum: 200 },
                        { resourceType: "memory", minimum: 2, maximum: 800 },
                    ],
                    autoProvisioningDefaults: {
                        oauthScopes: ["https://www.googleapis.com/auth/cloud-platform"],
                        serviceAccount: args.deployerServiceAccountEmail,
                        management: { autoUpgrade: true, autoRepair: true },
                        shieldedInstanceConfig: { enableIntegrityMonitoring: true, enableSecureBoot: true },
                        upgradeSettings: { strategy: "SURGE", maxSurge: 1, maxUnavailable: 0 },
                    },
                },
                masterAuthorizedNetworksConfig: { gcpPublicCidrsAccessEnabled: true },
                privateClusterConfig: {
                    enablePrivateNodes: true,
                    enablePrivateEndpoint: false,
                    masterIpv4CidrBlock: "172.16.0.0/28",
                },
                resourceLabels: labels,
            },
            { parent: this },
        );

        const systemPool = new gcp.container.NodePool(
            `${name}-system`,
            {
                name: "system",
                project: args.project,
                location: args.region,
                cluster: cluster.name,
                initialNodeCount: 1,
                autoscaling: { minNodeCount: 1, maxNodeCount: 3 },
                management: { autoRepair: true, autoUpgrade: true },
                nodeConfig: {
                    machineType: "e2-standard-4",
                    oauthScopes: ["https://www.googleapis.com/auth/cloud-platform"],
                    serviceAccount: args.deployerServiceAccountEmail,
                    shieldedInstanceConfig: { enableIntegrityMonitoring: true, enableSecureBoot: true },
                    workloadMetadataConfig: { mode: "GKE_METADATA" },
                    labels: { pool: "system" },
                    tags: ["gke-system"],
                },
            },
            { parent: this, dependsOn: [cluster] },
        );

        const kubeconfig = pulumi
            .all([cluster.endpoint, cluster.masterAuth, cluster.name])
            .apply(([endpoint, auth, clusterName]) => {
                const ca = auth!.clusterCaCertificate!;
                return `apiVersion: v1
clusters:
- cluster:
    server: https://${endpoint}
    certificate-authority-data: ${ca}
  name: ${clusterName}
contexts:
- context:
    cluster: ${clusterName}
    user: ${clusterName}
  name: ${clusterName}
current-context: ${clusterName}
kind: Config
users:
- name: ${clusterName}
  user:
    auth-provider:
      config:
        cmd-args: config config-helper --format=json
        cmd-path: gcloud
        expiry-key: '{.credential.token_expiry}'
        token-key: '{.credential.access_token}'
      name: gcp
`;
            });

        const k8sProvider = new k8s.Provider(
            `${name}-k8s`,
            { kubeconfig, enableServerSideApply: true },
            { parent: this, dependsOn: [systemPool] },
        );

        const psa = {
            "pod-security.kubernetes.io/enforce": "restricted",
            "pod-security.kubernetes.io/enforce-version": "latest",
            "pod-security.kubernetes.io/audit": "restricted",
            "pod-security.kubernetes.io/warn": "restricted",
        };
        const esoNamespace = new k8s.core.v1.Namespace(
            `${name}-eso-ns`,
            { metadata: { name: "external-secrets", labels: psa } },
            { provider: k8sProvider, parent: this },
        );
        const gatewayNamespace = new k8s.core.v1.Namespace(
            `${name}-gateway-ns`,
            { metadata: { name: "gateway-system", labels: psa } },
            { provider: k8sProvider, parent: this },
        );

        // External Secrets Operator: dedicated GSA + Workload Identity binding.
        const esoGsa = new gcp.serviceaccount.Account(
            `${name}-eso-sa`,
            {
                accountId: pulumi.interpolate`${args.clusterName}-eso`,
                displayName: "External Secrets Operator",
                project: args.project,
            },
            { parent: this },
        );
        new gcp.projects.IAMMember(
            `${name}-eso-sm-access`,
            {
                project: args.project,
                role: "roles/secretmanager.secretAccessor",
                member: pulumi.interpolate`serviceAccount:${esoGsa.email}`,
            },
            { parent: this },
        );
        new gcp.serviceaccount.IAMBinding(
            `${name}-eso-wi-binding`,
            {
                serviceAccountId: esoGsa.name,
                role: "roles/iam.workloadIdentityUser",
                members: [
                    pulumi.interpolate`serviceAccount:${args.project}.svc.id.goog[external-secrets/external-secrets]`,
                ],
            },
            { parent: this },
        );

        if (args.enableExternalSecrets) {
            new k8s.helm.v3.Release(
                `${name}-eso`,
                {
                    name: "external-secrets",
                    chart: "external-secrets",
                    version: args.externalSecretsChartVersion,
                    namespace: esoNamespace.metadata.name,
                    repositoryOpts: { repo: "https://charts.external-secrets.io" },
                    values: {
                        installCRDs: true,
                        serviceAccount: {
                            name: "external-secrets",
                            annotations: {
                                "iam.gke.io/gcp-service-account": esoGsa.email,
                            },
                        },
                    },
                },
                { provider: k8sProvider, parent: this, dependsOn: [systemPool] },
            );
        }

        // Gateway API ingress: dedicated GSA for the gateway controller, + GKE Gateway class.
        const gatewayGsa = new gcp.serviceaccount.Account(
            `${name}-gw-sa`,
            {
                accountId: pulumi.interpolate`${args.clusterName}-gateway`,
                displayName: "GKE Gateway Controller",
                project: args.project,
            },
            { parent: this },
        );
        new gcp.projects.IAMMember(
            `${name}-gw-networkadmin`,
            {
                project: args.project,
                role: "roles/compute.networkAdmin",
                member: pulumi.interpolate`serviceAccount:${gatewayGsa.email}`,
            },
            { parent: this },
        );
        new gcp.serviceaccount.IAMBinding(
            `${name}-gw-wi-binding`,
            {
                serviceAccountId: gatewayGsa.name,
                role: "roles/iam.workloadIdentityUser",
                members: [
                    pulumi.interpolate`serviceAccount:${args.project}.svc.id.goog[gateway-system:gateway-controller]`,
                ],
            },
            { parent: this },
        );

        if (args.enableIngressGateway) {
            // Example default GatewayClass is built into GKE (gke-l7-global-external-managed).
            // Blueprint ships a placeholder gateway namespace + service account so workload stacks
            // can drop Gateway / HTTPRoute resources with Workload Identity wired correctly.
            new k8s.core.v1.ServiceAccount(
                `${name}-gateway-sa`,
                {
                    metadata: {
                        name: "gateway-controller",
                        namespace: gatewayNamespace.metadata.name,
                        annotations: {
                            "iam.gke.io/gcp-service-account": gatewayGsa.email,
                        },
                    },
                },
                { provider: k8sProvider, parent: this },
            );
        }

        this.cluster = cluster;
        this.kubeconfig = kubeconfig;
        this.clusterEndpoint = pulumi.interpolate`https://${cluster.endpoint}`;
        this.clusterCertificateAuthority = cluster.masterAuth.apply(
            (auth) => auth!.clusterCaCertificate!,
        );
        this.workloadIdentityPool = pulumi.interpolate`${args.project}.svc.id.goog`;
        this.externalSecretsServiceAccountEmail = esoGsa.email;
        this.gatewayServiceAccountEmail = gatewayGsa.email;

        this.registerOutputs({
            kubeconfig: this.kubeconfig,
            clusterEndpoint: this.clusterEndpoint,
            workloadIdentityPool: this.workloadIdentityPool,
        });
    }
}

components/cluster.py

Provisions the GKE cluster, a system node pool sized for the controllers, workload-identity wiring (Workload Identity Federation for GKE), and the Helm releases for External Secrets Operator and the ingress controller for this cloud.

from __future__ import annotations

from dataclasses import dataclass
from typing import Mapping, Optional

import pulumi
import pulumi_gcp as gcp
import pulumi_kubernetes as k8s


@dataclass
class ClusterArgs:
    cluster_name: pulumi.Input[str]
    project: pulumi.Input[str]
    region: pulumi.Input[str]
    network: pulumi.Input[str]
    subnetwork: pulumi.Input[str]
    pods_range_name: pulumi.Input[str]
    services_range_name: pulumi.Input[str]
    deployer_service_account_email: pulumi.Input[str]
    secret_name: pulumi.Input[str]
    version: pulumi.Input[str]
    release_channel: pulumi.Input[str]
    enable_external_secrets: bool = True
    enable_ingress_gateway: bool = True
    external_secrets_chart_version: str = ""
    labels: Optional[Mapping[str, str]] = None


class Cluster(pulumi.ComponentResource):
    """Opinionated GKE Standard cluster with Workload Identity Federation, Node Auto
    Provisioning, Gateway API enabled, and External Secrets Operator."""

    def __init__(self, name: str, args: ClusterArgs, opts: Optional[pulumi.ResourceOptions] = None) -> None:
        super().__init__("kubernetes:gcp:Cluster", name, {}, opts)
        labels = dict(args.labels or {})
        child = pulumi.ResourceOptions(parent=self)

        cluster = gcp.container.Cluster(
            f"{name}-gke",
            name=args.cluster_name,
            project=args.project,
            location=args.region,
            network=args.network,
            subnetwork=args.subnetwork,
            remove_default_node_pool=True,
            initial_node_count=1,
            deletion_protection=False,
            release_channel=gcp.container.ClusterReleaseChannelArgs(channel=args.release_channel),
            min_master_version=args.version,
            ip_allocation_policy=gcp.container.ClusterIpAllocationPolicyArgs(
                cluster_secondary_range_name=args.pods_range_name,
                services_secondary_range_name=args.services_range_name,
            ),
            workload_identity_config=gcp.container.ClusterWorkloadIdentityConfigArgs(
                workload_pool=pulumi.Output.concat(args.project, ".svc.id.goog"),
            ),
            gateway_api_config=gcp.container.ClusterGatewayApiConfigArgs(channel="CHANNEL_STANDARD"),
            cluster_autoscaling=gcp.container.ClusterClusterAutoscalingArgs(
                enabled=True,
                autoscaling_profile="OPTIMIZE_UTILIZATION",
                resource_limits=[
                    gcp.container.ClusterClusterAutoscalingResourceLimitArgs(
                        resource_type="cpu", minimum=1, maximum=200,
                    ),
                    gcp.container.ClusterClusterAutoscalingResourceLimitArgs(
                        resource_type="memory", minimum=2, maximum=800,
                    ),
                ],
                auto_provisioning_defaults=gcp.container.ClusterClusterAutoscalingAutoProvisioningDefaultsArgs(
                    oauth_scopes=["https://www.googleapis.com/auth/cloud-platform"],
                    service_account=args.deployer_service_account_email,
                    management=gcp.container.ClusterClusterAutoscalingAutoProvisioningDefaultsManagementArgs(
                        auto_upgrade=True, auto_repair=True,
                    ),
                    shielded_instance_config=gcp.container.ClusterClusterAutoscalingAutoProvisioningDefaultsShieldedInstanceConfigArgs(
                        enable_integrity_monitoring=True,
                        enable_secure_boot=True,
                    ),
                    upgrade_settings=gcp.container.ClusterClusterAutoscalingAutoProvisioningDefaultsUpgradeSettingsArgs(
                        strategy="SURGE", max_surge=1, max_unavailable=0,
                    ),
                ),
            ),
            master_authorized_networks_config=gcp.container.ClusterMasterAuthorizedNetworksConfigArgs(
                gcp_public_cidrs_access_enabled=True,
            ),
            private_cluster_config=gcp.container.ClusterPrivateClusterConfigArgs(
                enable_private_nodes=True,
                enable_private_endpoint=False,
                master_ipv4_cidr_block="172.16.0.0/28",
            ),
            resource_labels=labels,
            opts=child,
        )

        system_pool = gcp.container.NodePool(
            f"{name}-system",
            name="system",
            project=args.project,
            location=args.region,
            cluster=cluster.name,
            initial_node_count=1,
            autoscaling=gcp.container.NodePoolAutoscalingArgs(min_node_count=1, max_node_count=3),
            management=gcp.container.NodePoolManagementArgs(auto_repair=True, auto_upgrade=True),
            node_config=gcp.container.NodePoolNodeConfigArgs(
                machine_type="e2-standard-4",
                oauth_scopes=["https://www.googleapis.com/auth/cloud-platform"],
                service_account=args.deployer_service_account_email,
                shielded_instance_config=gcp.container.NodePoolNodeConfigShieldedInstanceConfigArgs(
                    enable_integrity_monitoring=True, enable_secure_boot=True,
                ),
                workload_metadata_config=gcp.container.NodePoolNodeConfigWorkloadMetadataConfigArgs(mode="GKE_METADATA"),
                labels={"pool": "system"},
                tags=["gke-system"],
            ),
            opts=pulumi.ResourceOptions(parent=self, depends_on=[cluster]),
        )

        def _build_kubeconfig(args_tuple):
            endpoint, auth, cluster_name = args_tuple
            ca = auth["cluster_ca_certificate"]
            return f"""apiVersion: v1
clusters:
- cluster:
    server: https://{endpoint}
    certificate-authority-data: {ca}
  name: {cluster_name}
contexts:
- context:
    cluster: {cluster_name}
    user: {cluster_name}
  name: {cluster_name}
current-context: {cluster_name}
kind: Config
users:
- name: {cluster_name}
  user:
    auth-provider:
      config:
        cmd-args: config config-helper --format=json
        cmd-path: gcloud
        expiry-key: '{{.credential.token_expiry}}'
        token-key: '{{.credential.access_token}}'
      name: gcp
"""

        kubeconfig = pulumi.Output.all(cluster.endpoint, cluster.master_auth, cluster.name).apply(_build_kubeconfig)
        k8s_provider = k8s.Provider(
            f"{name}-k8s",
            kubeconfig=kubeconfig,
            enable_server_side_apply=True,
            opts=pulumi.ResourceOptions(parent=self, depends_on=[system_pool]),
        )
        k8s_opts = pulumi.ResourceOptions(parent=self, provider=k8s_provider)

        psa = {
            "pod-security.kubernetes.io/enforce": "restricted",
            "pod-security.kubernetes.io/enforce-version": "latest",
            "pod-security.kubernetes.io/audit": "restricted",
            "pod-security.kubernetes.io/warn": "restricted",
        }
        eso_ns = k8s.core.v1.Namespace(
            f"{name}-eso-ns",
            metadata=k8s.meta.v1.ObjectMetaArgs(name="external-secrets", labels=psa),
            opts=k8s_opts,
        )
        gateway_ns = k8s.core.v1.Namespace(
            f"{name}-gateway-ns",
            metadata=k8s.meta.v1.ObjectMetaArgs(name="gateway-system", labels=psa),
            opts=k8s_opts,
        )

        eso_gsa = gcp.serviceaccount.Account(
            f"{name}-eso-sa",
            account_id=pulumi.Output.concat(args.cluster_name, "-eso"),
            display_name="External Secrets Operator",
            project=args.project,
            opts=child,
        )
        gcp.projects.IAMMember(
            f"{name}-eso-sm-access",
            project=args.project,
            role="roles/secretmanager.secretAccessor",
            member=pulumi.Output.concat("serviceAccount:", eso_gsa.email),
            opts=child,
        )
        gcp.serviceaccount.IAMBinding(
            f"{name}-eso-wi-binding",
            service_account_id=eso_gsa.name,
            role="roles/iam.workloadIdentityUser",
            members=[
                pulumi.Output.concat("serviceAccount:", args.project, ".svc.id.goog[external-secrets/external-secrets]"),
            ],
            opts=child,
        )

        if args.enable_external_secrets:
            k8s.helm.v3.Release(
                f"{name}-eso",
                name="external-secrets",
                chart="external-secrets",
                version=args.external_secrets_chart_version,
                namespace=eso_ns.metadata["name"],
                repository_opts=k8s.helm.v3.RepositoryOptsArgs(
                    repo="https://charts.external-secrets.io",
                ),
                values={
                    "installCRDs": True,
                    "serviceAccount": {
                        "name": "external-secrets",
                        "annotations": {
                            "iam.gke.io/gcp-service-account": eso_gsa.email,
                        },
                    },
                },
                opts=pulumi.ResourceOptions(parent=self, provider=k8s_provider, depends_on=[system_pool]),
            )

        gateway_gsa = gcp.serviceaccount.Account(
            f"{name}-gw-sa",
            account_id=pulumi.Output.concat(args.cluster_name, "-gateway"),
            display_name="GKE Gateway Controller",
            project=args.project,
            opts=child,
        )
        gcp.projects.IAMMember(
            f"{name}-gw-networkadmin",
            project=args.project,
            role="roles/compute.networkAdmin",
            member=pulumi.Output.concat("serviceAccount:", gateway_gsa.email),
            opts=child,
        )
        gcp.serviceaccount.IAMBinding(
            f"{name}-gw-wi-binding",
            service_account_id=gateway_gsa.name,
            role="roles/iam.workloadIdentityUser",
            members=[
                pulumi.Output.concat("serviceAccount:", args.project, ".svc.id.goog[gateway-system:gateway-controller]"),
            ],
            opts=child,
        )

        if args.enable_ingress_gateway:
            k8s.core.v1.ServiceAccount(
                f"{name}-gateway-sa",
                metadata=k8s.meta.v1.ObjectMetaArgs(
                    name="gateway-controller",
                    namespace=gateway_ns.metadata["name"],
                    annotations={
                        "iam.gke.io/gcp-service-account": gateway_gsa.email,
                    },
                ),
                opts=k8s_opts,
            )

        self.cluster = cluster
        self.kubeconfig = kubeconfig
        self.cluster_endpoint = cluster.endpoint.apply(lambda ep: f"https://{ep}")
        self.cluster_certificate_authority = cluster.master_auth.apply(lambda a: a["cluster_ca_certificate"])
        self.workload_identity_pool = pulumi.Output.concat(args.project, ".svc.id.goog")
        self.external_secrets_service_account_email = eso_gsa.email
        self.gateway_service_account_email = gateway_gsa.email

        self.register_outputs(
            {
                "kubeconfig": self.kubeconfig,
                "cluster_endpoint": self.cluster_endpoint,
                "workload_identity_pool": self.workload_identity_pool,
            }
        )

cluster/cluster.go

Provisions the GKE cluster, a system node pool sized for the controllers, workload-identity wiring (Workload Identity Federation for GKE), and the Helm releases for External Secrets Operator and the ingress controller for this cloud.

package cluster

import (
	"fmt"

	"github.com/pulumi/pulumi-gcp/sdk/v8/go/gcp/container"
	"github.com/pulumi/pulumi-gcp/sdk/v8/go/gcp/projects"
	"github.com/pulumi/pulumi-gcp/sdk/v8/go/gcp/serviceaccount"
	corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
	helm "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/helm/v3"
	metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
	k8s "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

type Args struct {
	ClusterName                 pulumi.StringInput
	Project                     pulumi.StringInput
	Region                      pulumi.StringInput
	Network                     pulumi.StringInput
	Subnetwork                  pulumi.StringInput
	PodsRangeName               pulumi.StringInput
	ServicesRangeName           pulumi.StringInput
	DeployerServiceAccountEmail pulumi.StringInput
	SecretName                  pulumi.StringInput
	Version                     pulumi.StringInput
	ReleaseChannel              pulumi.StringInput
	EnableExternalSecrets       bool
	EnableIngressGateway        bool
	ExternalSecretsChartVersion string
	Labels                      pulumi.StringMapInput
}

type Cluster struct {
	pulumi.ResourceState

	Container                           *container.Cluster
	Kubeconfig                          pulumi.StringOutput
	ClusterEndpoint                     pulumi.StringOutput
	ClusterCertificateAuthority         pulumi.StringOutput
	WorkloadIdentityPool                pulumi.StringOutput
	ExternalSecretsServiceAccountEmail  pulumi.StringOutput
	GatewayServiceAccountEmail          pulumi.StringOutput
}

func New(ctx *pulumi.Context, name string, args *Args, opts ...pulumi.ResourceOption) (*Cluster, error) {
	c := &Cluster{}
	if err := ctx.RegisterComponentResource("kubernetes:gcp:Cluster", name, c, opts...); err != nil {
		return nil, err
	}
	parent := pulumi.Parent(c)

	workloadPool := args.Project.ToStringOutput().ApplyT(func(p string) string {
		return fmt.Sprintf("%s.svc.id.goog", p)
	}).(pulumi.StringOutput)

	cluster, err := container.NewCluster(ctx, fmt.Sprintf("%s-gke", name), &container.ClusterArgs{
		Name:                  args.ClusterName,
		Project:               args.Project,
		Location:              args.Region,
		Network:               args.Network,
		Subnetwork:            args.Subnetwork,
		RemoveDefaultNodePool: pulumi.Bool(true),
		InitialNodeCount:      pulumi.Int(1),
		DeletionProtection:    pulumi.Bool(false),
		ReleaseChannel: &container.ClusterReleaseChannelArgs{
			Channel: args.ReleaseChannel,
		},
		MinMasterVersion: args.Version,
		IpAllocationPolicy: &container.ClusterIpAllocationPolicyArgs{
			ClusterSecondaryRangeName:  args.PodsRangeName,
			ServicesSecondaryRangeName: args.ServicesRangeName,
		},
		WorkloadIdentityConfig: &container.ClusterWorkloadIdentityConfigArgs{
			WorkloadPool: workloadPool,
		},
		GatewayApiConfig: &container.ClusterGatewayApiConfigArgs{
			Channel: pulumi.String("CHANNEL_STANDARD"),
		},
		ClusterAutoscaling: &container.ClusterClusterAutoscalingArgs{
			Enabled:            pulumi.Bool(true),
			AutoscalingProfile: pulumi.String("OPTIMIZE_UTILIZATION"),
			ResourceLimits: container.ClusterClusterAutoscalingResourceLimitArray{
				&container.ClusterClusterAutoscalingResourceLimitArgs{
					ResourceType: pulumi.String("cpu"),
					Minimum:      pulumi.Int(1),
					Maximum:      pulumi.Int(200),
				},
				&container.ClusterClusterAutoscalingResourceLimitArgs{
					ResourceType: pulumi.String("memory"),
					Minimum:      pulumi.Int(2),
					Maximum:      pulumi.Int(800),
				},
			},
			AutoProvisioningDefaults: &container.ClusterClusterAutoscalingAutoProvisioningDefaultsArgs{
				OauthScopes:    pulumi.StringArray{pulumi.String("https://www.googleapis.com/auth/cloud-platform")},
				ServiceAccount: args.DeployerServiceAccountEmail,
				Management: &container.ClusterClusterAutoscalingAutoProvisioningDefaultsManagementArgs{
					AutoUpgrade: pulumi.Bool(true),
					AutoRepair:  pulumi.Bool(true),
				},
				ShieldedInstanceConfig: &container.ClusterClusterAutoscalingAutoProvisioningDefaultsShieldedInstanceConfigArgs{
					EnableIntegrityMonitoring: pulumi.Bool(true),
					EnableSecureBoot:          pulumi.Bool(true),
				},
				UpgradeSettings: &container.ClusterClusterAutoscalingAutoProvisioningDefaultsUpgradeSettingsArgs{
					Strategy:       pulumi.String("SURGE"),
					MaxSurge:       pulumi.Int(1),
					MaxUnavailable: pulumi.Int(0),
				},
			},
		},
		MasterAuthorizedNetworksConfig: &container.ClusterMasterAuthorizedNetworksConfigArgs{
			GcpPublicCidrsAccessEnabled: pulumi.Bool(true),
		},
		PrivateClusterConfig: &container.ClusterPrivateClusterConfigArgs{
			EnablePrivateNodes:    pulumi.Bool(true),
			EnablePrivateEndpoint: pulumi.Bool(false),
			MasterIpv4CidrBlock:   pulumi.String("172.16.0.0/28"),
		},
		ResourceLabels: args.Labels,
	}, parent)
	if err != nil {
		return nil, err
	}

	systemPool, err := container.NewNodePool(ctx, fmt.Sprintf("%s-system", name), &container.NodePoolArgs{
		Name:             pulumi.String("system"),
		Project:          args.Project,
		Location:         args.Region,
		Cluster:          cluster.Name,
		InitialNodeCount: pulumi.Int(1),
		Autoscaling: &container.NodePoolAutoscalingArgs{
			MinNodeCount: pulumi.Int(1),
			MaxNodeCount: pulumi.Int(3),
		},
		Management: &container.NodePoolManagementArgs{
			AutoRepair:  pulumi.Bool(true),
			AutoUpgrade: pulumi.Bool(true),
		},
		NodeConfig: &container.NodePoolNodeConfigArgs{
			MachineType:    pulumi.String("e2-standard-4"),
			OauthScopes:    pulumi.StringArray{pulumi.String("https://www.googleapis.com/auth/cloud-platform")},
			ServiceAccount: args.DeployerServiceAccountEmail,
			ShieldedInstanceConfig: &container.NodePoolNodeConfigShieldedInstanceConfigArgs{
				EnableIntegrityMonitoring: pulumi.Bool(true),
				EnableSecureBoot:          pulumi.Bool(true),
			},
			WorkloadMetadataConfig: &container.NodePoolNodeConfigWorkloadMetadataConfigArgs{
				Mode: pulumi.String("GKE_METADATA"),
			},
			Labels: pulumi.StringMap{"pool": pulumi.String("system")},
			Tags:   pulumi.StringArray{pulumi.String("gke-system")},
		},
	}, pulumi.Parent(c), pulumi.DependsOn([]pulumi.Resource{cluster}))
	if err != nil {
		return nil, err
	}

	kubeconfig := pulumi.All(cluster.Endpoint, cluster.MasterAuth.ClusterCaCertificate().Elem(), cluster.Name).ApplyT(func(parts []interface{}) string {
		endpoint := parts[0].(string)
		ca := parts[1].(string)
		clusterName := parts[2].(string)
		return fmt.Sprintf(`apiVersion: v1
clusters:
- cluster:
    server: https://%s
    certificate-authority-data: %s
  name: %s
contexts:
- context:
    cluster: %s
    user: %s
  name: %s
current-context: %s
kind: Config
users:
- name: %s
  user:
    auth-provider:
      config:
        cmd-args: config config-helper --format=json
        cmd-path: gcloud
        expiry-key: '{.credential.token_expiry}'
        token-key: '{.credential.access_token}'
      name: gcp
`, endpoint, ca, clusterName, clusterName, clusterName, clusterName, clusterName, clusterName)
	}).(pulumi.StringOutput)

	k8sProvider, err := k8s.NewProvider(ctx, fmt.Sprintf("%s-k8s", name), &k8s.ProviderArgs{
		Kubeconfig:            kubeconfig,
		EnableServerSideApply: pulumi.BoolPtr(true),
	}, pulumi.Parent(c), pulumi.DependsOn([]pulumi.Resource{systemPool}))
	if err != nil {
		return nil, err
	}
	k8sOpts := []pulumi.ResourceOption{pulumi.Provider(k8sProvider), pulumi.Parent(c)}

	psa := pulumi.StringMap{
		"pod-security.kubernetes.io/enforce":         pulumi.String("restricted"),
		"pod-security.kubernetes.io/enforce-version": pulumi.String("latest"),
		"pod-security.kubernetes.io/audit":           pulumi.String("restricted"),
		"pod-security.kubernetes.io/warn":            pulumi.String("restricted"),
	}
	esoNs, err := corev1.NewNamespace(ctx, fmt.Sprintf("%s-eso-ns", name), &corev1.NamespaceArgs{
		Metadata: &metav1.ObjectMetaArgs{Name: pulumi.String("external-secrets"), Labels: psa},
	}, k8sOpts...)
	if err != nil {
		return nil, err
	}
	gatewayNs, err := corev1.NewNamespace(ctx, fmt.Sprintf("%s-gateway-ns", name), &corev1.NamespaceArgs{
		Metadata: &metav1.ObjectMetaArgs{Name: pulumi.String("gateway-system"), Labels: psa},
	}, k8sOpts...)
	if err != nil {
		return nil, err
	}

	esoGsaId := args.ClusterName.ToStringOutput().ApplyT(func(v string) string { return v + "-eso" }).(pulumi.StringOutput)
	esoGsa, err := serviceaccount.NewAccount(ctx, fmt.Sprintf("%s-eso-sa", name), &serviceaccount.AccountArgs{
		AccountId:   esoGsaId,
		DisplayName: pulumi.String("External Secrets Operator"),
		Project:     args.Project,
	}, parent)
	if err != nil {
		return nil, err
	}
	if _, err := projects.NewIAMMember(ctx, fmt.Sprintf("%s-eso-sm-access", name), &projects.IAMMemberArgs{
		Project: args.Project,
		Role:    pulumi.String("roles/secretmanager.secretAccessor"),
		Member:  esoGsa.Email.ApplyT(func(e string) string { return "serviceAccount:" + e }).(pulumi.StringOutput),
	}, parent); err != nil {
		return nil, err
	}
	if _, err := serviceaccount.NewIAMBinding(ctx, fmt.Sprintf("%s-eso-wi-binding", name), &serviceaccount.IAMBindingArgs{
		ServiceAccountId: esoGsa.Name,
		Role:             pulumi.String("roles/iam.workloadIdentityUser"),
		Members: pulumi.StringArray{
			args.Project.ToStringOutput().ApplyT(func(p string) string {
				return fmt.Sprintf("serviceAccount:%s.svc.id.goog[external-secrets/external-secrets]", p)
			}).(pulumi.StringOutput),
		},
	}, parent); err != nil {
		return nil, err
	}

	if args.EnableExternalSecrets {
		if _, err := helm.NewRelease(ctx, fmt.Sprintf("%s-eso", name), &helm.ReleaseArgs{
			Name:      pulumi.String("external-secrets"),
			Chart:     pulumi.String("external-secrets"),
			Version:   pulumi.String(args.ExternalSecretsChartVersion),
			Namespace: esoNs.Metadata.Name(),
			RepositoryOpts: &helm.RepositoryOptsArgs{
				Repo: pulumi.String("https://charts.external-secrets.io"),
			},
			Values: pulumi.Map{
				"installCRDs": pulumi.Bool(true),
				"serviceAccount": pulumi.Map{
					"name": pulumi.String("external-secrets"),
					"annotations": pulumi.Map{
						"iam.gke.io/gcp-service-account": esoGsa.Email,
					},
				},
			},
		}, append(k8sOpts, pulumi.DependsOn([]pulumi.Resource{systemPool}))...); err != nil {
			return nil, err
		}
	}

	gatewayGsaId := args.ClusterName.ToStringOutput().ApplyT(func(v string) string { return v + "-gateway" }).(pulumi.StringOutput)
	gatewayGsa, err := serviceaccount.NewAccount(ctx, fmt.Sprintf("%s-gw-sa", name), &serviceaccount.AccountArgs{
		AccountId:   gatewayGsaId,
		DisplayName: pulumi.String("GKE Gateway Controller"),
		Project:     args.Project,
	}, parent)
	if err != nil {
		return nil, err
	}
	if _, err := projects.NewIAMMember(ctx, fmt.Sprintf("%s-gw-networkadmin", name), &projects.IAMMemberArgs{
		Project: args.Project,
		Role:    pulumi.String("roles/compute.networkAdmin"),
		Member:  gatewayGsa.Email.ApplyT(func(e string) string { return "serviceAccount:" + e }).(pulumi.StringOutput),
	}, parent); err != nil {
		return nil, err
	}
	if _, err := serviceaccount.NewIAMBinding(ctx, fmt.Sprintf("%s-gw-wi-binding", name), &serviceaccount.IAMBindingArgs{
		ServiceAccountId: gatewayGsa.Name,
		Role:             pulumi.String("roles/iam.workloadIdentityUser"),
		Members: pulumi.StringArray{
			args.Project.ToStringOutput().ApplyT(func(p string) string {
				return fmt.Sprintf("serviceAccount:%s.svc.id.goog[gateway-system:gateway-controller]", p)
			}).(pulumi.StringOutput),
		},
	}, parent); err != nil {
		return nil, err
	}

	if args.EnableIngressGateway {
		if _, err := corev1.NewServiceAccount(ctx, fmt.Sprintf("%s-gateway-sa", name), &corev1.ServiceAccountArgs{
			Metadata: &metav1.ObjectMetaArgs{
				Name:      pulumi.String("gateway-controller"),
				Namespace: gatewayNs.Metadata.Name(),
				Annotations: pulumi.StringMap{
					"iam.gke.io/gcp-service-account": gatewayGsa.Email,
				},
			},
		}, k8sOpts...); err != nil {
			return nil, err
		}
	}

	c.Container = cluster
	c.Kubeconfig = kubeconfig
	c.ClusterEndpoint = cluster.Endpoint.ApplyT(func(ep string) string { return "https://" + ep }).(pulumi.StringOutput)
	c.ClusterCertificateAuthority = cluster.MasterAuth.ClusterCaCertificate().Elem()
	c.WorkloadIdentityPool = workloadPool
	c.ExternalSecretsServiceAccountEmail = esoGsa.Email
	c.GatewayServiceAccountEmail = gatewayGsa.Email

	if err := ctx.RegisterResourceOutputs(c, pulumi.Map{
		"kubeconfig":           c.Kubeconfig,
		"clusterEndpoint":      c.ClusterEndpoint,
		"workloadIdentityPool": c.WorkloadIdentityPool,
	}); err != nil {
		return nil, err
	}
	return c, nil
}

Frequently asked questions

Do I need the Pulumi landing-zone stack first?
Yes. The blueprint consumes landing-zone outputs (network ids, key ids, deployer identity) through a StackReference. Deploy the landing-zone family in the same cloud account first, then point this stack at it with pulumi config set landingZoneStack <your-org>/landing-zone/dev. If you want to bring your own network, replace the StackReference block in the entrypoint with the ids you already have.
Which add-ons does this blueprint install?
External Secrets Operator (for syncing cloud-native secret stores into the cluster), a cloud-native Layer-7 ingress path (AWS Load Balancer Controller on EKS, Application Gateway for Containers on AKS, GKE Gateway API on GKE), and a cloud-native node autoscaler (Karpenter on EKS, Node Auto Provisioning on AKS and GKE). All are installed through pinned Helm charts or managed-cluster config. Each add-on has a config flag so you can disable any of them on pulumi up.
How does workload identity work here?
On AWS the blueprint creates IRSA (IAM Roles for Service Accounts) roles scoped per service account and binds them through OIDC federation. On AKS it turns on Workload Identity + OIDC and wires FederatedIdentityCredential resources per service account. On GKE it enables Workload Identity Federation on the cluster and annotates each controller’s service account so it maps to a scoped Google service account. In every case pods call cloud APIs with short-lived tokens, never with static credentials.
How do I consume the cluster from another Pulumi project?
The stack exports kubeconfig, clusterName, clusterEndpoint, and clusterOidcIssuerUrl plus an escEnvironment name. Downstream workload stacks either import the Pulumi ESC environment this stack attaches to, or use a StackReference to pull those outputs. The Consume the cluster section shows both patterns with TypeScript, Python, and Go examples.
How do I upgrade Kubernetes versions later?
Bump the clusterVersion config value and run pulumi up. EKS and AKS upgrade the managed control plane in place; GKE follows the release channel you selected. Node pools refresh behind the same config value - Karpenter rolls AMIs per NodeClass, AKS NAP rolls through its AKSNodeClass, and GKE Node Auto Provisioning rolls through its NodePool templates.
What does this cost?
The control plane is a per-hour charge on every cloud even when no workloads are running. Add the system node pool, any network egress from the landing-zone network, and the Layer-7 data-plane service when you start creating Ingress / Gateway / HTTPRoute resources. This blueprint does not deploy application workloads, so the baseline is the control plane plus the system node pool.