Skip to main content

Command Palette

Search for a command to run...

Simple Notes App for Community: End-to-End Implementation using CI/CD"

Published
7 min read
Simple Notes App for Community: End-to-End Implementation using CI/CD"
B

Tech enthusiast with 15 years of experience in IT, specializing in server management, VMware, AWS, Azure, and automation. Passionate about DevOps, cloud, and modern infrastructure tools like Terraform, Ansible, Packer, Jenkins, Docker, Kubernetes, and Azure DevOps. Passionate about technology and continuous learning, I enjoy sharing my knowledge and insights through blogging and real-world experiences to help the tech community grow!

Prerequisites

Before diving into this project, here are some skills and tools you should be familiar with:

  • [x] Clone repository for terraform code
    Note: Replace resource names and variables as per your requirement in terraform code

    • from the Virtual machine main.tf (i.e key name- MYLABKEY*)
  • [x] App Repo

  • [x] Basic Knowledge of GitHub: You need to be familiar with version control, pull requests, and branching.

  • [x] Understanding of Docker: Docker is used for containerizing the application. Make sure you know how to create, manage, and run Docker containers.

  • [x] Familiarity with Jenkins: Jenkins is the CI tool that automates the testing and deployment process. A basic understanding of setting up Jenkins pipelines is essential.

  • [x] Experience with Python and Node.js: Since the backend of the app is likely in Python and the frontend in React (which relies on Node.js), make sure you're comfortable with both technologies.

  • [x] React Framework: This is used to build the user interface. Familiarity with React components, state management, and hooks is important.

Setting Up the Environment

I have created a Terraform code to set up the entire environment, including the installation of required applications, and tools.

  • ⇒ Two EC2 machines will be created named "Jenkins Server & Agent"

  • ⇒ Docker Install

Setting Up the Virtual Machines (EC2)

First, we'll create the necessary virtual machines using terraform.

Below is a terraform configuration:

Once you clone the repo then go to folder "14.Real-Time-DevOps-Project/Terraform_Code/Code_IAC_Terraform_box" and run the terraform command.

cd Terraform_Code/Code_IAC_Terraform_box

$ ls -l
da---l          29/09/24  12:02 PM                k8s_setup_file
-a---l          29/09/24  10:44 AM            507 .gitignore
-a---l          01/10/24  10:50 AM           3771 agent_install.sh
-a---l          01/10/24  10:59 AM           8149 main.tf
-a---l          16/07/21   4:53 PM           1696 MYLABKEY.pem
-a---l          25/07/24   9:16 PM            239 provider.tf
-a---l          01/10/24  11:26 AM          10257 terrabox_install.sh

Note ⇒ Make sure to run main.tf from inside the folders.

13.Real-Time-DevOps-Project/Terraform_Code/Code_IAC_Terraform_box/

da---l          29/09/24  12:02 PM                k8s_setup_file
-a---l          29/09/24  10:44 AM            507 .gitignore
-a---l          01/10/24  10:50 AM           3771 agent_install.sh
-a---l          01/10/24  10:59 AM           8149 main.tf
-a---l          16/07/21   4:53 PM           1696 MYLABKEY.pem
-a---l          25/07/24   9:16 PM            239 provider.tf
-a---l          01/10/24  11:26 AM          10257 terrabox_install.sh

You need to run main.tf file using the following terraform command.

Now, run the following command.

terraform init
terraform fmt
terraform validate
terraform plan
terraform apply 
# Optional <terraform apply --auto-approve>

Once you run the terraform command, then we will verify the following things to make sure everything is set up via a terraform.

Inspect the Cloud-Init logs:

Once connected to the EC2 instance then you can check the status of the user_data script by inspecting the log files.

# Primary log file for cloud-init
sudo tail -f /var/log/cloud-init-output.log
  • If the user_data script runs successfully, you will see output logs and any errors encountered during execution.

  • If there’s an error, this log will provide clues about what failed.

Outcome of "cloud-init-output.log"

image

image-1

Verify the Installation

  • [x] Docker version
ubuntu@ip-172-31-95-197:~$ docker --version
Docker version 24.0.7, build 24.0.7-0ubuntu4.1


docker ps -a
ubuntu@ip-172-31-94-25:~$ docker ps
  • [x] Terraform version
ubuntu@ip-172-31-89-97:~$ terraform version
Terraform v1.9.6
on linux_amd64
  • [x] AWS Cli version
ubuntu@ip-172-31-89-97:~$ aws version
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
  aws help
  aws <command> help
  aws <command> <subcommand> help
Change the hostname: (optional)

sudo terraform show

sudo hostnamectl set-hostname jenkins-svr
sudo hostnamectl set-hostname jenkins-agent
  • Update the /etc/hosts file:
    • Open the file with a text editor, for example:
sudo vi /etc/hosts

Replace the old hostname with the new one where it appears in the file.

Apply the new hostname without rebooting:

sudo systemctl restart systemd-logind.service

Verify the change:

hostnamectl

Update the package

sudo -i
apt update

Setup the Jenkins

Access Jenkins via http://:8080. Retrieve the initial admin password using:

sudo cat /var/lib/jenkins/secrets/initialAdminPassword

image-5

image-6

Setup the Jenkins agent

sudo passwd ubuntu

image-3

  • Need to do the password-less authentication between both servers.
sudo su
cat /etc/ssh/sshd_config | grep "PasswordAuthentication"
echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config
cat /etc/ssh/sshd_config | grep "PasswordAuthentication"

cat /etc/ssh/sshd_config | grep "PermitRootLogin"
echo "PermitRootLogin yes"  >> /etc/ssh/sshd_config
cat /etc/ssh/sshd_config | grep "PermitRootLogin"
  • Restart the sshd reservices.
systemctl daemon-reload
      or 
sudo service ssh restart
  • Generate the SSH key and share it with the agent.
ssh-keygen
  • Copy the public SSH key from Jenkins to Agent.

    • The public key from Jenkins master.
ubuntu@ip-172-31-89-97:~$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG4BFDIh47LkE6huSzi6ryMKcw+Rj1+6ErnplFbOK5Nz ubuntu@ip-172-31-89-97

From Agent.

image-4

Now, try to do the SSH to agent, and it should be connected without any credentials.

ssh ubuntu@<private IP address of agent VM>

image-7

Open Jenkins UI and configure the agent. Dashboard> Manage Jenkins> Nodes

Remote root directory: define the path. Launch method: Launch agents via ssh

  • Host: public IP address of agent VM

  • Credential of the agent. (will create the credential)

    • Kind: SSH Username with private key

    • private key from Jenkins Master server.

      image-10

  • Host Key Verification Strategy: Non-Verifying Verification Strategy

image-8

image-9

image-11

Congratulations: the Agent is successfully configured and alive.

image-12

Install the plugin in Jenkins

Blue Ocean
Pipeline: Stage View
  • Run any job and verify that the job is executing on the agent node.

    • create a below pipeline build it and verify the outcomes in the agent machine.
pipeline {
    agent { label "balraj"}

    stages {
        stage('code') {
            steps {
                echo 'This is cloning the code'
                git branch: 'main', url: 'https://github.com/mrbalraj007/django-notes-app.git'
                echo "This is cloning the code"
            }
        }
    }
}

image-13

  • Build a Docker image. add the following state to main pipeline
         stage('build') {
           steps {
               echo 'This is building the docker image'
               sh "docker build -t notes-app:latest ."
               echo "Image has been created successfully"
           }
       }

image-14

  • Add the below the deploy the image.
 stage('test') {
            steps {
                echo 'This is testing the code'
            }
        }
        stage('Deploy') {
            steps {
                echo 'This is deploying the code'
                sh 'docker run -d -p 8000:8000 notes-app:latest '
            }
        }

image-15

pipeline {
    agent { label "balraj"}

    stages {
        stage('code') {
            steps {
                echo 'This is cloning the code'
                git branch: 'main', url: 'https://github.com/mrbalraj007/django-notes-app.git'
                echo "This is cloning the code"
            }
        }
        stage('build') {
            steps {
                echo 'This is building the docker image'
                sh "docker build -t notes-app:latest ."
                echo "Image has been created successfully"
            }
        }
        stage('test') {
            steps {
                echo 'This is testing the code'
            }
        }
        stage('Deploy') {
            steps {
                echo 'This is deploying the code'
                sh 'docker run -d -p 8000:8000 notes-app:latest '
            }
        }
    }
}

Now, try to access it via the 8000 port

If you rerun the build, then you will get an error because port 8000 has already been used. So we will use here Docker Compose.

image-17

here is the updated pipeline

pipeline {
    agent { label "balraj"}

    stages {
        stage('code') {
            steps {
                echo 'This is cloning the code'
                git branch: 'main', url: 'https://github.com/mrbalraj007/django-notes-app.git'
                echo "This is cloning the code"
            }
        }
        stage('build') {
            steps {
                echo 'This is building the docker image'
                sh "docker build -t notes-app:latest ."
                echo "Image has been created successfully"
            }
        }
        stage('test') {
            steps {
                echo 'This is testing the code'
            }
        }
        stage('Deploy') {
            steps {
                echo 'This is deploying the code'
                sh 'docker compose up -d'
            }
        }
    }
}

Kill the existing image/deployment before build/executing it.

docker container ls
docker stop aa48f961a5de && docker rm aa48f961a5de

image-18

image-19

  • Push image to Docker hub.

    • create credential in Jenkins for Dockerhub

      image-20

  • Bind credential in the pipeline

pipeline {
    agent { label "balraj"}

    stages {
        stage('code') {
            steps {
                echo 'This is cloning the code'
                git branch: 'main', url: 'https://github.com/mrbalraj007/django-notes-app.git'
                echo "This is cloning the code"
            }
        }
        stage('build') {
            steps {
                echo 'This is building the docker image'
                sh "docker build -t notes-app:latest ."
                echo "Image has been created successfully"
            }
        }
        stage('test') {
            steps {
                echo 'This is testing the code'
            }
        }
        stage('Push to DockerHub') {
            steps {
                echo "This is pushing image to Docker Hub"
                withCredentials([usernamePassword(credentialsId:"dockerHubCred",passwordVariable:"dockerHubPass",usernameVariable:"dockerHubUser")]){
                sh "docker login -u ${env.dockerHubUser} -p ${env.dockerHubPass}"
                sh "docker image tag notes-app:latest ${env.dockerHubUser}/notes-app:latest"
                sh "docker push ${env.dockerHubUser}/notes-app:latest"
                }
            }
        }
        stage('Deploy') {
            steps {
                echo 'This is deploying the code'
                sh 'docker compose up -d'
            }
        }
    }
}

image-21

image-22

[!Note] I was getting a 302 error message, and when I followed the below procedure, it fixed itself.

  • I clicked on webhooks clicked on the recent deliveries and clicked on redeliver and the issue was fixed.

    image-23

    image-24

Now, we have to tick this option in Jenkins: "GitHub hook trigger for GITScm polling."

image-25

Try to modify anything in the Github repo and the build should be auto trigger.

image-26

it works :-)

Ref Link

More from this blog

Balraj Singh's blog

38 posts