How to write a Golang microservice for Kubernetes

Go Service for Kubernetes

If you’ve ever tried Golang, you know that writing services in Go are very easy. We need a few lines of code so that you can start the http service. But what needs to be added if we want to prepare such a production application? Let’s look at this using an example of service ready to run in Kubernetes.

All steps in this article can be found in a single tag, or you can follow the examples of the article commit by commit.

Step 1. The simplest Golang web service

So, we have a very simple application:

package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/home", func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, "Hello! Your request was processed.")
},
)
http.ListenAndServe(":8000", nil)
}

If we want to try running it, go run main.go will suffice. We can check how this service works with curl -i http://127.0.0.1:8000/home. But when we run this application, we see no information about its state in the terminal.

Step 2. Adding logging with Golang

First of all, let’s add logging to understand what’s going on with the service and to be able to log errors or other critical situations. We will use the most straightforward logger from the Go standard library in this example. Still, more complex solutions may be of interest for an accurate service running in production, such as glog or logrus.

We may be interested in 3 situations: when the service starts, when the service is ready to process requests, and when http.ListenAndServe returns an error. The result will be something like this:

func main() {
log.Print("Starting the service...")
http.HandleFunc("/home", func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, " Hello! Your request was processed.")
},
)
log.Print("The service is ready to listen and serve.")
log.Fatal(http.ListenAndServe(":8000", nil))
}

Better!

Step 3: Adding a Router

For this application, we’ll most likely want to use a router to make it easier to handle different URIs, HTTP methods, or other rules. There is no router in the Go standard library, so let’s try gorilla/mux, which is entirely compatible with the net/http standard library.

It makes sense to put everything related to routing into a separate package. Let’s move the initialization and setting of the routing rules and the handler functions to the handlers package (you can see the complete changes here).

Let’s add a Router function that will return the configured router, and a home function that will handle the rule for the /home path. I prefer to separate such functions into separate files:

handlers/handlers.go:

package handlers
import (
"github.com/gorilla/mux"
)
// Router register necessary routes and returns an instance of a router.
func Router() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/home", home).Methods("GET")
return r
}

handlers/home.go:

package handlers
import (
"fmt"
"net/http"
)
// home is a simple HTTP handler function which writes a response.
func home(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, "Hello! Your request was processed.")
}

We also need a small change to main.go: