Introduction

In an earlier article, we saw how to bootstrap a new EKS cluster using Python and Pulumi. In this article, we will build upon the EKS cluster and deploy a Python Flask application.

Prerequisites

  • A running EKS cluster with AWS Load Balancer Controller

Setup Python web application

For this tutorial, we will set up a simple Python web application which can be found at https://github.com/abhishekray07/python-web-app.

This Python app returns Hello, world! whenever a request is made.

Clone the repo

First, we will clone the sample repo and use that as the application we want to deploy to the EKS cluster. This repository also contains the Kubernetes manifests required for our application.

git clone https://github.com/abhishekray07/python-web-app/

Create a new repo on AWS Elastic Container Registry (ECR)

Next, we will create a new repository to store our Docker images. In the Pulumi file where we created our EKS cluster, we can add the following to create a new ECR repo.

We are creating the following resources:

  • A new repo called eks-tutorial-ecr-repo
  • An IAM policy to access the repo
  • Attaching the IAM policy to the EKS worker nodes
  • We also store the repo URL to our stack output so that we can use that in our application manifest
ecr_repo = aws.ecr.Repository("eks-tutorial-ecr-repo")

repo_policy = aws.ecr.RepositoryPolicy(
    "eks-tutorial-ecr-repo-policy",
    policy="""{
        "Version": "2008-10-17",
        "Statement": [
            {
                "Sid": "new policy",
                "Effect": "Allow",
                "Principal": "*",
                "Action": [
                    "ecr:GetDownloadUrlForLayer",
                    "ecr:BatchGetImage",
                    "ecr:BatchCheckLayerAvailability",
                    "ecr:PutImage",
                    "ecr:InitiateLayerUpload",
                    "ecr:UploadLayerPart",
                    "ecr:CompleteLayerUpload",
                    "ecr:DescribeRepositories",
                    "ecr:GetRepositoryPolicy",
                    "ecr:ListImages",
                    "ecr:DeleteRepository",
                    "ecr:BatchDeleteImage",
                    "ecr:SetRepositoryPolicy",
                    "ecr:DeleteRepositoryPolicy"
                ]
            }
        ]
    }""",
    repository=ecr_repo.name
)

ecr_access_policy = aws.iam.Policy(
    "ecr-access-iam-policy",
    policy="""{
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "ecr:BatchCheckLayerAvailability",
            "ecr:BatchGetImage",
            "ecr:GetDownloadUrlForLayer",
            "ecr:GetAuthorizationToken"
          ],
          "Resource": "*"
        }
      ]
    }"""
)

nodeInstanceRole = aws.iam.RolePolicyAttachment(
    "eks-NodeInstanceRole-policy-attach",
    role=cluster.instance_roles[0].name,
    policy_arn=ecr_access_policy.arn,
)

pulumi.export("ecr_repo_url", ecr_repo.repository_url)

Next, let’s run pulumi preview to see what changes will be made to our infrastructure.

/assets/img/pulumi-python-web/pulumi-preview-ecr.png

We can now go ahead and create the repo by running pulumi up. Once the command finishes, the new ECR repo should have been created. We can retrieve the URL by running the following command:


pulumi stack output ecr_repo_url
xxx.dkr.ecr.us-west-2.amazonaws.com/eks-tutorial-ecr-repo-f991f93


Build / Push Docker image

To be able to deploy our application onto Kubernetes, we need to create a Docker image for our application. The python repo has a helper script called build-push-docker.sh that builds and pushed the docker image to the ECR repo.

Before we can use that script, we need to update the ECR repo URL in the script using the following bash command:

sed -i "s/REPLACE_ME/xxxx.dkr.ecr.us-west-2.amazonaws.com\/eks-tutorial-ecr-repo-f991f93/g" build-push-docker.sh

Replace the ECR repo URL with the URL of your own repo and then invoke the script:

./build-push-docker.sh

Login Succeeded
The push refers to repository [xxx.dkr.ecr.us-west-2.amazonaws.com/eks-tutorial-ecr-repo-f991f93]
06795f03e699: Pushed
4ff77643664d: Pushed
8afc6cc250fa: Pushed
4969992dbe82: Pushed
87a873215083: Pushed
55131b45c60c: Pushed
8506f6dcbf82: Pushed
4a36e2652108: Pushed
f9ef7f1bcb19: Pushed
02c055ef67f5: Pushed
latest: digest: sha256:fe88536bfd911e5af0bec2f887b7a42308d3a1ffa84ef8ebc382bfe49369459d size: 24

Deploy Application to EKS Cluster

There are 4 manifest files under the kubernetes folder in the repository linked above. These files are:

  • Namespace: Creates a new Namespace for our application
  • Deployment: Creates a Deployment object for our application
  • Service: Creates a Service object for our application
  • Ingress: Creates an ingress for the application so that it is accessible externally

Before we can apply these manifests, we need to update the Deployment manifest to point to the location of our docker image. Run the following command and add in your ECR repo URL:

sed -i "s/REPLACE_ME/xxx.dkr.ecr.us-west-2.amazonaws.com\/eks-tutorial-ecr-repo-f991f93:latest/g" kubernetes/deployment.yaml

We can now apply these manifests to create the Kuberntes resources.

kubectl apply -f namespace.yaml
kubectl get ns

NAME                STATUS   AGE
aws-lb-controller   Active   37h
default             Active   37h
kube-node-lease     Active   37h
kube-public         Active   37h
kube-system         Active   37h
python-web          Active   14s

Then, we create a service for our application:

kubectl apply -f service.yaml
kubectl get svc -n python-web

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
python-web NodePort 10.100.67.103 <none> 80:32026/TCP 9s

Next, we will create the deployment for the application:

kubectl apply -f deployment.yaml

The pod will initially be Pending and will eventually become Ready if everything is set up successfully.

kubectl get pods -n python-web

NAME READY STATUS RESTARTS AGE
python-web-5744b776c5-2xrvw 0/1 Pending 0 9s
kubectl get pods -n python-web

NAME READY STATUS RESTARTS AGE
python-web-5744b776c5-2xrvw 1/1 Running 0 94s

Lastly, we will apply the ingress manifest to expose our application to the public internet.

kubectl apply -f kubernetes/ingress.yaml -n python-web

To check the status of the ingress, we can run the following command. Note the Address in the output since this is the public URL for our application.


kubectl describe ing -n python-web python-web


Name:             python-web
Namespace:        python-web
Address:          k8s-pythonwe-pythonwe-f008f8de3b-250225160.us-west-2.elb.amazonaws.com
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host        Path  Backends
  ----        ----  --------
  *
              /   python-web:80 (172.31.57.7:8080)
Annotations:  alb.ingress.kubernetes.io/scheme: internet-facing
              alb.ingress.kubernetes.io/target-type: ip
              kubernetes.io/ingress.class: alb
Events:
  Type    Reason                  Age   From     Message
  ----    ------                  ----  ----     -------
  Normal  SuccessfullyReconciled  1s    ingress  Successfully reconciled


The ALB can take a few minutes to be provisioned. Once it is functional, we should be able to access it by hitting the endpoint:

curl http://k8s-pythonwe-pythonwe-f008f8de3b-250225160.us-west-2.elb.amazonaws.com

Hello, World!

Congrats, your web application is up and running on Kubernetes.

Cleaning up the stack

Before finishing up, let’s clean up all the resources we created to ensure we don’t get billed for any of the resources we created. To clean up your stack, run pulumi destroy and then select yes.