Csenge Papp

Csenge Papp

  · 7 min read

EKS Cluster with Pulumi

How to create and manage an AWS EKS Cluster with the help of Pulumi.

How to create and manage an AWS EKS Cluster with the help of Pulumi.

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:

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

modules


How to set up

For this project you will need the following:

  • An AWS Account
  • Pulumi installed on your computer

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.

project structure

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.

stacks

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", {});
  1. Use the kubernetes provider to interact with the EKS cluster
const k8sProvider = new kubernetes.Provider("k8s-provider", {
    kubeconfig: cluster.kubeconfig.apply(JSON.stringify),
});
  1. 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@v4
      - uses: actions/setup-node@v4
        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 ask for our help!

Back to Blog