EKS Cluster with Pulumi

Papp Csenge
CodeFactory
Published in
7 min readJan 17, 2024

About the project

Pulumi is a fast-evolving tool with many possibilities. It allows you to write IaC code in popular languages like Typescript or Go leaving much more room for customization than Terraform. At first glance, it seemed like a concept worth experimenting with. That is why we created a small project building an EKS cluster with Pulumi. If you want to check out the repository for this project you can see it here: https://github.com/codefactoryhu/pulumi-starter-kit

The goal of this article is to introduce this project and our experience with working with Pulumi for the first time.

The project includes the following modules:

  1. KMS Key
  2. VPS with its resources (Subnets, internet gateway, nat gateway, route tables, elastic IP and Flow Logs)
  3. EKS Cluster
  4. Helm Releases in the Cluster
  5. S3 Buckets and ECR Repositories

How to set up

For this project you will need the following:

For starting a new Pulumi project make a new folder and run pulumi new. It will ask you about some basic project information as well as offer you to create a project based on prompting the Pulumi AI, which is a pretty cool new feature, but we will skip that this time. From the templates choose aws-typescript.

$ pulumi new
Would you like to create a project from a template or using a Pulumi AI prompt? template
Please choose a template (38/221 shown):
aws-typescript A minimal AWS TypeScript Pulumi program
This command will walk you through creating a new Pulumi project.

Enter a value or leave blank to accept the (default), and press <ENTER>.
Press ^C at any time to quit.

project name (new-project):

Project structure

For our project, we wanted to create a structure that is reusable in future projects. We used modules for AWS resources and Pulumi stack configuration files for storing the resource’s attributes as variables. That way if want to make a change later, we will only need to make changes to the config files. This makes managing the project a lot cleaner and easier.

In addition to the aws-typscript template Pulumi provides you can also see a folder for Github workflows. In there, we created pipelines for viewing and deploying our Pulumi structure.

Configuring our Pulumi project

To understand the way we this up first we have to talk about the concept of stacks in Pulumi. Pulumi provides a configuration system for managing different environments like development and production in the form of stacks. It is similar to Terragrunt’s approach to managing different environments of the same infrastructure. However, in this case, there is no need to create the same folder structure twice. You can simply change the attributes of resources in the stack’s configuration file. You can view stacks in Pulumi’s visual UI or by typing pulumi stack ls in your terminal.

NAME  LAST UPDATE  RESOURCE COUNT  URL
dev 5 days ago 0 https://app.pulumi.com/user/aws-starter-kit/dev
prod 4 days ago 0 https://app.pulumi.com/user/aws-starter-kit/prod
test 1 hour ago 22 https://app.pulumi.com/user/aws-starter-kit/test

Let’s have a look at the config files. Pulumi by default creates a global Pulumi.yaml. The information stored in this file applies to all stacks.

name: aws-starter-kit
runtime: nodejs
description: A starter kit for AWS with basic features.

We also have the stack configuration files with individual attributes for each created AWS resource. Here you can see a block for the KMS key we created….

  aws-starter-kit:kmsKey:
name: pulumi-kms-key
deletionWindowInDays: 10
description: KMS key for EKS
enableKeyRotation: false
isEnabled: true
keyUsage: ENCRYPT_DECRYPT
multiRegion: false

…as well as variables that apply to every resource like tags.

aws:defaultTags:
tags:
project: pulumi-starter-kit
env: dev
version: "v1.0.0"

AWS resources

If you want to create an AWS resource for example a KMS Key there are many options you can use to make the process less painful and time-consuming. If you already have the resource defined you can use the Pulumi Converter. It can generate Pulumi code from:

  1. ARM templates
  2. CloudFormation templates
  3. K8s CustomResourceDefinitions
  4. K8s YAML
  5. Terraform
Pulumi Converter

If you have already existing resources in AWS you want to include them in your project you can use the pulumi import command. This will generate Pulumi code from the resource in any language you choose and include it in the project’s state.

Another option can be the Pulumi AI which can be a huge help that can save you a lot of time you would spend searching for the relevant pages in the documentation.

Pulumi AI

Here you can see the code we created to define a KMS key. We will need a resource for the key itself and a separate one for the key’s alias.

import * as pulumi  from '@pulumi/pulumi';
import * as aws from '@pulumi/aws';

// Import Interfaces
import { kmsType, kmsAliasType } from '../kms-interface';

const config = new pulumi.Config();
const project = config.require("project");
const env = config.require("env");
const pulumikmsKey = config.requireObject<kmsType>("kmsKey");
const pulumikmsKeyAlias = config.requireObject<kmsAliasType>("kmsKeyAlias");

export let kmsArn:pulumi.Output<string>;

export function kms() {
const kmsKey = new aws.kms.Key(`${pulumikmsKey.name}`, {
isEnabled: pulumikmsKey.isEnabled,
keyUsage: pulumikmsKey.keyUsage,
multiRegion: pulumikmsKey.multiRegion,
deletionWindowInDays: pulumikmsKey.deletionWindowInDays,
enableKeyRotation: pulumikmsKey.enableKeyRotation,
description: pulumikmsKey.description,
tags: {
"Name" : `${env}-${pulumikmsKey.name}`,
"Env" : env,
"Project": project
},
})
kmsArn = kmsKey.arn;
kmsAlias(kmsKey);
};

function kmsAlias(kmsKey:aws.kms.Key) {
const kmsAlias = new aws.kms.Alias(`${pulumikmsKeyAlias.name}-${env}`, {
targetKeyId: kmsKey.id,
name: `${pulumikmsKeyAlias.displayName}-${env}`,
}, {dependsOn: [ kmsKey ]})
};

We also created two interfaces that help us use the attributes we defined in config file.

interface kmsType {
name: string;
deletionWindowInDays: number;
description: string;
enableKeyRotation: boolean;
isEnabled: boolean;
keyUsage: string;
multiRegion: boolean;
}

interface kmsAliasType {
name: string
displayName: string
}

export {
kmsType,
kmsAliasType
}

Helm release

Creating Helm releases on the cluster requires an extra step apart for creating a Helm Release resource https://www.pulumi.com/registry/packages/kubernetes/api-docs/helm/v3/release/ . Here is the whole process step by step:

  1. Create an EKS Cluster
const cluster = new eks.Cluster("pulumi-cluster", {});

2. Use the kubernetes provider to interact with the EKS cluster

const k8sProvider = new kubernetes.Provider("k8s-provider", {
kubeconfig: cluster.kubeconfig.apply(JSON.stringify),
});

3. Deploy a Helm chart to the cluster

const release = new k8s.helm.v3.Release(helmRelease.name, {
chart: helmRelease.name,
version: helmRelease.chartVersion,
namespace: helmRelease.namespace,
}, {
provider: k8sProvider,
});

Deploying the project

If you are ready with our project and you want to deploy to AWS there are two options. You either do it locally with the help of the pulumi cli or write a pipeline.

  • pulumi preview: with this command, you can view the resources in the project
  • pulumi up: deploying resources in the stack you choose
$ pulumi up
Please choose a stack, or create a new one: dev
Previewing update (dev)

Type Name Plan
+ pulumi:pulumi:Stack aws-starter-kit-dev create
+ ├─ eks:index:Cluster pulumi-cluster create
+ │ ├─ eks:index:ServiceRole pulumi-cluster-instanceRole create
+ │ │ ├─ aws:iam:Role pulumi-cluster-instanceRole-role create
+ │ │ ├─ aws:iam:RolePolicyAttachment pulumi-cluster-instanceRole-3eb088f2 create
+ │ │ ├─ aws:iam:RolePolicyAttachment pulumi-cluster-instanceRole-03516f97 create
+ │ │ └─ aws:iam:RolePolicyAttachment pulumi-cluster-instanceRole-e1b295bd create
+ │ ├─ eks:index:ServiceRole pulumi-cluster-eksRole create
+ │ │ ├─ aws:iam:Role pulumi-cluster-eksRole-role create
+ │ │ └─ aws:iam:RolePolicyAttachment pulumi-cluster-eksRole-4b490823 create
+ │ ├─ aws:ec2:SecurityGroup pulumi-cluster-eksClusterSecurityGroup create
+ │ ├─ aws:ec2:SecurityGroupRule pulumi-cluster-eksClusterInternetEgressRule create
+ │ ├─ aws:eks:Cluster pulumi-cluster-eksCluster create
+ │ ├─ pulumi:providers:kubernetes pulumi-cluster-eks-k8s create
+ │ ├─ pulumi:providers:kubernetes pulumi-cluster-provider create
+ │ ├─ aws:ec2:SecurityGroup pulumi-cluster-nodeSecurityGroup create
+ │ ├─ kubernetes:core/v1:ConfigMap pulumi-cluster-nodeAccess create
+ │ ├─ aws:iam:OpenIdConnectProvider pulumi-cluster-oidcProvider create
+ │ ├─ eks:index:VpcCni pulumi-cluster-vpc-cni create
+ │ ├─ aws:ec2:SecurityGroupRule pulumi-cluster-eksNodeIngressRule create
+ │ ├─ aws:ec2:SecurityGroupRule pulumi-cluster-eksExtApiServerClusterIngressRule create
+ │ ├─ aws:ec2:SecurityGroupRule pulumi-cluster-eksNodeClusterIngressRule create
+ │ ├─ aws:ec2:SecurityGroupRule pulumi-cluster-eksClusterIngressRule create
+ │ └─ aws:ec2:SecurityGroupRule pulumi-cluster-eksNodeInternetEgressRule create
+ ├─ aws:ecr:Repository pulumi-ecr2 create
+ ├─ aws:kms:Key pulumi-kms-key-dev create
+ ├─ aws:ecr:Repository pulumi-ecr1 create
+ ├─ aws:s3:Bucket pulumi-bucket2 create
+ ├─ aws:iam:Policy cloudwatch-irsa-policy create
+ ├─ aws:ec2:Vpc pulumi-vpc create
+ ├─ aws:ec2:Eip pulumi-elastic-ip create
+ ├─ aws:s3:Bucket pulumi-bucket1 create

Pulumi provides guides and resources to help with continuous integration/continuous delivery. Here is an example Github Actions workflow:

name: Pulumi
on:
- pull_request
jobs:
preview:
name: Preview
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3.5.0
with:
node-version-file: package.json
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-region: ${{ secrets.AWS_REGION }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- run: npm install
- uses: pulumi/actions@v3
with:
command: preview
stack-name: org-name/stack-name
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}

Conclusions

As well as cloud solutions, Infrastructure as Code (IaC) technologies are rapidly evolving, offering flexibility and efficiency in managing IT infrastructure with new features like continuous integration and deployment, enhanced security measures or the use of AI based technology.

If you are looking for a partner experienced in cloud solutions, whether you need to migrate your resources to the cloud, optimize your cloud environment or create automated CICD processes, feel free to visit:

--

--