Scaling your Go Application with Kubernetes

October 17, 2022
Written by
Reviewed by
Paul Kamp
Twilion

scaling-go-app-with-kubernetes

In this tutorial we’ll walk through scaling your Go application with Kubernetes. We will do this by building an application in Go. We will then get it up and running locally on your development machine, containerize it with Docker, deploy it to a Kubernetes cluster with Minikube, and create a load balancer that will serve as its public facing entry point.

Docker is a containerization tool used to provide applications with a filesystem holding everything they need to run. This ensures that apps will have a consistent run-time environment and behave the same way regardless of where they are deployed. Kubernetes is a cloud platform for automating the deployment, scaling, and management of containerized applications.

Prerequisites

Before you can complete the tutorial, you’ll need a few things.

  • Go 1.19
  • Kubernetes 1.25
  • Minikube 1.26
  • You preferred editor or IDE
  • Some prior experience with containers and with developing applications in Go would also be ideal

Build a Sample Web Application

First up, you will build a sample application written in Go. Once you containerize this app with Docker, it will serve requests to your server’s IP address port 3000.

Create a new directory to contain the project files by running the following command in your development workspace:

mkdir my-go-app
cd- my-go-app

We’ll then go ahead and create a service with Gin to respond to HTTP requests.

package main

import (
	"fmt"
	"time"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.New()

	r.SetTrustedProxies([]string{"192.168.1.*"})

	// Example pingog request.
	r.GET("/", func(c *gin.Context) {
		c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
	})

	// Example when panic happen.
	r.GET("/panic", func(c *gin.Context) {
		panic("An unexpected error happen!")
	})

	// Listen and Server in 0.0.0.0:8080
	r.Run(":3000")
}

Next, run the application using the following command. This will compile the code in your main.go file and run it locally on your development machine.

go run main.go

In the next step we are going to add some logging to our application to make debugging easier.

Add Logging to our Go Application

We’ll use Gin Zap as middleware to our application to handle logging of HTTP requests in our application.

package main

import (
	"fmt"
	"time"

	ginzap "github.com/gin-contrib/zap"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

func main() {
	r := gin.New()
	logger, _ := zap.NewProduction()

	// Add a ginzap middleware, which:
	//   - Logs all requests, like a combined access and error log.
	//   - Logs to stdout.
	//   - RFC3339 with UTC time format.
	r.Use(ginzap.Ginzap(logger, time.RFC3339, true))

	// Logs all panic to error log
	//   - stack means whether output the stack info.
	r.Use(ginzap.RecoveryWithZap(logger, true))

	// Example ping request.
	r.GET("/", func(c *gin.Context) {
		c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
	})

	// Example when panic happen.
	r.GET("/panic", func(c *gin.Context) {
		panic("An unexpected error happen!")
	})

	// Listen and Server in 0.0.0.0:8080
	r.Run(":3000")
}

In the next step, we will dockerize the application.

Dockerize Your Go Application

Create a new file in the top-level project directory named Dockerfile

# syntax=docker/dockerfile:1

FROM golang:1.19-alpine

WORKDIR /app

COPY go.mod ./
COPY go.sum ./
RUN go mod download

COPY *.go ./

RUN go build -o /main

EXPOSE 8080

CMD [ "/main" ]
docker build -t <your_user_name>/my-go-app

Next use the following command to create and start the container.

docker run -it -p 3000:3000 <your_user_name>/my-go-app

When you deploy your containerized application to your Kubernetes cluster, you’ll need to pull the image from a centralized location. So you need to first push your newly created image to your Docker Hub image repo. However, before you can do that, you first have to log in to Docker Hub.

Run the following command to do this:

docker login

After logging in push your new image up to Docker Hub using the docker push command

docker push <your_user_name>/my-go-app

Now that we have pushed the image to a central location we are ready to deploy it to your Kubernetes cluster.

Start your local kubernetes cluster

We’ll use minikube which implements a local Kubernetes cluster on macOS, Linux, and Windows. Run the following command on your command line to begin:

minikube start

If you're interested, you can view a Kubernetes dashboard via the minikube dashboard command.

Minikube Kubernetes Dashboard

Create a Deployment

One kind of Kubernetes object, known as a deployment, is a set of identical, indistinguishable pods, where a pod is a grouping of one or more containers which are able to communicate over the same shared network and interact with the same shared storage.

A deployment runs more than one replica of the parent application at a time and automatically replaces any instances that fail ensuring that your application is always available to serve user requests.

In this step, you’ll create a Kubernetes object description file known as a manifest for the deployment. The manifest will contain all the configuration details needed to deploy your Go app to your cluster.

Begin by creating a deployment file in the root of your directory named deployment.yaml.

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-go-app
spec:
  replicas: 5
  selector:
    matchLabels:
      name: my-go-app
  template:
    metadata:
      labels:
        name: my-go-app
    spec:
      containers:
      - name: application
        image: gjonestwilio/my-go-app:latest
        imagePullPolicy: IfNotPresent
        envFrom:
        - secretRef:
            name: test-secret
        ports:
          - containerPort: 3000

Next apply your new deployment using this command:

kubectl apply -f deployment.yml

In the next step you’ll create another kind of Kubernetes object, a service, which will manage how you access the pods that exist in your deployment. This service will create a load balancer which will then expose a single IP address. Requests to this IP address will be distributed to the replicas in your deployment. This service will also handle port forwarding rules so that you can access your application over HTTP.

Create a Service

Now that you have a successful Kubernetes deployment, you’re ready to expose your application to the outside world. In order to do this you’ll need to define another kind of kubernetes object: a service. This service will expose the same port on all of your cluster’s nodes. Your nodes will then forward any incoming traffic on that port to the pods running your application.

Create a new file called service.yml

---
apiVersion: v1
kind: Service
metadata:
  name: my-go-app-service
spec:
  type: LoadBalancer
  ports:
  - name: http
    port: 3001
    targetPort: 3000
  selector:
    name: my-go-app

After adding these lines, save and close the file. Following that, apply this service to your Kubernetes cluster by, once again, using the kubectl apply command:

kubectl apply -f service.yml

This command will apply the new Kubernetes service as well as create a load balancer. This load balancer will serve as the public-facing entry point to your application running within the cluster.

As we are using a custom Kubernetes Cluster with minikube, there is no load balancer integrated (unlike with AWS or Google Cloud) so we need to tunnel requests. You can do this with minikube tunnel command

To view the application, you will need the new load balancer’s IP address. Find it by running the command:

kubectl get services

The load balancer will take in the request on the port 80 and forward it to one of the pods running within your cluster.

Conclusion

Congratulations! With that you’ve created a Kubernetes service coupled with a load balancer, giving you a single, stable entry point to your application.

Gareth Paul Jones is a Group Product Manager for our Developer Experience (DX) Team. He's invested in helping solve large scale software problems. When he is not staring at screens, he enjoys family time with his wife and daughter. He can be reached at gjones [at] twilio.com.