How to develop Go gRPC microservices and deploy in Kubernates

Forex stock trading

How to develop Go gRPC microservices and deploy in Kubernetes

A few month back, I started my journey learning gRPC. This article is to demonstrate how we can use gRPC to develop microservices using Go and deploy them in kubernetes cluster.
We will develop two microservice. One microservice will be responsible for calculating summation of two integer and other will serve a public REST API.

Prerequisites

There are many many way to run kubernetes cluster in local machine. I am going to use Minikube for this article. We also need to install kubectl, Docker and Protobuf Compiler.

To start Minikube you have to run following command with root privileges

$ minikube start [--vm-driver=]

Define communication protocol

As an underlying transport protocol we will use gRPC. For that we need to write definitions for message types and services in Protocol Buffer’s interface definition language and compile them. In your project root directory create a file named add.proto inside pb directory.

syntax = "proto3";

package pb;

message AddRequest {
uint64 a = 1;
uint64 b = 2;
}

message AddResponse {
uint64 result = 1;
}

service AddService {
rpc Compute (AddRequest) returns (AddResponse) {}
}

To compile this proto file navigate to pb directory and run the following command

$ protoc -I . --go_out=plugins=grpc:. ./*.proto

After successful compilation it will produce add.pb.go file in the same directory.

Implement summation service

To implement summation service we need to use auto-generated code. Now create main.go file inside add directory and make sure you import correct packages

package main

import (
"fmt"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"log"
"net"

// replace this with your own project
"github.com/shuza/kubernetes-go-grpc/pd"
)

Now implement the Compute handler function that will add two integer from using auto-generated pb.AddServiceClient interface.

func (s *server) Compute(cxt context.Context, r *pb.AddRequest) (*pb.AddResponse, error) {
result := &pb.AddResponse{}
result.Result = r.A + r.B

logMessage := fmt.Sprintf("A: %d B: %d sum: %d", r.A, r.B, result.Result)
log.Println(logMessage)

return result, nil
}

Noe in the main function, register a server type which will handle requests. Then start the gRPC server.

type server struct{}

func main() {
lis, err := net.Listen("tcp", ":3000")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}

s := grpc.NewServer()
pb.RegisterAddServiceServer(s, &server{})
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}

API Service

API service use Gorilla Mux to serve REST API response to the client and route them.Create a client to communicate with the Add Service. To communicate with Add Service we will use service name add-service because later we will deploy our service in kubernetes cluster. Kubernetes has a built-in DNS service, so we can access by service name.

func main() {
// Connect to Add service
conn, err := grpc.Dial("add-service:3000", grpc.WithInsecure())
if err != nil {
log.Fatalf("Dial Failed: %v", err)
}
addClient := pb.NewAddServiceClient(conn)

routes := mux.NewRouter()
routes.HandleFunc("/add/{a}/{b}", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UFT-8")

vars := mux.Vars(r)
a, err := strconv.ParseUint(vars["a"], 10, 64)
if err != nil {
json.NewEncoder(w).Encode("Invalid parameter A")
}
b, err := strconv.ParseUint(vars["b"], 10, 64)
if err != nil {
json.NewEncoder(w).Encode("Invalid parameter B")
}

ctx, cancel := context.WithTimeout(context.TODO(), time.Minute)
defer cancel()

req := &pb.AddRequest{A: a, B: b}
if resp, err := addClient.Compute(ctx, req); err == nil {
msg := fmt.Sprintf("Summation is %d", resp.Result)
json.NewEncoder(w).Encode(msg)
} else {
msg := fmt.Sprintf("Internal server error: %s", err.Error())
json.NewEncoder(w).Encode(msg)
}
}).Methods("GET")

fmt.Println("Application is running on : 8080 .....")
http.ListenAndServe(":8080", routes)
}

Here I have declared a handler for /add/{a}/{b} endpoint which reads parameters A and B and then call Add Service for summation.

Build Docker Images

Now your services are ready but need to containerize them to deploy in kubernetes cluster. For Add Service create a Dockerfile inside add directory

FROM golang
COPY . /go/src/add
WORKDIR /go/src/add
RUN go get .
ENTRYPOINT go run main.go
EXPOSE 3000

To build and push summation-service image in DockerHub navigate to add directory and run following commands

$ docker build . -t shuzasa/summation-service:v1.0
$ docker push shuzasa/summation-service:v1.0

For Api Service create Dockerfile inside api directory

FROM golang
COPY . /go/src/api
WORKDIR /go/src/api
RUN go get .
ENTRYPOINT go run main.go
EXPOSE 8080

To build and push api-service image navigate to api directory and run following commands

$ docker build . -t shuzasa/api-service:v1.0
$ docker push shuzasa/api-service:v1.0

Deploying to Kubernetes cluster

For each service we need to configure two object in kubernetes Deployment and Service. Deployment will create poda inside kubernetes cluster and manage desire status of those pods to make sure we have our application running to serve traffic. Service will provide fixed address to access those pods.
For Summation service create add-service.yaml file and insert following commands

apiVersion: apps/v1
kind: Deployment
metadata:
name: add-deployment
labels:
app: add
spec:
selector:
matchLabels:
app: add
replicas: 1
template:
metadata:
labels:
app: add
spec:
containers:
- name: add
image: shuzasa/add-service:v1.2
ports:
- name: add-service
containerPort: 3000

---
apiVersion: v1
kind: Service
metadata:
name: add-service
spec:
selector:
app: add
ports:
- port: 3000
targetPort: add-service

For api-service create api-service.yaml file

apiVersion: apps/v1
kind: Deployment
metadata:
name: api-deployment
labels:
app: api
spec:
selector:
matchLabels:
app: api
replicas: 1
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: shuzasa/api-service:v1.0
ports:
- name: api-service
containerPort: 8080

---
apiVersion: v1
kind: Service
metadata:
name: api-service
spec:
selector:
app: api
ports:
- name: http
port: 8080
nodePort: 30080
type: NodePort

Main difference between two service definition is in api-service we have declared it as NodePort type as it can be accessible from outside of kubernetes cluster.
Now create those resources by running following commands

$ kubectl create -f summation-service.yaml
$ kubectl create -f api-service.yaml

Wait until all the pods become available or in running state

$ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
add-deployment-66df6c78b6-qcj77 1/1 Running 0 2m
api-deployment-577f4965f5-d2bkd 1/1 Running 0 2m

Conclusion

Let’s verify our system. To get URL of our api-service run

$ minikube service api-service --url

Make a request to the service using previously found IP address

curl http://192.168.99.100:30080/add/1/3

And it will show the summation result.

You can find entire source code on GitHub.

Originally published at shuza.ninja on February 2, 2019.


How to develop Go gRPC microservices and deploy in Kubernates was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.

Read about withdrawable no deposit bonus and make profit trading now