You are currently viewing Optimizing Kubernetes Deployment for a Stateless Go App with Redis: A Comprehensive Guide

Optimizing Kubernetes Deployment for a Stateless Go App with Redis: A Comprehensive Guide

In my previous article, I discussed the process of deploying a standalone Go web app on Kubernetes. Now, let’s take it a step further and explore the deployment of a stateless Go web app with Redis on Kubernetes. This time, we’ll delve into the fascinating world of multiple distinct Pods and discover how they can effectively communicate with each other within the cluster. By the end of this article, you’ll have a comprehensive understanding of deploying interconnected Pods and their role in creating powerful and scalable applications on Kubernetes.

Building a Robust Go Application with Redis: A Practical Guide

In this tutorial, we will guide you through the process of creating a straightforward web application using Go. Our application will feature an API that displays the “Quote of the day.”

To accomplish this, our app will retrieve the quote of the day from a public API hosted at Subsequently, it will cache the obtained result in Redis, ensuring it remains accessible until the end of the day. For subsequent API calls, our application will retrieve the quote from the Redis cache rather than fetching it from the public API again.

Let’s get started by opening your terminal and executing the following commands to create the project and initialize Go modules.

mkdir go-redis-kubernetes
cd go-redis-kubernetes
go mod init # Change `codeacademia01` to your Github username

Next, create a file called main.go with the following code:

package main

import (


func indexHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Welcome! Please hit the `/qod` API to get the quote of the day."))

func quoteOfTheDayHandler(client *redis.Client) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		currentTime := time.Now()
		date := currentTime.Format("2006-01-02")

		val, err := client.Get(date).Result()
		if err == redis.Nil {
			log.Println("Cache miss for date ", date)
			quoteResp, err := getQuoteFromAPI()
			if err != nil {
				w.Write([]byte("Sorry! We could not get the Quote of the Day. Please try again."))
			quote := quoteResp.Contents.Quotes[0].Quote
			client.Set(date, quote, 24*time.Hour)
		} else {
			log.Println("Cache Hit for date ", date)

func main() {
	// Create Redis Client
	var (
		host     = getEnv("REDIS_HOST", "localhost")
		port     = string(getEnv("REDIS_PORT", "6379"))
		password = getEnv("REDIS_PASSWORD", "")

	client := redis.NewClient(&redis.Options{
		Addr:     host + ":" + port,
		Password: password,
		DB:       0,

	_, err := client.Ping().Result()
	if err != nil {

	// Create Server and Route Handlers
	r := mux.NewRouter()

	r.HandleFunc("/", indexHandler)
	r.HandleFunc("/qod", quoteOfTheDayHandler(client))

	srv := &http.Server{
		Handler:      r,
		Addr:         ":8080",
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,

	// Start Server
	go func() {
		log.Println("Starting Server")
		if err := srv.ListenAndServe(); err != nil {

	// Graceful Shutdown

func waitForShutdown(srv *http.Server) {
	interruptChan := make(chan os.Signal, 1)
	signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)

	// Block until we receive our signal.

	// Create a deadline to wait for.
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()

	log.Println("Shutting down")

func getQuoteFromAPI() (*QuoteResponse, error) {
	API_URL := ""
	resp, err := http.Get(API_URL)
	if err != nil {
		return nil, err
	defer resp.Body.Close()
	log.Println("Quote API Returned: ", resp.StatusCode, http.StatusText(resp.StatusCode))

	if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
		quoteResp := &QuoteResponse{}
		return quoteResp, nil
	} else {
		return nil, errors.New("Could not get quote from API")


func getEnv(key, defaultValue string) string {
	value := os.Getenv(key)
	if value == "" {
		return defaultValue
	return value

Also, create the following structs in a file named quote.go to parse the JSON response returned from API.

package main

type QuoteData struct {
	Id         string   `json:"id"`
	Quote      string   `json:"quote"`
	Length     string   `json:"length"`
	Author     string   `json:"author"`
	Tags       []string `json:"tags"`
	Category   string   `json:"category"`
	Date       string   `json:"date"`
	Permalink  string   `json:"parmalink"`
	Title      string   `json:"title"`
	Background string   `json:"Background"`

type QuoteResponse struct {
	Success  APISuccess   `json:"success"`
	Contents QuoteContent `json:"contents"`

type QuoteContent struct {
	Quotes    []QuoteData `json:"quotes"`
	Copyright string      `json:"copyright"`

type APISuccess struct {
	Total string `json:"total"`

Let’s now build and run the app locally:

go build
2023/06/26 13:32:05 Starting Server
curl localhost:8080
Welcome! Please hit the `/qod` API to get the quote of the day.

curl localhost:8080/qod
I’ve missed more than 9000 shots in my career. I’ve lost almost 300 games. 26 times, I’ve been trusted to take the game winning shot and missed. I’ve failed over and over and over again in my life. And that is why I succeed.

Containerizing the Go app

Let’s now containerize our Go app by creating a Dockerfile with the following configurations:

# Dockerfile References:

# Start from the latest golang base image
FROM golang:latest as builder

# Add Maintainer Info
LABEL maintainer="code academia <>"

# Set the Current Working Directory inside the container

# Copy go mod and sum files
COPY go.mod go.sum ./

# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
RUN go mod download

# Copy the source from the current directory to the Working Directory inside the container
COPY . .

# Build the Go app
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

######## Start a new stage from scratch #######
FROM alpine:latest  

RUN apk --no-cache add ca-certificates

WORKDIR /root/

# Copy the Pre-built binary file from the previous stage
COPY --from=builder /app/main .

# Expose port 8080 to the outside world

# Command to run the executable
CMD ["./main"] 

To gain further insights into containerizing a Go application, I highly recommend reading the article titled “Building Docker Containers for Go Applications.” It provides detailed instructions and valuable information on this topic.

In this case, I have already taken the initiative to build and publish the Docker image for our application on Docker Hub. To utilize this image, you can utilize the following commands:

# Build the image
$ docker build -t go-redis-kube .

# Tag the image
$ docker tag go-redis-kube codeacademia/go-redis-app:1.0.0

# Login to docker with your docker Id
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don\'t have a Docker ID, head over to to create one.
Username (codeacademia): codeacademia
Login Succeeded

# Push the image to docker hub
$ docker push codeacademia/go-redis-app:1.0.0

Creating the Kubernetes deployment and service manifest for Redis

Now, it’s time to create the necessary configuration for deploying our Redis app on Kubernetes. We will need to establish a deployment to manage the Redis instance, as well as a Service to route traffic from our Go app to the Redis Pod.

To begin, let’s create a folder named “deployments” within the project’s root directory. This folder will serve as a storage location for all our deployment manifests. Inside the “deployments” folder, create a file called “redis-master.yml” and populate it with the following configurations:

type: post
apiVersion: apps/v1  # API version
kind: Deployment
  name: redis-master # Unique name for the deployment
    app: redis       # Labels to be applied to this deployment
    matchLabels:     # This deployment applies to the Pods matching these labels
      app: redis
      role: master
      tier: backend
  replicas: 1        # Run a single pod in the deployment
  template:          # Template for the pods that will be created by this deployment
      labels:        # Labels to be applied to the Pods in this deployment
        app: redis
        role: master
        tier: backend
    spec:            # Spec for the container which will be run inside the Pod.
      - name: master
        image: redis
            cpu: 100m
            memory: 100Mi
        - containerPort: 6379
type: post
apiVersion: v1
kind: Service        # Type of Kubernetes resource
  name: redis-master # Name of the Kubernetes resource
  labels:            # Labels that will be applied to this resource
    app: redis
    role: master
    tier: backend
  - port: 6379       # Map incoming connections on port 6379 to the target port 6379 of the Pod
    targetPort: 6379
  selector:          # Map any Pod with the specified labels to this service
    app: redis
    role: master
    tier: backend

The redis-master Service is only accessible within the container cluster because the default type for a Service is ClusterIP. ClusterIP provides a single IP address for the set of Pods the Service is pointing to. This IP address is accessible only within the cluster.

Kubernetes deployment manifest for the Go app

Let’s now create a deployment and a service for our Go app. We’ll run 3 Pods for the Go app and the Pods will be exposed via a Service to the outside world:

type: post
apiVersion: apps/v1
kind: Deployment                 # Type of Kubernetes resource
  name: go-redis-app             # Unique name of the Kubernetes resource
  replicas: 3                    # Number of pods to run at any given time
      app: go-redis-app          # This deployment applies to any Pods matching the specified label
  template:                      # This deployment will create a set of pods using the configurations in this template
      labels:                    # The labels that will be applied to all of the pods in this deployment
        app: go-redis-app 
      - name: go-redis-app
        image: codeacademia/go-redis-app:1.0.0 
        imagePullPolicy: IfNotPresent
            cpu: 100m
            memory: 100Mi
          - containerPort: 8080  # Should match the port number that the Go application listens on    
        env:                     # Environment variables passed to the container
          - name: REDIS_HOST
            value: redis-master
          - name: REDIS_PORT
            value: "6379"    
type: post
apiVersion: v1
kind: Service                    # Type of kubernetes resource
  name: go-redis-app-service     # Unique name of the resource
  type: NodePort                 # Expose the Pods by opening a port on each Node and proxying it to the service.
  ports:                         # Take incoming HTTP requests on port 9090 and forward them to the targetPort of 8080
  - name: http
    port: 9090
    targetPort: 8080
    app: go-redis-app            # Map any pod with label `app=go-redis-app` to this service

The Golang app can communicate with Redis using the hostname redis-master. This is automatically resolved by Kubernetes to point to the IP address of the service redis-master.

Deploying the Go app and Redis on Kubernetes

We’ll deploy the Go web app and Redis on a local kubernetes cluster created using Minikube.

Please install Minikube and Kubectl if you haven’t installed them already. Check out the Kubernetes official documentation for instructions.

Start a Kubernetes cluster using minikube

$ minikube start

Deploy Redis

$ kubectl apply -f deployments/redis-master.yml
deployment.apps/redis-master created
service/redis-master created
$ kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE
redis-master-7b44998456-pl8h9   1/1     Running   0          34s

Deploy the Go app

$ kubectl apply -f deployments/go-redis-app.yml
deployment.apps/go-redis-app created
service/go-redis-app-service created
$ kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE
go-redis-app-57b7d4d4cd-fkddw   1/1     Running   0          27s
go-redis-app-57b7d4d4cd-l9wg9   1/1     Running   0          27s
go-redis-app-57b7d4d4cd-m9t8b   1/1     Running   0          27s
redis-master-7b44998456-pl8h9   1/1     Running   0          82s

Accessing the application

The Go app is exposed as NodePort via the service. You can get the service URL using minikube like this –

$ minikube service go-redis-app-service --url

You can use the above endpoint to access the application:

$ curl
Welcome! Please hit the `/qod` API to get the quote of the day.

$  curl
I’ve missed more than 9000 shots in my career. I’ve lost almost 300 games. 26 times, I’ve been trusted to take the game winning shot and missed. I’ve failed over and over and over again in my life. And that is why I succeed.

Leave a Reply