Devopsified Go Web App - Complete Devops Project

Amit singh deoraAmit singh deora
14 min read

We will use a GitHub repository called GoWebApp containing Source code, Unit test cases, Static content and see steps to run the application.

Let’s summarize the project : Complete DevOps Project

  • Containerizing the Project

    • Writing a multi-stage Dockerfile for reduced size and security.

    • Creating Kubernetes Manifests

      • Defining Deployment, Service, and Ingress configurations.
    • Setting Up Continuous Integration (CI)

      • Using GitHub Actions to implement CI with multiple stages.
    • Implementing Continuous Delivery (CD) with GitOps

      • Using ArgoCD for automated deployments.
    • Setting Up a Kubernetes Cluster

      • Creating and configuring an EKS cluster as the target platform.
    • Creating a Helm Chart

      • Managing deployments across Dev, QA, and Production environments. instead of writing manifest file (.yml) for each environment will use Helm Chart.
    • Setting Up an Ingress Controller

      • Ingress controller will create load balancer depend upon ingress configuration, so that application will expose to outside world.

      • Will also see how to map the LB IP Address to the local DNS, so we can test if the app is accessed from outside world.

    • Mapping Load Balancer IP to Local DNS

      • Testing application access from the outside world.

Project Repository

  • The GoWebApp DevOps repository contains:

    • EKS cluster setup steps

    • ArgoCD configuration

    • Helm chart implementation

    • Ingress controller configuration

    • Kubernetes manifests

    • Docker file

    • Detailed README for DevOps implementation

Getting Started with Containerization

STEP 1 : Before containerizing, Run the application locally:

  1. $ git clone https://github.com/iam-veeramalla/go-web-app

  2. Create go application binary: $ go build -o main .

  3. To execute binary: $./main

  4. Run in browser: http://localhost:8080/courses

STEP 2 : Create a Docker file (Multistage)

Stage 1: Building the Application

  1. Use the Golang base image:

     FROM golang:1.21 AS base
    
  2. Set up the working directory:

     WORKDIR /app
    
  3. Copy dependencies and install them:

     COPY go.mod .
     RUN go mod download
    
  4. Copy source code and build:

     COPY . .
     RUN go build -o main .
    

Stage 2: Creating a Secure and Lightweight Image

  • Use a distroless base image:

      FROM gcr.io/distroless/base AS final
    
  • Copy the binary and static files from the first stage:

      COPY --from=base /app/main .
      COPY --from=base /app/static ./static
    
  • Expose port and set entrypoint:

      EXPOSE 8080
      CMD ["/main"]
    

    Combined the above Dockerfile below

    ```plaintext #Stage 1 ---- as base FROM golang:1.22 as base # Gave alias to our golang base image, to use in final stage or 2nd stage WORKDIR /app # Create a working directory COPY go.mod . # Copy dependencies and RUN go mod download # installing dependencies COPY . . #Copy source code into workdir /app docker image RUN go build -o main . #Building

#Final stage FROM gcr.io/distroless/base COPY --from=base /app/main . COPY --from=base /app/static ./static EXPOSE 8080 CMD ["./main"]


    **Build the image with Dockerfile:** `$docker build -t amitsinghs98/go-web-app:v1 .`

* **Run Image we built:** `$docker run -p 8080:8080 -it amitsinghs98/go-web-app:v1`

* **Go to Browser:** `http://localhost:8080/courses` (Successfully running)


**Note:** Always check the go language version used by your developer, check in go.mod. Once you check make sure you have used the same version in your Dockerfile.

**Done with Containerization with docker**

---

### STEP 3 : Push your image on docker hub “registry”

Push the image to the Docker repository using:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736835868739/1fba295b-2819-48e2-aa75-908d7a7d7e25.png align="center")

```plaintext
$docker login # To login your dockerhub 
docker push <Docker_Username>/go-web-app:v1
#docker push amitsinghs98/go-web-app:v1

Make sure you add the version (tag) of your Docker file while pushing and fetching images from Docker hub Registry.


STEP 4: Writing Kubernetes YAML Manifest

Creating Folders

  • Create a folder k8s.

  • Inside k8s, create another folder manifest.

  • Create deployment.yml file inside manifest folder

# This is a sample deployment manifest file for a simple web application.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-web-app
  labels:
    app: go-web-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: go-web-app
  template:
    metadata:
      labels:
        app: go-web-app
    spec:
      containers:
      - name: go-web-app
        image: <docker-user-name>/go-web-app
        ports:
        - containerPort: 8080
  • Create service.yml file
# Service for the application
apiVersion: v1
kind: Service
metadata:
  name: go-web-app
  labels:
    app: go-web-app
spec:
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
  selector:
    app: go-web-app
  type: ClusterIP
  • Create ingress .yml file

  • We ill use HOST BASED Ingress

# Ingress resource for the application
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: go-web-app
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: go-web-app.local
    http:
      paths: 
      - path: /
        pathType: Prefix
        backend:
          service:
            name: go-web-app
            port:
              number: 80

STEP 5: Deploying to Kubernetes

5.1 Setting Up Kubernetes Cluster

  • Use Amazon EKS (Elastic Kubernetes Service) as an enterprise Kubernetes cluster.

  • Ensure that the following are installed:

    • kubectl: A command line tool for working with Kubernetes clusters. For more information, see Installing or updating kubectl.

    • eksctl: A command line tool for working with EKS clusters that automates many individual tasks. For more information, see Installing or updating.

    • AWS CLI:

5.2 Authenticating AWS CLI

  • Run aws configure and enter the following:

    • AWS Access Key ID

    • AWS Secret Access Key

    • Default Region (e.g., us-east-1)

    • Output Format (default: json)

5.3 Creating EKS Cluster

  • Run the following command:

      eksctl create cluster --name go-web-app-cluster --region us-east-1
    

  • This process takes about 30 minutes.

  • Alternatively, use Terraform to deploy EKS.

5.4 Applying Kubernetes Manifests

  • Deploy the resources using:

      kubectl apply -f k8s/manifest/deployment.yaml
      kubectl apply -f k8s/manifest/service.yaml
      kubectl apply -f k8s/manifest/ingress.yaml
    

    These commands will deploy the yaml file inside your EKS


What is an Ingress Controller?

An Ingress Controller is a component in Kubernetes that manages external access to services inside the cluster, typically via HTTP/HTTPS. It acts as a reverse proxy, routing requests based on defined rules.

Why Do You Need an Ingress Controller?

  • Ingress resource alone is not enough – Just creating an Ingress resource (ingress.yaml) does not automatically expose your application.

  • Ingress needs a controller – The controller processes the Ingress rules and assigns an external IP or DNS.

  • Without an Ingress Controller – The kubectl get ing command will show <none> under the ADDRESS column.

We need an ingress controller it will assign the address for ingress resource. Once address is assigned we will take ip address and will map it will the domain name that we have created which is mentioned under HOSTS: go-web-app.local

Verify Service in Node Port Mode

  • will take the ip address an will map it with the domain name that we have created in my itc host we will do that but before doing that let's verify at least if the service is working fine"

  • "what we can do is we can expose the service in the node port mode"

  • "okay before we move forward with the ingress controller configuration we can do this and we can verify if the service is working fine"

Edit Service to Node Port

  • so let's go to the easy to instance first because if we are running the service in the node port mode so first let's edit the service kubectl edit svc the name of the service is go web app

  • find type cluster ip so we will change it to node port

kubectl edit svc go-web-app
  • verifying if my service is working fine or not even without the ingress configuration

Expose Service on Node Port

  • exposed the service on the node port mode if do kubectl get svc you will see that now service is running on node port and it can be accessed on the port 32009 on my node ip address"

Get Node IP

  • "what are my node ip addresses i can simply do

  •          kubectl get nodes -o wide
    
  • so these are two nodes and these are the external ip addresses pick up any of the node it doesn't matter if you expose the service in the node port mode you can pick up any node ip address

  • just copy the ip address followed by the node port so this is the node port

  • Copy external IP and :31620 PORT

Test the Service

http://54.90.252.184:31620/courses

Successfully running on node port now we are ready to setup our ingress

NGINX Ingress Controller Setup

  • complete the ingress controller configuration

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

Ingress Controller Role

  • The ingress controller watches the ingress resource and creates a load balancer.

  • You can't manually create a load balancer in Kubernetes, so the ingress controller automates this process.

  • Ingress controllers are usually written by load balancer companies (e.g., the community-driven one we're using, or AWS and Traefik also offer their own).

  • It’s a program (usually written in Go) that helps manage the load balancing for incoming traffic based on ingress rules.

Functionality of Ingress Controller

  • The ingress controller watches the ingress resource and creates a load balancer based on the configuration.

  • In our case, the configuration tells the controller to create a load balancer.

  • When you access the load balancer using go-web-app.local, it forwards the request to the service and then to the pods. We

Verify Ingress Controller

  • To check if the ingress controller is running, use the command:

      kubectl get pods -n ingress-nginx
    
  • Yes ingress is installed in a namespace only

  • The ingress controller pod should appear in the output.

Edit Ingress Controller Pod

  • You can edit or describe the pod to view its details:

  •         kubectl edit pod <ingress-engine-x-pod-name>
    
  • Inside the ingress controller, you will see the ingress class name as nginx. This is important because the ingress controller listens to resources with this class name (nginx), as defined in the ingress file.

  • To check our ingress resource is watched by ingress controller

  •         kubectl get ing
    
  • It gave us domain name now instead of IP Address, ingress did it’s job. Also call as fqdn “fully qualified domain name” - With help of ingress we have achieved.

Access Load Balancer

  • The ingress controller has created a network load balancer on AWS. LB did it’s job and gave us the IP Address (domain name)

  • After finding the DNS name of the load balancer, you can attempt to access it directly via the browser.

    • However, you might get a 404 error because the load balancer only accepts requests to go-web-app.local, as specified in your ingress file.

Local DNS Mapping

  • write command: nslookup → this will provide IP Address of our ingress

  •       nslookup <address>
          nslookup a01d25ef2bnlkdkfmndfn
    
  • Go to sudo vim/etc/hosts

  • We are doing dns mapping here

  • To access it locally, you need to map the load balancer's DNS name to go-web-app.local in your hosts file:

sudo vim /etc/hosts
  • Map the load balancer IP to go-web-app.local.

  • Once the changes are reflected, try accessing the app via go-web-app.local/home

  • Note in realtime we don’t use go-web-app.local, instead we use the address like amazon.com or flipkart.com. We also don’t do dns mapping locally. Different scenario in real world.

Step by Step for Helm chart config

  1. Creating Helm Chart for Application Deployment:

    • Run the command:

        helm create go-web-app-chart
      
    • After running this command, Helm will generate a basic folder structure for your chart. You will see a folder named go-web-app-chart with several files:

      • Chart.yaml (metadata)

      • values.yaml (variable configuration)

      • templates/ (Kubernetes YAML templates)

    • Delete unnecessary files inside the chart (like charts/ directory).

  2. Modify Helm Chart for Your Application:

    • Open the templates/ folder inside your chart and remove everything. Now, add your existing Kubernetes manifests, such as:

      • deployment.yaml

      • service.yaml

      • ingress.yaml

  3. Variableizing Kubernetes Manifests:

    • In your deployment.yaml, replace the hardcoded image tag with the Helm templating syntax:

        # This is a sample deployment manifest file for a simple web application.
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: go-web-app
          labels:
            app: go-web-app
        spec:
          replicas: 1
          selector:
            matchLabels:
              app: go-web-app
          template:
            metadata:
              labels:
                app: go-web-app
            spec:
              containers:
              - name: go-web-app
                image: amitsinghs98/go-web-app:{{ .Values.image.tag }}
                ports:
                - containerPort: 8080
                                                     ~
      
    • This tells Helm to fetch the image.tag from the values.yaml file.

  4. Updating values.yaml:

    • Modify the values.yaml to include the image.tag field:

        # 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: amitsinghs98/go-web-app
          pullPolicy: IfNotPresent
          # Overrides the image tag whose default is the chart appVersion.
          tag: "10016307834"
      
        ingress:
          enabled: false
          className: ""
          annotations: {}
            # kubernetes.io/ingress.class: nginx
            # kubernetes.io/tls-acme: "true"
          hosts:
            - host: chart-example.local
              paths:
                - path: /
                  pathType: ImplementationSpecific
        ~
      
    • This field will hold the tag (version) of your Docker image.

  5. Installing Helm Chart:

    • Once the files are in place, and Helm is properly configured, you can install the application using Helm:

        helm install go-web-app ./go-web-app-chart
      
    • This will deploy the application to Kubernetes using the Helm chart.

  6. Verify Deployment with Helm:

    • Use kubectl commands to verify that everything was installed successfully:

        kubectl get all
        kubectl get deployment go-web-app
        kubectl get svc go-web-app
        kubectl get ing go-web-app
      
    • You should see the deployment, service, and ingress resources set up.

  7. Uninstalling Helm Chart:

    • You can uninstall the Helm release at any time with:

        helm uninstall go-web-app
      

Moving to CI/CD:

Now, moving on to Continuous Integration (CI) and Continuous Delivery (CD):

  1. CI with GitHub Actions:

    • Build and Unit Test: In the first stage, you will run tests for the application whenever a new commit happens.

    • Static Code Analysis: Run code quality tools to ensure the code is clean and follows best practices.

    • Docker Image Creation: Create a Docker image of the application with the tag corresponding to the commit.

    • Update Helm with Docker Image: Update the values.yaml file dynamically with the new Docker image tag.

  2. CD with Argo CD:

    • Watching Helm Chart: Argo CD will continuously monitor the Helm chart for any changes, especially changes to the values.yaml file.

    • Deployment: Whenever the image tag is updated in values.yaml, Argo CD will automatically deploy the new version to the Kubernetes cluster.


Detailed Steps for Implementing CI/CD Workflow:

  1. GitHub Actions Workflow (CI):

    • Create a .github/workflows/cicd.yml file in your repository.

    • Inside this file, define the steps:

      • Build and Unit Test: Run docker build and docker test.

      • Static Code Analysis: Run tools like eslint, flake8, or SonarQube.

      • Docker Image Creation: Build and push the image to a registry (e.g., Docker Hub, AWS ECR).

      • Update Helm Chart: Modify the values.yaml file to set the correct image tag (which will match the new Docker image).

  2. Argo CD (CD):

    • Install Argo CD in your Kubernetes cluster and link it to your Git repository.

    • Configure Argo CD to watch the Helm chart.

      • Whenever the Helm chart or values.yaml changes (e.g., when the Docker image tag is updated), Argo CD will detect the change and automatically deploy the application with the new version.

Example GitHub Action for CI:

# 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 "amitsinghs2798@gmail.com"
        git config --global user.name "Amit Singh"
        git add helm/go-web-app-chart/values.yaml
        git commit -m "Update tag in Helm chart"
        git push

Example Argo CD Configuration:

  1. Install Argo CD:
  • Follow Argo CD documentation to install it in your Kubernetes cluster.

kubectl get svc - argocd
kubectl edit secret argocd-initial-admin-secret

kubectl edit secret <secret name> -n argocd

  1. Connect to Git Repository:

    • Connect Argo CD to your GitHub repository where your Helm chart is located.

  2. Monitor Helm Chart:

    • Configure Argo CD to automatically deploy whenever there is a change to values.yaml or the Helm chart.


Summary:

  • We set up Helm for Kubernetes application management.

  • Implemented CI using GitHub Actions to build, test, create a Docker image, and update the Helm chart.

  • Integrated CD using Argo CD to deploy the latest changes to the Kubernetes cluster automatically.

1
Subscribe to my newsletter

Read articles from Amit singh deora directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Amit singh deora
Amit singh deora

DevOps | Cloud Practitioner | AWS | GIT | Kubernetes | Terraform | ArgoCD | Gitlab