Cloud Development Kit (CDK) is great for writing infrastructure as code within AWS. Writing CDK in Go is now here as a Developer Preview, and what better way to try it out than to create an EKS (Elastic Kubernetes Service) cluster. For the completed code, you can view this Gist.

Requirements

Before we get started, you’ll need a handful of things:

Getting Started

I’m going to create a new directory called eks-cdk-go to work from. Once you’ve entered into the newly created directory, you can initialize a new cdk app.

steve@home:~/code/eks-cdk-go$ cdk init --language=go
Applying project template app for go
# Welcome to your CDK Go project!

This is a blank project for Go development with CDK.

**NOTICE**: Go support is still in Developer Preview. This implies that APIs may
change while we address early feedback from the community. We would love to hear
about your experience through GitHub issues.

## Useful commands

 * `cdk deploy`      deploy this stack to your default AWS account/region
 * `cdk diff`        compare deployed stack with current state
 * `cdk synth`       emits the synthesized CloudFormation template
 * `go test`         run unit tests

Initializing a new git repository...
✅ All done!

At this point, we’ve got a fully functional CDK stack that will create an SNS topic. CDK synthesizes to CloudFormation. Let’s check out the resulting YAML:

steve@ubuntu:~/code/eks-cdk-go$ cdk synth
Resources:
  MyTopic86869434:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: MyCoolTopic
    Metadata:
      aws:cdk:path: EksWithCdkGoStack/MyTopic/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Analytics: v2:deflate64

For any Gophers wondering why we haven’t had to run go mod download or go run, it is because CDK runs them automatically when running cdk synth or cdk deploy.

Creating an EKS Cluster

In order to use EKS constructs, we’ll need to add the awseks package. You can also remove the awssns package, as we won’t be using any SNS topics.

package main

import (
	"github.com/aws/aws-cdk-go/awscdk"
	"github.com/aws/aws-cdk-go/awscdk/awseks" # awssns was replaced here
	"github.com/aws/constructs-go/constructs/v3"
	"github.com/aws/jsii-runtime-go"
)

Once awseks is imported, replace the SNS topic with a basic EKS cluster:

    # Replace this SNS topic:
        awssns.NewTopic(stack, jsii.String("MyTopic"), &awssns.TopicProps{
    		DisplayName: jsii.String("MyCoolTopic"),
    	})
    
    # With an EKS cluster:
    	awseks.NewCluster(stack, jsii.String("MyNewCluster"), &awseks.ClusterProps{
    		Version: awseks.KubernetesVersion_V1_19(),
			ClusterName: jsii.String("my-new-cluster"), // Overrides the auto-generated name
    	})

That’s it! With two small changes, we’ve got all the CDK needed to create a basic EKS cluster.

Run cdk synth again to verify that things are working. When creating an EKS cluster with the default options, your cluster is created in a new VPC with two m5.large nodes.

Note: If you’ve done any CDK before, the jsii.String("MyNewCluster") may look a little odd to you. It’s used to get a pointer to the string “MyNewCluster”. Parameters in CDK for Go generally expect pointers to be passed.

Deploying your cluster

Let’s deploy a cluster! When you’re ready, run cdk deploy

steve@ubuntu:~/code/eks-cdk-go$ cdk deploy
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬───────────────────────────────────────────────────┬────────┬───────────────────────────────────────────────────┬───────────────────────────────────────────────────┬───────────┐
│   │ Resource                                          │ Effect │ Action                                            │ Principal                                         │ Condition │
├───┼───────────────────────────────────────────────────┼────────┼───────────────────────────────────────────────────┼───────────────────────────────────────────────────┼───────────┤
│ + │ ${MyNewCluster/Resource/CreationRole.Arn}         │ Allow  │ sts:AssumeRole                                    │ AWS:arn:${AWS::Partition}:iam::${AWS::AccountId}: │           │
│   │                                                   │        │                                                   │ root                                              │           │
├───┼───────────────────────────────────────────────────┼────────┼───────────────────────────────────────────────────┼───────────────────────────────────────────────────┼───────────┤
│ + │ ${MyNewCluster/MastersRole.Arn}                   │ Allow  │ sts:AssumeRole                                    │ AWS:arn:${AWS::Partition}:iam::${AWS::AccountId}: │           │
│   │                                                   │        │                                                   │ root                                              │           │
├───┼───────────────────────────────────────────────────┼────────┼───────────────────────────────────────────────────┼───────────────────────────────────────────────────┼───────────┤
│ + │ ${MyNewCluster/NodegroupDefaultCapacity/NodeGroup │ Allow  │ sts:AssumeRole                                    │ Service:ec2.${AWS::URLSuffix}                     │           │
│   │ Role.Arn}                                         │        │                                                   │                                                   │           │
├───┼───────────────────────────────────────────────────┼────────┼───────────────────────────────────────────────────┼───────────────────────────────────────────────────┼───────────┤
│ + │ ${MyNewCluster/Role.Arn}                          │ Allow  │ sts:AssumeRole                                    │ Service:eks.amazonaws.com                         │           │
│ + │ ${MyNewCluster/Role.Arn}                          │ Allow  │ iam:PassRole                                      │ AWS:${MyNewCluster/Resource/CreationRole}         │           │
├───┼───────────────────────────────────────────────────┼────────┼───────────────────────────────────────────────────┼───────────────────────────────────────────────────┼───────────┤
│ + │ *                                                 │ Allow  │ eks:CreateCluster                                 │ AWS:${MyNewCluster/Resource/CreationRole}         │           │
│   │                                                   │        │ eks:CreateFargateProfile                          │                                                   │           │
│   │                                                   │        │ eks:DeleteCluster                                 │                                                   │           │
│   │                                                   │        │ eks:DescribeCluster                               │                                                   │           │
│   │                                                   │        │ eks:DescribeUpdate                                │                                                   │           │
│   │                                                   │        │ eks:TagResource                                   │                                                   │           │
│   │                                                   │        │ eks:UntagResource                                 │                                                   │           │
│   │                                                   │        │ eks:UpdateClusterConfig                           │                                                   │           │
│   │                                                   │        │ eks:UpdateClusterVersion                          │                                                   │           │
│ + │ *                                                 │ Allow  │ eks:DeleteFargateProfile                          │ AWS:${MyNewCluster/Resource/CreationRole}         │           │
│   │                                                   │        │ eks:DescribeFargateProfile                        │                                                   │           │
│ + │ *                                                 │ Allow  │ iam:GetRole                                       │ AWS:${MyNewCluster/Resource/CreationRole}         │           │
│   │                                                   │        │ iam:listAttachedRolePolicies                      │                                                   │           │
│ + │ *                                                 │ Allow  │ iam:CreateServiceLinkedRole                       │ AWS:${MyNewCluster/Resource/CreationRole}         │           │
│ + │ *                                                 │ Allow  │ ec2:DescribeDhcpOptions                           │ AWS:${MyNewCluster/Resource/CreationRole}         │           │
│   │                                                   │        │ ec2:DescribeInstances                             │                                                   │           │
│   │                                                   │        │ ec2:DescribeNetworkInterfaces                     │                                                   │           │
│   │                                                   │        │ ec2:DescribeRouteTables                           │                                                   │           │
│   │                                                   │        │ ec2:DescribeSecurityGroups                        │                                                   │           │
│   │                                                   │        │ ec2:DescribeSubnets                               │                                                   │           │
│   │                                                   │        │ ec2:DescribeVpcs                                  │                                                   │           │
└───┴───────────────────────────────────────────────────┴────────┴───────────────────────────────────────────────────┴───────────────────────────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────┐
│   │ Resource                                               │ Managed Policy ARN                                                       │
├───┼────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
│ + │ ${MyNewCluster/NodegroupDefaultCapacity/NodeGroupRole} │ arn:${AWS::Partition}:iam::aws:policy/AmazonEKSWorkerNodePolicy          │
│ + │ ${MyNewCluster/NodegroupDefaultCapacity/NodeGroupRole} │ arn:${AWS::Partition}:iam::aws:policy/AmazonEKS_CNI_Policy               │
│ + │ ${MyNewCluster/NodegroupDefaultCapacity/NodeGroupRole} │ arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly │
├───┼────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
│ + │ ${MyNewCluster/Role}                                   │ arn:${AWS::Partition}:iam::aws:policy/AmazonEKSClusterPolicy             │
└───┴────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────┘
Security Group Changes
┌───┬───────────────────────────────────────────────────┬─────┬────────────┬─────────────────┐
│   │ Group                                             │ Dir │ Protocol   │ Peer            │
├───┼───────────────────────────────────────────────────┼─────┼────────────┼─────────────────┤
│ + │ ${MyNewCluster/ControlPlaneSecurityGroup.GroupId} │ Out │ Everything │ Everyone (IPv4)└───┴───────────────────────────────────────────────────┴─────┴────────────┴─────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)?

The deployment time may vary, but it took me about 20 minutes to deploy the entire stack.

Testing out your cluster

After creating your cluster, CDK should output a command that will add your new cluster to your kubeconfig. In this case, it would be:

aws eks update-kubeconfig --name MyNewCluster1524786D-5b1b19f1578646af981dbd07dfa4120f --region us-east-1 --role-arn arn:aws:iam::123456789000:role/EksWithCdkGoStack-MyNewClusterMastersRoleEB8CEC7C-5VP5QB7C0WAV

 ✅  EksWithCdkGoStack

Outputs:
EksWithCdkGoStack.MyNewClusterConfigCommand1E0E0138 = aws eks update-kubeconfig --name MyNewCluster1524786D-5b1b19f1578646af981dbd07dfa4120f --region us-east-1 --role-arn arn:aws:iam::123456789000:role/EksWithCdkGoStack-MyNewClusterMastersRoleEB8CEC7C-5VP5QB7C0WAV
EksWithCdkGoStack.MyNewClusterGetTokenCommand4893D1B1 = aws eks get-token --cluster-name MyNewCluster1524786D-5b1b19f1578646af981dbd07dfa4120f --region us-east-1 --role-arn arn:aws:iam::123456789000:role/EksWithCdkGoStack-MyNewClusterMastersRoleEB8CEC7C-5VP5QB7C0WAV

Let’s run kubectl get pods -A to get the pods in all namespaces:

steve@ubuntu:~/code/eks-cdk-go$ kubectl get pods -A
NAMESPACE     NAME                       READY   STATUS    RESTARTS   AGE
kube-system   aws-node-s9v7d             1/1     Running   0          15m
kube-system   aws-node-v4zjr             1/1     Running   0          15m
kube-system   coredns-7d74b564bd-dl8rw   1/1     Running   0          15m
kube-system   coredns-7d74b564bd-kx5x9   1/1     Running   0          15m
kube-system   kube-proxy-8hz68           1/1     Running   0          15m
kube-system   kube-proxy-rn6vh           1/1     Running   0          15m

Great! We’ve got a working cluster!

Adding a Helm Chart with CDK

Not only can you create EKS clusters in CDK, but you can also deploy Kubernetes manifests and Helm charts.

Let’s add a helm chart to our cluster! Note that we’ve now assigned the cluster to a variable so that we can access it later in the stack.

	cluster := awseks.NewCluster(stack, jsii.String("MyNewCluster"), &awseks.ClusterProps{
    	Version: awseks.KubernetesVersion_V1_19(),
		ClusterName: jsii.String("my-new-cluster"), // Overrides the auto-generated name
    })

	cluster.AddHelmChart(jsii.String("KubePrometheusStack"), &awseks.HelmChartOptions{
		Chart: jsii.String("kube-prometheus-stack"),
		Repository: jsii.String("https://prometheus-community.github.io/helm-charts"),
		Namespace: jsii.String("prometheus"),
		CreateNamespace: jsii.Bool(true), // Defaults to true and not required, but shown for clarity
		Release: jsii.String("prometheus"), // Overrides the auto-generated release name
	})

When deploying a Helm chart, CDK will automatically create the needed namespace. You don’t need to include CreateNamespace here (that entire line can be omitted), but it illustrates the use of using jsii.Bool(true) to create a pointer to the value true.

Checking the diff and deploying the chart

Running cdk diff will enable you to see the difference between what’s currently deployed, and what is being synthesized from your CDK stack locally. Once you’re happy with it, you can deploy the stack with cdk deploy.

CDK uses lambda layers with kubectl, helm, and the AWS CLI to deploy manifests to your cluster (and can even deploy to a cluster with its API endpoints set to private VPC subnets only).

After the deployment has finished, run kubectl get all -n prometheus to view your newly created resources.

steve@ubuntu:~/code/eks-cdk-go$ kubectl get pods -A
NAMESPACE     NAME                                                              READY   STATUS    RESTARTS   AGE
prometheus    alertmanager-prometheus-kube-prometheus-alertmanager-0   2/2     Running   0          11m
prometheus    prometheus-grafana-556b6745d8-pmjpt                      2/2     Running   0          11m
prometheus    prometheus-kube-prometheus-operator-b8c8df77c-t24fj      1/1     Running   0          11m
prometheus    prometheus-kube-state-metrics-685b975bb7-mbgxw           1/1     Running   0          11m
prometheus    prometheus-prometheus-kube-prometheus-prometheus-0       2/2     Running   0          11m
prometheus    prometheus-prometheus-node-exporter-2rj28                1/1     Running   0          11m
prometheus    prometheus-prometheus-node-exporter-s8qjz                1/1     Running   0          11m

Cleaning up

To clean up when you’re done, all you have to do is run a single command:

steve@ubuntu:~/code/eks-cdk-go$ cdk destroy
Are you sure you want to delete: EksWithCdkGoStack (y/n)? y
EksWithCdkGoStack: destroying...
10:00:03 AM | DELETE_IN_PROGRESS   | AWS::CloudFormation::Stack            | EksWithCdkGoStack
10:11:40 AM | DELETE_IN_PROGRESS   | AWS::EC2::RouteTable                  | MyNewCluster/Defau...Subnet1/RouteTable
 ✅  EksWithCdkGoStack: destroyed

Learning More

To learn more about what the EKS module can do, check out the CDK docs: https://docs.aws.amazon.com/cdk/api/latest/docs/aws-eks-readme.html

For more specific information on the CDK EKS package for Go, you can view the docs here: https://pkg.go.dev/github.com/aws/aws-cdk-go/awscdk/awseks