每天进步一点点:使用Go gRPC 和k8s开始你的微服务之旅

Posted Go技术大全

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每天进步一点点:使用Go gRPC 和k8s开始你的微服务之旅相关的知识,希望对你有一定的参考价值。

原文链接:https://outcrawl.com/getting-started-microservices-go-grpc-kubernetes/


This article aims to provide a simple introduction to building microservices in Go, using gRPC, and deploying them to a Kubernetes cluster. It shows how to set up two containerized, scalable services, talking to each other using request/response mechanism.

One microservice will be responsible for calculating the greatest common divisor (GCD) of two integers. That's not something that requires building a separate service, but it will serve as an introductory example just fine. The other will serve a public REST API, and depend on first service when needed.

Prerequisites

The best way to get a running Kubernetes cluster for development, at least for now, is to use Minikube.

Install Minikube, kubectl, Docker and Protocol Buffers compiler.

Run Minikube. Certain commands will require root privileges.

$ minikube start [--vm-driver=<driver>]
$ kubectl cluster-info

To speed up the development, set docker to reuse Docker daemon running inside the Minikube virtual machine.

$ eval $(minikube docker-env)

This way, you don't have to push images to a Docker registry. For running your apps on Google Container Engine you'd use Container Registry or set up a private container registry inside the cluster.

Defining communication protocol

Services will interact synchronously, which means a client calls a service and waits for the response. One of the popular frameworks, that provides this functionality, is gRPC. To use it, you need to write definitions for message types and services in Protocol Buffer's interface definition language and compile them.

Create gcd.proto file inside pb directory, which is located inside your project's root directory.

syntax = "proto3";package pb;message GCDRequest {  uint64 a = 1;  uint64 b = 2;
}message GCDResponse {  uint64 result = 1;
}service GCDService {  rpc Compute (GCDRequest) returns (GCDResponse) {} }

Navigate to the pb directory and run the following command.

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

Compilation should produce gcd.pb.go file.

Greatest common divisor service

The GCD service will use auto-generated code to serve a RPC endpoint which calculates the GCD.

Create main.go file inside gcd directory. Make sure you import correct packages.

package mainimport (  "log"
  "net"
  // Change this for your own project
  "github.com/tinrab/kubernetes-go-grpc-tutorial/pb"
  context "golang.org/x/net/context"
  "google.golang.org/grpc"
  "google.golang.org/grpc/reflection")

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.RegisterGCDServiceServer(s, &server{})
  reflection.Register(s)  if err := s.Serve(lis); err != nil {
    log.Fatalf("Failed to serve: %v", err)
  }
}

Declare the Compute handler function. This makes the server type conform to the auto-generated pb.GCDServiceServer interface.

func (s *server) Compute(ctx context.Context, r *pb.GCDRequest) (*pb.GCDResponse, error) {
  a, b := r.A, r.B  for b != 0 {
    a, b = b, a%b
  }  return &pb.GCDResponse{Result: a}, nil}

Frontend API service

Frontend service uses gin web framework to serve a REST API and calls the GCD service for the actual calculation.

Create a client to communicate with the GCD service inside main function. Because Kubernetes (v1.3+) has a built-in DNS service, you can refer to the GCD service with the name "gcd-service", defined later on.

func main() {
  conn, err := grpc.Dial("gcd-service:3000", grpc.WithInsecure())  if err != nil {
    log.Fatalf("Dial failed: %v", err)
  }
  gcdClient := pb.NewGCDServiceClient(conn)
}

After that, declare a handler for /gcd/:a/:b endpoint which reads parameters A and B, and then calls the GCD service.

r := gin.Default()
r.GET("/gcd/:a/:b", func(c *gin.Context) {  // Parse parameters
  a, err := strconv.ParseUint(c.Param("a"), 10, 64)  if err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid parameter A"})    return
  }
  b, err := strconv.ParseUint(c.Param("b"), 10, 64)  if err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid parameter B"})    return
  }  // Call GCD service
  req := &pb.GCDRequest{A: a, B: b}  if res, err := gcdClient.Compute(c, req); err == nil {
    c.JSON(http.StatusOK, gin.H{      "result": fmt.Sprint(res.Result),
    })
  } else {
    c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  }
})

Run the server.

if err := r.Run(":3000"); err != nil {
  log.Fatalf("Failed to run server: %v", err)
}

Building Docker images

Now that your services are ready, you need to containerize them. Create Dockerfiles, one for each service, inside your project's root directory. Dockerfiles can't be located in a subdirectory, because Docker's COPY command can't copy files from the parent directory, which is needed in this example.

Create Dockerfile.api.

FROM golang:1.9.1# Change this path!WORKDIR /go/src/github.com/tinrab/kubernetes-go-grpc-tutorial/apiCOPY api .COPY pb ../pbRUN go get -v ./...RUN go install -v ./...EXPOSE 3000CMD [ "api" ]

And the Dockerfile.gcd.

FROM golang:1.9.1WORKDIR /go/src/github.com/tinrab/kubernetes-go-grpc-tutorial/gcdCOPY gcd .COPY pb ../pbRUN go get -v ./...RUN go install -v ./...EXPOSE 3000CMD [ "gcd" ]

Build both images. If you switched to Minikube's Docker daemon, they will become available inside the VM.

$ docker build -t local/gcd -f Dockerfile.gcd .
$ docker build -t local/api -f Dockerfile.api .

Deploying to Kubernetes cluster

For each service, you'll need to configure two Kubernetes objects—a deployment and a service.

Explained simply, deployments manage sets of pods to keep the "desired state" of the cluster. Pods are collections of containers. Because they are created and destroyed dynamically, services are needed to provide fixed addresses by which to access them. Which pods are targeted by which services, is determined by label selectors.

Configuration files below are fairly standard. The important parts to keep note of are the ports, names of Docker images built earlier, and labels. Also, imagePullPolicy is set to Never to ensure Kubernetes uses images built locally.

Create gcd.yaml file. It declares a deployment and a service in the same file. Another practice is to separate them into gcd-deployment.yaml and gcd-service.yaml.

apiVersion: apps/v1beta1kind: Deploymentmetadata:  name: gcd-deployment  labels:    app: gcdspec:  selector:    matchLabels:      app: gcd  replicas: 3  template:    metadata:      labels:        app: gcd    spec:      containers:      - name: gcd        image: local/gcd        imagePullPolicy: Never        ports:        - name: gcd-service          containerPort: 3000---apiVersion: v1kind: Servicemetadata:  name: gcd-servicespec:  selector:    app: gcd  ports:  - port: 3000    targetPort: gcd-service

Create api.yaml file. The service type is set to NodePort, which makes this service accessible outside of the cluster. For the GCD service, this is set to a default value of ClusterIP, which means a "cluster-internal" IP address.

apiVersion: apps/v1beta1kind: Deploymentmetadata:  name: api-deployment  labels:    app: apispec:  selector:    matchLabels:      app: api  replicas: 1  template:    metadata:      labels:        app: api    spec:      containers:      - name: api        image: local/api        imagePullPolicy: Never        ports:        - name: api-service          containerPort: 3000---apiVersion: v1kind: Servicemetadata:  name: api-servicespec:  type: NodePort  selector:    app: api  ports:  - port: 3000    targetPort: api-service

To create these resources inside the cluster, run the following commands.

$ kubectl create -f api.yaml
$ kubectl create -f gcd.yaml

Check if all pods are running. By specifying -w flag, you can watch for changes.

$ kubectl get pods -w
NAME                             READY     STATUS    RESTARTS   AGE
api-deployment-778049682-3vd0z   1/1       Running   0          3s
gcd-deployment-544390878-0zgc8   1/1       Running   0          2s
gcd-deployment-544390878-p78g0   1/1       Running   0          2s
gcd-deployment-544390878-r26nx   1/1       Running   0          2s

As set in the configuration files, API service runs on a single pod and the GCD service runs on 3.

Get the URL of the API service.

$ minikube service api-service --url

Finally, try it out.

$ curl http://192.168.99.100:32602/gcd/294/462

Conclusion

This article showed how to get a simple stateless application running on a Kubernetes cluster. Future posts will demonstrate more thorough and interesting examples of building applications with Go and Kubernetes.

Until then, learn more about concepts of Kubernetes and find out how to perform specific tasks.

Entire source code is available on GitHub.


以上是关于每天进步一点点:使用Go gRPC 和k8s开始你的微服务之旅的主要内容,如果未能解决你的问题,请参考以下文章

每天进步一点点:为Go语言代码编写CI/CD工具的Makefile

哪个更快?对 gRPC 的 Rust 和 Go 版本进行基准测试

每天进步一点点,希望每天都很充足

gRPC双向数据流的交互控制(go语言实现)

每天进步一点点003

每天进步一点点028