CI/CD Pipeline for Golang Application

DevOps

Β·

12 min read

CI/CD Pipeline for Golang Application

The main goal of this project is to implement DevOps practices in a Go web application. The project is a simple website written in Golang, using the net/http package to serve HTTP requests. πŸš€πŸ’»

What We Will Implement:

  1. Containerizing the Project:

    • Multi-stage Dockerfile
  2. Kubernetes:

    • Deployments, services, and ingress
  3. Continuous Integration (CI):

    • Using GitHub Actions
  4. Continuous Deployment (CD):

    • Using GitOps with ArgoCD
  5. Kubernetes Cluster Setup

  6. Helm Chart for the Application

  7. Ingress Controller

steps:

  1. Test the project locally.

    (you can clone the repo from GitHub using git clone)

    Build project: go build -o main .

    To run binary: ./main

    Now that the application is running, let's see it in the browser by navigating to localhost:8080.

    It throws a 404 Page Not Found error because the development team has indicated that the application is accessible at, not at http://localhost:8080. Please check the GitHub repository for more details.

    Here, the application is working locally.

    1. Creating Dockerfile (Multi-stage build)

      The Dockerfile is used to build a Docker image. The Docker image contains the Go web application and its dependencies. The Docker image is then used to create a Docker container. 🐳

      We will use a multi-stage build to create the Docker image. Multi-stage builds are a feature of Docker that allows you to use multiple build stages in a single Dockerfile. This reduces the size of the final Docker image and secures the image by removing unnecessary files and packages. πŸš€πŸ”’

      Our Dockerfile is ready. Let's test it by building the image. (Note: you must have docker on your local machine)

      docker build -t rohitd4/go-web-app:v1 .

      *rohitd4 is my docker username.

      The error message indicates that the version of Go installed on the system is lower than the version required by the go.mod file. Specifically, the go.mod file requires Go version 1.22.5, but the system is currently running Go version 1.21.12.

      we will update the version in the dockerfile.

      No try to build the image again docker build -t rohitd4/go-web-app:v1 .

      The image has been built. Now, run the container using the image.

  2. Containerization

    Containerization is the process of packaging an application and its dependencies into a container. The container is then run on a container platform such as Docker. Containerization allows you to run the application in a consistent environment, regardless of the underlying infrastructure.

    We will use Docker to containerize the Go web application. Docker is a container platform that allows you to build, ship, and run containers.

    Command to run the Docker container:

     docker run -it -p 8080:8080 rohitd4/go-web-app:v1
    

    You can use your Docker username and whatever image name you have used.

    Before running the container, check the go-web-app in the browser. You will see "This site can’t be reached."

    Now use the docker run command and run the container.

    Now the container is running, and you can check the go-web-app in the browser. It should be in a working state.

    to stop container use Ctrl c

    Push the Docker image to Docker Hub

    docker push rohitd4/go-web-app:v1

    We have completed containerization by writing a Dockerfile that uses a multi-stage Docker build. Next, we will write Kubernetes manifests.

  3. K8s Manifests

    Create a folder k8s and inside it create manifests folder.

    Inside the manifest folder, we will start by writing deployment.yaml. Create a file named deployment.yaml.

    Create file service.yaml

    Create file: ingress.yaml

    Now that we have the deployment, service, and ingress resources, it's time to validate the Kubernetes manifests that we have written.
    For that we need to have k8s cluster and we are going to use AKS.

  4. AKS Cluster

    Command: az login

    Create a resource group: az group create --name gowebrg --location eastus

    This command creates a resource group named gowebrg in the eastus region.

    Create an AKS cluster:

     az aks create --resource-group gowebrg --name gowebcluster --node-count 1 --enable-addons monitoring
    

    This command creates an AKS cluster named gowebcluster in the gowebrg resource group with one node and enables monitoring. The --generate-ssh-keys option generates SSH keys if you don't already have them.

    Get Kubernetes Credentials:
    To interact with your AKS cluster using kubectl, you need to get the Kubernetes credentials.

     az aks get-credentials --resource-group gowebrg --name gowebcluster
    

    Verify Access to the Cluster:
    Use kubectl to verify access to your cluster and list the nodes.

     kubectl get nodes
    

    The cluster is ready for deployment.

  5. Applying YAML Files to AKS

    We already have our YAML files ready, we can apply them to

    our AKS cluster using the kubectl apply command.

  6.     kubectl apply -f deployment.yaml
    

     kubectl apply -f service.yaml
    
     kubectl apply -f ingress.yaml
    

    As we don't have an ingress controller you can see there is no ADDRESS assigned for the ingress resource.

  7. Install the Nginx Ingress Controller on Azure

    and deploy the below manifest.

     kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.1/deploy/static/provider/cloud/deploy.yaml
    

    Run this command and it will create an ingress controller resource.

    use the command to see the ingress controller pod: kubectl get pods -n ingress-nginx

    Let's see if the ingress controller is able to watch our ingress resource.

    command: kubectl get ingress

    We got an IP address, so we can say that the ingress resource is being watched by the ingress controller, which provided us with the IP address.

    Now map the IP address with go-web-app.local

    Command :sudo vim /etc/hosts

    Now using the host go-web-app.local try to access the application.

  8. Helm Configuration

    Install and check the Helm version.

    create a helm folder.

    inside the helm folder run this command: helm create go-web-app-chart

    from go-web-app-chart folder remove charts folder and

    to remove use rm -rf charts command.

    Now go to the templates folder and remove everything.

    Inside the templates folder copy and paste the manifests.

    We have deployment.yml, ingress.yml, and service.yml in the templates folder.

  9. The advantage of Helm is that we can variablize our YAML files.

    In deployment.yaml change the image tag with this {{ .Values.image.tag }}

    {{ .Values.image.tag }} is accessing a value from the Helm chart's values.yaml file, where image.tag represents the tag of a container image.

  10. Go to values.yaml and remove everything from the file.

    remove all

    You can update the values.yaml something as below.

    # Default values for go-web-app-chart.
    # This is a YAML-formatted file.
    # Declare variables to be passed into your templates.
    replicaCount: 1
    
    image:
      repository: abhishekf5/go-web-app
      pullPolicy: IfNotPresent
      # Overrides the image tag whose default is the chart appVersion.
      tag: "v1"
    
    ingress:
      enabled: false
      className: ""
      annotations: {}
        # kubernetes.io/ingress.class: nginx
        # kubernetes.io/tls-acme: "true"
      hosts:
        - host: chart-example.local
          paths:
            - path: /
              pathType: ImplementationSpecific
    
  11. Verify if Helm is working as expected or not.

    Delete everything from the namespace.

    kubectl get all
    kubectl delete deploy go-web-app

    kubectl delete svc go-web-app

    kubectl delete ing go-web-app

    use kubectl get all and you will find nothing related to our go-web-app.

  12. Go to the helm folder and run the command helm install go-web-app ./go-web-app-chart

    Verify all the resources are deployed or not kubectl get all

    We are done with Helm. Just uninstall everything.

    helm uninstall go-web-app

  13. Continuous Integration (CI)

    Continuous Integration (CI) is the practice of automating the integration of code changes into a shared repository. CI helps to catch bugs early in the development process and ensures that the code is always in a deployable state.

    We will use GitHub Actions to implement CI for the Go web application. GitHub Actions is a feature of GitHub that allows you to automate workflows, such as building, testing, and deploying code.

    The GitHub Actions workflow will run the following steps:

    • Checkout the code from the repository

    • Build the Docker image

    • Run the Docker container

    • Run tests

Create a folder and name it .github. Inside this folder, create another folder named workflows .

Inside the workflows folder create a file name as cicd.yaml

or you can give whatever name you want and paste the below code into your cicd.yaml .

    # CICD using GitHub actions

    name: CI/CD

    # Exclude the workflow to run on changes to the helm chart
    on:
      push:
        branches:
          - main
        paths-ignore:
          - 'helm/**'
          - 'k8s/**'
          - 'README.md'

    jobs:

      build:
        runs-on: ubuntu-latest

        steps:
        - name: Checkout repository
          uses: actions/checkout@v4

        - name: Set up Go 1.22
          uses: actions/setup-go@v2
          with:
            go-version: 1.22

        - name: Build
          run: go build -o go-web-app

        - name: Test
          run: go test ./...

      code-quality:
        runs-on: ubuntu-latest

        steps:
        - name: Checkout repository
          uses: actions/checkout@v4

        - name: Run golangci-lint
          uses: golangci/golangci-lint-action@v6
          with:
            version: v1.56.2

      push:
        runs-on: ubuntu-latest

        needs: build

        steps:
        - name: Checkout repository
          uses: actions/checkout@v4

        - name: Set up Docker Buildx
          uses: docker/setup-buildx-action@v1

        - name: Login to DockerHub
          uses: docker/login-action@v3
          with:
            username: ${{ secrets.DOCKERHUB_USERNAME }}
            password: ${{ secrets.DOCKERHUB_TOKEN }}

        - name: Build and Push action
          uses: docker/build-push-action@v6
          with:
            context: .
            file: ./Dockerfile
            push: true
            tags: ${{ secrets.DOCKERHUB_USERNAME }}/go-web-app:${{github.run_id}}

      update-newtag-in-helm-chart:
        runs-on: ubuntu-latest

        needs: push

        steps:
        - name: Checkout repository
          uses: actions/checkout@v4
          with:
            token: ${{ secrets.TOKEN }}

        - name: Update tag in Helm chart
          run: |
            sed -i 's/tag: .*/tag: "${{github.run_id}}"/' helm/go-web-app-chart/values.yaml

        - name: Commit and push changes
          run: |
            git config --global user.email "rjd3955@gmail.com"
            git config --global user.name "Rohit Deore"
            git add helm/go-web-app-chart/values.yaml
            git commit -m "Update tag in Helm chart"
            git push

Now we have to store our secrets in "GitHub Actions secrets and variables"

Go to settings in the GitHub repo, under the security section select the secrets and variables -> select Actions.

Click on the repository secrets.

provide secrets and click on add secret.

do the same steps for the docker hub token. (follow below screenshots)

Sign in to the docker hub and then go to account settings.

Go to Personal Access tokens and generate new token.

Click on Generate.

Copy the token

and paste it into the secret section.

Again create a new secret for the GitHub token within the same repository.

Generate the GitHub personal access token.

Let us check whether CI is working or not.

you will see a new commit has to be pushed.

Go to actions and see implemented CI.

Now let's make some changes, push the code to GitHub again and it will trigger the CI pipeline.

Remove this - 'k8s/**'

    # CICD using GitHub actions

    name: CI/CD

    # Exclude the workflow to run on changes to the helm chart
    on:
      push:
        branches:
          - main
        paths-ignore:
          - 'helm/**'
          - 'README.md'

    jobs:

      build:
        runs-on: ubuntu-latest

        steps:
        - name: Checkout repository
          uses: actions/checkout@v4

        - name: Set up Go 1.22
          uses: actions/setup-go@v2
          with:
            go-version: 1.22

        - name: Build
          run: go build -o go-web-app

        - name: Test
          run: go test ./...

      code-quality:
        runs-on: ubuntu-latest

        steps:
        - name: Checkout repository
          uses: actions/checkout@v4

        - name: Run golangci-lint
          uses: golangci/golangci-lint-action@v6
          with:
            version: v1.56.2

      push:
        runs-on: ubuntu-latest

        needs: build

        steps:
        - name: Checkout repository
          uses: actions/checkout@v4

        - name: Set up Docker Buildx
          uses: docker/setup-buildx-action@v1

        - name: Login to DockerHub
          uses: docker/login-action@v3
          with:
            username: ${{ secrets.DOCKERHUB_USERNAME }}
            password: ${{ secrets.DOCKERHUB_TOKEN }}

        - name: Build and Push action
          uses: docker/build-push-action@v6
          with:
            context: .
            file: ./Dockerfile
            push: true
            tags: ${{ secrets.DOCKERHUB_USERNAME }}/go-web-app:${{github.run_id}}

      update-newtag-in-helm-chart:
        runs-on: ubuntu-latest

        needs: push

        steps:
        - name: Checkout repository
          uses: actions/checkout@v4
          with:
            token: ${{ secrets.TOKEN }}

        - name: Update tag in Helm chart
          run: |
            sed -i 's/tag: .*/tag: "${{github.run_id}}"/' helm/go-web-app-chart/values.yaml

        - name: Commit and push changes
          run: |
            git config --global user.email "rjd3955@gmail.com"
            git config --global user.name "Rohit Deore"
            git add helm/go-web-app-chart/values.yaml
            git commit -m "Update tag in Helm chart"
            git push

Save and commit the changes.

CI is successful, now go to values.yaml and check for the image tag.

The CI part is completed.

  1. Continuous Deployment (CD)

    Continuous Deployment (CD) is the practice of automatically deploying code changes to a production environment. CD helps to reduce the time between code changes and deployment, allowing you to deliver new features and fixes to users faster.

    We will use Argo CD to implement CD for the Go web application. Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. It allows you to deploy applications to Kubernetes clusters using Git as the source of truth.

    The Argo CD application will deploy the Go web application to a Kubernetes cluster. The application will be automatically synced with the Git repository, ensuring that the application is always up to date.

    1. Install Argo CD

      Install argocd using manifests:

       kubectl create namespace argocd
       kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
      

      Access the Argo CD UI (Loadbalancer service):

       kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
      

      Get the Loadbalancer service IP:

       kubectl get svc argocd-server -n argocd
      

      To access argocd UI use the external IP and and port 80.
      (In my case : 57.152.10.206:80)

      You need to provide a username and password, the username is admin and for the password run the command kubectl get secrets -n argocd

      use command : kubectl edit secret argocd-initial-admin-secret -n argocd
      The password will be base64 encoded just copy and decode it.

      base64 encoded: UFdROVBtYXdOVWUxRGZiUA==

      to decode the password use the command: echo UFdROVBtYXdOVWUxRGZiUA== | base64 --decode

      decoded: PWQ9PmawNUe1DfbP

      Paste the decoded password in argocd login.

      Click on the new app.

      Now click on Create.

      Now argocd will look for all the files in your repository, within the helm chart it will update the values.yaml.

      click here and you will see argocd start deploying everything; Pod, svc, and ingress are created.

      Check for the deployment, svc and ingress on the cluster.

      kubectl get all

      Now try to access the application using go-web-app.local/courses

      Here we have implemented our CICD and deployed the application successfully.

  2. Verifying End-To-End CICD

    Let's make a simple change in static content.

    In home.html we will add a background color.

    CICD will be triggered.

    The image is also pushed in the docker hub a minute ago.

    Now argocd will pickup this new change.

    ArgoCD has updated the cluster with new changes and lets go and check the application.

    You can see there is a new background color.

    Congratulations, we've successfully DevOpsified the project! πŸŽ‰πŸ₯³

    Here's what we've achieved:

    • Containerization completed

    • Kubernetes (K8s) manifests created

    • CI/CD pipelines implemented

    • Kubernetes cluster (AKS) set up

    • Helm charts developed

    • Ingress controller configured

    • Argo CD configured

All these in one comprehensive project!

Github repo: https://github.com/DeoreRohit4/Go-Web-App-CICD
If you have any issues while performing the project practically please reach out to me on Linkedin - rohit deore


Keep Exploring...

Β