From Code to Cloud: A Hands-On DevOps Journey with Kubernetes Deployment
Published: 2024-01-20 13:34:52

---
title: From Code to Cloud: A Hands-On DevOps Journey with Kubernetes Deployment
published: true
date: 2024-01-20 08:02:42 UTC
tags: 
canonical_url: 
---

Welcome to a hands-on journey through the realms of DevOps! In this blog, we'll dive into the practical world of code deployment, exploring the seamless integration of development and operations that powers modern software delivery. Join me as we unravel the intricacies of a real-world DevOps assignment, focusing on Kubernetes deployment. From transforming code into containerized brilliance to orchestrating it in the cloud, this blog is your guide to mastering the practical aspects of DevOps. Ready to embark on a journey that bridges code, clouds, and containers? Let's dive in!

##### DevOps Workshop 2

### You should try the workshop on your own first.
# Workshop Requirements  

1. **Source Code:**

2. **Version Control:**

3. **Continuous Integration (CI):**

4. **Containerization:**

5. **Continuous Deployment (CD):**

6. **Monitoring (Optional):**

Follow these steps, explaining your choices and demonstrating the flow. This assignment will showcase your skills in setting up a CI/CD pipeline and deploying applications to Kubernetes. If you have any questions along the way, feel free to ask!

## First, build a simple Web server

You can choose the language for this task, I am using the Go language.

- Create a **_main.go_** file.

```
touch main.go

go mod init webserver

package mainimport ("fmt""net/http")func main() { fs := http.FileServer(http.Dir("./static")) http.Handle("/", fs) port := 3000 fmt.Printf("Server is listening on 3000") err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil) if err != nil { fmt.Printf("Error: ", err) }}
```

> This server is just serving a **_index.html_** and **_main.js_** file.

- Create a **_static_** directory for html and js files.

```
mkdir statictouch index.html main.js
```

- Run the website

```
go run main.go
```

- Go to 127.0.0.1:3000

## Containerize your application using Docker

We use docker to containerize our application, let's write the **_Dockerfile_**.

We are going to use the Multistage Docker build or Distroless Images.

> Must read about [Multi-Stage Docker Build of GO web server](https://harisheoran.github.io/projects/multi_stage_docker_go/)

```
# Build stageFROM golang:latest as buildWORKDIR /appCOPY go.mod ./RUN go mod download# Copy the necessary files of applicationCOPY main.go .COPY static ./static# build the binary of app named "main"RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .# 2nd stage, run the binary of applicationFROM scratch# Copy "main" binary and static folder into current dirCOPY --from=build /app/main .COPY --from=build /app/static ./staticEXPOSE 3000# execute the binaryCMD ["./main"]
```

- Build the Docker Image

```
docker build -t memesoftheday-image .
```

- Build & run the container and check whether the application is running or not.

```
docker container run -d -p 4000:3000 --name memes-container memesoftheday-image
```

## Version Control

Use **_Git_** to control the version of source code and push it to GitHub.

### Continuous Integration (CI)

If you are a beginner to CI, so understand the problem first

- The Image of the application is present on your machine and let's say we are deploying our application on AWS or Azure -

> We don't like manual things, we are Engineers, lets Automate it using CI pipeline.

#### What's the solution?

We build a Ci pipeline which uses our **_Dockerfile_** from the repository to build the image and push it to a registry.

- so, you don't have to worry about building images each time you make changes.

- and it uses CI server computing to build the image.

There are so many CI tools like Jenkins, Github Actions and many more. I think GitHub Actions is the right choice for our requirements.

#### Understand the workflow of the CI pipeline

- Check out the Github repository.

- Build the docker image using Dockerfile.

- Log in to Docker Hub.

- Push the image to Docker Hub.

Read about using Github Actions

- [Manage Secrets](https://itsmetommy.com/2021/07/05/push-to-docker-hub-using-github-actions/)

- Github Actions Officials(https://docs.github.com/en/actions/publishing-packages/publishing-docker-images)

- Create a **.github/workflows** directory.

```
mkdir -p .github/workflows && cd .github/workflows
```

It triggers the build-on code push in the main branch.

```
name: Create and build docker image to Docker Hubon: push: branches: ["main"]env: REGISTRY: docker.io IMAGE_NAME: "memesoftheday-image"jobs: push_to_registry: name: Push Image to Docker Hub runs-on: ubuntu-latest steps: - name: checkout the repository uses: actions/checkout@v4 - name: Login to Docker Hub uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a with: username: ${{secrets.DOCKERHUB_USERNAME}} password: ${{secrets.DOCKERHUB_PASSWORD}} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 with: images: ${{secrets.DOCKERHUB_USERNAME}}/memesoftheday-image - name: Build & push the image uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 with: context: . file: ./Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}
```

![](https://harisheoran.hashnode.dev/actions.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705736079372/93a19402-fefd-44bf-83a8-fd96da55e997.png)

## Continuous Deployment (CD)

We want to reflect the changes we made in our code into [the](https://itsmetommy.com/2021/07/05/push-to-docker-hub-using-github-actions/) applications which we deploy on Kubernetes, which comes into the picture of the CD.

We are going to use the **_ArgoCD_** , so install the ArgoCD as an operator on our k8s cluster.

- Create Kubernetes Cluster We use Minikuebe as local k8s cluster [Install Minikube](https://minikube.sigs.k8s.io/docs/start/)

- Start Minikube cluster

```
minikube start --driver=docker
```

- [Install Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/)

- [Install ArgoCD operator](https://operatorhub.io/operator/argocd-operator)

- Create a new Argo CD cluster with the default configuration using this manifest file.

```
mkdir k8stouch argocd.yml

apiVersion: argoproj.io/v1alpha1kind: ArgoCDmetadata: name: example-argocd labels: example: basicspec: {}
```

ArgoCD is created as a service, you can verify using

```
kubectl get svc
```

Now if you see that it is created as **_ClusterIP_** , which can access only within the cluster, so, to access ArgoCD in our browser we have to change its type to **_NodePort_**.

```
kubectl edit svc example-argocd-server
```

change the type from _Cluster IP_ to _NodePort_

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705736116284/1b7a5b7a-ec89-4f77-9930-3c7c982705ac.png)

![](https://harisheoran.hashnode.dev/argo_nodeport.png)

- Check the service again, and check its port - 31080

```
kubectl get svc

example-argocd-server NodePort 10.105.171.247 <none> 80:31080/TCP,443:32101/TCP 21h
```

- So, to access the argocd on the browser using node port, we need the IP of the cluster

```
minikube ip
```

- Visit the URL - http://minikubeip:port

In my case, it is _192.168.49.2:31080_

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705736144106/f379b538-0066-4384-8bf1-c41ac2ce3f74.png)

![](https://harisheoran.hashnode.dev/argocd_login.png)

- Username is _admin._

- Password is stored as a secret.

```
kubectl get secrets
```

you'll get _example-argocd-cluster_, password is stored in this secret.

```
kubectl edit secrets example-argocd-cluster
```

Copy the password.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705736166114/24195a2a-558c-46d2-9d6c-8de820422eb2.png)

![](https://harisheoran.hashnode.dev/argocd_secret.png)

- Minikube uses a simple encryption algorithm _base64._

```
echo dkxpeEEzeWVnYk53NEZrU1VtdW4xUlBhOHBoTWNyN0k= | base64 -d
```

- Login into ArgoCD.

#### Create Deployment and service for your application.

- Create Deployment.yml in _k8s_ directory.

```
# This file describes a Kubernetes Deployment, which manages a set of pods.apiVersion: apps/v1# Specifies the Kubernetes API version used for this resource.kind: Deployment# Indicates that this is a Deployment resource, used to manage application deployments.metadata: name: memesoftheday-deployment # Assigns a name to the Deployment, in this case, "memesoftheday-deployment." labels: app: memesoftheday # Labels are used to organize and select resources. This label is named "app" and set to "memesoftheday."spec: replicas: 3 # Specifies that the desired number of replicas (pods) is 3. selector: matchLabels: app: memesoftheday # Defines how the Deployment identifies which pods to manage based on labels. template: metadata: labels: app: memesoftheday # Labels assigned to the pods created by this Deployment. spec: containers: - name: memesostheday-container # Specifies the name of the container within the pod. image: harisheoran/memesoftheday-image:main # Specifies the Docker image to be used for the container, pulled from "harisheoran/memesoftheday-image" with the "main" tag. ports: - containerPort: 3000 # Specifies that the container within the pod will listen on port 3000. imagePullPolicy: If
```

- Create service.yml in the same directory.

```
# This file describes a Kubernetes Service, which exposes pods to the network.apiVersion: v1# Specifies the Kubernetes API version used for this resource.kind: Service# Indicates that this is a Service resource, used to expose pods.metadata: name: memesoftheday-service # Assigns a name to the Service, in this case, "memesoftheday-service."spec: type: NodePort # Specifies that the Service should be of type NodePort, making it accessible externally on each node. selector: app: memesoftheday # Specifies the labels used to select pods that this service will route traffic to. In this case, pods with the label "app: memesoftheday." ports: - port: 80 # Specifies that the Service should be accessible on port 80 externally. targetPort: 3000 # Specifies that incoming traffic on port 80 should be forwarded to the pods on port 3000. nodePort: 30007 # Specifies a static port on each node (accessible externally) where the Service will be available, set to 30007.
```

- and create a new app and fill in the details accordingly to your repo in the below format.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705736238425/91cc03ce-a705-433d-afcd-a75abcabd3ef.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705736275711/0b0d4e21-e364-4d7a-9eda-be3b7e839a0b.png)

- Please wait for it, it will create the pods of our application defined in our deployment.

#### We successfully deployed the application pods.

You can check the pods using command.

```
kubectl get pods -o wide
```

![](https://harisheoran.hashnode.dev/pods.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705736355154/b72ca163-0d10-4078-bb02-84af460081a0.png)

- Check the port of pods.

```
kubectl get svc
```

![](https://harisheoran.hashnode.dev/memes_svc.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705736371520/f3f84ef0-61b6-4284-a876-9e07a10bfde8.png)

visit your minikube ip with node port, in my case it is - http://192.168.49.2:30007/

## Monitoring

To monitor our k8s cluster, we use Prometheus and Grafana.

- Install Prometheus using Helm charts

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

helm repo update

helm install prometheus prometheus-community/prometheus
```

Now, it will create Prometheus pod and service but it is available as Cluster IP, to access the Prometheus, we need to change its service to NodePort.

```
kubectl expose service prometheus-server --type=NodePort --target-port=9090 --name=prometheus-server-ext
```

- Check it port using the command.

```
kubectl get svc
```

![](https://harisheoran.hashnode.dev/prom.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705736402083/2aa196bd-4923-49d1-927f-e7d224bdf7b2.png)

Visit this port in browser with minikube IP.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705736425096/f9ab153e-cb48-4ed7-a594-268f31e66a49.png)

![](https://harisheoran.hashnode.dev/promui.png)

To view these logs in graph form, we use Grafana.

- Install Grafana using Helm Chart.

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

helm repo update

helm install grafana grafana/grafana
```

- Expose Grafana service as NodePort to access in browser.

```
kubectl expose service grafana --type=NodePort --target-port=3000 --name=grafana-ext
```

- Check the Grafana Nodeport using command and visit the port with minikube ip.

```
kubectl get svc
```

- Click on it to create your first Data source.

- Add a Data Source to Prometheus.

- Enter the Prometheus IP address.

- Go back to Home and click on Create DashBoard > Import DashBoard > Enter **_3662_** ID to import the template of Dashboard.

- Our Grafana Dashboard is ready.

**_That's it, our project is done, if you have any questions about this project or if stuck at any point, feel free to ping me._**

## Source Code

- GitHub Repository - [https://github.com/harisheoran/WebServer-Docker-CI-CD-k8s](https://github.com/harisheoran/WebServer-Docker-CI-CD-k8s)

- Connect with me on