Devopsified Go Web App - Complete Devops Project


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:
$ git clone https://github.com/iam-veeramalla/go-web-app
Create go application binary: $ go build -o main .
To execute binary: $./main
Run in browser: http://localhost:8080/courses
STEP 2 : Create a Docker file (Multistage)
Stage 1: Building the Application
Use the Golang base image:
FROM golang:1.21 AS base
Set up the working directory:
WORKDIR /app
Copy dependencies and install them:
COPY go.mod . RUN go mod download
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:

```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 foldermanifest
.Create
deployment.yml
file insidemanifest 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
fileWe 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 theADDRESS
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 appfind 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.
- However, you might get a 404 error because the load balancer only accepts requests to
Local DNS Mapping
write command:
nslookup
→ this will provide IP Address of our ingressnslookup <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 yourhosts
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
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).
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
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 thevalues.yaml
file.
Updating values.yaml:
Modify the
values.yaml
to include theimage.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.
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.
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.
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):
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.
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:
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
anddocker test
.Static Code Analysis: Run tools like
eslint
,flake8
, orSonarQube
.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).
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.
- Whenever the Helm chart or
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:
- 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
Connect to Git Repository:
Connect Argo CD to your GitHub repository where your Helm chart is located.
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.
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