How to Deploy a Containerized Application to Amazon EKS Using a Jenkins Pipeline

How to Deploy a Containerized Application to Amazon EKS Using a Jenkins Pipeline

Our previous articles discussed concepts such as building a containerized application, setting up an EKS cluster, and navigating Jenkins. This article will combine all this knowledge and implement a workflow similar to what you'll see in a real-world use case.

This article will guide you through deploying a containerized application to EKS using a Jenkins pipeline. You'll go through steps like checking out the code repository, building the Docker image and deploying it to your cluster on EKS. By the end of this article, you can replicate this workflow by yourself using an application of your choice.

Prerequisites

This article will take you from start to finish through this entire process; however, it is assumed that you have the following:

  • A Kubernetes cluster running on EKS. You can use eksctl to do this. See this article on how to use this tool.

  • A running Jenkins server. Follow the procedure in this article to set it up if you haven't already.

  • An ECR repository. If you don't have one, you can set one up by following this article.

  • Knowledge of basic Jenkins procedures like installing plugins and configuring your Jenkins server. The guide above is an excellent resource for this.

Project Setup

Although you can set up your cluster from your Jenkins server, this isn't how you'll see it in production. For this reason, you'll have your cluster set up on your local machine and have a separate machine or EC2 instance for your Jenkins server.

On this Jenkins server, you'll install plugins allowing you to authenticate with your EKS cluster. With these plugins, building and deploying your docker image to your cluster will be super easy.

Application Setup

You can use any application of your choice, but if you prefer to follow this tutorial, follow the steps below to clone this JavaScript word counter application from GitHub:

git clone https://github.com/Aahil13/Word-counter.git

This command will clone the application code from GitHub. This project's Dockerfile, Jenkinsfile, and EKS-deployment files are in this repository.

Step 1: Configure the Jenkins Server

Configuring your Jenkins server will involve installing the necessary tools for this project. These tools include Docker, AWS CLI, and kubectl.

To install Docker, refer to the official Docker documentation and follow the instructions specific to your operating system. Once installed, ensure Docker has the necessary privileges; otherwise, it might fail to function correctly.

If, like me, you're using an Ubuntu server, you can set these privileges using the following command:

sudo chown $USER:$USER /var/run/docker.sock

Installing and configuring the AWS CLI is straightforward. You can follow the instructions here. After the installation, run the aws configure command to set your AWS credentials.

The final tool is kubectl. Ensure that kubectl is installed on your server. You can install it by following the instructions here. To confirm that kubectl is installed, run the following command:

kubectl version --client

You should get the output similar to the following:

Client Version: v1.30.1
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3

Step 2: Install Jenkins Plugins

After setting up the above tools, head to the Jenkins UI to install the necessary plugins for this project. On your Jenkins plugin page, you'll need the following plugins:

  • Pipeline: AWS Steps: This plugin provides Jenkins pipeline steps for interacting with AWS services such as S3, ECR, EC2, Lambda, and EKS.

  • Docker: This plugin allows you to run your builds inside Docker containers, ensuring that your builds are repeatable and consistent across different environments.

  • Docker Pipeline: This plugin provides a set of pipeline steps for working with Docker, such as building Docker images, running containers, and pushing images to Docker registries.

You should have these plugins selected like this:

Figure 1*: Jenkins Plugins*

Once you've selected the plugins as in the figure above, click Install.

Step 3: Setup AWS Credentials

The Pipeline: AWS Steps plugin requires that you provide your AWS credentials to authenticate with AWS services. Navigate to the Credentials section of the Jenkins UI and input your AWS Access Key ID and Secret Access Key.

Your screen should look similar to the following:

Figure 2*: Credentials*

After adding your credentials, click on the Create button.

Step 4: Create and Configure a Pipeline

On the Jenkins UI, navigate to the New item section and create a new pipeline. You can name this pipeline anything you want.

Figure 3*: New pipeline*

Navigate to the Configure section of the pipeline, and input the following Jenkinsfile:

pipeline {
    agent any

    stages {
        stage('Configure') {
            steps {
                checkout scmGit(branches: [[name: '*/main']], extensions: [], userRemoteConfigs: [[url: 'https://github.com/Aahil13/Word-counter']])
            }
        }
        stage('Building image') {
            steps {
                sh 'docker build -t word-counter .'
            }
        }

        stage('Pushing to ECR') {
            steps {
                withAWS(credentials: 'AWS-CREDS', region: '<AWS-REGION>') {
                    sh 'aws ecr get-login-password --region <AWS-REGION> | docker login --username AWS --password-stdin <ECR-REGISTRY-ID>'
                    sh 'docker tag word-counter:latest <ECR-REGISTRY-ID>/<IMAGE_NAME>:latest'
                    sh 'docker push <ECR-REGISTRY-ID>/<IMAGE_NAME>:latest'
                }
            }
        }

        stage('K8S Deploy') {
            steps {
                script {
                    withAWS(credentials: 'AWS-CREDS', region: '<AWS-REGION>') {
                        sh 'aws eks update-kubeconfig --name <EKS-CLUSTER> --region <AWS-REGION>'
                        sh 'kubectl apply -f EKS-deployment.yaml'
                    }
                }
            }
        }

        stage('Get Service URL') {
            steps {
                script {
                    def serviceUrl = ""
                    // Wait for the LoadBalancer IP to be assigned
                    timeout(time: 5, unit: 'MINUTES') {
                        while(serviceUrl == "") {
                            serviceUrl = sh(script: "kubectl get svc word-counter-service -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'", returnStdout: true).trim()
                            if(serviceUrl == "") {
                                echo "Waiting for the LoadBalancer IP..."
                                sleep 10
                            }
                        }
                    }
                    echo "Service URL: http://${serviceUrl}"
                }
            }
        }
    }
}

All this looks intimidating at first, but it's pretty simple. Here's what each step does:

Build-Step 1: Git Checkout

The first stage of this pipeline checks out the code from the Git repository. This is done using the checkout step, which retrieves the code from the specified branch of the repository. In this case, it checks out the 'main' branch from the repository at https://github.com/Aahil13/Word-counter.

stage('Configure') {
    steps {
        checkout scmGit(branches: [[name: '*/main']], extensions: [], userRemoteConfigs: [[url: 'https://github.com/Aahil13/Word-counter']])
    }
}

Build-Step 2: Building the Docker Image

In the Building image stage, this pipeline builds the Docker image from the codebase. It uses the docker build command to create the' word-counter' image.

stage('Building image') {
    steps {
        sh 'docker build -t word-counter .'
    }
}

Build-Step 3: Pushing the Docker Image to Amazon ECR

The Pushing to ECR stage pushes the Docker image to ECR. The withAWS step is a template from the Pipeline: AWS Steps plugin. This step logs into the ECR registry, tags the Docker image, and then pushes it to the specified repository.

stage('Pushing to ECR') {
    steps {
        withAWS(credentials: 'AWS-CREDS', region: '<AWS-REGION>') {
            sh 'aws ecr get-login-password --region <AWS-REGION> | docker login --username AWS --password-stdin <ECR-REGISTRY-ID>'
            sh 'docker tag word-counter:latest <ECR-REGISTRY-ID>/<IMAGE_NAME>:latest'
            sh 'docker push <ECR-REGISTRY-ID>/<IMAGE_NAME>:latest'
        }
    }
}

Build-Step 4: Deploying to EKS

The K8S Deploy stage deploys the Docker image to your EKS cluster. It updates the kubeconfig to use the specified EKS cluster and then applies the Kubernetes deployment configuration from the specified file.

stage('K8S Deploy') {
    steps {
        script {
            withAWS(credentials: 'AWS-CREDS', region: '<AWS-REGION>') {
                sh 'aws eks update-kubeconfig --name <EKS-CLUSTER> --region <AWS-REGION>'
                sh 'kubectl apply -f EKS-deployment.yaml'
            }
        }
    }
}

NOTE: Make sure to replace all the placeholder values with your actual values.

Build-Step 5: Retrieving the Service URL

The Get Service URL stage retrieves the URL of the service deployed in the Kubernetes cluster. It waits for the LoadBalancer to assign an IP address or hostname and then prints the service URL.

stage('Get Service URL') {
    steps {
        script {
            def serviceUrl = ""
            // Wait for the LoadBalancer IP to be assigned
            timeout(time: 5, unit: 'MINUTES') {
                while(serviceUrl == "") {
                    serviceUrl = sh(script: "kubectl get svc word-counter-service -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'", returnStdout: true).trim()
                    if(serviceUrl == "") {
                        echo "Waiting for the LoadBalancer IP..."
                        sleep 10
                    }
                }
            }
            echo "Service URL: http://${serviceUrl}"
        }
    }
}

Once you've pasted this Jenkinsfile, click the Apply and Save buttons.

Step 6: Run the Pipeline

Immediately after setting the pipeline script, and you're sure your cluster is running, you can run the pipeline.

Once you do and your build is successful, you should have your application running.

Figure 4*: Successful build steps*

You can see the service URL by clicking on the logs for the service-url stage.

Figure 5*: Service URL*

Navigate to your browser and paste the URL. You should see the word counter application running.

Figure 6*: Word counter application running*

Common Issues

While working on this project, you might encounter some issues. Below are some of these issues and how to fix them:

Deploying Kubernetes Files

One major issue I faced was figuring out how to deploy my Kubernetes files into my cluster. I struggled until I found the Pipeline: AWS Steps plugin.

This plugin allows Jenkins to communicate with the AWS CLI, enabling easy integration and deployment. Additionally, I had to configure the AWS CLI on my Jenkins server with my credentials to make this work.

Docker Permissions

Another common issue is Jenkins being unable to find Docker, even though Docker is installed on the server. This often occurs because Docker does not have the appropriate permissions. If Docker lacks the necessary privileges, Jenkins will throw an error.

To fix this issue, set the appropriate Docker permissions as discussed above. For Ubuntu, you can do this by running the following command:

sudo chown $USER:$USER /var/run/docker.sock

After running this command, Jenkins should be able to interact with Docker without any issues.

Conclusion

In this article, you configured a Jenkins pipeline to deploy a containerized application to EKS. You went through installing the necessary plugins, configuring the Jenkins server, and deploying the application to EKS.

By combining the concepts you learned in this article, you can replicate this workflow by yourself using an application of your choice.