前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >小白零基础--gRPC整合Kubernetes

小白零基础--gRPC整合Kubernetes

作者头像
用户3904122
发布2022-06-29 14:58:47
1.1K1
发布2022-06-29 14:58:47
举报
文章被收录于专栏:光华路程序猿光华路程序猿

上一篇,我们简单介绍了下mac下单节点Kubernetes的安装,今天我们乘热打铁,感受下grpc整合Kubernetes的魅力。好了Talk is cheap,Show me the graph 我们要做的是下面这么一个架构的小demo。

grpc-k8s

后续计划写个gRPC的专题,我们先来简单认识下gRPC的基本玩法。

gRPC

在微服务盛行的今天,如果你不会个RPC框架你都不好意思出门跟人打招呼。业界流行的目前主要有dubbo、motan、rpcx、gRPC、thrift,排名不分先后,各有千秋。今天主要带来的是go中gRPC的使用。

正如其官方介绍的那样,gRPC是一个高性能、通用的开源RPC框架,由Google开发主要面向移动应用开发并基于HTTP/2协议标准而设计,基于Protocol Buffers序列化协议开发,且支持众多开发语言。至于gRPC优势不再赘述,合适才是最好的。

零基础在Go中使用gRPC大致分为四个步骤:

  • 安装golang以及Protocol Buffer compiler,go module撸一个工程引用下gRPC库
  • 创建.proto文件,并定义一个service
  • 使用protocol buffer compiler生成服务端和客户端代码
  • 使用Go gRPC API为你的服务写一个简单的客户端和服务端

安装软件和撸一个工程

先决条件

  1. golang 安装 关于这部分网上大把文章Google之
  2. Protocol Buffer compiler,protoc 关于protocol buffers的玩法可以参考往期文章。

选择适合你平台的预编译好的二进制文件(https://github.com/google/protobuf/releases),解压并将可执行文件protoc放到你的环境变了中

  1. 使用以下命令为Go安装protobuf协议编译器插件:
代码语言:javascript
复制
$ export GO111MODULE=on  # Enable module mode
$ go get google.golang.org/protobuf/cmd/protoc-gen-go \
         google.golang.org/grpc/cmd/protoc-gen-go-grpc
  1. 更新你的PATH,以便protoc可以找到插件:
代码语言:javascript
复制
$ export PATH="$PATH:$(go env GOPATH)/bin"
  1. Go module撸一个工程
代码语言:javascript
复制
cd ${your workspace}
mkdir -p grpc-k8s-demo && cd grpc-k8s-demo
go mod init github.com/xxx/grpc-k8s-demo
#引入gRPC库
go get -u google.golang.org/grpc

定义服务

在gRPC中,我们是使用protocol buffers定义gRPC服务以及方法请求和响应类型。首先要定义服务,需要在.proto文件中指定一个命名service

代码语言:javascript
复制
service Greeter {
   ...
}

然后,你可以在服务定义中定义rpc方法,并指定它们的请求和响应类型。gRPC允许您定义四种服务方法,所有这些方法都在 Greeter服务中使用:

  • 一个简单的RPC,客户端使用存根将请求发送到服务器,然后等待响应返回,就像正常的函数调用一样。
代码语言:javascript
复制
rpc SayHello (HelloRequest) returns (HelloReply) {}
  • 服务器端流式RPC,客户端向服务器发送请求,并获取流来读取后续的一系列消息。客户端从返回的流中读取数据,直到没有更多消息为止。如下你可以通过在响应类型之前放置stream关键字来指定服务器端流方法。
代码语言:javascript
复制
rpc SayHello (HelloRequest) returns (stream HelloReply) {}
  • 客户端流式RPC,客户端编写消息序列,然后使用提供的流将消息发送到服务器。一旦客户端写完消息后,它将等待服务器读取所有消息并返回其响应。你可以通过将stream关键字放在请求类型之前指定客户端流方法。
代码语言:javascript
复制
rpc SayHello (stream HelloRequest) returns (HelloReply) {}
  • 双向流式RPC,双方都使用读写流发送一系列消息。这两个流是独立运行的,因此客户端和服务器可以按照自己喜欢的顺序进行读写:例如,服务器可以在写响应之前等待接收所有客户端消息,或者可以先读取消息再写入消息,或读写的其他组合。每个流中的消息顺序都会保留。你可以通过在请求和响应之前都放置stream关键字来指定这种类型的方法。
代码语言:javascript
复制
rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}

.proto文件还包含用于服务方法中所有请求和响应类型的protobuf协议消息类型定义-例如,

代码语言:javascript
复制
// The request message containing the user's name.
message HelloRequest {
    string name = 1;
}

// The response message containing the greetings
message HelloReply {
    string message = 1;
    map<string, HelloRequest> maps = 2;
}

本文为了简单演示,使用第一种简单RPC的方式。

生成客户端和服务器代码

接下来,我们需要根据.proto服务定义生成gRPC客户端和服务器接口。可以使用带有特殊gRPC Go插件的protocol buffer compiler protoc 进行此操作。运行以下命令:(关于protoc 原理可见往期《搞定protocol buffers 下-原理篇》)

代码语言:javascript
复制
$ protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    pb/hello.proto

运行此命令将在pb目录中生成以下文件:

  • hello.pb.go,其中包含用于填充,序列化和检索请求和响应消息类型的所有protocol buffers代码。
  • hello_grpc.pb.go,其中包含以下内容:
    • 客户端使用Greeter服务中定义的方法调用的接口类型(或存根)。
    • 服务器要实现的接口类型,也具有Greeter服务中定义的方法。

也可以简单点儿

代码语言:javascript
复制
 protoc  hello/service.proto --go_out=hello/ --go-grpc_out=hello/ 

这样只会生成一个文件,大同小异。

创建服务器

要使我们的Greeter服务发挥作用,服务端编写分为两个部分:

  • 实现根据我们的服务定义生成的服务接口:完成我们服务的实际工作。
  • 运行gRPC服务器以侦听来自客户端的请求,并将其分发到正确的服务实现。
代码语言:javascript
复制
package main

import (
 "context"
 "fmt"
 hello "github.com/leoshus/proto-demo/pb"
 "google.golang.org/grpc"
 "log"
 "net"
 "os"
 "time"
)

type HelloServer struct {
}

func (h *HelloServer) SayHello(ctx context.Context, req *hello.HelloRequest) (*hello.HelloReply, error) {
 now := time.Now().Format("2006-01-02 15:04:05")
 hostname, _ := os.Hostname()
 log.Printf("%s say hello:%s\n", hostname, now)
 return &hello.HelloReply{
  Message: fmt.Sprintf("%s say hello %s :%s", hostname, req.Name, now),
 }, nil
}

func main() {
 server := grpc.NewServer()
 hello.RegisterGreeterServer(server, &HelloServer{})
 listener, err := net.Listen("tcp", ":8088")
 if err != nil {
  log.Printf("start server listen error:%v", err)
  return
 }
 log.Println("start server...")
 if err := server.Serve(listener); err != nil {
  log.Printf("start server error:%v", err)
 }
}

要构建和启动服务器,我们:

  1. 使用以下命令指定我们要用于侦听客户端请求的端口:lis,err:= net.Listen(...)
  2. 使用grpc.NewServer(...)创建gRPC服务器的实例。
  3. 在gRPC服务器上注册我们的服务实现。
  4. 使用我们的端口详细信息在服务器上调用Serve()进行阻塞等待,直到进程被杀死或调用Stop()为止。

创建客户端

客户端代码主要是调用服务方法,我们首先需要

  1. 创建一个gRPC通道来与服务器通信。我们通过将服务器地址和端口号传递给grpc.Dial()来创建它,当服务需要它们时,可以使用DialOptions在grpc.Dial中设置身份验证凭据(例如TLS,GCE凭据或JWT凭据)。Greeter服务不需要任何凭据。
  2. 设置gRPC通道后,我们需要一个客户端存根来执行RPC。例如,我们使用从.proto文件生成的pb包提供的NewGreeterClient方法获取它。
代码语言:javascript
复制
client := hello.NewGreeterClient(conn)
  1. 调用服务方法:在gRPC-Go中,RPC在阻塞/同步模式下运行,这意味着RPC调用等待服务器响应,并且将返回响应或错误。

整体代码如下:

代码语言:javascript
复制
package main

import (
 "context"
 "flag"
 "fmt"
 hello "github.com/leoshus/proto-demo/pb"
 "google.golang.org/grpc"
 "google.golang.org/grpc/backoff"
 "google.golang.org/grpc/balancer/roundrobin"
 "log"
 "strings"
 "time"
)

func main() {
 log.SetFlags(log.Lshortfile | log.Ldate)
 var address string
 flag.StringVar(&address, "address", "localhost:8088", "grpc server address")
 flag.Parse()
 conn, err := grpc.Dial(strings.Join([]string{"dns:///", address}, ""), grpc.WithInsecure(),
  grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy":"%s"}`, roundrobin.Name)),
  //grpc.WithBlock(),
  grpc.WithConnectParams(grpc.ConnectParams{
   Backoff: backoff.Config{
    MaxDelay: 2 * time.Second,
   },
   MinConnectTimeout: 2 * time.Second,
  }))
 if err != nil {
  fmt.Println(err)
  return
 }
 defer conn.Close()
 client := hello.NewGreeterClient(conn)
 for range time.Tick(time.Second) {
  resp, err := client.SayHello(context.TODO(), &hello.HelloRequest{
   Name: "tom",
  })
  if err != nil {
   fmt.Println(err)
   log.Printf("say hello occur error:%v\n", err)
   return
  }
  log.Printf("say hello : %s \n", resp)
 }

}

制作镜像

通过Dockfile定义一个镜像。

代码语言:javascript
复制
FROM golang:1.16.3

COPY . /app/src/grpc-demo
WORKDIR /app/src/grpc-demo

RUN go get -d -v ./...
RUN go install -gcflags=all="-N -l " ./...

这样只需cd到Dockerfile所在目录执行docker build -t leoshus/grpc-demo:v1 .即可构建一个镜像。

push镜像

因为构建的镜像要被之后的Kubernetes使用,所以需要讲镜像push到远端仓库。当然你也可以搭建私有仓库来管理镜像,这里我们使用官方的镜像仓库(https://hub.docker.com/)的演示。

推送仓库前你需要进行登录注册。

代码语言:javascript
复制
docker login #使用注册的用户名密码登陆
docker push leoshus/grpc-demo:v1 # 完成镜像的推送

编写k8s资源文件

首先是服务端在k8s上部署的资源文件编写

代码语言:javascript
复制
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grpc-server
  labels:
    app-name: grpc-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app-name: grpc-server
  template:
    metadata:
      labels:
        app-name: grpc-server
      name: grpc-server
    spec:
      containers:
        - command:
          - server
          image: docker.io/leoshus/grpc-demo:v6
          imagePullPolicy: Always
          name: server
          resources:
            limits:
              cpu: "0.5"
              memory: 100Mi
            requests:
              cpu: "0.5"
              memory: 100Mi
      restartPolicy: Always

这里我们为了方便演示gRPC的负载均衡机制,我们使用了Headless Service,保证该服务不会分配Cluster IP,也不通过kube-proxy做反向代理和负载均衡。而是通过DNS提供稳定的网络ID来访问,将headless service的后端直接解析为pod ip列表,然后由gRPC的负载均衡机制来选择使用哪台server

代码语言:javascript
复制
apiVersion: v1
kind: Service
metadata:
  labels:
    app-name: grpc-server
  name: grpc-server-service
spec:
  clusterIP: None
  ports:
    - name: grpc
      port: 31250
      protocol: TCP
      targetPort: 8088
  selector:
    app-name: grpc-server

然后你可以只需下面命令来进行服务部署

代码语言:javascript
复制
kubectl apply -f grpc_server.yaml

有了服务端集群,自然需要有客户端来访问,接下来我们需要编写客户端的资源文件并部署在k8s上

代码语言:javascript
复制
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grpc-client
  labels:
    app-name: grpc-client
spec:
  selector:
    matchLabels:
      app-name: grpc-client
  template:
    metadata:
      labels:
        app-name: grpc-client
      name: grpc-client
    spec:
      containers:
        - command:
            - client
          args:
            - --address
            - grpc-server-service.default.svc.cluster.local:8088"
          env:
            - name: GRPC_GO_RETRY
              value: "on"
          image: docker.io/leoshus/grpc-demo:v6
          imagePullPolicy: Always
          name: client
          resources:
            limits:
              cpu: "0.5"
              memory: 100Mi
            requests:
              cpu: "0.5"
              memory: 100Mi
      restartPolicy: Always

部署客户端

代码语言:javascript
复制
kubectl apply -f grpc_client.yaml

执行kubectl get po -o wide可以看到当前启动的pod的情况

代码语言:javascript
复制
$ kubectl get po -o wide
NAME                           READY   STATUS    RESTARTS   AGE     IP           NODE             NOMINATED NODE   READINESS GATES
grpc-client-5b595ff864-kqw5g   1/1     Running   0          2m40s   10.1.0.104   docker-desktop   <none>           <none>
grpc-server-6f579c4f88-cd8tj   1/1     Running   0          4m20s   10.1.0.101   docker-desktop   <none>           <none>
grpc-server-6f579c4f88-kkqkm   1/1     Running   0          4m20s   10.1.0.102   docker-desktop   <none>           <none>
grpc-server-6f579c4f88-p9zxp   1/1     Running   0          4m20s   10.1.0.103   docker-desktop   <none>           <none>

看到STATUS一栏所有pod都处于Running状态了,那么可以看下grpc_client打印的日志:

代码语言:javascript
复制
$ kubectl logs -f grpc-client-5b595ff864-kqw5g
2021/05/16 client.go:45: say hello : message:"grpc-server-6f579c4f88-p9zxp say hello tom :2021-05-16 05:45:26"  
2021/05/16 client.go:45: say hello : message:"grpc-server-6f579c4f88-cd8tj say hello tom :2021-05-16 05:45:27"  
2021/05/16 client.go:45: say hello : message:"grpc-server-6f579c4f88-kkqkm say hello tom :2021-05-16 05:45:28"  
2021/05/16 client.go:45: say hello : message:"grpc-server-6f579c4f88-p9zxp say hello tom :2021-05-16 05:45:29"  
2021/05/16 client.go:45: say hello : message:"grpc-server-6f579c4f88-cd8tj say hello tom :2021-05-16 05:45:30"  

日志从输出可见,此时的grpc的负载均衡已经起作用了。

对于服务的扩缩容,也只需要一行命令:

代码语言:javascript
复制
#缩容到1台
kubectl scale --replicas=1 deployment grpc-server
#扩容到5台
kubectl scale --replicas=5 deployment grpc-server

当然具体的扩缩容的细节策略可以在yaml资源文件进行配置

至此一个简单的grpc整合k8s的工程就完成了。详细代码可见(https://github.com/leoshus/grpc-k8s-demo)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-05-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 光华路程序猿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • gRPC
    • 安装软件和撸一个工程
      • 定义服务
        • 生成客户端和服务器代码
          • 创建服务器
            • 创建客户端
            • 制作镜像
            • push镜像
            • 编写k8s资源文件
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档