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 for this article. We also need to install , and Protobuf . Minikube kubectl Docker Compiler To start Minikube you have to run following command with root privileges $ minikube start [--vm-driver=<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 and compile them. In your project root directory create a file named inside directory. interface definition language add.proto pb 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 file navigate to directory and run the following command proto pb $ protoc -I . --go_out=plugins=grpc:. ./*.proto After successful compilation it will produce file in the same directory. add.pb.go Implement summation service To implement summation service we need to use auto-generated code. Now create file inside directory and make sure you import correct packages main.go add package mainimport ( "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 handler function that will add two integer from using auto-generated interface. Compute pb.AddServiceClient 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 use to serve REST API response to the client and route them.Create a client to communicate with the . To communicate with we will use service name because later we will deploy our service in kubernetes cluster. Kubernetes has a built-in DNS service, so we can access by service name. API service Gorilla Mux Add Service Add Service add-service 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 endpoint which reads parameters and and then call Add Service for summation. /add/{a}/{b} A B Build Docker Images Now your services are ready but need to containerize them to deploy in kubernetes cluster. For Add Service create a inside directory Dockerfile add FROM golangCOPY . /go/src/addWORKDIR /go/src/addRUN go get .ENTRYPOINT go run main.goEXPOSE 3000 To build and push image in navigate to directory and run following commands summation-service DockerHub add $ docker build . -t shuzasa/summation-service:v1.0$ docker push shuzasa/summation-service:v1.0 For Api Service create inside directory Dockerfile api FROM golangCOPY . /go/src/apiWORKDIR /go/src/apiRUN go get .ENTRYPOINT go run main.goEXPOSE 8080 To build and push image navigate to directory and run following commands api-service api $ 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 and . will create poda inside kubernetes cluster and manage desire status of those pods to make sure we have our application running to serve traffic. will provide fixed address to access those pods. For Summation service create file and insert following commands Deployment Service Deployment Service add-service.yaml apiVersion: apps/v1kind: Deploymentmetadata: name: add-deployment labels: app: addspec: 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: v1kind: Servicemetadata: name: add-servicespec: selector: app: add ports: - port: 3000 targetPort: add-service For create file api-service api-service.yaml apiVersion: apps/v1kind: Deploymentmetadata: name: api-deployment labels: app: apispec: 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: v1kind: Servicemetadata: name: api-servicespec: selector: app: api ports: - name: http port: 8080 nodePort: 30080 type: NodePort Main difference between two service definition is in we have declared it as type as it can be accessible from outside of kubernetes cluster. Now create those resources by running following commands api-service NodePort $ 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 -wNAME READY STATUS RESTARTS AGEadd-deployment-66df6c78b6-qcj77 1/1 Running 0 2mapi-deployment-577f4965f5-d2bkd 1/1 Running 0 2m Conclusion Let’s verify our system. To get URL of our run api-service $ 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.
Share Your Thoughts