我命由我,不由天!


  • 搜索
prometheus docker golang linux kubernetes

gRPC初学

发表于 2021-01-11 | 分类于 golang | 1 | 阅读次数 2110

起因

grpc这个大名早就有所耳闻,之前学python时,也同样学过一段时间的grpc。现在学go,想学下原汁原味的grpc框架,搞搞清楚其中的原理。为之后的k8s源码提供基础

概念

所谓RPC(remote procedure call 远程过程调用)框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。

image.png

gRPC vs. Restful APIServer端:

gRPC和restful API都提供了一套通信机制,用于server/client模型通信,而且它们都使用http作为底层的传输协议(严格地说, gRPC使用的http2.0,而restful api则不一定)。不过gRPC还是有些特有的优势,如下:

  • gRPC可以通过protobuf来定义接口,从而可以有更加严格的接口约束条件。
  • 另外,通过protobuf可以将数据序列化为二进制编码,这会大幅减少需要传输的数据量,从而大幅提高性能。
  • gRPC可以方便地支持流式通信(理论上通过http2.0就可以使用streaming模式, 但是通常web服务的restful api似乎很少这么用,通常的流式数据应用如视频流,一般都会使用专门的协议如HLS,RTMP等,这些就不是我们通常web服务了,而是有专门的服务器应用。)

使用场景

  • 需要对接口进行严格约束的情况,比如我们提供了一个公共的服务,很多人,甚至公司外部的人也可以访问这个服务,这时对于接口我们希望有更加严格的约束,我们不希望客户端给我们传递任意的数据,尤其是考虑到安全性的因素,我们通常需要对接口进行更加严格的约束。这时gRPC就可以通过protobuf来提供严格的接口约束。
  • 对于性能有更高的要求时。有时我们的服务需要传递大量的数据,而又希望不影响我们的性能,这个时候也可以考虑gRPC服务,因为通过protobuf我们可以将数据压缩编码转化为二进制格式,通常传递的数据量要小得多,而且通过http2我们可以实现异步的请求,从而大大提高了通信效率。

gRPC实例详解

单向认证

服务端:

项目目录:

image.png

Prod.proto
// 指定协议版本
syntax="proto3";

package service;

// 数据类型,相当于结构体
message ProdRequest{
    int32 prod_id=1;
}

message ProdResponse{
    int32 prod_stock=1;
}
// 服务
service ProdService{
    rpc GetProdStock(ProdRequest)returns(ProdResponse);
}
  • 运行 protoc --go_out=plugins=grpc:../service Prod.proto,其中go语言使用--go_out,plugins=grpc,../service 为生成文件的目录,Prod.proto为生成文件的名称

  • 会自动在service下生成Prod.pb.go文件

其中自动生成的Prod.pb.go文件中会生成两个接口类型,而之后服务端就是要实现这接口

image.png

ProdService.go

定义一个结构体来实现上述的接口

package service

import "context"

type ProdService struct {

}

func (this *ProdService) GetProdStock(ctx context.Context,request *ProdRequest) (*ProdResponse, error){
	return &ProdResponse{ProdStock:20},nil
}
keys

keys 文件夹中有两个文件分别为server.crt 和 server.key是使用openssl 工具生成的自签名SSL证书

  1. 生成私钥

    openssl genrsa -des3 -out server.pass.key 2048
    
  2. 去除私钥中的密码

    openssl rsa -in server.pass.key -out server.key 
    
  3. 生成CSR(证书签名请求)这里的 /CN(Common Name) 后面还会见到

openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=Guangdong/L=Guangzhou/O=xdevops/OU=xdevops/CN=gitlab.xdevops.cn"
  • req 生成证书签名请求

  • -new 新生成

  • -key 私钥文件

  • -out 生成的CSR文件

  • -subj 生成CSR证书的参数

  • subj参数说明如下:

字段字段含义示例
/C=Country 国家CN
/ST=State or Province 省Guangdong
/L=Location or City 城市Guangzhou
/O=Organization 组织或企业xdevops
/OU=Organization Unit 部门xdevops
/CN=Common Name 域名或IPgitlab.xdevops.cn
  1. 生成自签名SSL证书 openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

X.509证书包含三个文件:key,csr,crt。

  • key是服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到数据的解密
  • csr是证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名
  • crt是由证书颁发机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者的签名等信息

备注:在密码学中,X.509是一个标准,规范了公开秘钥认证、证书吊销列表、授权凭证、凭证路径验证算法等。

main.go
package main

import (
	"awesomeProject5/service"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc"
	"log"
	"net"
)

func main(){
	creds,err := credentials.NewServerTLSFromFile("keys/server.crt","keys/server.key")
	if err != nil{
		log.Fatal(err)
		return
	}
	rpcServer := grpc.NewServer(grpc.Creds(creds))
	service.RegisterProdServiceServer(rpcServer,&service.ProdService{})

	lis,_ := net.Listen("tcp",":8081")

	rpcServer.Serve(lis)
}

客户端:

项目目录:

image.png

service

只需要将服务端定义好的 Prod.pb.go 拷贝过来即可

keys

现在为单向认证,只需要拷贝.crt 文件

main.go
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"grpcclient/service"
	"log"
)

func main(){
  // 这里Common Name 为刚刚生成自签证书所填写的
	creds, err := credentials.NewClientTLSFromFile("keys/server.crt",
		"gitlab.xdevops.cn")
	if err != nil{
		log.Fatal(err)
		return
	}
  // 如果服务端没有证书认证,这里 	conn,err := grpc.Dial(":8081",grpc.WithInsecure())
	conn,err := grpc.Dial(":8081",grpc.WithTransportCredentials(creds))
	if err != nil{
		log.Fatal(err)
		return
	}
	defer conn.Close()

	prodClient := service.NewProdServiceClient(conn)
	prdRes,err :=prodClient.GetProdStock(context.Background(),
		&service.ProdRequest{ProdId:1})
	if err != nil{
		log.Fatal(err)
	}
	fmt.Println(prdRes.ProdStock)
}

这里可能会出现

rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0"

如果出现上述报错,是因为 go 1.15 版本开始废弃 CommonName,因此推荐使用 SAN 证书。 如果想兼容之前的方式,需要设置环境变量 GODEBUG 为 x509ignoreCN=0。

export GODEBUG="x509ignoreCN=0"

双向认证:

生成证书

生成ca证书
# 会生成 ca.pem 和 ca.key
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 3650 -key ca.key -out ca.pem
生成服务端证书
# 会生成 ca.srl server.pem server.csr server.key
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
openssl x509 -req -sha256 -CA ca.pem -CAkey ca.key -CAcreateserial -days 3650 -in server.csr -out server.pem
生成客户端证书
# 会生成 client.pem client.csr client.key
openssl ecparam -genkey -name secp384r1 -out client.key
openssl req -new -key client.key -out client.csr
openssl x509 -req -sha256 -CA ca.pem -CAkey ca.key -CAcreateserial -days 3650 -in client.csr -out client.pem

服务端

将 server.pem、server.key、ca.pem 放入cert 文件夹中,这里我的certPool中AppendCertsFromPEM方法老是爆红,但运行时正常的,发现是goland 运行插件的时候报错了,而导致不能很好的提示。关闭插件后正常

package main

import (
	"awesomeProject5/service"
	"crypto/tls"
	"crypto/x509"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"io/ioutil"
	"net"
)

func main(){
	//从证书相关文件中读取和解析信息,得到证书公钥、密钥对
	cert,_ := tls.LoadX509KeyPair("cert/server.pem","cert/server.key")
	// 创建一个新的、空的 CertPool
	certPool  := x509.NewCertPool()
	ca,_ := ioutil.ReadFile("cert/ca.pem")
	//尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用
	certPool.AppendCertsFromPEM(ca)
	//构建基于 TLS 的 TransportCredentials 选项
	creds := credentials.NewTLS(&tls.Config{
		//设置证书链,允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		//要求必须校验客户端的证书。可以根据实际情况选用以下参数
		ClientAuth: tls.RequireAndVerifyClientCert,
		//设置根证书的集合,校验方式使用 ClientAuth 中设定的模式
		ClientCAs: certPool,
	})
	rpcServer := grpc.NewServer(grpc.Creds(creds))

	service.RegisterProdServiceServer(rpcServer,&service.ProdService{})

	lis,_ := net.Listen("tcp",":8081")

	rpcServer.Serve(lis)
}

客户端

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"grpcclient/service"
	"io/ioutil"
	"log"
	"crypto/tls"
	"crypto/x509"
)


func main(){
	//从证书相关文件中读取和解析信息,得到证书公钥、密钥对
	cert,_ := tls.LoadX509KeyPair("cert/client.pem","cert/client.key")
	// 创建一个新的、空的 CertPool
	certPool  := x509.NewCertPool()
	ca,_ := ioutil.ReadFile("cert/ca.pem")
	//尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用
	certPool.AppendCertsFromPEM(ca)
	//构建基于 TLS 的 TransportCredentials 选项
	creds := credentials.NewTLS(&tls.Config{
		//设置证书链,允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		//要求必须校验客户端的证书。可以根据实际情况选用以下参数
		ServerName: "localhost",
		RootCAs:certPool,
	})
	conn,err := grpc.Dial(":8081",grpc.WithTransportCredentials(creds))
	if err != nil{
		log.Fatal(err)
		return
	}
	defer conn.Close()

	prodClient := service.NewProdServiceClient(conn)
	prdRes,err :=prodClient.GetProdStock(context.Background(),
		&service.ProdRequest{ProdId:1})
	if err != nil{
		log.Fatal(err)
	}
	fmt.Println(prdRes.ProdStock)
}

参考:

  • b站gRPC学习视频
  • 使用OpenSSL生成自签名SSL证书
  • [openssl 生成证书上 grpc 报 legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0]
  • gRPC详解
  • grpc-gateway
  • 本文作者: Dante
  • 本文链接: https://gaodongfei.com/archives/start-grpc
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# golang
goroutine and channel
字段校验:第三方库protoc-gen-validate的使用
  • 文章目录
  • 站点概览
Dante

Dante

119 日志
5 分类
5 标签
RSS
Creative Commons
0%
© 2023 Dante
由 Halo 强力驱动
|
主题 - NexT.Pisces v5.1.4
沪ICP备2020033702号