Keys Included Kubernetes Cluster With EKS Auto Mode and CDK With Java

Keys Included Kubernetes Cluster With EKS Auto Mode and CDK With Java

Amazon Elastic Kubernetes Service (EKS) is a fully managed Kubernetes service that enables you to run Kubernetes seamlessly in both AWS Cloud and on-premises data centers. Just a few days before AWS re:Invent 2024, a new, exciting feature was released - Amazon EKS Auto Mode. This feature gives you a possibility to have a fully functional EKS cluster in a matter of minutes, with all necessary components included. There are several methods of deploying an EKS cluster. You can find some of them in the documentation or in the launch blog, but today I will use another one - deploying with AWS Cloud Development Kit (CDK) in Java. You can use a similar approach if you use other programming languages with AWS CDK.

Overview of a solution

The CDK L2 eks.Cluster construct doesn’t support Auto Mode yet, so I will use the more low-level L1 eks.CfnCluster construct, which already has support for Amazon EKS Auto Mode.

Prerequisites

Before you start, you need to ensure that you have:

Walkthrough

Creating a Java CDK application

  1. Use the instructions below or documentation and create the CDK application:
1
2
3
4
5
6
7
8
9
# create a folder for a CDK application
mkdir eks-auto-mode && cd eks-auto-mode
# set the CDK application name
CDK_APP_NAME=EksAutoModeApp
# create the CDK application
cdk init app --language=java
# Build application to make sure that setup is working
mvn clean package
cdk synth
  1. Create the implementation of the src/main/java/com/myorg/EksAutoModeStack.java class. The content of the file below includes comments for the most important parts of the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
cat <<EOF > src/main/java/com/myorg/EksAutoModeStack.java
package com.myorg;
// 1. Define Java importts:
import software.constructs.Construct;

import java.util.Arrays;
import java.util.List;
import java.util.Collections;

import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.iam.Role;
import software.amazon.awscdk.services.iam.ServicePrincipal;
import software.amazon.awscdk.services.iam.ManagedPolicy;
import software.amazon.awscdk.services.iam.PolicyStatement;
import software.amazon.awscdk.services.iam.Effect;
import software.amazon.awscdk.services.ec2.Vpc;
import software.amazon.awscdk.services.ec2.SubnetConfiguration;
import software.amazon.awscdk.services.ec2.SubnetType;
import software.amazon.awscdk.services.ec2.ISubnet;
import software.amazon.awscdk.services.ec2.SubnetSelection;
import software.amazon.awscdk.services.eks.CfnCluster;
import software.amazon.awscdk.services.eks.CfnCluster.LoggingProperty;
import software.amazon.awscdk.services.eks.CfnCluster.ClusterLoggingProperty;
import software.amazon.awscdk.services.eks.CfnCluster.LoggingTypeConfigProperty;
import software.amazon.awscdk.services.eks.CfnCluster.AccessConfigProperty;
import software.amazon.awscdk.services.eks.CfnCluster.ResourcesVpcConfigProperty;
import software.amazon.awscdk.services.eks.CfnCluster.UpgradePolicyProperty;
import software.amazon.awscdk.services.eks.CfnCluster.ComputeConfigProperty;
import software.amazon.awscdk.services.eks.CfnCluster.KubernetesNetworkConfigProperty;
import software.amazon.awscdk.services.eks.CfnCluster.ElasticLoadBalancingProperty;
import software.amazon.awscdk.services.eks.CfnCluster.StorageConfigProperty;
import software.amazon.awscdk.services.eks.CfnCluster.BlockStorageProperty;
import software.amazon.awscdk.services.eks.CfnAccessEntry;
import software.amazon.awscdk.services.eks.CfnAccessEntry.AccessScopeProperty;
import software.amazon.awscdk.services.eks.CfnAccessEntry.AccessPolicyProperty;
import software.amazon.awscdk.Tags;
import software.amazon.awscdk.CfnOutput;
import software.amazon.awscdk.Names;

public class EksAutoModeStack extends Stack {
public EksAutoModeStack(final Construct scope, final String id) {
this(scope, id, null);
}

public EksAutoModeStack(final Construct scope, final String id, final StackProps props) {
super(scope, id, props);

// 2. Create VPC for an Amazon EKS cluster:
var vpc = Vpc.Builder.create(this, "EKSAutoModeVPC")
.maxAzs(2) // Use 2 Availability Zone
.subnetConfiguration(Arrays.asList(
SubnetConfiguration.builder()
.name("Public")
.subnetType(SubnetType.PUBLIC)
.cidrMask(24)
.build(),
SubnetConfiguration.builder()
.name("Private")
.subnetType(SubnetType.PRIVATE_WITH_EGRESS)
.cidrMask(24)
.build()
))
// Use 1 NAT Gateway to save cost - don't use in production!
.natGateways(1)
.build();

// 3. Add tags to VPC subnets to enable Load Balancers
for (ISubnet subnet : vpc.getPublicSubnets()) {
Tags.of(subnet).add("kubernetes.io/role/elb", "1");
}
for (ISubnet subnet : vpc.getPrivateSubnets()) {
Tags.of(subnet).add("kubernetes.io/role/internal-elb", "1");
}

// 4. Create IAM Role for an EKS cluster to allow resource management
// https://docs.aws.amazon.com/eks/latest/userguide/auto-cluster-iam-role.html
var clusterRole = Role.Builder.create(this, "EKSAutoModeClusterRole")
.assumedBy(new ServicePrincipal("eks.amazonaws.com"))
.managedPolicies(Arrays.asList(
ManagedPolicy.fromAwsManagedPolicyName("AmazonEKSClusterPolicy"),
ManagedPolicy.fromAwsManagedPolicyName("AmazonEKSComputePolicy"),
ManagedPolicy.fromAwsManagedPolicyName("AmazonEKSBlockStoragePolicy"),
ManagedPolicy.fromAwsManagedPolicyName("AmazonEKSLoadBalancingPolicy"),
ManagedPolicy.fromAwsManagedPolicyName("AmazonEKSNetworkingPolicy")
))
.build();
clusterRole.getAssumeRolePolicy().addStatements(
PolicyStatement.Builder.create()
.effect(Effect.ALLOW)
.principals(Collections.singletonList(
new ServicePrincipal("eks.amazonaws.com")
))
.actions(Arrays.asList(
"sts:AssumeRole",
"sts:TagSession"
))
.build()
);

// 5. Create IAM ole for EKS cluster nodes
// https://docs.aws.amazon.com/eks/latest/userguide/auto-create-node-role.html
var nodeRole = Role.Builder.create(this, "EKSAutoModeNodeRole")
.assumedBy(new ServicePrincipal("ec2.amazonaws.com"))
.managedPolicies(Arrays.asList(
ManagedPolicy.fromAwsManagedPolicyName("AmazonEKSWorkerNodeMinimalPolicy"),
ManagedPolicy.fromAwsManagedPolicyName("AmazonEC2ContainerRegistryPullOnly")
))
.build();
clusterRole.getAssumeRolePolicy().addStatements(
PolicyStatement.Builder.create()
.effect(Effect.ALLOW)
.principals(Collections.singletonList(
new ServicePrincipal("ec2.amazonaws.com")
))
.actions(Arrays.asList(
"sts:AssumeRole"
))
.build()
);

// 6. Create an EKS cluster
var cluster = CfnCluster.Builder.create(this, "EKSAutoModeCluster")
.version("1.31")
// Enable EKS Auto Mode
.computeConfig(ComputeConfigProperty.builder()
.enabled(true)
.nodePools(Arrays.asList("system", "general-purpose"))
.nodeRoleArn(nodeRole.getRoleArn())
.build())
// Enable the load balancing capability on EKS Auto Mode
.kubernetesNetworkConfig(KubernetesNetworkConfigProperty.builder()
.elasticLoadBalancing(ElasticLoadBalancingProperty.builder()
.enabled(true)
.build())
.ipFamily("ipv4")
.build())
// !!! for EKS Auto Mode we need to enable computeConfig + elasticLoadBalancing + storageConfig
.storageConfig(StorageConfigProperty.builder()
.blockStorage(BlockStorageProperty.builder()
.enabled(true)
.build())
.build())
// Deploy cluster nodes in Private subnets
.resourcesVpcConfig(ResourcesVpcConfigProperty.builder()
.subnetIds(vpc.selectSubnets(SubnetSelection.builder()
.subnetType(SubnetType.PRIVATE_WITH_EGRESS)
.build()).getSubnetIds())
.endpointPrivateAccess(true)
.endpointPublicAccess(true)
.build())
.roleArn(clusterRole.getRoleArn())
// Use API mode for cluster access
.accessConfig(AccessConfigProperty.builder()
.authenticationMode("API")
.bootstrapClusterCreatorAdminPermissions(false)
.build())
.logging(LoggingProperty.builder()
.clusterLogging(ClusterLoggingProperty.builder()
.enabledTypes(List.of(
LoggingTypeConfigProperty.builder().type("api").build(),
LoggingTypeConfigProperty.builder().type("audit").build(),
LoggingTypeConfigProperty.builder().type("authenticator").build(),
LoggingTypeConfigProperty.builder().type("controllerManager").build(),
LoggingTypeConfigProperty.builder().type("scheduler").build()
)).build())
.build())
.upgradePolicy(UpgradePolicyProperty.builder()
.supportType("STANDARD")
.build())
.build();
// Generate unique name for a cluster
Names.uniqueId(cluster);

var accountId = this.getAccount();
// Add access to your AIM Role to the EKS cluster
CfnAccessEntry.Builder.create(this, "EKSAutoModeClusterRoleAccessEntry")
.clusterName(cluster.getRef())
// !!! replace with your role name/arn:
.principalArn("arn:aws:iam::" + accountId + ":role/Admin")
.accessPolicies(List.of(AccessPolicyProperty.builder()
.accessScope(AccessScopeProperty.builder()
.type("cluster")
.build())
.policyArn("arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy")
.build()))
.build();

CfnOutput.Builder.create(this, "KubeconfigCommand")
.value(String.format("aws eks --region %s update-kubeconfig --name %s",
Stack.of(this).getRegion(),
cluster.getRef()))
.description("Command to update kubeconfig")
.build();
}
}
EOF
  1. Before you build the project, open the src/main/java/com/myorg/EksAutoModeStack.java file and replace the placeholder with your IAM Role ARN:
1
2
// !!! replace with your role name/arn:
.principalArn("arn:aws:iam::" + accountId + ":role/Admin")
  1. Build the CDK project:
1
2
mvn clean package
cdk synth
  1. Deploy the CDK project to your AWS account:
1
2
cdk bootstrap
cdk deploy
  1. Review the changes which proposed by CDK, say “yes” to the CDK question, and in 10-15 minutes you will have a ready-to-use Amazon EKS cluster!

eks-auto-mode

Deploying workloads

  1. Get access to the EKS cluster:
1
2
3
$(aws cloudformation describe-stacks --stack-name EksAutoModeStack \
--query "Stacks[0].Outputs[?OutputKey=='KubeconfigCommand'].OutputValue" \
--output text)

If you have problems accessing the EKS cluster, you can go to EKS cluster console and add your role to “Access/IAM access entries” with “AmazonEKSClusterAdminPolicy”

  1. Get the EKS cluster nodes:
1
2
kubectl get nodes
eks-node-viewer
  1. You should receive “No resources found”

eks-node-viewer-initial

  1. Deploy a sample load balancer workload to the EKS Auto Mode cluster using this instructions or the code snippet below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Namespace
metadata:
name: game-2048
EOF

cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: game-2048
name: deployment-2048
spec:
selector:
matchLabels:
app.kubernetes.io/name: app-2048
replicas: 5
template:
metadata:
labels:
app.kubernetes.io/name: app-2048
spec:
containers:
- image: public.ecr.aws/l6m2t8p7/docker-2048:latest
imagePullPolicy: Always
name: app-2048
ports:
- containerPort: 80
resources:
requests:
cpu: "0.5"
EOF

cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Service
metadata:
namespace: game-2048
name: service-2048
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
type: NodePort
selector:
app.kubernetes.io/name: app-2048
EOF

cat <<EOF | kubectl create -f -
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
namespace: game-2048
labels:
app.kubernetes.io/name: LoadBalancerController
name: alb
spec:
controller: eks.amazonaws.com/alb
EOF

cat <<EOF | kubectl create -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: game-2048
name: ingress-2048
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-2048
port:
number: 80
EOF
  1. Check that all resources have deployed properly. It might take 3-5 minutes to deploy the Application Load Balancer (ALB):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kubectl get nodes -o wide
# NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
# i-04602f286ba355450 Ready <none> 28m v1.31.1-eks-1b3e656 10.0.3.172 <none> Bottlerocket (EKS Auto) 2024.12.8 (aws-k8s-1.31) 6.1.115 containerd://1.7.22+bottlerocket
kubectl get pods -n game-2048
# NAME READY STATUS RESTARTS AGE
# deployment-2048-98ddb8c75-55qcx 1/1 Running 0 29m
# deployment-2048-98ddb8c75-5szlq 1/1 Running 0 29m
# deployment-2048-98ddb8c75-8drv4 1/1 Running 0 29m
# deployment-2048-98ddb8c75-l6q89 1/1 Running 0 29m
# deployment-2048-98ddb8c75-qtvs5 1/1 Running 0 29m
kubectl get svc -n game-2048
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service-2048 NodePort 172.20.151.169 <none> 80:31472/TCP 29m
kubectl get ingress -n game-2048
# NAME CLASS HOSTS ADDRESS PORTS AGE
# ingress-2048 alb * k8s-game2048-ingress2-2f6e07220b-1922490733.eu-central-1.elb.amazonaws.com 80 29m
1
2
URL=$(kubectl get ingress -n game-2048 -o jsonpath='{.items[0].status.loadBalancer.ingress[0].hostname}')
echo http://$URL
  1. Now you have an up and running EKS cluster and can play the game using the ALB address above. You can also scale the number of replicas in the deployment to 10 and back to zero to see how EKS cluster nodes are created and destroyed.

eks-node-viewer-5

1
kubectl scale deployment deployment-2048 -n game-2048 --replicas=10

eks-node-viewer-10

1
kubectl scale deployment deployment-2048 -n game-2048 --replicas=0

eks-node-viewer-0

Cleaning up

  1. Delete deployed resources:
1
kubectl delete namespace game-2048
  1. Destroy the deployed stacks. Review the changes proposed by CDK, and say “yes” to the CDK question:
1
cdk destroy

Conclusion

In this blog post, I demonstrated how to deploy an Amazon EKS cluster in Auto Mode using AWS CDK with Java. This setup not only gives you the ability to automate EKS cluster deployment, but also delivers a ready-to-use EKS cluster with:

  • Managed Kubernetes Control Plane

  • Managed Kubernetes Data Plane with Amazon EKS Auto Node Managed Capabilities including:

Developers can now deploy their applications to the EKS cluster immediately after its creation. Let’s go build!

Comments