AWS Cloud

Demo App using ReactJS and TypeScript launched in AWS EKS using CDK — Part 2

AWS EKS | CDK | ReactJS | Typescript | Vite

Arun Kumar Singh
8 min readSep 30, 2024
Photo by Sonika Agarwal on Unsplash

In the previous post of this series we prepared our app and containerise it. Now its time for deployment of EKS cluster.

Before we proceed let’s talk about EKS a little bit, As per AWS,

Amazon Elastic Kubernetes Service (Amazon EKS) is a managed Kubernetes service that makes it easy for you to run Kubernetes on AWS and on-premises. It runs vanilla Kubernetes; EKS is upstream and a certified conformant version of Kubernetes (with backported security fixes). Amazon EKS lets you run your Kubernetes applications on both Amazon Elastic Compute Cloud (Amazon EC2) and AWS Fargate. Amazon EKS automatically manages the availability and scalability of the Kubernetes control plane nodes responsible for scheduling containers, managing application availability, storing cluster data, and other key tasks.

From AWS

Why EKS?

In my opinion 2 very important factors,

  • Kubernetes management itself is a mammoth task, so managed services can significantly reduce your overhead.
  • Applications need a native way to integrate with other AWS services securely and reliably, EKS reduces this pain as well with native features.

EKS Deployment

Now let’s deploy the EKS cluster using AWS CDK. I will provide some references for Terraform based deployment as well in future. I have selected CDK for IaC as it comes with great defaults and abstraction which reduces the complexity of CloudFormation for me.

Create an EKS Cluster
I have created a public GitHub repo for all CDK code base. We will deep dive for few concepts in a moment. But before that lets’s create a cluster first.

# Set AWS profile and region
export AWS_DEFAULT_REGION="eu-central-1"
# clone the repo
git clone https://github.com/aws-samples/amazon-eks-using-cdk-typescript.git
# install dependant packages
npm install
# bootstrap: If you have not used cdk before, you may be advised to create cdk resources
cdk bootstrap aws://ACCOUNT_ID/REGION
# check the diff before deployment to understand any changes, on first run all resources will created
# dont forget to create a keypair in EC2 console and keep the key for login in bastion
# pass the keyname
cdk diff -c keyName="arun" --all
# Deploy the stack, you will be prompted for confirmation for creation resources
cdk deploy -c keyName="arun" --all

The above set of commands will create your cluster and a bastion box which will have admin access on cluster! Please note before you proceed make sure you AWS EC2 Key pair name handy. It need to be passed in Cluster. The CDK script will also deploy required utilities in bastion box e.g. aws cli, kubectl and eksctl.

# once you are ready then login in bastion server and run the command to 
# update the kubeconfig
aws eks --region eu-central-1 update-kubeconfig --name eks-cluster

# if the above command is successful then it will fire up a message like below
Added new context arn:aws:eks:eu-central-1:1234567:cluster/eks-cluster to /home/ec2-user/.kube/config

All set, lets run our first command to verify the cluster.

Deep Dive AWS CDK for EKS

The VPC stack in CDK is pretty decent to understand. It creates a VPC, ECR repo for holding our images, a bastion server with required utilities for accessing the cluster.

The EKS Stack creates a EKS Cluster with Kubernetes V1_30 version and with API authentication mode.

this.cluster = new eks.Cluster(this, 'EKSCluster', {
clusterName: 'eks-cluster',
vpc: props.vpc,
defaultCapacity: 0, // number of nodes
ipFamily: eks.IpFamily.IP_V4,
version: eks.KubernetesVersion.V1_30,
outputClusterName: true,
outputConfigCommand: true,
securityGroup: props.ekssecgrp,
authenticationMode: eks.AuthenticationMode.API,
// The kubectl handler uses kubectl, helm and the aws CLI in order to interact with the cluster. These are bundled into AWS Lambda layers included in the @aws-cdk/lambda-layer-awscli and @aws-cdk/lambda-layer-kubectl modules.
kubectlLayer: new KubectlV30Layer(this, 'kubectl'),
kubectlMemory: cdk.Size.gibibytes(4),
});

Amazon EKS supports three modes of authentication: CONFIG_MAP, API_AND_CONFIG_MAP, and API. Use authenticationMode CONFIG_MAP to continue using aws-auth configMap exclusively. When API_AND_CONFIG_MAP is enabled, the cluster will source authenticated AWS IAM principals from both Amazon EKS access entry APIs and the aws-auth configMap, with priority given to the access entry API.

Our EKS Cluster is using API authentication mode that requires little more understanding —

API (Cluster access management API)— Cluster administrators can now grant AWS IAM principals access to all supported versions (v1.23 and beyond) of Amazon EKS clusters and Kubernetes objects directly through Amazon EKS APIs. This new functionality relies on two new concepts: access entries and access policies.

  • An access entry is a cluster identity — directly linked to an AWS IAM principal user or role — that is used to authenticate to an Amazon EKS cluster.
  • An Amazon EKS access policy authorises an access entry to perform specific cluster actions.

We used access entry and access policy to create bastion role as admin! Thats why we are able to hit and access the EKS cluster from that bastion.

// Cluster Admin role for this cluster
this.cluster.grantAccess('clusterAdminAccess', props.role.roleArn, [
eks.AccessPolicy.fromAccessPolicyName('AmazonEKSClusterAdminPolicy', {
accessScopeType: eks.AccessScopeType.CLUSTER,
}),
]);

Please note we can always list the available access policies for usage —

# list access policy
aws eks list-access-policies

You can also find out existing access entries as well and can create via command line as well if required.

# access entries 
aws eks list-access-entries --cluster-name eks-cluster
{
"accessEntries": [
"arn:aws:iam::12345:role/EKSStack-EKSClusterCreationRoleDSD9E8-SDSe",
"arn:aws:iam::12345:role/EKSStack-EksClusterRoles36171-da",
"arn:aws:iam::12345:role/VpcStack-BastionHostRolerfnh2EgBA-aa",
"arn:aws:iam::12345:role/aws-reserved/sso.amazonaws.com/eu-west-1/<role name>"
]
}
# Create cluster access entry to add cluster administrators to existing clusters
aws eks create-access-entry --cluster-name eks-cluster \
--principal-arn <IAM_PRINCIPAL_ARN>
# Associate access policy to access entry
aws eks associate-access-policy --cluster-name eks-cluster \
--principal-arn <IAM_PRINCIPAL_ARN> \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy
--access-scope type=cluster

Public End point

By default, this EKS API server endpoint is public to the internet, and access to the API server is secured using a combination of AWS Identity and Access Management (IAM) and native Kubernetes Role Based Access Control (RBAC).

You can configure the cluster endpoint access by using the endpointAccess property:

const cluster = new eks.Cluster(this, 'hello-eks', {
version: eks.KubernetesVersion.V1_30,
endpointAccess: eks.EndpointAccess.PRIVATE, // No access outside of your VPC.
});

The default value is eks.EndpointAccess.PUBLIC_AND_PRIVATE. Which means the cluster endpoint is accessible from outside of your VPC, but worker node traffic and kubectl commands issued by this library stay within your VPC.

KubectlHandler

this.cluster = new eks.Cluster(this, 'EKSCluster', {
clusterName: 'eks-cluster',
authenticationMode: eks.AuthenticationMode.API,
// The kubectl handler uses kubectl, helm and the aws CLI in order to interact with the cluster. These are bundled into AWS Lambda layers included in the @aws-cdk/lambda-layer-awscli and @aws-cdk/lambda-layer-kubectl modules.
kubectlLayer: new KubectlV30Layer(this, 'kubectl'),
kubectlMemory: cdk.Size.gibibytes(4),
});

The KubectlHandler is a Lambda function responsible to issuing kubectl and helm commands against the cluster when you add resource manifests to the cluster. The kubectl handler uses kubectl, helm and the aws CLI in order to interact with the cluster. These are bundled into AWS Lambda layers included in the @aws-cdk/lambda-layer-awscli and @aws-cdk/lambda-layer-kubectl modules.

Its time to deploy our app now. Cluster is ready and we need to push our image to ECR so that it can be referenced in K8s deployment YAML.

# login in ECR
aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin <AWS Account>.dkr.ecr.eu-central-1.amazonaws.com

# now lets tag our image and push it in ECR, so that we can reference it in our EKS deployment
docker tag my-react-app:latest <AWS Account>.dkr.ecr.eu-central-1.amazonaws.com/eks-repository:latest

# push
docker push <AWS Account>.dkr.ecr.eu-central-1.amazonaws.com/eks-repository:latest

Now our image is in ECR and we can copy the URL

Please note we already granted permission to EKS for accessing ECR in CDK.

    const policy = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['ecr:*'],
resources: ['*'],
});

const EksClusterRole = new iam.Role(this, 'EksClusterRole', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonEKSWorkerNodePolicy"),
iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonEC2ContainerRegistryReadOnly"),
iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonEKS_CNI_Policy"),
],
});

EksClusterRole.addToPolicy(policy);

Kubernetes Deployment

First, you need to create a Deployment. A Deployment will ensure that a specified number of replicas (pods) of your application are running in the cluster.

apiVersion: apps/v1
kind: Deployment
metadata:
name: react-app
labels:
app: react
spec:
replicas: 2
selector:
matchLabels:
app: react
template:
metadata:
labels:
app: react
spec:
containers:
- name: reactapp
image: <AWS_Account>.dkr.ecr.eu-central-1.amazonaws.com/eks-repository:latest
ports:
- containerPort: 80
resources:
limits:
memory: "512Mi"
cpu: "500m"
requests:
memory: "256Mi"
cpu: "250m"

The above config has image location pointing to our ECR.

% kubectl apply -f deployment.yaml
deployment.apps/react-deployment created

% kubectl get pods
NAME READY STATUS RESTARTS AGE
react-deployment-5bffdbbbb6-b7jzp 1/1 Running 0 3m32s
react-deployment-5bffdbbbb6-qx97n 1/1 Running 0 3m32s

Service deployment

We can expose the app service using a LoadBalancer service, which will create an external load balancer (e.g., an AWS ELB) to allow traffic from the internet to your service.

# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: react-service
spec:
type: LoadBalancer # This creates an external load balancer in AWS
selector:
app: react
ports:
- protocol: TCP
port: 80
targetPort: 80

The above config will deploy the service mapped with pods. The AWS Load Balancer will be provisioned automatically by Kubernetes. When you create a Kubernetes Service of type LoadBalancer, EKS automatically provisions Classic Load Balancer by default.

% kubectl apply -f service.yml
service/react-service created

% kubectl get svc react-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
react-service LoadBalancer 172.20.49.237 xx-xx.eu-central-1.elb.amazonaws.com 80:32444/TCP 84s

Initially, the EXTERNAL-IP might show as <pending>, but after a minute or two, the AWS load balancer will be provisioned, and an external IP or hostname will be assigned.

Now access the URL is browser —

Important Point

While access from Bastion initially, i faced a lot of issues and the problem was aws cli version. Make sure you are using latest version of AWS CLI and kubectl.

E0925 15:28:47.411816 20409 memcache.go:265] “Unhandled Error” err=”couldn’t get current server API group list: Get \”https://sasasa.gr7.eu-central-1.eks.amazonaws.com/api?timeout=32s\": getting credentials: decoding stdout: no kind \”ExecCredential\” is registered for version \”client.authentication.k8s.io/v1alpha1\” in scheme \”pkg/client/auth/exec/exec.go:62\””

That’s all for this post. In the next post, I will be talking about deploying this app using end to end automation in EKS and exposing it securely.

Till then, Stay Safe and Take Care.

Let’s Connect

LinkedIn: https://www.linkedin.com/in/arunksingh16/
GitHub: https://github.com/arunksingh16
Twitter: https://twitter.com/arun16

--

--

Arun Kumar Singh
Arun Kumar Singh

Written by Arun Kumar Singh

In quest of understanding How Systems Work !

No responses yet