# Project 17: CI/CD for 3-Tier TODO Application(Step By Step Implementation)

### **Introduction:**

🌟 Welcome to Project 17: CI/CD for a 3-Tier ToDo Application! 🚀 In this journey, we'll navigate through setting up a robust Continuous Integration and Continuous Delivery pipeline using AWS, Kubernetes, and Terraform. 💻 The project aims to streamline the development and deployment of a ToDo application, ensuring efficiency and scalability. 🌐 From launching an EC2 instance to configuring monitoring with Prometheus and Grafana, we'll cover each step comprehensively. 🛠️ Get ready to embrace modern DevOps practices, simplify workflows, and elevate your software development experience! 🚀✨

### **Technologies Used in This Project:**

1. **AWS (Amazon Web Services):**
    
    * *Purpose:* AWS provides the infrastructure for hosting our EC2 instance, EKS cluster, and ECR repositories. Leveraging AWS services ensures scalability, reliability, and seamless integration with other tools.
        
2. **Terraform:**
    
    * *Purpose:* Terraform is employed for infrastructure as code (IaC), enabling the automated provisioning and management of AWS resources. This ensures consistency in our environment and simplifies the setup of the Jenkins server.
        
3. **Jenkins:**
    
    * *Purpose:* Jenkins serves as the backbone of our CI/CD pipeline. It automates the build, test, and deployment processes, enhancing collaboration and efficiency among development teams.
        
4. **Docker:**
    
    * *Purpose:* Docker facilitates containerization, encapsulating application code and dependencies. This ensures consistency across different environments, easing deployment and minimizing compatibility issues.
        
5. **Kubernetes (EKS):**
    
    * *Purpose:* Kubernetes orchestrates containerized applications, providing scalability and resilience. EKS (Elastic Kubernetes Service) simplifies the deployment and management of Kubernetes clusters on AWS.
        
6. **ArgoCD:**
    
    * *Purpose:* ArgoCD automates the deployment of applications in Kubernetes. It ensures consistency between the desired and actual state of applications, promoting continuous delivery in a Kubernetes environment.
        
7. **Prometheus and Grafana:**
    
    * *Purpose:* Prometheus monitors Kubernetes clusters, collecting metrics and alerts. Grafana visualizes these metrics, providing insights into the system's health and performance, crucial for maintaining reliability.
        
8. **SonarQube:**
    
    * *Purpose:* SonarQube performs static code analysis, identifying and fixing code quality issues early in the development process. This enhances code maintainability, security, and overall software quality.
        

### **Project Overview:**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705894540061/a8405d43-999f-43e2-9cd5-beaa835d0d93.png align="center")

### **Project:**

GITHUB REPO: [https://github.com/patelajay745/ThreeTierAppToDoApp.git](https://github.com/patelajay745/ThreeTierAppToDoApp.git)

`Step 1 : Launch Ec2 Instance` with Ubnuntu AMI and t2.large type. I have used following terraform file to launch it.

```bash
# main.tf

provider "aws" {
  region = "us-east-1"
}

variable "vpc-name" {
    default = "Jenkins-vpc"
}

variable "igw-name" {
    default = "Jenkins-igw"
}

variable "rt-name" {
    default = "Jenkins-route-table"
}

variable "subnet-name" {
    default = "Jenkins-subnet"
}

variable "sg-name" {
    default = "Jenkins-sg"
}

variable "instance-name" {
    default = "Jenkins-server"
}

variable "key-name" {
    default = "ajay-admin"
}

variable "iam-role" {
    default = "Jenkins-iam-role"
} 

variable "ami_id" {
    default = "ami-0c7217cdde317cfec"
}

resource "aws_vpc" "vpc" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = var.vpc-name
  }
}

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.vpc.id

  tags = {
    Name = var.igw-name
  }
}

resource "aws_subnet" "public-subnet" {
  vpc_id                  = aws_vpc.vpc.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "us-east-1a"
  map_public_ip_on_launch = true

  tags = {
    Name = var.subnet-name
  }
}

resource "aws_route_table" "rt" {
  vpc_id = aws_vpc.vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }

  tags = {
    Name = var.rt-name
  }
}

resource "aws_route_table_association" "rt-association" {
  route_table_id = aws_route_table.rt.id
  subnet_id      = aws_subnet.public-subnet.id
}

resource "aws_security_group" "security-group" {
  vpc_id      = aws_vpc.vpc.id
  description = "Allowing Jenkins, Sonarqube, SSH Access"

  ingress = [
    for port in [22, 8080, 9000, 9090, 80] : {
      description      = "TLS from VPC"
      from_port        = port
      to_port          = port
      protocol         = "tcp"
      ipv6_cidr_blocks = ["::/0"]
      self             = false
      prefix_list_ids  = []
      security_groups  = []
      cidr_blocks      = ["0.0.0.0/0"]
    }
  ]

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = var.sg-name
  }
}

resource "aws_instance" "ec2" {
  ami                    = var.ami_id
  instance_type          = "t2.large"
  key_name               = var.key-name
  subnet_id              = aws_subnet.public-subnet.id
  vpc_security_group_ids = [aws_security_group.security-group.id]
  iam_instance_profile   = aws_iam_instance_profile.instance-profile.name
  root_block_device {
    volume_size = 30
  }

  tags = {
    Name = var.instance-name
  }
}

resource "aws_iam_instance_profile" "instance-profile" {
  name = "Jenkins-instance-profile"
  role = aws_iam_role.iam-role.name
}

resource "aws_iam_role_policy_attachment" "iam-policy" {
  role        = aws_iam_role.iam-role.name
  policy_arn  = "arn:aws:iam::aws:policy/AdministratorAccess"
}

resource "aws_iam_role" "iam-role" {
  name               = var.iam-role
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}
```

`Step 2: Install Required packages.`

Install the required packages by SSHing into the EC2 instance.

Create two scripts, namely "[`packages.sh`](http://jenkins-docker.sh/)"

[`packages.sh`](http://jenkins-docker.sh/)

```bash
#!/bin/bash
# For Ubuntu 22.04
# Intsalling Java
sudo apt update -y
sudo apt install openjdk-17-jre -y
sudo apt install openjdk-17-jdk -y
java --version

# Installing Jenkins
curl -fsSL https://pkg.jenkins.io/debian/jenkins.io-2023.key | sudo tee \
  /usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
  https://pkg.jenkins.io/debian binary/ | sudo tee \
  /etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt-get update -y
sudo apt-get install jenkins -y

# Installing Docker 
#!/bin/bash
sudo apt update
sudo apt install docker.io -y
sudo usermod -aG docker jenkins
sudo usermod -aG docker ubuntu
sudo systemctl restart docker
sudo chmod 777 /var/run/docker.sock

# If you don't want to install Jenkins, you can create a container of Jenkins
# docker run -d -p 8080:8080 -p 50000:50000 --name jenkins-container jenkins/jenkins:lts

# Run Docker Container of Sonarqube
#!/bin/bash
docker run -d  --name sonar -p 9000:9000 sonarqube:lts-community


# Installing AWS CLI
#!/bin/bash
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
sudo apt install unzip -y
unzip awscliv2.zip
sudo ./aws/install

# Installing Kubectl
#!/bin/bash
sudo apt update
sudo apt install curl -y
sudo curl -LO "https://dl.k8s.io/release/v1.28.4/bin/linux/amd64/kubectl"
sudo chmod +x kubectl
sudo mv kubectl /usr/local/bin/
kubectl version --client


# Installing eksctl
#! /bin/bash
curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv /tmp/eksctl /usr/local/bin
eksctl version

# Installing Terraform
#!/bin/bash
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update
sudo apt install terraform -y

# Installing Trivy
#!/bin/bash
sudo apt-get install wget apt-transport-https gnupg lsb-release -y
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt update
sudo apt install trivy -y


# Intalling Helm
#! /bin/bash
sudo snap install helm --classic
```

Execute script using the following command.

```bash
chmod 777 packages.sh
./packages.sh
```

☕ Enjoy a cup of coffee while everything gets set up. It might take a bit, but your patience will pay off with a hassle-free configuration. 😊✨

Verify the installation of all packages.

```bash
jenkins --version
docker --version
docker ps
terraform --version
kubectl version
aws --version
trivy --version
eksctl version
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705840685937/0019b598-ad47-455c-a232-70194c75aac7.png align="center")

`Step 3: Establish a connection between Jenkins and SonarQube.`

Copy the IP address of the EC2 instance and paste it into the browser.

```bash
<Ec2-ip:8080>
```

It will prompt for a password.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705840937773/14ee4d65-3a63-439f-b101-4817524cd190.png align="center")

Retrieve the password and complete the installation.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705841010607/a4ec21a9-559a-442c-896d-aff5d3319a64.png align="center")

Jenkins Dashboard.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705841165094/2aeaf545-1d46-4ca0-ad32-210b5152f809.png align="center")

Now access SonarQube using:

```bash
<ec2-ip:9000>
```

It will ask for the default username and password. Enter "admin" in both.

The SonarQube dashboard will look like this.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705841284949/24225a4d-8576-40e4-bc37-78087d160ae1.png align="center")

`Step 4: Plugin Installation and Setup (Java, Sonar, Node.js, OWASP, Docker)`

Navigate to the Jenkins dashboard.

Go to "Manage Jenkins" → "Plugins" → "Available Plugins."

Search for the following plugins:

1. Eclipse Temurin Installer
    
2. SonarQube Scanner
    
3. OWASP Dependency-Check
    
4. Docker
    
5. Docker Commons
    
6. Docker Pipeline
    
7. Docker API
    
8. Docker Build Step
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705841771393/fad5f157-dfb3-4a1c-adbe-e384fdd87a15.png align="center")

`Step 5: Configuration of Global tools.`

Navigate to "Manage Jenkins" → "Tools" → "SonarQube Scanner"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705841994540/2cfcb2b8-f97a-43a2-93b7-d7277ab50fc9.avif align="center")

For Owasp:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705842117369/83bc7ad7-9bb2-442c-ac4e-a3ad9d072920.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705842707200/f4702220-5b04-4634-b189-399b5fa750b5.avif align="center")

For Docker:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705842735159/eae61a4f-1f07-4abd-99b5-c983f8332864.avif align="center")

Click on "Apply and Save."

`Step 6: Configure Sonar Server in Manage Jenkins`

Retrieve the Public IP Address of your EC2 Instance. Since SonarQube operates on Port 9000, access it via &lt;Public IP&gt;:9000.

Visit your SonarQube Server, navigate to Administration → Security → Users, click on Tokens, update the token by assigning it a name, and then generate the token.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705842920921/9f407675-bf3b-40bd-8730-757c8a015a1c.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705842985122/f9bc8e18-3026-4c6a-bd57-4066e9e5ae8d.png align="center")

Enter name of token then click on "Generate"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705843070798/63f4befd-e251-4123-8850-2fbaa4be50fb.png align="center")

Copy the token, then go to the Jenkins Dashboard → Manage Jenkins → Credentials → Add Secret Text. The entry should resemble this.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705846504406/0630339e-babe-4b07-bda5-a7b1c4c4553b.avif align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705846522399/4f70bb3d-fdbe-46b3-b871-aa5caad25cd5.avif align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705846540790/c6ee121f-14a5-43f9-87a0-6963a352418e.avif align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705846562526/8b36862e-fb49-4ac5-93d1-89cd450e593b.avif align="center")

Now, Navigate to Dashboard → Manage Jenkins → System and Add like the below image.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705846702375/44a632a5-fc72-43e1-9a6d-f26abd482ef7.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705846799052/5f3a1173-eabc-4dae-941c-3e45e1e3a78f.png align="center")

Click on "Apply and Save."

In the Sonarqube Dashboard, also include a quality gate by navigating to Administration → Configuration → Webhooks.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705846872942/d5786348-ecc0-4b2a-8fed-b3c884b1fb08.png align="center")

Click on "Create"

```bash
In URL Section:
<http://jenkins-public-ip:8080>/sonarqube-webhook/>
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705846967003/5c1206be-8636-42db-8c46-876a4dcb8c2a.png align="center")

`Step 7: Setting up secret for pipeline`

Navigate to Jenkins Dashboard → Manage Jenkins → Credentials → Add Credentials → Secret Text

Add Your AWS Account ID Number. Click on "Create"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705847724269/825cbff8-0f6b-4e45-9d7c-a8c8cdf4ca84.png align="center")

Follow the same instructions to set up the following secrets. Create two ECR repositories (either private or public).

```bash
ECR_REPO1: Your FrontEnd ECR Repository name.
ECR_REPO2: Your BackEND ECR Repository name.
GIT_USER_NAME: Your Git username.
GITHUB_TOKEN: Your Guthub personal Token
```

Your secret will appear as shown in the image below.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705849749180/94a957e1-6467-4260-a65c-8ac6c5bb87b8.png align="center")

Before proceeding further, make sure you make changes to the ECR URL in the following files (of the forked repo):

**k8s\_files -&gt; Backend -&gt; deployment.yaml**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705890438501/43fe72f9-8d4c-4ae5-abf2-75191ca16a66.png align="center")

```bash
The URL should be <your AWS account number>.dkr.ecr.<region>.amazonaws.com/<Backend ECR Repo Name>:latest
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705890687146/e3ab1274-eb55-45fa-891c-7f36c3475f0b.png align="center")

**k8s\_files -&gt; Frontend -&gt; deployment.yaml**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705890464256/f999354a-0456-41fa-bf84-2bcc6bf3423a.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705890706999/27a22b9f-b397-462b-895b-7728f516f92d.png align="center")

###   
<mark>Most important: Review all Kubernetes manifest files to implement the necessary changes; otherwise, errors may occur.</mark>

`Step 8: Pipeline up to Docker`

Now, let's create a new job for our pipeline.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705847344938/549d0c5a-62bd-43b1-97e9-f6b49e6405c0.png align="center")

Add Following script in pipeline section.

```bash
pipeline {
    agent any
   
    environment {
        SCANNER_HOME=tool 'sonar-scanner'

        YOUR_EMAIL_ID = 'patel.ajay745@gmail.com'

        AWS_ACCOUNT_ID = credentials('ACCOUNT_ID')
        AWS_ECR_BACKEND_REPO_NAME = credentials('ECR_REPO2')
        AWS_ECR_FRONTFRONT_REPO_NAME = credentials('ECR_REPO1')
        AWS_DEFAULT_REGION = 'us-east-2' //your Region
        REPOSITORY_URI = "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/"
        
        GIT_REPO_NAME = "ThreeTierAppToDoApp"
        GIT_USER_NAME = credentials('GIT_USER_NAME')
        
        TIMESTAMP = sh(script: 'date "+%Y-%m-%d_%H-%M-%S"', returnStdout: true).trim()
    }

  

    stages {
        stage('Clean Workspace') {
            steps {
                cleanWs()
            }
        }

        stage('Checkout from Git') {
            steps {
                git branch: 'main', url: 'https://github.com/patelajay745/ThreeTierAppToDoApp.git'
            }
        }
        
        stage("Sonarqube Analysis "){
            steps{
                withSonarQubeEnv('sonar-server') {
                    sh ''' $SCANNER_HOME/bin/sonar-scanner -Dsonar.projectName=ToDo \
                    -Dsonar.projectKey=ToDO'''
                }
            }
        }
        stage("quality gate"){
           steps {
                script {
                    waitForQualityGate abortPipeline: false, credentialsId: 'Sonar-Token'
                }
            }
        }
    
        stage('OWASP FS SCAN') {
            steps {
                dependencyCheck additionalArguments: '--scan ./ --disableYarnAudit --disableNodeAudit', odcInstallation: 'DP-Check'
                dependencyCheckPublisher pattern: '**/dependency-check-report.xml'
            }
        }
         stage('TRIVY FS SCAN') {
            steps {
                sh "trivy fs . > trivyfs.txt"
            }
        }

        stage("Build and Push Frontend Image") {
            steps {
                script {
                    dir('frontend') {
                        sh "aws ecr get-login-password --region us-east-2 | docker login --username AWS --password-stdin ${REPOSITORY_URI}"
                        buildAndPushDockerImage(AWS_ECR_FRONTFRONT_REPO_NAME)
                    }
                }
            }
        }


        stage("Build BackEnd and Push Image") {
            steps {
                script {
                    dir('backend') {
                        buildAndPushDockerImage(AWS_ECR_BACKEND_REPO_NAME)
                    }
                }
            }
        }

        stage("Trivy Image Scan"){
            steps{
                sh "trivy image ${REPOSITORY_URI}${AWS_ECR_FRONTFRONT_REPO_NAME}:${TIMESTAMP} > trivyimage.txt"
                 sh "trivy image ${REPOSITORY_URI}${AWS_ECR_BACKEND_REPO_NAME}:${TIMESTAMP} > trivyimage1.txt"                
            }
        }

        stage('Update Deployment file') {
            steps {
                script {
                    dir('k8s_files/Backend') {
                        withCredentials([string(credentialsId: 'GITHUB_TOKEN', variable: 'GITHUB_TOKEN')]) {
                            updateDeploymentFile(AWS_ECR_BACKEND_REPO_NAME)
                        }
                    }

                    echo "Updating frontend"
                    
                    dir('k8s_files/Frontend') {
                        withCredentials([string(credentialsId: 'GITHUB_TOKEN', variable: 'GITHUB_TOKEN')]) {
                            updateDeploymentFile(AWS_ECR_FRONTFRONT_REPO_NAME)
                        }
                    }
                }
            }
        }
    }
}

// Function to build and push Docker image
def buildAndPushDockerImage(imageName) {
        sh "docker build -t ${imageName}:${TIMESTAMP} ."
        sh "docker tag ${imageName}:${TIMESTAMP} " + REPOSITORY_URI + "${imageName}:${TIMESTAMP}"
        sh "docker push ${REPOSITORY_URI}${imageName}:${TIMESTAMP}"
        sh "docker image prune -f"
    } 	

// Function to update deployment file
def updateDeploymentFile(repoName) {
        gitConfig()
        sh '''   
            imageTag=$(grep -oP '(?<=registry:)[^ ]+' deployment.yaml)
            sed -i "s/${repoName}:${imageTag}/${repoName}:${TIMESTAMP}/" deployment.yaml
            git add deployment.yaml
            git commit -m "Update deployment Image to version \${TIMESTAMP}"
            git push https://${GITHUB_TOKEN}@github.com/${GIT_USER_NAME}/${GIT_REPO_NAME} HEAD:main
        '''
}

// Function for common Git configuration
def gitConfig() {
    sh "git config user.email $YOUR_EMAIL_ID"
    sh "git config user.name 'Ajay Patel'"
}
```

Click on "Apply and Save."

Click on "Build Now"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705852780644/15313961-335e-4f68-bfff-94c16ac5f070.png align="center")

Now see the reports on SonarQube dashboard.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705852836302/a68f0136-e883-420f-ae9c-66daab7862a5.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705852867971/d70c8cd1-31ac-4dfc-b4b6-c2d9db11e322.png align="center")

When you log in to ECR , you will see a new image is created

No CI part has been completed. Now it's time for CD. Let's proceed with the EKS and ArgoCD setup.

`Step 9 : Setup EKS and ArgoCD, Application Load Balancer controller.`

Execute the following command on the Jenkins server.

```bash
eksctl create cluster --name 3-tier-cluster --region us-east-1 --node-type t2.medium --nodes-min 2 --nodes-max 2
aws eks update-kubeconfig --region us-east-1 --name 3-tier-cluster


source <(kubectl completion bash) # set up autocomplete in bash into the current shell, bash-completion package should be installed first.
echo "source <(kubectl completion bash)" >> ~/.bashrc
alias k=kubectl
complete -o default -F __start_kubectl k

k get nodes
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705853889025/c5feec22-dc41-4f78-a3d7-3be8555cfe41.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705854759968/f0d2fe42-e4b1-4f31-83f4-824826cba202.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705854936762/60e0eebb-97c0-466a-b299-54889409059f.png align="center")

Next, install Application LoadBalancer controller. on the cluster:

Run Following command. Don't Forget to update name and region of EKS cluster in below commands.

```bash
curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.5.4/docs/install/iam_policy.json
aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json
eksctl utils associate-iam-oidc-provider --region=us-west-2 --cluster=three-tier-cluster --approve
eksctl create iamserviceaccount --cluster=3-tier-cluster --namespace=kube-system --name=aws-load-balancer-controller --role-name AmazonEKSLoadBalancerControllerRole --attach-policy-arn=arn:aws:iam::<Your AWS Account NO>:policy/AWSLoadBalancerControllerIAMPolicy --approve --region=us-east-1
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705860653239/8c354930-0790-47f4-a9d4-d22d74f5c07c.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705860708622/29d73abd-a1e3-46eb-a16a-449480dccb98.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705860952239/17d180e0-7aaf-4ac0-b5b4-e5ba6b16ec3a.png align="center")

```bash
sudo snap install helm --classic
helm repo add eks https://aws.github.io/eks-charts
helm repo update eks
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=my-cluster --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller
kubectl get deployment -n kube-system aws-load-balancer-controller
```

If you observe that your `aws-load-balancer-controller` pod is running, then everything is set up perfectly.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705861082839/af481dcb-93ee-4652-9bf7-5561459ede43.png align="center")

Next, Install ArgoCD on the cluster:

```bash
k create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.4.7/manifests/install.yaml
```

This will deploy the necessary resources. You can check running pods.

```bash
kubectl get pods -n argocd
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705855613803/39b2124c-4de5-409e-aebc-2c2190b2c3b9.png align="center")

To access ArgoCD in a web browser, we need to configure its service to use a LoadBalancer.

```bash
kubectl patch svc argocd-server -n argocd --type='json' -p='[{"op": "replace", "path": "/spec/type", "value":"LoadBalancer"}]'
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705855757112/e7181e81-263e-4a03-9fd7-160546dc9104.png align="center")

Get URL of Loadbalancer by following command.

```bash
kubectl get svc argocd-server -n argocd -o jsonpath="{.status.loadBalancer.ingress[0].hostname}"
```

Open URL on web browser.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705856594869/7ed4f511-14d7-4893-ba75-4c50737b91ee.png align="center")

You may encounter a connection is not secure error, but you can click on Advanced and then proceed to open it.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705856710614/4905d279-e238-4d9e-a808-46c0106ecffa.png align="center")

Username is admin and to get password run following command.

```bash
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705856888072/4c1362a6-d45d-4cef-a7d8-2aef7f544525.png align="center")

`Step 10 : Deploy application on cluster using ArgoCD`

Navigate to the "Settings" in the left panel of the ArgoCD dashboard.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705889678001/1bc366f8-02d6-42b4-972b-61451ae7cf7d.png align="center")

Click on "Repositories."

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705889725244/8dcd1bee-1cc9-48b0-a1ce-ab03e1c12279.png align="center")

Select "CONNECT REPO USING HTTPS."

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705889786195/a2a73ff8-0272-40e4-8d78-41bdb8eeb905.png align="center")

Provide the repository name where your Manifest files are located and click on "CONNECT."

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705858495338/091c7fba-b202-46c6-a797-c77a4eeb156a.png align="center")

Verify the connection status; a successful status indicates the repository connection was established.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705858578585/f2107398-0245-47da-854d-0e31ab7b7655.png align="center")

Next, create your initial application, starting with a database.

Click on "CREATE APPLICATION."

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705858757824/b48480e3-c65d-4717-a6f9-be54865e4470.png align="center")

Provide the details as it is provided in the below snippet and scroll down.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705858860551/a0b983a5-2948-4e75-81ab-102078e43914.png align="center")

Select the repository you configured in the earlier step.

In the "Path" field, specify the location where your manifest files are located, and provide the other necessary details as illustrated in the screenshot below.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705889881967/65877bb3-64b1-4c9f-bb0b-5351b337f458.png align="center")

Click on CREATE.

While your database application is starting to deploy, we will create an application for the backend.

Click on "New App."

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705859305907/ad313e3a-6045-4126-b086-03e3f7849b8d.png align="center")

In the Path, provide the location where your backend manifest files are presented, and fill in other details as shown in the screenshot below.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705889940769/5f14071d-1ff9-4c8e-b5e5-1fba1ac6366e.png align="center")

Click on "CREATE." While your backend application is starting to deploy, we will create an application for the frontend. Click on "New App."

Provide the details as provided in the snippet below and scroll down.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705859396877/19d9637c-91bc-4e88-85d8-8cd86f38a9ec.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705890043039/fb3d8629-ccb8-4e5e-9f55-5bf3b91ac72a.png align="center")

Click on "CREATE." While your frontend application is starting to deploy, we will create an application for the ingress.

Provide the details as shown in the snippet below and scroll down.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705859726276/abbca6e7-52dc-475e-8bba-7097784488c6.png align="center")

In the **Path**, provide the location where your Manifest file of ingress are presented and provide other things as shown in the below screenshot.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705890058160/8c428881-6273-46f0-ac7c-259549dfae5d.png align="center")

Now, check your deployment. Everything should look nice and green.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705861273707/755f2658-ffa0-479d-b87b-85f36ac040a0.png align="center")

You can observe the deployment of your new Kubernetes 3-tier app's main load balancer and wait until it is fully provisioned.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705861358327/c83d8af0-a1be-47fe-bcb7-71f75e8c6ed2.png align="center")

Get URL of Loadbalancer by running following command.

```bash
k get ing mainlb -n 3-tier-app -o jsonpath="{.status.loadBalancer.ingress[0].hostname}"
```

If you attempt to use that URL in a web browser, it will not function because we have specified a domain name in the Ingress. Therefore, setting up the DNS for the domain is necessary to access our web application.

`Step 11 : Setting up DNS on Route53.`

  
Navigate to Route53 -&gt; Hosted zones, select the domain name you want to configure. Remember to use the same domain or subdomain name as used in the Ingress manifest file.

Click on "Create Record."

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705890137684/9933eaf0-4896-4e27-a9f2-dac075aa960c.png align="center")

If you have utilised a subdomain in the Ingress file, please input it as the record name. Choose "A" as the record type and select "Alias." For the endpoint, choose "Application and Classic Load Balancer." Specify the region, locate your load balancer name, select it, and click on "Create Records."

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705886831781/23f1ba8d-53b0-4a85-aa21-91467ec588ad.png align="center")

Awesome! Your ToDo Application is now live! 🚀 We've implemented Persistent Volumes (pv) and Persistent Volume Claims (pvc), ensuring your data stays intact even if the database pods are unexpectedly deleted. 🛡️📊

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705887464951/071289e1-bfeb-425b-be92-e350eefedfa0.png align="center")

`Step 12 : Setting up Monitoring.`

Use the following shell script to install Grafana and Prometheus using Helm charts. The provided script will also set up everything for monitoring. Run the script on the Jenkins server.

`monitoring.sh`

```bash
#!/bin/bash

# Add Helm repositories
helm repo add stable https://charts.helm.sh/stable
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts

# Deploy Prometheus
kubectl get namespace prometheus >/dev/null 2>&1
if [ $? -ne 0 ]; then
    kubectl create namespace prometheus
    helm install stable prometheus-community/kube-prometheus-stack -n prometheus
else
    echo 'Namespace prometheus already exists.'
fi

# Patch Services
kubectl patch svc stable-kube-prometheus-sta-prometheus -n prometheus --type=json -p='[{"op":"replace","path":"/spec/type","value":"LoadBalancer"}]'
kubectl patch svc stable-grafana -n prometheus --type=json -p='[{"op":"replace","path":"/spec/type","value":"LoadBalancer"}]'

# Get Service URLs
prometheus=$(kubectl get svc stable-kube-prometheus-sta-prometheus -n prometheus -o jsonpath="{.status.loadBalancer.ingress[0].hostname}" | tr -d '\n')
grafana=$(kubectl get svc stable-grafana -n prometheus -o jsonpath="{.status.loadBalancer.ingress[0].hostname}" | tr -d '\n')
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705888104995/6ee88636-9649-4941-8896-ec26cfca6bf7.png align="center")

You can run following command on terminal to get URL of Grafana and Prometheus.

```bash
kubectl get svc stable-kube-prometheus-sta-prometheus -n prometheus -o jsonpath="{.status.loadBalancer.ingress[0].hostname}"

#for grafana url 
kubectl get svc stable-grafana -n prometheus -o jsonpath="{.status.loadBalancer.ingress[0].hostname}"
```

To open promethus url in browser you need to attach ":9090/targets" at end of URL.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705888852094/19cffe19-d0e8-4c46-a9ce-8b325f642759.png align="center")

To open grafana url you might need to wait for a while.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1704404146431/4b4a2ace-74c1-4340-811c-0c80cbbb2126.png?auto=compress,format&format=webp align="left")

`Enter user name "admin" and password "prom-operator"`

Click on "+" on right hand side and then click on import dashboard as shown in below screenshot.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1704404465151/9b59615c-7b30-4a2e-bb19-5fc7f60a7de2.png?auto=compress,format&format=webp align="left")

Enter "15661" and click on "Load"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1704404793978/60df81d4-a9f7-4b7f-a9bc-01eb31361be5.png?auto=compress,format&format=webp align="left")

Then choose "prometheus" as source and Click on "Import"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1704404736432/ee99f77e-f572-4edc-9589-80850d461645.png?auto=compress,format&format=webp align="left")

Here you go. Your dashboard is ready.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705889146121/8acff1de-5dea-410e-af1b-36397065b626.png align="center")

You can import dashboard as per your requirement from this site. [**https://grafana.com/grafana/dashboards/**](https://grafana.com/grafana/dashboards/)

`Step 13: Destroy EKS cluster when your done with project.`

```bash
eksctl delete cluster --name 3-tier-cluster --region us-east-1
```

Don't Forget to Destroy Terraform created Ec2 instance for Jenkins server.

### **Conclusion**

🚀 In this project, we successfully implemented a robust CI/CD pipeline for a 3-tier ToDo application, leveraging AWS services, Kubernetes, ArgoCD, and Helm charts. 🌐 The pipeline ensures efficient code analysis, containerization, and seamless deployment. 🛠️ With comprehensive monitoring using Prometheus and Grafana, we guarantee the application's stability and performance. 📈 The use of infrastructure as code (Terraform) simplifies setup and teardown, enhancing project scalability. 💡 This solution addresses the challenges of continuous integration and delivery, empowering developers to focus on innovation and productivity.

Stay connected on **LinkedIn**: [LinkedIn Profile](https://www.linkedin.com/in/patelajay745/)

Stay up-to-date with **GitHub**: [GitHub Profile](https://github.com/patelajay745)
