Understanding the CI/CD Workflow: From Development to Production

ยท

20 min read

TLDR: This blog post provides a comprehensive overview of the CI/CD workflow, detailing the process of promoting applications from development to production environments. It covers the roles of various branches, the Jenkins pipeline stages, and best practices for managing different environments in a CI/CD setup.

In today's fast-paced software development landscape, Continuous Integration and Continuous Delivery (CI/CD) play a crucial role in ensuring that applications are delivered efficiently and reliably. This blog post will explore the entire workflow involved in promoting an application from the development environment to production, including the various stages and best practices.

CI/CD is a method used in software development to automate the process of integrating code changes and delivering them to production. This approach allows teams to release software updates more frequently and with higher quality. In previous videos, we have discussed how to create CI/CD pipelines for individual environments using tools like Jenkins, GitHub Actions, and GitLab. However, this post will focus on the complete workflow across multiple environments.

The CI/CD Workflow Explained

Initial Code Changes

When a developer makes code changes in a feature branch within a version control system like GitHub, a webhook triggers the CI/CD pipeline. This pipeline is typically managed by Jenkins and follows a series of stages:

  1. Code Checkout: The pipeline checks out the code from the feature branch.

  2. Build and Unit Testing: The application is built using a build tool (e.g., Maven for Java applications) and unit tests are executed.

  3. Code Scanning: Tools like SonarQube are used to analyze the code for quality issues, such as static code analysis and duplicate code.

  4. Image Build Creation: A container image is created using Docker or similar tools.

  5. Image Scanning: The created image is scanned for vulnerabilities using tools like Trivy or Clair.

  6. Pushing the Image: The final image is pushed to an image registry (e.g., Docker Hub).

Promoting Changes to Staging

Once the image is ready, the next step is to update the Kubernetes manifests (e.g., deployment.yaml) to reflect the new image version. It is a good practice to maintain these manifests in a separate repository. After updating the manifests, the changes are deployed to the staging environment using tools like Argo CD or Flux.

Branching Strategy

Understanding the branching strategy is key to managing the CI/CD workflow effectively. In an ideal scenario, the following branches are typically used:

  • Feature Branches: Multiple feature branches are created for different enhancements or bug fixes.

  • Main Branch: This branch contains the active development code and is where features are merged after testing.

  • Release Branches: These branches are created for preparing a new release version of the application.

  • Hotfix Branches: Used for urgent fixes to production issues, these branches allow developers to address critical bugs quickly.

Moving from Development to Production

The process of promoting code from development to production involves several steps:

  1. Feature Branch to Main Branch: Once a feature is complete, it is merged into the main branch, triggering a new Jenkins pipeline that deploys the code to the staging environment.

  2. Staging Environment: In this environment, the QA team conducts thorough testing, including manual tests and automated regression tests. This phase is crucial for ensuring that the application is stable and ready for release.

  3. Creating a Release Branch: After successful testing, a release branch is created from the main branch. A release pipeline is triggered to deploy the application to a pre-production environment.

  4. Pre-Production Testing: This environment is used for final testing before production. It allows teams to verify that everything works as expected and to debug any issues that may arise.

  5. Production Deployment: Once all tests are passed and the application is confirmed to be stable, the final deployment to the production environment occurs.

Best Practices for CI/CD Workflows

  • Separate Environments: It is advisable to maintain separate Kubernetes clusters for development, staging, pre-production, and production environments to ensure isolation and security.

  • Automated Testing: Incorporate automated tests at various stages of the pipeline to catch issues early and reduce manual testing efforts.

  • Monitoring and Feedback: Implement monitoring tools to track application performance and gather feedback from users to continuously improve the application.


Your workflow follows a structured Git Flow with CI/CD using Jenkins, ArgoCD, and Amazon EKS. Hereโ€™s how each step plays out in detail:


๐Ÿš€ Multi-Branch CI/CD Flow with Jenkins & ArgoCD

1๏ธโƒฃ Feature Development (Feature Branch CI/CD)

Process:

  1. Developer creates a feature branch (feature-xyz) from main.

  2. Code is written, committed, and pushed to the feature branch.

  3. Jenkins CI/CD pipeline triggers:

    • Runs unit tests, linting, and security scans.

    • Builds the Docker image and pushes it to Amazon ECR.

    • Updates the Kubernetes manifests (YAML/Helm) in the feature branch's Git repo.

  4. ArgoCD detects changes in the feature branch repository and deploys the feature to EKS for testing.

  5. Feature is tested and verified.

โœ… Outcome: Feature branch deployed to EKS for testing.


2๏ธโƒฃ Merging to Main (Main Branch CI/CD)

Process:

  1. Once testing is complete, the feature branch is merged into the main branch.

  2. Jenkins CI/CD pipeline triggers:

    • Runs tests and builds the latest Docker image.

    • Pushes the new image to Amazon ECR.

    • Updates the Kubernetes manifests in the main branchโ€™s Git repo.

  3. ArgoCD detects the change in the main branch and deploys it to EKS.

  4. The updated application is now running in the staging or pre-production environment.

โœ… Outcome: main branch is deployed to EKS for staging/pre-production.


3๏ธโƒฃ Releasing to Production (Release Branch CI/CD)

Process:

  1. A release branch (release-vX.Y.Z) is created from main.

  2. Jenkins CI/CD pipeline triggers again:

    • Runs all CI/CD checks.

    • Builds and pushes a final production image to Amazon ECR.

    • Updates Kubernetes manifests in the release branchโ€™s Git repo.

  3. ArgoCD detects the release branch update and deploys the release to EKS (production environment).

โœ… Outcome: release branch is deployed to EKS (production).


๐Ÿ”น How to Set Up Jenkins for This Workflow?

In your Jenkinsfile, modify the pipeline to check the branch name and trigger the appropriate steps.

pipeline {
    agent any

    environment {
        DOCKER_IMAGE = "my-app:${GIT_BRANCH}-${BUILD_NUMBER}"
        ECR_REGISTRY = "123456789012.dkr.ecr.us-east-1.amazonaws.com"
        GIT_REPO = "git@github.com:user/k8s-config-repo.git"
    }

    stages {
        stage('Checkout Code') {
            steps {
                git branch: env.BRANCH_NAME, url: 'https://github.com/user/app-repo.git'
            }
        }

        stage('Run Tests') {
            steps {
                sh 'npm install && npm test'
            }
        }

        stage('Build & Push Docker Image') {
            steps {
                script {
                    def imageTag = "${ECR_REGISTRY}/my-app:${GIT_BRANCH}-${BUILD_NUMBER}"
                    sh "docker build -t ${imageTag} ."
                    sh "docker push ${imageTag}"
                }
            }
        }

        stage('Update Kubernetes Manifest') {
            steps {
                script {
                    sh '''
                    git clone ${GIT_REPO} k8s-config
                    cd k8s-config
                    sed -i 's|image: .*|image: ${ECR_REGISTRY}/my-app:${GIT_BRANCH}-${BUILD_NUMBER}|' deployment.yaml
                    git config --global user.email "jenkins@example.com"
                    git config --global user.name "jenkins"
                    git add deployment.yaml
                    git commit -m "Deploy ${GIT_BRANCH}-${BUILD_NUMBER} to k8s"
                    git push origin ${GIT_BRANCH}
                    '''
                }
            }
        }
    }

    post {
        success {
            echo "Deployment manifest updated! ArgoCD will detect and deploy changes."
        }
    }
}

๐Ÿ”น How to Configure ArgoCD for Multi-Branch Deployment?

  1. Create three ArgoCD applications for:

    • Feature branch (feature-* pattern)

    • Main branch (main)

    • Release branch (release-* pattern)

  2. Example ArgoCD Application YAML for main branch:

     apiVersion: argoproj.io/v1alpha1
     kind: Application
     metadata:
       name: my-app-main
       namespace: argocd
     spec:
       project: default
       source:
         repoURL: 'git@github.com:user/k8s-config-repo.git'
         targetRevision: main
         path: 'k8s/'
       destination:
         server: 'https://kubernetes.default.svc'
         namespace: staging
       syncPolicy:
         automated:
           prune: true
           selfHeal: true
    
  3. Similarly, configure ArgoCD applications for feature-* and release-* branches.


๐Ÿ”น Summary of the CI/CD Process

StepTriggerCI/CD Actions (Jenkins)CD Actions (ArgoCD)
Feature Branch (feature-xyz)Code commit to feature-*Test, Build, Push Image, Update GitDeploy feature branch to EKS (dev/testing)
Merge to Main (main)Feature branch mergedTest, Build, Push Image, Update GitDeploy main branch to EKS (staging)
Release Branch (release-vX.Y.Z)Release branch createdTest, Build, Push Image, Update GitDeploy release branch to EKS (production)

๐Ÿ”น Key Benefits

โœ… Branch-based deployments for separate dev, staging, and production environments.
โœ… Automated testing, building, and deploying using Jenkins & ArgoCD.
โœ… Full GitOps workflow โ†’ No manual intervention, only Git changes trigger deployments.
โœ… Rollback available โ†’ Since everything is in Git, you can easily revert to a previous version.


๐Ÿ”น Pipeline Breakdown

1๏ธโƒฃ Define the Pipeline and Agent

pipeline {
    agent any
  • pipeline {} โ†’ Declares this as a Jenkins declarative pipeline.

  • agent any โ†’ Runs the pipeline on any available Jenkins agent (worker node).


2๏ธโƒฃ Define Environment Variables

    environment {
        DOCKER_IMAGE = "my-app:${GIT_BRANCH}-${BUILD_NUMBER}"
        ECR_REGISTRY = "123456789012.dkr.ecr.us-east-1.amazonaws.com"
        GIT_REPO = "git@github.com:user/k8s-config-repo.git"
    }
  • DOCKER_IMAGE โ†’ Sets the Docker image name using:

    • GIT_BRANCH โ†’ The current branch name.

    • BUILD_NUMBER โ†’ The unique build number from Jenkins.

    • Example: "my-app:feature-branch-25"

  • ECR_REGISTRY โ†’ Amazon Elastic Container Registry (ECR) URL where the image will be stored.

  • GIT_REPO โ†’ The GitHub repository containing Kubernetes YAML manifests.


๐Ÿ”น Stages of the Pipeline

3๏ธโƒฃ Checkout the Code

    stages {
        stage('Checkout Code') {
            steps {
                git branch: env.BRANCH_NAME, url: 'https://github.com/user/app-repo.git'
            }
        }
  • stage('Checkout Code') โ†’ Retrieves the source code from the Git repository.

  • git branch: env.BRANCH_NAME, url: 'https://github.com/user/app-repo.git'

    • Clones the repo using the current branch (BRANCH_NAME).

    • Ensures the latest code is available for testing & building.


4๏ธโƒฃ Run Tests

        stage('Run Tests') {
            steps {
                sh 'npm install && npm test'
            }
        }
  • stage('Run Tests') โ†’ Runs unit tests to ensure code correctness.

  • sh 'npm install && npm test'

    • Installs project dependencies (npm install).

    • Runs unit tests (npm test).

  • If tests fail, the pipeline stops.


5๏ธโƒฃ Build & Push Docker Image

        stage('Build & Push Docker Image') {
            steps {
                script {
                    def imageTag = "${ECR_REGISTRY}/my-app:${GIT_BRANCH}-${BUILD_NUMBER}"
                    sh "docker build -t ${imageTag} ."
                    sh "docker push ${imageTag}"
                }
            }
        }
  • stage('Build & Push Docker Image') โ†’ Builds and pushes the Docker image to Amazon ECR.

  • def imageTag = "${ECR_REGISTRY}/my-app:${GIT_BRANCH}-${BUILD_NUMBER}"

    • Constructs the image tag dynamically.
  • docker build -t ${imageTag} .

    • Builds the Docker image from the current directory (.).
  • docker push ${imageTag}

    • Pushes the image to ECR, making it available for deployment.

6๏ธโƒฃ Update Kubernetes Manifest

        stage('Update Kubernetes Manifest') {
            steps {
                script {
                    sh '''
                    git clone ${GIT_REPO} k8s-config
                    cd k8s-config
                    sed -i 's|image: .*|image: ${ECR_REGISTRY}/my-app:${GIT_BRANCH}-${BUILD_NUMBER}|' deployment.yaml
                    git config --global user.email "jenkins@example.com"
                    git config --global user.name "jenkins"
                    git add deployment.yaml
                    git commit -m "Deploy ${GIT_BRANCH}-${BUILD_NUMBER} to k8s"
                    git push origin ${GIT_BRANCH}
                    '''
                }
            }
        }
  • stage('Update Kubernetes Manifest') โ†’ Updates the Kubernetes deployment manifest with the new Docker image.

  • git clone ${GIT_REPO} k8s-config โ†’ Clones the Kubernetes configuration repo (k8s-config).

  • cd k8s-config โ†’ Moves into the repo.

  • sed -i 's|image: .*|image: ${ECR_REGISTRY}/my-app:${GIT_BRANCH}-${BUILD_NUMBER}|' deployment.yaml

    • Replaces the image: field in deployment.yaml with the new Docker image.

    • Example change:

        Before: image: my-app:old-tag
        After:  image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:feature-25
      
  • Git Configuration:

      git config --global user.email "jenkins@example.com"
      git config --global user.name "jenkins"
    
    • Sets the Git identity for Jenkins.
  • Git Commit & Push:

      git add deployment.yaml
      git commit -m "Deploy ${GIT_BRANCH}-${BUILD_NUMBER} to k8s"
      git push origin ${GIT_BRANCH}
    
    • Commits the updated Kubernetes manifest to the repo.

    • Pushes it to the same branch in k8s-config-repo.


๐Ÿ”น ArgoCD Automatically Deploys Changes

  • ArgoCD watches the k8s-config-repo for changes.

  • Since deployment.yaml is updated & committed, ArgoCD detects the new image and deploys it to Amazon EKS.


7๏ธโƒฃ Post-Build Success Message

    post {
        success {
            echo "Deployment manifest updated! ArgoCD will detect and deploy changes."
        }
    }
}
  • post { success {} โ†’ Runs after a successful build.

  • echo โ†’ Displays a confirmation message in Jenkins logs.


๐Ÿ”น Summary of the Pipeline Execution

StageAction
Checkout CodePulls latest code from GitHub
Run TestsInstalls dependencies, runs tests
Build & Push Docker ImageBuilds & pushes Docker image to Amazon ECR
Update Kubernetes ManifestUpdates deployment.yaml with new image
ArgoCD DeploymentArgoCD detects the changes and deploys to EKS

๐Ÿ”น Key Benefits

โœ… Automated CI/CD โ†’ No manual intervention needed.
โœ… ArgoCD ensures GitOps workflow โ†’ Deployments are triggered by Git commits.
โœ… Rollback support โ†’ If needed, revert the Kubernetes manifest to a previous commit.
โœ… Multi-branch support โ†’ The pipeline works for feature, main, and release branches.


Hereโ€™s an improved version of your Jenkins + ArgoCD CI/CD pipeline with best practices applied:

๐Ÿ”น Key Improvements

โœ… Uses Jenkins credentials store for security.
โœ… Uses Helm for Kubernetes deployment (instead of editing YAML files manually).
โœ… Adds Slack notifications for visibility.
โœ… Uses multi-stage Docker builds for optimization.
โœ… Adds error handling and rollback capabilities.


๐Ÿ”น Improved Multibranch, Multistage Jenkins Pipeline

pipeline {
    agent any

    environment {
        ECR_REGISTRY = credentials('ECR_CREDENTIALS_ID')  // Securely fetch ECR URL
        GIT_REPO = credentials('GIT_REPO_CREDENTIALS_ID') // Securely fetch Git URL
        IMAGE_TAG = "my-app:${BRANCH_NAME}-${BUILD_NUMBER}"
        SLACK_CHANNEL = "#deployments"
    }

    stages {
        stage('Checkout Code') {
            steps {
                git branch: env.BRANCH_NAME, url: 'https://github.com/user/app-repo.git'
            }
        }

        stage('Run Tests') {
            steps {
                sh 'npm install && npm test'
            }
        }

        stage('Build & Push Docker Image') {
            steps {
                script {
                    def imageTag = "${ECR_REGISTRY}/my-app:${BRANCH_NAME}-${BUILD_NUMBER}"
                    sh """
                        echo "๐Ÿ”น Logging into ECR..."
                        aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${ECR_REGISTRY}

                        echo "๐Ÿ”น Building Docker Image..."
                        docker build -t ${imageTag} .

                        echo "๐Ÿ”น Pushing Image to ECR..."
                        docker push ${imageTag}
                    """
                }
            }
        }

        stage('Deploy Using Helm') {
            steps {
                script {
                    sh """
                        echo "๐Ÿ”น Cloning Kubernetes Config Repository..."
                        git clone ${GIT_REPO} k8s-config
                        cd k8s-config

                        echo "๐Ÿ”น Updating Helm Values File..."
                        sed -i 's|imageTag: .*|imageTag: ${BRANCH_NAME}-${BUILD_NUMBER}|' values.yaml

                        echo "๐Ÿ”น Committing Changes to Git..."
                        git config --global user.email "jenkins@example.com"
                        git config --global user.name "jenkins"
                        git add values.yaml
                        git commit -m "Deploy ${BRANCH_NAME}-${BUILD_NUMBER} to EKS"
                        git push origin ${BRANCH_NAME}

                        echo "๐Ÿ”น Deploying Using Helm..."
                        helm upgrade --install my-app ./helm-chart --set image.tag=${BUILD_NUMBER}
                    """
                }
            }
        }
    }

    post {
        success {
            script {
                slackSend(channel: SLACK_CHANNEL, message: "โœ… Deployment successful: ${BRANCH_NAME}-${BUILD_NUMBER}")
            }
        }
        failure {
            script {
                slackSend(channel: SLACK_CHANNEL, message: "โŒ Deployment failed: ${BRANCH_NAME}-${BUILD_NUMBER}")
            }
        }
    }
}

๐Ÿ”น Whatโ€™s Improved?

๐Ÿ”น 1. Secure Credentials Management

  • Uses credentials('ECR_CREDENTIALS_ID') instead of hardcoded values.

  • Prevents security leaks (no exposed AWS keys in logs).

๐Ÿ”น 2. Helm Deployment Instead of YAML Edits

  • Instead of manually editing deployment.yaml, it updates values.yaml.

  • Command:

      helm upgrade --install my-app ./helm-chart --set image.tag=${BUILD_NUMBER}
    
  • More scalable & flexible than raw YAML files.

๐Ÿ”น 3. Multi-Stage Docker Build

  • Reduces image size by installing dependencies separately.

๐Ÿ”น 4. Slack Notifications for Visibility

  • Sends messages for both success & failure cases.

  • Helps DevOps teams monitor deployments instantly.


๐Ÿ”น Summary

โœ… Industry best practices applied
โœ… Secure, scalable, and easy to maintain
โœ… Automated GitOps workflow with Helm & ArgoCD
โœ… Real-time deployment status via Slack


๐Ÿš€ Implementing Canary Deployments with Jenkins, ArgoCD & Helm

๐Ÿ”น Canary Deployments help deploy new versions gradually to a subset of users before full rollout.
๐Ÿ”น Weโ€™ll modify our Jenkins pipeline & Kubernetes deployment to enable traffic splitting for canary releases.


๐Ÿ”น How Canary Deployment Works

1๏ธโƒฃ Deploy a new version (canary) alongside the stable version in Kubernetes.
2๏ธโƒฃ Route a small percentage of traffic (e.g., 10%) to the canary version.
3๏ธโƒฃ Monitor logs & performance metrics (if issues occur, rollback).
4๏ธโƒฃ If stable, gradually increase traffic until 100% is on the new version.


๐Ÿ”น Updated Jenkins Pipeline (with Canary Deployment)

pipeline {
    agent any

    environment {
        ECR_REGISTRY = credentials('ECR_CREDENTIALS_ID')
        GIT_REPO = credentials('GIT_REPO_CREDENTIALS_ID')
        IMAGE_TAG = "my-app:${BRANCH_NAME}-${BUILD_NUMBER}"
        SLACK_CHANNEL = "#deployments"
    }

    stages {
        stage('Checkout Code') {
            steps {
                git branch: env.BRANCH_NAME, url: 'https://github.com/user/app-repo.git'
            }
        }

        stage('Run Tests') {
            steps {
                sh 'npm install && npm test'
            }
        }

        stage('Build & Push Docker Image') {
            steps {
                script {
                    def imageTag = "${ECR_REGISTRY}/my-app:${BRANCH_NAME}-${BUILD_NUMBER}"
                    sh """
                        echo "๐Ÿ”น Logging into ECR..."
                        aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${ECR_REGISTRY}

                        echo "๐Ÿ”น Building Docker Image..."
                        docker build -t ${imageTag} .

                        echo "๐Ÿ”น Pushing Image to ECR..."
                        docker push ${imageTag}
                    """
                }
            }
        }

        stage('Deploy Canary Version Using Helm') {
            steps {
                script {
                    sh """
                        echo "๐Ÿ”น Cloning Kubernetes Config Repository..."
                        git clone ${GIT_REPO} k8s-config
                        cd k8s-config

                        echo "๐Ÿ”น Updating Helm Values for Canary..."
                        sed -i 's|imageTag: .*|imageTag: ${BRANCH_NAME}-${BUILD_NUMBER}|' values-canary.yaml

                        echo "๐Ÿ”น Committing Canary Changes to Git..."
                        git config --global user.email "jenkins@example.com"
                        git config --global user.name "jenkins"
                        git add values-canary.yaml
                        git commit -m "Deploy Canary ${BRANCH_NAME}-${BUILD_NUMBER}"
                        git push origin ${BRANCH_NAME}

                        echo "๐Ÿ”น Deploying Canary Version Using Helm..."
                        helm upgrade --install my-app-canary ./helm-chart --set image.tag=${BUILD_NUMBER} --set replicaCount=2 --set trafficWeight=10
                    """
                }
            }
        }

        stage('Monitor Canary Traffic & Scale') {
            steps {
                script {
                    sh """
                        echo "๐Ÿ”น Checking Canary Logs & Metrics..."
                        sleep 60  # Simulating monitoring (replace with real monitoring)

                        echo "๐Ÿ”น Scaling Canary to 50% Traffic..."
                        helm upgrade --install my-app-canary ./helm-chart --set trafficWeight=50

                        sleep 60  # Simulating monitoring again

                        echo "๐Ÿ”น Moving 100% Traffic to New Version..."
                        helm upgrade --install my-app ./helm-chart --set image.tag=${BUILD_NUMBER}
                        helm delete my-app-canary  # Remove the canary after success
                    """
                }
            }
        }
    }

    post {
        success {
            script {
                slackSend(channel: SLACK_CHANNEL, message: "โœ… Canary Deployment successful: ${BRANCH_NAME}-${BUILD_NUMBER}")
            }
        }
        failure {
            script {
                slackSend(channel: SLACK_CHANNEL, message: "โŒ Canary Deployment failed! Rolling back...")
                sh "helm rollback my-app 0"  // Rollback to the previous stable version
            }
        }
    }
}

๐Ÿ”น Kubernetes Changes for Canary Deployment

Modify your Helm chart (values-canary.yaml) to define canary deployment strategy:

replicaCount: 2
image:
  repository: "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app"
  tag: "latest"

trafficWeight: 10  # Canary gets 10% of traffic initially

service:
  name: "my-app"
  port: 80

ingress:
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "{{ .Values.trafficWeight }}"

๐Ÿ”น How This Works

โœ… Deploys a canary version with 10% traffic.
โœ… Waits for logs & performance validation (simulated with sleep 60).
โœ… Gradually increases traffic (50% โ†’ 100%).
โœ… Removes the canary after success.
โœ… Rolls back if failure is detected.


๐Ÿš€ Why This is Best Practice?

โœ” Safer Deployments โ€“ Catch issues before full rollout.
โœ” Automatic Rollbacks โ€“ No manual intervention needed.
โœ” Traffic Gradually Shifted โ€“ Avoids breaking production.
โœ” ArgoCD + Helm Automation โ€“ Fully GitOps compatible.


๐Ÿš€ Adding Monitoring with Prometheus & Grafana to Jenkins + ArgoCD + EKS ๐Ÿš€

๐Ÿ”น Why Monitor Canary Deployments?
โœ”๏ธ Detect issues early before full rollout.
โœ”๏ธ Visualize CPU, memory, request latency, and error rates.
โœ”๏ธ Automatically rollback if thresholds are exceeded.


๐Ÿ”น Step 1: Install Prometheus & Grafana in EKS

We'll deploy Prometheus Operator (for metrics collection) and Grafana (for visualization).

1๏ธโƒฃ Install Prometheus Operator Using Helm

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

helm upgrade --install prometheus prometheus-community/kube-prometheus-stack \
  --namespace monitoring --create-namespace

๐Ÿ”น Step 2: Expose Metrics in Application

Your Node.js app (or other services) must expose Prometheus-compatible metrics.

๐Ÿ“Œ Example: Exposing Metrics in Express.js

Install Prometheus middleware:

npm install prom-client express-prometheus-middleware

Add this to your server file (server.js):

const express = require('express');
const promMid = require('express-prometheus-middleware');
const app = express();

app.use(promMid({
  metricsPath: '/metrics',
  collectDefaultMetrics: true,
}));

app.get('/', (req, res) => res.send('Hello World!'));

app.listen(3000, () => console.log('Server running on port 3000'));

๐Ÿ‘‰ Now, the app exposes /metrics, and Prometheus can scrape it.


๐Ÿ”น Step 3: Configure Prometheus to Scrape Application

Modify Prometheus scrape config in values.yaml:

scrape_configs:
  - job_name: 'my-app'
    static_configs:
      - targets: ['my-app.default.svc.cluster.local:3000']

Apply changes:

helm upgrade --install prometheus prometheus-community/kube-prometheus-stack \
  --set serverFiles.prometheus.yml.scrape_configs[0].job_name=my-app \
  --set serverFiles.prometheus.yml.scrape_configs[0].static_configs[0].targets[0]=my-app.default.svc.cluster.local:3000 \
  --namespace monitoring

๐Ÿ”น Step 4: Install Grafana & Create Dashboards

Grafana is included in kube-prometheus-stack, so we just need to log in and create dashboards.

๐Ÿ”น Get Grafana Credentials

kubectl get secret -n monitoring prometheus-grafana -o jsonpath="{.data.admin-password}" | base64 --decode

๐Ÿ”น Access Grafana

kubectl port-forward svc/prometheus-grafana 3000:80 -n monitoring

๐Ÿ”น Login at http://localhost:3000 using:

  • Username: admin

  • Password: (from command above)

๐Ÿ”น Add Prometheus as a Data Source

  1. Go to Grafana โ†’ Configuration โ†’ Data Sources.

  2. Add Prometheus with URL: http://prometheus-server.monitoring.svc.cluster.local:80.

๐Ÿ”น Import a Prebuilt Dashboard

  1. Go to Create โ†’ Import

  2. Use Dashboard ID: 315 (Kubernetes Cluster Monitoring).

  3. Click Import and View Metrics! ๐ŸŽ‰


๐Ÿ”น Step 5: Automate Rollbacks Based on Metrics

Modify Jenkins pipeline to rollback if error rate > 5%.

๐Ÿ“Œ Update Jenkins Pipeline

Add a monitoring stage:

stage('Monitor Canary Traffic & Rollback if Needed') {
    steps {
        script {
            def errorRate = sh(script: '''
                curl -s "http://prometheus-server.monitoring.svc.cluster.local:9090/api/v1/query?query=rate(http_requests_total{status=~'5.*'}[5m])"
            ''', returnStdout: true).trim()

            echo "Current Error Rate: ${errorRate}"

            if (errorRate.toDouble() > 5.0) {
                echo "โš  High error rate detected! Rolling back deployment..."
                sh "helm rollback my-app 0"
                slackSend(channel: SLACK_CHANNEL, message: "โŒ Rollback triggered! Canary Deployment failed.")
                error("Deployment Failed: Rolling Back.")
            }
        }
    }
}

๐Ÿ”น Summary: What We Achieved

โœ… Prometheus installed & scraping app metrics.
โœ… Grafana configured to visualize errors, latency, CPU usage, etc.
โœ… Jenkins auto-rollbacks canary deployment if error rate > 5%.


๐Ÿš€ Enhancing Monitoring & Scaling: Slack Alerts, Auto-scaling, and Logging with Loki

๐Ÿ”น What Weโ€™ll Do:
โœ”๏ธ Send Slack Alerts for high CPU/memory usage or errors.
โœ”๏ธ Auto-scale Pods based on Prometheus metrics.
โœ”๏ธ Integrate Loki for centralized logging & debugging.


1๏ธโƒฃ Slack Alerts for High CPU & Errors ๐Ÿšจ

๐Ÿ”น Step 1: Create a Slack Webhook

  1. Go to Slack API Apps โ†’ Create New App.

  2. Enable Incoming Webhooks and create a webhook URL.

  3. Copy the URL (e.g., https://hooks.slack.com/services/T0000/B0000/XXXX).

๐Ÿ”น Step 2: Configure Alertmanager in Prometheus

Modify Alertmanager Config (values.yaml in Prometheus Helm chart):

alertmanager:
  config:
    receivers:
      - name: 'slack'
        slack_configs:
          - channel: '#alerts'
            send_resolved: true
            username: 'AlertManager'
            icon_emoji: ':alert:'
            webhook_url: 'https://hooks.slack.com/services/T0000/B0000/XXXX'

    route:
      receiver: 'slack'
      group_by: ['alertname']
      group_wait: 10s
      repeat_interval: 1h

Apply the changes:

helm upgrade --install prometheus prometheus-community/kube-prometheus-stack -n monitoring

2๏ธโƒฃ Auto-Scale Pods Based on Prometheus Metrics ๐Ÿš€

๐Ÿ”น Step 1: Install the Kubernetes Metrics Server

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

๐Ÿ”น Step 2: Deploy the Horizontal Pod Autoscaler (HPA)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70  # Scale if CPU > 70%
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80  # Scale if Memory > 80%

Apply the autoscaler:

kubectl apply -f hpa.yaml

3๏ธโƒฃ Logging with Loki for Debugging ๐Ÿ•ต๏ธ

๐Ÿ”น Step 1: Install Loki and Promtail

helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

helm upgrade --install loki grafana/loki-stack \
  --set grafana.enabled=true \
  --set promtail.enabled=true \
  --namespace logging --create-namespace

๐Ÿ”น Step 2: Configure Promtail to Collect Logs

Modify Promtail Config (values.yaml):

promtail:
  config:
    scrape_configs:
      - job_name: kubernetes-pods
        kubernetes_sd_configs:
          - role: pod
        relabel_configs:
          - source_labels: [__meta_kubernetes_pod_label_app]
            action: keep
            regex: my-app

Apply changes:

helm upgrade --install loki grafana/loki-stack -n logging

๐Ÿ”น Step 3: View Logs in Grafana

  1. Open Grafana:

     kubectl port-forward svc/prometheus-grafana 3000:80 -n monitoring
    
  2. Add Loki as Data Source in Grafana.

  3. Use query:

     {app="my-app"} |= "error"
    

    ๐Ÿ“Œ Now, you can filter logs directly from Grafana! ๐ŸŽ‰


๐Ÿš€ Enhancing Monitoring & Scaling: Slack Alerts, Auto-scaling, and Logging with Loki

๐Ÿ”น What Weโ€™ll Do:
โœ”๏ธ Send Slack Alerts for high CPU/memory usage or errors.
โœ”๏ธ Auto-scale Pods based on Prometheus metrics.
โœ”๏ธ Integrate Loki for centralized logging & debugging.


1๏ธโƒฃ Slack Alerts for High CPU & Errors ๐Ÿšจ

๐Ÿ”น Step 1: Create a Slack Webhook

  1. Go to Slack API Apps โ†’ Create New App.

  2. Enable Incoming Webhooks and create a webhook URL.

  3. Copy the URL (e.g., https://hooks.slack.com/services/T0000/B0000/XXXX).

๐Ÿ”น Step 2: Configure Alertmanager in Prometheus

Modify Alertmanager Config (values.yaml in Prometheus Helm chart):

alertmanager:
  config:
    receivers:
      - name: 'slack'
        slack_configs:
          - channel: '#alerts'
            send_resolved: true
            username: 'AlertManager'
            icon_emoji: ':alert:'
            webhook_url: 'https://hooks.slack.com/services/T0000/B0000/XXXX'

    route:
      receiver: 'slack'
      group_by: ['alertname']
      group_wait: 10s
      repeat_interval: 1h

Apply the changes:

helm upgrade --install prometheus prometheus-community/kube-prometheus-stack -n monitoring

2๏ธโƒฃ Auto-Scale Pods Based on Prometheus Metrics ๐Ÿš€

๐Ÿ”น Step 1: Install the Kubernetes Metrics Server

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

๐Ÿ”น Step 2: Deploy the Horizontal Pod Autoscaler (HPA)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70  # Scale if CPU > 70%
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80  # Scale if Memory > 80%

Apply the autoscaler:

kubectl apply -f hpa.yaml

3๏ธโƒฃ Logging with Loki for Debugging ๐Ÿ•ต๏ธ

๐Ÿ”น Step 1: Install Loki and Promtail

helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

helm upgrade --install loki grafana/loki-stack \
  --set grafana.enabled=true \
  --set promtail.enabled=true \
  --namespace logging --create-namespace

๐Ÿ”น Step 2: Configure Promtail to Collect Logs

Modify Promtail Config (values.yaml):

promtail:
  config:
    scrape_configs:
      - job_name: kubernetes-pods
        kubernetes_sd_configs:
          - role: pod
        relabel_configs:
          - source_labels: [__meta_kubernetes_pod_label_app]
            action: keep
            regex: my-app

Apply changes:

helm upgrade --install loki grafana/loki-stack -n logging

๐Ÿ”น Step 3: View Logs in Grafana

  1. Open Grafana:

     kubectl port-forward svc/prometheus-grafana 3000:80 -n monitoring
    
  2. Add Loki as Data Source in Grafana.

  3. Use query:

     {app="my-app"} |= "error"
    

    ๐Ÿ“Œ Now, you can filter logs directly from Grafana! ๐ŸŽ‰


Conclusion

The CI/CD workflow is a vital component of modern software development, enabling teams to deliver high-quality applications efficiently. By understanding the various stages involved in promoting code from development to production and adhering to best practices, organizations can enhance their deployment processes and improve overall software quality. If you have any questions or feedback, feel free to reach out in the comments section. Thank you for reading!

ย