Getting Started with Amazon EKS Auto Mode in Pulumi

Posted on

AWS recently announced Amazon EKS Auto Mode, a significant enhancement to Amazon EKS that streamlines cluster management by automating infrastructure decisions and operations. Today, we are excited to announce that Pulumi fully supports EKS Auto Mode across our AWS provider ecosystem, enabling you to leverage this powerful feature through infrastructure as code.

What is EKS Auto Mode?

EKS Auto Mode extends AWS management beyond the Kubernetes control plane to the underlying infrastructure that powers your workloads. While traditional EKS requires you to make numerous infrastructure decisions and maintain various components, Auto Mode simplifies this by automating these choices based on AWS’s operational expertise.

Instead of manually configuring and managing cluster add-ons, node groups, and infrastructure components, Auto Mode handles:

  • Compute Management: Automatically provisions and scales EC2 instances based on your workload demands while selecting optimal instance types and sizes. It also handles node lifecycle management, including OS updates and security patches.
  • Storage Automation: Eliminates the need to manually set up and configure the EBS CSI driver for block storage. Auto Mode manages volume provisioning, attachment, and cleanup for your persistent workloads.
  • Networking Simplification: Automatically configures and manages core networking components like Load Balancers.

This significantly reduces the operational overhead of running Kubernetes clusters. Instead of spending time on infrastructure decisions and maintenance, teams can now focus on their applications. For example, deploying a stateful application with load balancing no longer requires setting up and maintaining multiple add-ons and configurations - Auto Mode handles this automatically while following AWS best practices.

Common tasks that previously required significant expertise and time are now managed by AWS, making it simpler to run production-grade Kubernetes workloads. This includes:

  • Setting up and maintaining cluster autoscaling
  • Configuring and updating networking plugins
  • Managing storage drivers and volume provisioning
  • Handling node updates and security patches
  • Optimizing instance selection and placement

Pulumi Support for EKS Auto Mode

We have added EKS Auto Mode support across our AWS provider ecosystem:

Creating an EKS Auto Mode Cluster

Let’s explore how to leverage EKS Auto Mode with Pulumi by deploying an example. You can enable EKS Auto Mode by setting autoMode.enabled to true.

First, we’ll create a new EKS cluster with Auto Mode enabled. This requires properly configured networking and authentication settings:

  • VPC subnets are tagged appropriately for EKS Auto Mode load balancer discovery
  • Authentication Mode supports Access Entries (Api or ApiAndConfigMap mode) as required by EKS Auto Mode
  • Default node groups and security groups are skipped as Auto Mode manages these
import * as pulumi from "@pulumi/pulumi";
import * as awsx from "@pulumi/awsx";
import * as eks from "@pulumi/eks";
import * as k8s from "@pulumi/kubernetes";
import { SubnetType } from "@pulumi/awsx/ec2";

const config = new pulumi.Config();
const clusterName = config.require("clusterName");

const eksVpc = new awsx.ec2.Vpc("eks-auto-mode", {
    enableDnsHostnames: true,
    cidrBlock: "10.0.0.0/16",
    subnetSpecs: [
        // Necessary tags for EKS Auto Mode to identify the subnets for the load balancers.
        // See: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.1/deploy/subnet_discovery/
        {type: SubnetType.Public, tags: {[`kubernetes.io/cluster/${clusterName}`]: "shared", "kubernetes.io/role/elb": "1"}},
        {type: SubnetType.Private, tags: {[`kubernetes.io/cluster/${clusterName}`]: "shared", "kubernetes.io/role/internal-elb": "1"}},
    ],
    subnetStrategy: "Auto"
});

const cluster = new eks.Cluster("eks-auto-mode", {
    name: clusterName,
    // EKS Auto Mode requires Access Entries, use either the `Api` or `ApiAndConfigMap` authentication mode.
    authenticationMode: eks.AuthenticationMode.Api,
    vpcId: eksVpc.vpcId,
    publicSubnetIds: eksVpc.publicSubnetIds,
    privateSubnetIds: eksVpc.privateSubnetIds,
    // Enables compute, storage and load balancing for the cluster.
    autoMode: {
        enabled: true,
    }
});
import pulumi
import pulumi_awsx as awsx
import pulumi_eks as eks
import pulumi_kubernetes as kubernetes

eks_vpc = awsx.ec2.Vpc("eksVpc",
    enable_dns_hostnames=True,
    cidr_block="10.0.0.0/16",
    # Necessary tags for EKS Auto Mode to identify the subnets for the load balancers.
    # See: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.1/deploy/subnet_discovery/
    subnet_specs=[
        {
            "type": awsx.ec2.SubnetType.PUBLIC,
            "tags": {
                "kubernetes_io_cluster_eks_auto_mode_demo": "shared",
                "kubernetes_io_role_elb": "1",
            },
        },
        {
            "type": awsx.ec2.SubnetType.PRIVATE,
            "tags": {
                "kubernetes_io_cluster_eks_auto_mode_demo": "shared",
                "kubernetes_io_role_internal_elb": "1",
            },
        },
    ],
    subnet_strategy=awsx.ec2.SubnetAllocationStrategy.AUTO)
cluster = eks.Cluster("cluster",
    name="eks-auto-mode-demo",
    # EKS Auto Mode requires Access Entries, use either the `Api` or `ApiAndConfigMap` authentication mode.
    authentication_mode=eks.AuthenticationMode.API,
    vpc_id=eks_vpc.vpc_id,
    public_subnet_ids=eks_vpc.public_subnet_ids,
    private_subnet_ids=eks_vpc.private_subnet_ids,
    # Enables compute, storage and load balancing for the cluster.
    auto_mode={
        "enabled": True,
    })
package main

import (
	"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ec2"
	"github.com/pulumi/pulumi-eks/sdk/v3/go/eks"
	"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes"
	appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
	corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
	metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
	networkingv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/networking/v1"
	storagev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/storage/v1"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		eksVpc, err := ec2.NewVpc(ctx, "eksVpc", &ec2.VpcArgs{
			EnableDnsHostnames: pulumi.Bool(true),
			CidrBlock:          "10.0.0.0/16",
            // Necessary tags for EKS Auto Mode to identify the subnets for the load balancers.
            // See: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.1/deploy/subnet_discovery/
			SubnetSpecs: []ec2.SubnetSpecArgs{
				{
					Type: ec2.SubnetTypePublic,
					Tags: {
						"kubernetes.io/cluster/eks-auto-mode-demo": pulumi.String("shared"),
						"kubernetes.io/role/elb":                   pulumi.String("1"),
					},
				},
				{
					Type: ec2.SubnetTypePrivate,
					Tags: {
						"kubernetes.io/cluster/eks-auto-mode-demo": pulumi.String("shared"),
						"kubernetes.io/role/internal-elb":          pulumi.String("1"),
					},
				},
			},
			SubnetStrategy: ec2.SubnetAllocationStrategyAuto,
		})
		if err != nil {
			return err
		}
		cluster, err := eks.NewCluster(ctx, "cluster", &eks.ClusterArgs{
			Name:                      pulumi.String("eks-auto-mode-demo"),
            // EKS Auto Mode requires Access Entries, use either the `Api` or `ApiAndConfigMap` authentication mode.
			AuthenticationMode:        eks.AuthenticationModeApi,
			VpcId:                     eksVpc.VpcId,
			PublicSubnetIds:           eksVpc.PublicSubnetIds,
			PrivateSubnetIds:          eksVpc.PrivateSubnetIds,
            // Enables compute, storage and load balancing for the cluster.
			AutoMode: &eks.AutoModeOptionsArgs{
				Enabled: true,
			},
		})
		if err != nil {
			return err
		}
        return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Awsx = Pulumi.Awsx;
using Eks = Pulumi.Eks;
using Kubernetes = Pulumi.Kubernetes;

return await Deployment.RunAsync(() =>
{
    var eksVpc = new Awsx.Ec2.Vpc("eksVpc", new()
    {
        EnableDnsHostnames = true,
        CidrBlock = "10.0.0.0/16",
        // Necessary tags for EKS Auto Mode to identify the subnets for the load balancers.
        // See: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.1/deploy/subnet_discovery/
        SubnetSpecs = new()
        {
            new Awsx.Ec2.Inputs.SubnetSpecArgs
            {
                Type = Awsx.Ec2.SubnetType.Public,
                Tags =
                {
                    { "kubernetes.io/cluster/eks-auto-mode-demo", "shared" },
                    { "kubernetes.io/role/elb", "1" },
                },
            },
            new Awsx.Ec2.Inputs.SubnetSpecArgs
            {
                Type = Awsx.Ec2.SubnetType.Private,
                Tags =
                {
                    { "kubernetes.io/cluster/eks-auto-mode-demo", "shared" },
                    { "kubernetes.io/role/internal-elb", "1" },
                },
            },
        },
        SubnetStrategy = Awsx.Ec2.SubnetAllocationStrategy.Auto,
    });

    var cluster = new Eks.Cluster("cluster", new()
    {
        Name = "eks-auto-mode-demo",
        // EKS Auto Mode requires Access Entries, use either the `Api` or `ApiAndConfigMap` authentication mode.
        AuthenticationMode = Eks.AuthenticationMode.Api,
        VpcId = eksVpc.VpcId,
        PublicSubnetIds = eksVpc.PublicSubnetIds,
        PrivateSubnetIds = eksVpc.PrivateSubnetIds,
        // Enables compute, storage and load balancing for the cluster.
        AutoMode = new Eks.Inputs.AutoModeOptionsArgs
        {
            Enabled = true,
        },
    });
});
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.awsx.ec2.Vpc;
import com.pulumi.awsx.ec2.VpcArgs;
import com.pulumi.awsx.ec2.inputs.SubnetSpecArgs;
import com.pulumi.eks.Cluster;
import com.pulumi.eks.ClusterArgs;
import com.pulumi.eks.inputs.AutoModeOptionsArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var eksVpc = new Vpc("eksVpc", VpcArgs.builder()
            .enableDnsHostnames(true)
            .cidrBlock("10.0.0.0/16")
            // Necessary tags for EKS Auto Mode to identify the subnets for the load balancers.
            // See: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.1/deploy/subnet_discovery/
            .subnetSpecs(
                SubnetSpecArgs.builder()
                    .type("Public")
                    .tags(Map.ofEntries(
                        Map.entry("kubernetes.io/cluster/eks-auto-mode-demo", "shared"),
                        Map.entry("kubernetes.io/role/elb", "1")
                    ))
                    .build(),
                SubnetSpecArgs.builder()
                    .type("Private")
                    .tags(Map.ofEntries(
                        Map.entry("kubernetes.io/cluster/eks-auto-mode-demo", "shared"),
                        Map.entry("kubernetes.io/role/internal-elb", "1")
                    ))
                    .build())
            .subnetStrategy("Auto")
            .build());

        var cluster = new Cluster("cluster", ClusterArgs.builder()
            .name("eks-auto-mode-demo")
            // EKS Auto Mode requires Access Entries, use either the `Api` or `ApiAndConfigMap` authentication mode.
            .authenticationMode("API")
            .vpcId(eksVpc.vpcId())
            .publicSubnetIds(eksVpc.publicSubnetIds())
            .privateSubnetIds(eksVpc.privateSubnetIds())
            // Enables compute, storage and load balancing for the cluster.
            .autoMode(AutoModeOptionsArgs.builder()
                .enabled(true)
                .build())
            .build());
    }
}
name: eks-auto-mode-demo
description: A demo of EKS Auto Mode
runtime: yaml
resources:
  eksVpc:
    type: awsx:ec2:Vpc
    properties:
      enableDnsHostnames: true
      cidrBlock: "10.0.0.0/16"
      # Necessary tags for EKS Auto Mode to identify the subnets for the load balancers.
      # See: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.1/deploy/subnet_discovery/
      subnetSpecs:
        - type: "Public"
          tags:
            kubernetes.io/cluster/eks-auto-mode-demo: shared
            kubernetes.io/role/elb: "1"
        - type: "Private"
          tags:
            kubernetes.io/cluster/eks-auto-mode-demo: shared
            kubernetes.io/role/internal-elb: "1"
      subnetStrategy: "Auto"
  cluster:
    type: eks:Cluster
    properties:
      name: eks-auto-mode-demo
      # EKS Auto Mode requires Access Entries, use either the `Api` or `ApiAndConfigMap` authentication mode.
      authenticationMode: API
      vpcId: ${eksVpc.vpcId}
      publicSubnetIds: ${eksVpc.publicSubnetIds}
      privateSubnetIds: ${eksVpc.privateSubnetIds}
      # Enables compute, storage and load balancing for the cluster.
      autoMode:
        enabled: true

Deploying a Load-Balanced Application

Now that we have provisioned an EKS cluster with Auto Mode enabled, let’s deploy a web application fronted by an Application Load Balancer. EKS Auto Mode will automatically provision appropriately sized nodes for your workload, set up the Application Load Balancer according to the Ingress configuration, and manage ongoing maintenance and updates.

//...
const appName = "nginx";
const ns = new k8s.core.v1.Namespace(appName, {
    metadata: { name: appName },
}, { provider: cluster.provider });

const configMap = new k8s.core.v1.ConfigMap(appName, {
    metadata: {
        namespace: ns.metadata.name,
    },
    data: {
        "index.html": "<html><body><h1>Hello, Pulumi!</h1></body></html>",
    },
}, { provider: cluster.provider });

const deployment = new k8s.apps.v1.Deployment(appName, {
    metadata: {
        namespace: ns.metadata.name
    },
    spec: {
        selector: { matchLabels: { app: appName } },
        replicas: 3,
        template: {
            metadata: { labels: { app: appName } },
            spec: {
                containers: [{
                    name: appName,
                    image: appName,
                    ports: [{ containerPort: 80 }],
                    volumeMounts: [{ name: "nginx-index", mountPath: "/usr/share/nginx/html" }],
                }],
                volumes: [{
                    name: "nginx-index",
                    configMap: { name: configMap.metadata.name },
                }],
            },
        },
    },
}, { provider: cluster.provider });

const service = new k8s.core.v1.Service(appName, {
    metadata: {
        name: appName,
        namespace: ns.metadata.name
    },
    spec: {
        selector: { app: appName },
        ports: [{ port: 80, targetPort: 80 }],
    },
}, { provider: cluster.provider, dependsOn: [deployment] });

const ingressClass = new k8s.networking.v1.IngressClass("alb", {
    metadata: {
        namespace: ns.metadata.name,
        labels: {
            "app.kubernetes.io/name": "LoadBalancerController",
        },
        name: "alb",
    },
    spec: {
        controller: "eks.amazonaws.com/alb",
    }
}, { provider: cluster.provider });

const ingress = new k8s.networking.v1.Ingress(appName, {
    metadata: {
        namespace: ns.metadata.name,
        // Annotations for EKS Auto Mode to identify the Ingress as internet-facing and target-type as IP.
        annotations: {
            "alb.ingress.kubernetes.io/scheme": "internet-facing",
            "alb.ingress.kubernetes.io/target-type": "ip",
        }
    },
    spec: {
        ingressClassName: ingressClass.metadata.name,
        rules: [{
            http: {
                paths: [{
                    path: "/",
                    pathType: "Prefix",
                    backend: {
                        service: {
                            name: service.metadata.name,
                            port: {
                                number: 80,
                            },
                        },
                    },
                }],
            },
        }],
    }
}, { provider: cluster.provider });

export const url = ingress.status.apply(status => status?.loadBalancer?.ingress?.[0]?.hostname);
# ...
k8s_provider = kubernetes.Provider("k8sProvider", kubeconfig=cluster.kubeconfig)
ns = kubernetes.core.v1.Namespace("ns", metadata={
    "name": "nginx",
},
opts = pulumi.ResourceOptions(provider=k8s_provider))
index_content = kubernetes.core.v1.ConfigMap("index-content",
    metadata={
        "namespace": ns.metadata.name,
    },
    data={
        "index.html": "<html><body><h1>Hello, Pulumi!</h1></body></html>",
    },
    opts = pulumi.ResourceOptions(provider=k8s_provider))
deployment = kubernetes.apps.v1.Deployment("deployment",
    metadata={
        "namespace": ns.metadata.name,
    },
    spec={
        "selector": {
            "match_labels": {
                "app": "nginx",
            },
        },
        "replicas": 3,
        "template": {
            "metadata": {
                "labels": {
                    "app": "nginx",
                },
            },
            "spec": {
                "containers": [{
                    "name": "nginx",
                    "image": "nginx",
                    "ports": [{
                        "container_port": 80,
                    }],
                    "volume_mounts": [{
                        "name": "nginx-index",
                        "mount_path": "/usr/share/nginx/html",
                    }],
                }],
                "volumes": [{
                    "name": "nginx-index",
                    "config_map": {
                        "name": index_content.metadata.name,
                    },
                }],
            },
        },
    },
    opts = pulumi.ResourceOptions(provider=k8s_provider))
service = kubernetes.core.v1.Service("service",
    metadata={
        "name": "nginx",
        "namespace": ns.metadata.name,
    },
    spec={
        "selector": {
            "app": "nginx",
        },
        "ports": [{
            "port": 80,
            "target_port": 80,
        }],
    },
    opts = pulumi.ResourceOptions(provider=k8s_provider,
        depends_on=[deployment]))
ingress_class = kubernetes.networking.v1.IngressClass("ingressClass",
    metadata={
        "namespace": ns.metadata.name,
        "labels": {
            "app_kubernetes_io_name": "LoadBalancerController",
        },
        "name": "alb",
    },
    spec={
        "controller": "eks.amazonaws.com/alb",
    },
    opts = pulumi.ResourceOptions(provider=k8s_provider))
ingress = kubernetes.networking.v1.Ingress("ingress",
    metadata={
        "namespace": ns.metadata.name,
        "annotations": {
            "alb_ingress_kubernetes_io_scheme": "internet-facing",
            "alb_ingress_kubernetes_io_target_type": "ip",
        },
    },
    spec={
        "ingress_class_name": ingress_class.metadata.name,
        "rules": [{
            "http": {
                "paths": [{
                    "path": "/",
                    "path_type": "Prefix",
                    "backend": {
                        "service": {
                            "name": service.metadata.name,
                            "port": {
                                "number": 80,
                            },
                        },
                    },
                }],
            },
        }],
    },
    opts = pulumi.ResourceOptions(provider=k8s_provider))

pulumi.export("url", ingress.status.load_balancer.ingress[0].hostname)
package main

import (
	"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ec2"
	"github.com/pulumi/pulumi-eks/sdk/v3/go/eks"
	"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes"
	appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
	corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
	metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
	networkingv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/networking/v1"
	storagev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/storage/v1"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		// ...
		k8sProvider, err := kubernetes.NewProvider(ctx, "k8sProvider", &kubernetes.ProviderArgs{
			Kubeconfig: cluster.Kubeconfig,
		})
		if err != nil {
			return err
		}
		ns, err := corev1.NewNamespace(ctx, "ns", &corev1.NamespaceArgs{
			Metadata: &metav1.ObjectMetaArgs{
				Name: pulumi.String("nginx"),
			},
		}, pulumi.Provider(k8sProvider))
		if err != nil {
			return err
		}
		indexContent, err := corev1.NewConfigMap(ctx, "index-content", &corev1.ConfigMapArgs{
			Metadata: &metav1.ObjectMetaArgs{
				Namespace: ns.Metadata.ApplyT(func(metadata metav1.ObjectMeta) (*string, error) {
					return &metadata.Name, nil
				}).(pulumi.StringPtrOutput),
			},
			Data: pulumi.StringMap{
				"index.html": pulumi.String("<html><body><h1>Hello, Pulumi!</h1></body></html>"),
			},
		}, pulumi.Provider(k8sProvider))
		if err != nil {
			return err
		}
		deployment, err := appsv1.NewDeployment(ctx, "deployment", &appsv1.DeploymentArgs{
			Metadata: &metav1.ObjectMetaArgs{
				Namespace: ns.Metadata.ApplyT(func(metadata metav1.ObjectMeta) (*string, error) {
					return &metadata.Name, nil
				}).(pulumi.StringPtrOutput),
			},
			Spec: &appsv1.DeploymentSpecArgs{
				Selector: &metav1.LabelSelectorArgs{
					MatchLabels: pulumi.StringMap{
						"app": pulumi.String("nginx"),
					},
				},
				Replicas: pulumi.Int(3),
				Template: &corev1.PodTemplateSpecArgs{
					Metadata: &metav1.ObjectMetaArgs{
						Labels: pulumi.StringMap{
							"app": pulumi.String("nginx"),
						},
					},
					Spec: &corev1.PodSpecArgs{
						Containers: corev1.ContainerArray{
							&corev1.ContainerArgs{
								Name:  pulumi.String("nginx"),
								Image: pulumi.String("nginx"),
								Ports: corev1.ContainerPortArray{
									&corev1.ContainerPortArgs{
										ContainerPort: pulumi.Int(80),
									},
								},
								VolumeMounts: corev1.VolumeMountArray{
									&corev1.VolumeMountArgs{
										Name:      pulumi.String("nginx-index"),
										MountPath: pulumi.String("/usr/share/nginx/html"),
									},
								},
							},
						},
						Volumes: corev1.VolumeArray{
							&corev1.VolumeArgs{
								Name: pulumi.String("nginx-index"),
								ConfigMap: &corev1.ConfigMapVolumeSourceArgs{
									Name: indexContent.Metadata.ApplyT(func(metadata metav1.ObjectMeta) (*string, error) {
										return &metadata.Name, nil
									}).(pulumi.StringPtrOutput),
								},
							},
						},
					},
				},
			},
		}, pulumi.Provider(k8sProvider))
		if err != nil {
			return err
		}
		service, err := corev1.NewService(ctx, "service", &corev1.ServiceArgs{
			Metadata: &metav1.ObjectMetaArgs{
				Name: pulumi.String("nginx"),
				Namespace: ns.Metadata.ApplyT(func(metadata metav1.ObjectMeta) (*string, error) {
					return &metadata.Name, nil
				}).(pulumi.StringPtrOutput),
			},
			Spec: &corev1.ServiceSpecArgs{
				Selector: pulumi.StringMap{
					"app": pulumi.String("nginx"),
				},
				Ports: corev1.ServicePortArray{
					&corev1.ServicePortArgs{
						Port:       pulumi.Int(80),
						TargetPort: pulumi.Any(80),
					},
				},
			},
		}, pulumi.Provider(k8sProvider), pulumi.DependsOn([]pulumi.Resource{
			deployment,
		}))
		if err != nil {
			return err
		}
		ingressClass, err := networkingv1.NewIngressClass(ctx, "ingressClass", &networkingv1.IngressClassArgs{
			Metadata: &metav1.ObjectMetaArgs{
				Namespace: ns.Metadata.ApplyT(func(metadata metav1.ObjectMeta) (*string, error) {
					return &metadata.Name, nil
				}).(pulumi.StringPtrOutput),
				Labels: pulumi.StringMap{
					"app.kubernetes.io/name": pulumi.String("LoadBalancerController"),
				},
				Name: pulumi.String("alb"),
			},
			Spec: &networkingv1.IngressClassSpecArgs{
				Controller: pulumi.String("eks.amazonaws.com/alb"),
			},
		}, pulumi.Provider(k8sProvider))
		if err != nil {
			return err
		}
		ingress, err := networkingv1.NewIngress(ctx, "ingress", &networkingv1.IngressArgs{
			Metadata: &metav1.ObjectMetaArgs{
				Namespace: ns.Metadata.ApplyT(func(metadata metav1.ObjectMeta) (*string, error) {
					return &metadata.Name, nil
				}).(pulumi.StringPtrOutput),
				Annotations: pulumi.StringMap{
					"alb.ingress.kubernetes.io/scheme":      pulumi.String("internet-facing"),
					"alb.ingress.kubernetes.io/target-type": pulumi.String("ip"),
				},
			},
			Spec: &networkingv1.IngressSpecArgs{
				IngressClassName: ingressClass.Metadata.ApplyT(func(metadata metav1.ObjectMeta) (*string, error) {
					return &metadata.Name, nil
				}).(pulumi.StringPtrOutput),
				Rules: networkingv1.IngressRuleArray{
					&networkingv1.IngressRuleArgs{
						Http: &networkingv1.HTTPIngressRuleValueArgs{
							Paths: networkingv1.HTTPIngressPathArray{
								&networkingv1.HTTPIngressPathArgs{
									Path:     pulumi.String("/"),
									PathType: pulumi.String("Prefix"),
									Backend: &networkingv1.IngressBackendArgs{
										Service: &networkingv1.IngressServiceBackendArgs{
											Name: service.Metadata.ApplyT(func(metadata metav1.ObjectMeta) (*string, error) {
												return &metadata.Name, nil
											}).(pulumi.StringPtrOutput),
											Port: &networkingv1.ServiceBackendPortArgs{
												Number: pulumi.Int(80),
											},
										},
									},
								},
							},
						},
					},
				},
			},
		}, pulumi.Provider(k8sProvider))
		if err != nil {
			return err
		}
		
		ctx.Export("url", ingress.Status.ApplyT(func(status networkingv1.IngressStatus) (*string, error) {
			return &status.LoadBalancer.Ingress[0].Hostname, nil
		}).(pulumi.StringPtrOutput))
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Awsx = Pulumi.Awsx;
using Eks = Pulumi.Eks;
using Kubernetes = Pulumi.Kubernetes;

return await Deployment.RunAsync(() =>
{
    // ...
    var k8sProvider = new Kubernetes.Provider.Provider("k8sProvider", new()
    {
        KubeConfig = cluster.Kubeconfig,
    });

    var ns = new Kubernetes.Core.V1.Namespace("ns", new()
    {
        Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
        {
            Name = "nginx",
        },
    }, new CustomResourceOptions
    {
        Provider = k8sProvider,
    });

    var indexContent = new Kubernetes.Core.V1.ConfigMap("index-content", new()
    {
        Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
        {
            Namespace = ns.Metadata.Apply(metadata => metadata.Name),
        },
        Data =
        {
            { "index.html", "<html><body><h1>Hello, Pulumi!</h1></body></html>" },
        },
    }, new CustomResourceOptions
    {
        Provider = k8sProvider,
    });

    var deployment = new Kubernetes.Apps.V1.Deployment("deployment", new()
    {
        Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
        {
            Namespace = ns.Metadata.Apply(metadata => metadata.Name),
        },
        Spec = new Kubernetes.Types.Inputs.Apps.V1.DeploymentSpecArgs
        {
            Selector = new Kubernetes.Types.Inputs.Meta.V1.LabelSelectorArgs
            {
                MatchLabels =
                {
                    { "app", "nginx" },
                },
            },
            Replicas = 3,
            Template = new Kubernetes.Types.Inputs.Core.V1.PodTemplateSpecArgs
            {
                Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
                {
                    Labels =
                    {
                        { "app", "nginx" },
                    },
                },
                Spec = new Kubernetes.Types.Inputs.Core.V1.PodSpecArgs
                {
                    Containers = new[]
                    {
                        new Kubernetes.Types.Inputs.Core.V1.ContainerArgs
                        {
                            Name = "nginx",
                            Image = "nginx",
                            Ports = new[]
                            {
                                new Kubernetes.Types.Inputs.Core.V1.ContainerPortArgs
                                {
                                    ContainerPortValue = 80,
                                },
                            },
                            VolumeMounts = new[]
                            {
                                new Kubernetes.Types.Inputs.Core.V1.VolumeMountArgs
                                {
                                    Name = "nginx-index",
                                    MountPath = "/usr/share/nginx/html",
                                },
                            },
                        },
                    },
                    Volumes = new[]
                    {
                        new Kubernetes.Types.Inputs.Core.V1.VolumeArgs
                        {
                            Name = "nginx-index",
                            ConfigMap = new Kubernetes.Types.Inputs.Core.V1.ConfigMapVolumeSourceArgs
                            {
                                Name = indexContent.Metadata.Apply(metadata => metadata.Name),
                            },
                        },
                    },
                },
            },
        },
    }, new CustomResourceOptions
    {
        Provider = k8sProvider,
    });

    var service = new Kubernetes.Core.V1.Service("service", new()
    {
        Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
        {
            Name = "nginx",
            Namespace = ns.Metadata.Apply(metadata => metadata.Name),
        },
        Spec = new Kubernetes.Types.Inputs.Core.V1.ServiceSpecArgs
        {
            Selector =
            {
                { "app", "nginx" },
            },
            Ports = new[]
            {
                new Kubernetes.Types.Inputs.Core.V1.ServicePortArgs
                {
                    Port = 80,
                    TargetPort = 80,
                },
            },
        },
    }, new CustomResourceOptions
    {
        Provider = k8sProvider,
        DependsOn =
        {
            deployment,
        },
    });

    var ingressClass = new Kubernetes.Networking.V1.IngressClass("ingressClass", new()
    {
        Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
        {
            Namespace = ns.Metadata.Apply(metadata => metadata.Name),
            Labels =
            {
                { "app.kubernetes.io/name", "LoadBalancerController" },
            },
            Name = "alb",
        },
        Spec = new Kubernetes.Types.Inputs.Networking.V1.IngressClassSpecArgs
        {
            Controller = "eks.amazonaws.com/alb",
        },
    }, new CustomResourceOptions
    {
        Provider = k8sProvider,
    });

    var ingress = new Kubernetes.Networking.V1.Ingress("ingress", new()
    {
        Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
        {
            Namespace = ns.Metadata.Apply(metadata => metadata.Name),
            Annotations =
            {
                { "alb.ingress.kubernetes.io/scheme", "internet-facing" },
                { "alb.ingress.kubernetes.io/target-type", "ip" },
            },
        },
        Spec = new Kubernetes.Types.Inputs.Networking.V1.IngressSpecArgs
        {
            IngressClassName = ingressClass.Metadata.Apply(metadata => metadata.Name),
            Rules = new[]
            {
                new Kubernetes.Types.Inputs.Networking.V1.IngressRuleArgs
                {
                    Http = new Kubernetes.Types.Inputs.Networking.V1.HTTPIngressRuleValueArgs
                    {
                        Paths = new[]
                        {
                            new Kubernetes.Types.Inputs.Networking.V1.HTTPIngressPathArgs
                            {
                                Path = "/",
                                PathType = "Prefix",
                                Backend = new Kubernetes.Types.Inputs.Networking.V1.IngressBackendArgs
                                {
                                    Service = new Kubernetes.Types.Inputs.Networking.V1.IngressServiceBackendArgs
                                    {
                                        Name = service.Metadata.Apply(metadata => metadata.Name),
                                        Port = new Kubernetes.Types.Inputs.Networking.V1.ServiceBackendPortArgs
                                        {
                                            Number = 80,
                                        },
                                    },
                                },
                            },
                        },
                    },
                },
            },
        },
    }, new CustomResourceOptions
    {
        Provider = k8sProvider,
    });

    return new Dictionary<string, object?>
    {
        ["url"] = ingress.Status.Apply(status => status?.LoadBalancer?.Ingress[0]?.Hostname),
    };
});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.awsx.ec2.Vpc;
import com.pulumi.awsx.ec2.VpcArgs;
import com.pulumi.awsx.ec2.inputs.SubnetSpecArgs;
import com.pulumi.eks.Cluster;
import com.pulumi.eks.ClusterArgs;
import com.pulumi.eks.inputs.AutoModeOptionsArgs;
import com.pulumi.kubernetes.Provider;
import com.pulumi.kubernetes.ProviderArgs;
import com.pulumi.kubernetes.core_v1.Namespace;
import com.pulumi.kubernetes.core_v1.NamespaceArgs;
import com.pulumi.kubernetes.meta_v1.inputs.ObjectMetaArgs;
import com.pulumi.kubernetes.core_v1.ConfigMap;
import com.pulumi.kubernetes.core_v1.ConfigMapArgs;
import com.pulumi.kubernetes.apps_v1.Deployment;
import com.pulumi.kubernetes.apps_v1.DeploymentArgs;
import com.pulumi.kubernetes.apps_v1.inputs.DeploymentSpecArgs;
import com.pulumi.kubernetes.meta_v1.inputs.LabelSelectorArgs;
import com.pulumi.kubernetes.core_v1.inputs.PodTemplateSpecArgs;
import com.pulumi.kubernetes.core_v1.inputs.PodSpecArgs;
import com.pulumi.kubernetes.core_v1.Service;
import com.pulumi.kubernetes.core_v1.ServiceArgs;
import com.pulumi.kubernetes.core_v1.inputs.ServiceSpecArgs;
import com.pulumi.kubernetes.networking.k8s.io_v1.IngressClass;
import com.pulumi.kubernetes.networking.k8s.io_v1.IngressClassArgs;
import com.pulumi.kubernetes.networking.k8s.io_v1.inputs.IngressClassSpecArgs;
import com.pulumi.kubernetes.networking.k8s.io_v1.Ingress;
import com.pulumi.kubernetes.networking.k8s.io_v1.IngressArgs;
import com.pulumi.kubernetes.networking.k8s.io_v1.inputs.IngressSpecArgs;
import com.pulumi.kubernetes.storage.k8s.io_v1.StorageClass;
import com.pulumi.kubernetes.storage.k8s.io_v1.StorageClassArgs;
import com.pulumi.kubernetes.core_v1.PersistentVolumeClaim;
import com.pulumi.kubernetes.core_v1.PersistentVolumeClaimArgs;
import com.pulumi.kubernetes.core_v1.inputs.PersistentVolumeClaimSpecArgs;
import com.pulumi.kubernetes.core_v1.inputs.VolumeResourceRequirementsArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        // ...
        var k8sProvider = new Provider("k8sProvider", ProviderArgs.builder()
            .kubeconfig(cluster.kubeconfig())
            .build());

        var ns = new Namespace("ns", NamespaceArgs.builder()
            .metadata(ObjectMetaArgs.builder()
                .name("nginx")
                .build())
            .build(), CustomResourceOptions.builder()
                .provider(k8sProvider)
                .build());

        var indexContent = new ConfigMap("indexContent", ConfigMapArgs.builder()
            .metadata(ObjectMetaArgs.builder()
                .namespace(ns.metadata().applyValue(metadata -> metadata.name()))
                .build())
            .data(Map.of("index.html", "<html><body><h1>Hello, Pulumi!</h1></body></html>"))
            .build(), CustomResourceOptions.builder()
                .provider(k8sProvider)
                .build());

        var deployment = new Deployment("deployment", DeploymentArgs.builder()
            .metadata(ObjectMetaArgs.builder()
                .namespace(ns.metadata().applyValue(metadata -> metadata.name()))
                .build())
            .spec(DeploymentSpecArgs.builder()
                .selector(LabelSelectorArgs.builder()
                    .matchLabels(Map.of("app", "nginx"))
                    .build())
                .replicas(3)
                .template(PodTemplateSpecArgs.builder()
                    .metadata(ObjectMetaArgs.builder()
                        .labels(Map.of("app", "nginx"))
                        .build())
                    .spec(PodSpecArgs.builder()
                        .containers(ContainerArgs.builder()
                            .name("nginx")
                            .image("nginx")
                            .ports(ContainerPortArgs.builder()
                                .containerPort(80)
                                .build())
                            .volumeMounts(VolumeMountArgs.builder()
                                .name("nginx-index")
                                .mountPath("/usr/share/nginx/html")
                                .build())
                            .build())
                        .volumes(VolumeArgs.builder()
                            .name("nginx-index")
                            .configMap(ConfigMapVolumeSourceArgs.builder()
                                .name(indexContent.metadata().applyValue(metadata -> metadata.name()))
                                .build())
                            .build())
                        .build())
                    .build())
                .build())
            .build(), CustomResourceOptions.builder()
                .provider(k8sProvider)
                .build());

        var service = new Service("service", ServiceArgs.builder()
            .metadata(ObjectMetaArgs.builder()
                .name("nginx")
                .namespace(ns.metadata().applyValue(metadata -> metadata.name()))
                .build())
            .spec(ServiceSpecArgs.builder()
                .selector(Map.of("app", "nginx"))
                .ports(ServicePortArgs.builder()
                    .port(80)
                    .targetPort(80)
                    .build())
                .build())
            .build(), CustomResourceOptions.builder()
                .provider(k8sProvider)
                .dependsOn(deployment)
                .build());

        var ingressClass = new IngressClass("ingressClass", IngressClassArgs.builder()
            .metadata(ObjectMetaArgs.builder()
                .namespace(ns.metadata().applyValue(metadata -> metadata.name()))
                .labels(Map.of("app.kubernetes.io/name", "LoadBalancerController"))
                .name("alb")
                .build())
            .spec(IngressClassSpecArgs.builder()
                .controller("eks.amazonaws.com/alb")
                .build())
            .build(), CustomResourceOptions.builder()
                .provider(k8sProvider)
                .build());

        var ingress = new Ingress("ingress", IngressArgs.builder()
            .metadata(ObjectMetaArgs.builder()
                .namespace(ns.metadata().applyValue(metadata -> metadata.name()))
                .annotations(Map.ofEntries(
                    Map.entry("alb.ingress.kubernetes.io/scheme", "internet-facing"),
                    Map.entry("alb.ingress.kubernetes.io/target-type", "ip")
                ))
                .build())
            .spec(IngressSpecArgs.builder()
                .ingressClassName(ingressClass.metadata().applyValue(metadata -> metadata.name()))
                .rules(IngressRuleArgs.builder()
                    .http(HTTPIngressRuleValueArgs.builder()
                        .paths(HTTPIngressPathArgs.builder()
                            .path("/")
                            .pathType("Prefix")
                            .backend(IngressBackendArgs.builder()
                                .service(IngressServiceBackendArgs.builder()
                                    .name(service.metadata().applyValue(metadata -> metadata.name()))
                                    .port(ServiceBackendPortArgs.builder()
                                        .number(80)
                                        .build())
                                    .build())
                                .build())
                            .build())
                        .build())
                    .build())
                .build())
            .build(), CustomResourceOptions.builder()
                .provider(k8sProvider)
                .build());

        ctx.export("url", ingress.status().applyValue(status -> status.loadBalancer().ingress()[0].hostname()));
    }
}
name: eks-auto-mode-yaml
description: A Pulumi YAML program to deploy a Kubernetes cluster on AWS
runtime: yaml
resources:
  # ...
  k8sProvider:
    type: pulumi:providers:kubernetes
    properties:
      kubeconfig: ${cluster.kubeconfig}
  ns:
    type: kubernetes:core/v1:Namespace
    properties:
      metadata:
        name: nginx
    options:
      provider: ${k8sProvider}
  index-content:
    type: kubernetes:core/v1:ConfigMap
    properties:
      metadata:
        namespace: ${ns.metadata.name}
      data:
        "index.html": "<html><body><h1>Hello, Pulumi!</h1></body></html>"
    options:
      provider: ${k8sProvider}
  deployment:
    type: kubernetes:apps/v1:Deployment
    properties:
      metadata:
        namespace: ${ns.metadata.name}
      spec:
        selector:
          matchLabels:
            app: nginx
        replicas: 3
        template:
          metadata:
            labels:
              app: nginx
          spec:
            containers:
              - name: nginx
                image: nginx
                ports:
                  - containerPort: 80
                volumeMounts:
                  - name: nginx-index
                    mountPath: /usr/share/nginx/html
            volumes:
              - name: nginx-index
                configMap:
                  name: ${index-content.metadata.name}
    options:
      provider: ${k8sProvider}
  service:
    type: kubernetes:core/v1:Service
    properties:
      metadata:
        name: nginx
        namespace: ${ns.metadata.name}
      spec:
        selector:
          app: nginx
        ports:
          - port: 80
            targetPort: 80
    options:
      provider: ${k8sProvider}
      dependsOn:
        - ${deployment}
  ingressClass:
    type: kubernetes:networking.k8s.io/v1:IngressClass
    properties:
      metadata:
        namespace: ${ns.metadata.name}
        labels:
          app.kubernetes.io/name: LoadBalancerController
        name: alb
      spec:
        controller: eks.amazonaws.com/alb
    options:
      provider: ${k8sProvider}
  ingress:
    type: kubernetes:networking.k8s.io/v1:Ingress
    properties:
      metadata:
        namespace: ${ns.metadata.name}
        annotations:
          alb.ingress.kubernetes.io/scheme: internet-facing
          alb.ingress.kubernetes.io/target-type: ip
      spec:
        ingressClassName: ${ingressClass.metadata.name}
        rules:
          - http:
              paths:
                - path: /
                  pathType: Prefix
                  backend:
                    service:
                      name: ${service.metadata.name}
                      port:
                        number: 80
    options:
      provider: ${k8sProvider}
outputs:
  url: ${ingress.status.loadBalancer.ingress[0].hostname}

After deploying this with pulumi up, you can access your application with curl once the Load Balancer has been provisioned:

~ curl $(pulumi stack output url)
Hello, Pulumi!

Deploying a Stateful Workload

EKS Auto Mode simplifies persistent storage management by automatically handling EBS volume provisioning for your stateful workloads. To leverage this feature, you can create a StorageClass that uses the EBS CSI driver (ebs.csi.eks.amazonaws.com) as its provisioner. EKS Auto Mode will then automatically manage the lifecycle of your persistent volumes, including provisioning, attaching, and cleanup.

This is how you’d configure persistent storage for a sample application:

// ...
const storageClass = new k8s.storage.v1.StorageClass("storage-class", {
    metadata: {
        name: "auto-ebs-sc",
        annotations: {
            "storageclass.kubernetes.io/is-default-class": "true",
        },
    },
    provisioner: "ebs.csi.eks.amazonaws.com",
    volumeBindingMode: "WaitForFirstConsumer",
    parameters: {
        type: "gp3",
        encrypted: "true",
    },
}, {
    provider: cluster.provider,
});
const pvc = new k8s.core.v1.PersistentVolumeClaim("pvc", {
    metadata: {
        name: "auto-ebs-claim",
        namespace: ns.metadata.apply(metadata => metadata.name),
    },
    spec: {
        accessModes: ["ReadWriteOnce"],
        storageClassName: storageClass.metadata.apply(metadata => metadata.name),
        resources: {
            requests: {
                storage: "8Gi",
            },
        },
    },
}, {
    provider: cluster.provider,
});
const statefulWorkload = new k8s.apps.v1.Deployment("stateful-workload", {
    metadata: {
        name: "inflate-stateful",
        namespace: ns.metadata.apply(metadata => metadata.name),
    },
    spec: {
        replicas: 1,
        selector: {
            matchLabels: {
                app: "inflate-stateful",
            },
        },
        template: {
            metadata: {
                labels: {
                    app: "inflate-stateful",
                },
            },
            spec: {
                terminationGracePeriodSeconds: 0,
                nodeSelector: {
                    "eks.amazonaws.com/compute-type": "auto",
                },
                containers: [{
                    name: "bash",
                    image: "public.ecr.aws/docker/library/bash:4.4",
                    command: ["/usr/local/bin/bash"],
                    args: [
                        "-c",
                        "while true; do echo $(date -u) >> /data/out.txt; sleep 60; done",
                    ],
                    resources: {
                        requests: {
                            cpu: "1",
                        },
                    },
                    volumeMounts: [{
                        name: "persistent-storage",
                        mountPath: "/data",
                    }],
                }],
                volumes: [{
                    name: "persistent-storage",
                    persistentVolumeClaim: {
                        claimName: pvc.metadata.name,
                    },
                }],
            },
        },
    },
}, {
    provider: cluster.provider,
});

export const url = ingress.status.loadBalancer.ingress[0].hostname.apply(h => `http://${h}:80`);
# ...
storage_class = kubernetes.storage.v1.StorageClass("storage-class",
    metadata={
        "name": "auto-ebs-sc",
        "annotations": {
            "storageclass_kubernetes_io_is_default_class": "true",
        },
    },
    provisioner="ebs.csi.eks.amazonaws.com",
    volume_binding_mode="WaitForFirstConsumer",
    parameters={
        "type": "gp3",
        "encrypted": "true",
    },
    opts = pulumi.ResourceOptions(provider=k8_s_provider))
pvc = kubernetes.core.v1.PersistentVolumeClaim("pvc",
    metadata={
        "name": "auto-ebs-claim",
        "namespace": ns.metadata.name,
    },
    spec={
        "access_modes": ["ReadWriteOnce"],
        "storage_class_name": storage_class.metadata.name,
        "resources": {
            "requests": {
                "storage": "8Gi",
            },
        },
    },
    opts = pulumi.ResourceOptions(provider=k8_s_provider))
stateful_workload = kubernetes.apps.v1.Deployment("stateful-workload",
    metadata={
        "name": "inflate-stateful",
        "namespace": ns.metadata.name,
    },
    spec={
        "replicas": 1,
        "selector": {
            "match_labels": {
                "app": "inflate-stateful",
            },
        },
        "template": {
            "metadata": {
                "labels": {
                    "app": "inflate-stateful",
                },
            },
            "spec": {
                "termination_grace_period_seconds": 0,
                "node_selector": {
                    "eks_amazonaws_com_compute_type": "auto",
                },
                "containers": [{
                    "name": "bash",
                    "image": "public.ecr.aws/docker/library/bash:4.4",
                    "command": ["/usr/local/bin/bash"],
                    "args": [
                        "-c",
                        "while true; do echo $(date -u) >> /data/out.txt; sleep 60; done",
                    ],
                    "resources": {
                        "requests": {
                            "cpu": "1",
                        },
                    },
                    "volume_mounts": [{
                        "name": "persistent-storage",
                        "mount_path": "/data",
                    }],
                }],
                "volumes": [{
                    "name": "persistent-storage",
                    "persistent_volume_claim": {
                        "claim_name": pvc.metadata.name,
                    },
                }],
            },
        },
    },
    opts = pulumi.ResourceOptions(provider=k8_s_provider))
package main

import (
	"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ec2"
	"github.com/pulumi/pulumi-eks/sdk/v3/go/eks"
	"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes"
	appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
	corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
	metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
	networkingv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/networking/v1"
	storagev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/storage/v1"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		// ...
		storageClass, err := storagev1.NewStorageClass(ctx, "storage-class", &storagev1.StorageClassArgs{
			Metadata: &metav1.ObjectMetaArgs{
				Name: pulumi.String("auto-ebs-sc"),
				Annotations: pulumi.StringMap{
					"storageclass.kubernetes.io/is-default-class": pulumi.String("true"),
				},
			},
			Provisioner:       pulumi.String("ebs.csi.eks.amazonaws.com"),
			VolumeBindingMode: pulumi.String("WaitForFirstConsumer"),
			Parameters: pulumi.StringMap{
				"type":      pulumi.String("gp3"),
				"encrypted": pulumi.String("true"),
			},
		}, pulumi.Provider(k8SProvider))
		if err != nil {
			return err
		}
		pvc, err := corev1.NewPersistentVolumeClaim(ctx, "pvc", &corev1.PersistentVolumeClaimArgs{
			Metadata: &metav1.ObjectMetaArgs{
				Name: pulumi.String("auto-ebs-claim"),
				Namespace: ns.Metadata.ApplyT(func(metadata metav1.ObjectMeta) (*string, error) {
					return &metadata.Name, nil
				}).(pulumi.StringPtrOutput),
			},
			Spec: &corev1.PersistentVolumeClaimSpecArgs{
				AccessModes: pulumi.StringArray{
					pulumi.String("ReadWriteOnce"),
				},
				StorageClassName: storageClass.Metadata.ApplyT(func(metadata metav1.ObjectMeta) (*string, error) {
					return &metadata.Name, nil
				}).(pulumi.StringPtrOutput),
				Resources: &corev1.VolumeResourceRequirementsArgs{
					Requests: pulumi.StringMap{
						"storage": pulumi.String("8Gi"),
					},
				},
			},
		}, pulumi.Provider(k8SProvider))
		if err != nil {
			return err
		}
		_, err = appsv1.NewDeployment(ctx, "stateful-workload", &appsv1.DeploymentArgs{
			Metadata: &metav1.ObjectMetaArgs{
				Name: pulumi.String("inflate-stateful"),
				Namespace: ns.Metadata.ApplyT(func(metadata metav1.ObjectMeta) (*string, error) {
					return &metadata.Name, nil
				}).(pulumi.StringPtrOutput),
			},
			Spec: &appsv1.DeploymentSpecArgs{
				Replicas: pulumi.Int(1),
				Selector: &metav1.LabelSelectorArgs{
					MatchLabels: pulumi.StringMap{
						"app": pulumi.String("inflate-stateful"),
					},
				},
				Template: &corev1.PodTemplateSpecArgs{
					Metadata: &metav1.ObjectMetaArgs{
						Labels: pulumi.StringMap{
							"app": pulumi.String("inflate-stateful"),
						},
					},
					Spec: &corev1.PodSpecArgs{
						TerminationGracePeriodSeconds: pulumi.Int(0),
						NodeSelector: pulumi.StringMap{
							"eks.amazonaws.com/compute-type": pulumi.String("auto"),
						},
						Containers: corev1.ContainerArray{
							&corev1.ContainerArgs{
								Name:  pulumi.String("bash"),
								Image: pulumi.String("public.ecr.aws/docker/library/bash:4.4"),
								Command: pulumi.StringArray{
									pulumi.String("/usr/local/bin/bash"),
								},
								Args: pulumi.StringArray{
									pulumi.String("-c"),
									pulumi.String("while true; do echo $(date -u) >> /data/out.txt; sleep 60; done"),
								},
								Resources: &corev1.ResourceRequirementsArgs{
									Requests: pulumi.StringMap{
										"cpu": pulumi.String("1"),
									},
								},
								VolumeMounts: corev1.VolumeMountArray{
									&corev1.VolumeMountArgs{
										Name:      pulumi.String("persistent-storage"),
										MountPath: pulumi.String("/data"),
									},
								},
							},
						},
						Volumes: corev1.VolumeArray{
							&corev1.VolumeArgs{
								Name: pulumi.String("persistent-storage"),
								PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSourceArgs{
									ClaimName: pvc.Metadata.ApplyT(func(metadata metav1.ObjectMeta) (*string, error) {
										return &metadata.Name, nil
									}).(pulumi.StringPtrOutput),
								},
							},
						},
					},
				},
			},
		}, pulumi.Provider(k8SProvider))
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Awsx = Pulumi.Awsx;
using Eks = Pulumi.Eks;
using Kubernetes = Pulumi.Kubernetes;

return await Deployment.RunAsync(() =>
{
    // ...
    var storageClass = new Kubernetes.Storage.V1.StorageClass("storage-class", new()
    {
        Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
        {
            Name = "auto-ebs-sc",
            Annotations =
            {
                { "storageclass.kubernetes.io/is-default-class", "true" },
            },
        },
        Provisioner = "ebs.csi.eks.amazonaws.com",
        VolumeBindingMode = "WaitForFirstConsumer",
        Parameters =
        {
            { "type", "gp3" },
            { "encrypted", "true" },
        },
    }, new CustomResourceOptions
    {
        Provider = k8SProvider,
    });

    var pvc = new Kubernetes.Core.V1.PersistentVolumeClaim("pvc", new()
    {
        Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
        {
            Name = "auto-ebs-claim",
            Namespace = ns.Metadata.Apply(metadata => metadata.Name),
        },
        Spec = new Kubernetes.Types.Inputs.Core.V1.PersistentVolumeClaimSpecArgs
        {
            AccessModes = new[]
            {
                "ReadWriteOnce",
            },
            StorageClassName = storageClass.Metadata.Apply(metadata => metadata.Name),
            Resources = new Kubernetes.Types.Inputs.Core.V1.VolumeResourceRequirementsArgs
            {
                Requests =
                {
                    { "storage", "8Gi" },
                },
            },
        },
    }, new CustomResourceOptions
    {
        Provider = k8SProvider,
    });

    var statefulWorkload = new Kubernetes.Apps.V1.Deployment("stateful-workload", new()
    {
        Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
        {
            Name = "inflate-stateful",
            Namespace = ns.Metadata.Apply(metadata => metadata.Name),
        },
        Spec = new Kubernetes.Types.Inputs.Apps.V1.DeploymentSpecArgs
        {
            Replicas = 1,
            Selector = new Kubernetes.Types.Inputs.Meta.V1.LabelSelectorArgs
            {
                MatchLabels =
                {
                    { "app", "inflate-stateful" },
                },
            },
            Template = new Kubernetes.Types.Inputs.Core.V1.PodTemplateSpecArgs
            {
                Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
                {
                    Labels =
                    {
                        { "app", "inflate-stateful" },
                    },
                },
                Spec = new Kubernetes.Types.Inputs.Core.V1.PodSpecArgs
                {
                    TerminationGracePeriodSeconds = 0,
                    NodeSelector =
                    {
                        { "eks.amazonaws.com/compute-type", "auto" },
                    },
                    Containers = new[]
                    {
                        new Kubernetes.Types.Inputs.Core.V1.ContainerArgs
                        {
                            Name = "bash",
                            Image = "public.ecr.aws/docker/library/bash:4.4",
                            Command = new[]
                            {
                                "/usr/local/bin/bash",
                            },
                            Args = new[]
                            {
                                "-c",
                                "while true; do echo $(date -u) >> /data/out.txt; sleep 60; done",
                            },
                            Resources = new Kubernetes.Types.Inputs.Core.V1.ResourceRequirementsArgs
                            {
                                Requests =
                                {
                                    { "cpu", "1" },
                                },
                            },
                            VolumeMounts = new[]
                            {
                                new Kubernetes.Types.Inputs.Core.V1.VolumeMountArgs
                                {
                                    Name = "persistent-storage",
                                    MountPath = "/data",
                                },
                            },
                        },
                    },
                    Volumes = new[]
                    {
                        new Kubernetes.Types.Inputs.Core.V1.VolumeArgs
                        {
                            Name = "persistent-storage",
                            PersistentVolumeClaim = new Kubernetes.Types.Inputs.Core.V1.PersistentVolumeClaimVolumeSourceArgs
                            {
                                ClaimName = pvc.Metadata.Apply(metadata => metadata.Name),
                            },
                        },
                    },
                },
            },
        },
    }, new CustomResourceOptions
    {
        Provider = k8SProvider,
    });
});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.awsx.ec2.Vpc;
import com.pulumi.awsx.ec2.VpcArgs;
import com.pulumi.awsx.ec2.inputs.SubnetSpecArgs;
import com.pulumi.eks.Cluster;
import com.pulumi.eks.ClusterArgs;
import com.pulumi.eks.inputs.AutoModeOptionsArgs;
import com.pulumi.kubernetes.Provider;
import com.pulumi.kubernetes.ProviderArgs;
import com.pulumi.kubernetes.core_v1.Namespace;
import com.pulumi.kubernetes.core_v1.NamespaceArgs;
import com.pulumi.kubernetes.meta_v1.inputs.ObjectMetaArgs;
import com.pulumi.kubernetes.core_v1.ConfigMap;
import com.pulumi.kubernetes.core_v1.ConfigMapArgs;
import com.pulumi.kubernetes.apps_v1.Deployment;
import com.pulumi.kubernetes.apps_v1.DeploymentArgs;
import com.pulumi.kubernetes.apps_v1.inputs.DeploymentSpecArgs;
import com.pulumi.kubernetes.meta_v1.inputs.LabelSelectorArgs;
import com.pulumi.kubernetes.core_v1.inputs.PodTemplateSpecArgs;
import com.pulumi.kubernetes.core_v1.inputs.PodSpecArgs;
import com.pulumi.kubernetes.core_v1.Service;
import com.pulumi.kubernetes.core_v1.ServiceArgs;
import com.pulumi.kubernetes.core_v1.inputs.ServiceSpecArgs;
import com.pulumi.kubernetes.networking.k8s.io_v1.IngressClass;
import com.pulumi.kubernetes.networking.k8s.io_v1.IngressClassArgs;
import com.pulumi.kubernetes.networking.k8s.io_v1.inputs.IngressClassSpecArgs;
import com.pulumi.kubernetes.networking.k8s.io_v1.Ingress;
import com.pulumi.kubernetes.networking.k8s.io_v1.IngressArgs;
import com.pulumi.kubernetes.networking.k8s.io_v1.inputs.IngressSpecArgs;
import com.pulumi.kubernetes.storage.k8s.io_v1.StorageClass;
import com.pulumi.kubernetes.storage.k8s.io_v1.StorageClassArgs;
import com.pulumi.kubernetes.core_v1.PersistentVolumeClaim;
import com.pulumi.kubernetes.core_v1.PersistentVolumeClaimArgs;
import com.pulumi.kubernetes.core_v1.inputs.PersistentVolumeClaimSpecArgs;
import com.pulumi.kubernetes.core_v1.inputs.VolumeResourceRequirementsArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        // ...
        var storageClass = new StorageClass("storageClass", StorageClassArgs.builder()
            .metadata(ObjectMetaArgs.builder()
                .name("auto-ebs-sc")
                .annotations(Map.of("storageclass.kubernetes.io/is-default-class", "true"))
                .build())
            .provisioner("ebs.csi.eks.amazonaws.com")
            .volumeBindingMode("WaitForFirstConsumer")
            .parameters(Map.ofEntries(
                Map.entry("type", "gp3"),
                Map.entry("encrypted", "true")
            ))
            .build(), CustomResourceOptions.builder()
                .provider(k8SProvider)
                .build());

        var pvc = new PersistentVolumeClaim("pvc", PersistentVolumeClaimArgs.builder()
            .metadata(ObjectMetaArgs.builder()
                .name("auto-ebs-claim")
                .namespace(ns.metadata().applyValue(metadata -> metadata.name()))
                .build())
            .spec(PersistentVolumeClaimSpecArgs.builder()
                .accessModes("ReadWriteOnce")
                .storageClassName(storageClass.metadata().applyValue(metadata -> metadata.name()))
                .resources(VolumeResourceRequirementsArgs.builder()
                    .requests(Map.of("storage", "8Gi"))
                    .build())
                .build())
            .build(), CustomResourceOptions.builder()
                .provider(k8SProvider)
                .build());

        var statefulWorkload = new Deployment("statefulWorkload", DeploymentArgs.builder()
            .metadata(ObjectMetaArgs.builder()
                .name("inflate-stateful")
                .namespace(ns.metadata().applyValue(metadata -> metadata.name()))
                .build())
            .spec(DeploymentSpecArgs.builder()
                .replicas(1)
                .selector(LabelSelectorArgs.builder()
                    .matchLabels(Map.of("app", "inflate-stateful"))
                    .build())
                .template(PodTemplateSpecArgs.builder()
                    .metadata(ObjectMetaArgs.builder()
                        .labels(Map.of("app", "inflate-stateful"))
                        .build())
                    .spec(PodSpecArgs.builder()
                        .terminationGracePeriodSeconds(0)
                        .nodeSelector(Map.of("eks.amazonaws.com/compute-type", "auto"))
                        .containers(ContainerArgs.builder()
                            .name("bash")
                            .image("public.ecr.aws/docker/library/bash:4.4")
                            .command("/usr/local/bin/bash")
                            .args(
                                "-c",
                                "while true; do echo $(date -u) >> /data/out.txt; sleep 60; done")
                            .resources(ResourceRequirementsArgs.builder()
                                .requests(Map.of("cpu", "1"))
                                .build())
                            .volumeMounts(VolumeMountArgs.builder()
                                .name("persistent-storage")
                                .mountPath("/data")
                                .build())
                            .build())
                        .volumes(VolumeArgs.builder()
                            .name("persistent-storage")
                            .persistentVolumeClaim(PersistentVolumeClaimVolumeSourceArgs.builder()
                                .claimName(pvc.metadata().applyValue(metadata -> metadata.name()))
                                .build())
                            .build())
                        .build())
                    .build())
                .build())
            .build(), CustomResourceOptions.builder()
                .provider(k8SProvider)
                .build());
    }
}
name: eks-auto-mode-yaml
description: A Pulumi YAML program to deploy a Kubernetes cluster on AWS
runtime: yaml
resources:
  # ...
  storage-class:
    type: kubernetes:storage.k8s.io/v1:StorageClass
    properties:
      metadata:
        name: auto-ebs-sc
        annotations:
          storageclass.kubernetes.io/is-default-class: "true"
      provisioner: ebs.csi.eks.amazonaws.com
      volumeBindingMode: WaitForFirstConsumer
      parameters:
        type: gp3
        encrypted: "true"
    options:
      provider: ${k8sProvider}
  pvc:
    type: kubernetes:core/v1:PersistentVolumeClaim
    properties:
      metadata:
        name: auto-ebs-claim
        namespace: ${ns.metadata.name}
      spec:
        accessModes:
          - ReadWriteOnce
        storageClassName: ${storage-class.metadata.name}
        resources:
          requests:
            storage: 8Gi
    options:
      provider: ${k8sProvider}
  stateful-workload:
    type: kubernetes:apps/v1:Deployment
    properties:
      metadata:
        name: inflate-stateful
        namespace: ${ns.metadata.name}
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: inflate-stateful
        template:
          metadata:
            labels:
              app: inflate-stateful
          spec:
            terminationGracePeriodSeconds: 0
            nodeSelector:
              eks.amazonaws.com/compute-type: auto
            containers:
              - name: bash
                image: public.ecr.aws/docker/library/bash:4.4
                command: ["/usr/local/bin/bash"]
                args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 60; done"]
                resources:
                  requests:
                    cpu: "1"
                volumeMounts:
                  - name: persistent-storage
                    mountPath: /data
            volumes:
              - name: persistent-storage
                persistentVolumeClaim:
                  claimName: ${pvc.metadata.name}
    options:
      provider: ${k8sProvider}
outputs:
  url: ${ingress.status.loadBalancer.ingress[0].hostname}

Summary

EKS Auto Mode represents a significant step forward in simplifying Kubernetes operations on AWS. Pulumi’s support for this feature allows you to define EKS Auto Mode clusters using your favorite programming language and integrate them with your existing infrastructure code.

To get started with EKS Auto Mode in Pulumi:

  1. Update your AWS providers to the latest versions
  2. Check out our EKS documentation
  3. Join our Community Slack for support and discussions

We are excited to see what you build with EKS Auto Mode and Pulumi!