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:
- An AWS account with credentials setup locally
- Go version >= 1.16 (https://golang.org/doc/install)
- CDK version >= 1.96 (to install, run
npm install -g aws-cdk
) - To have run
cdk bootstrap
within your AWS account - kubectl installed locally (https://kubernetes.io/docs/tasks/tools/)
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