构建无缝集成的gRPC-Web和Istio的云原生应用教程

栏目: 服务器 · 发布时间: 6年前

内容简介:在这篇文章中,我将引导你构建一个简单的Web应用,使用emoji替换用户输入文本中的关键字,并使用gRPC-Web和Istio与gRPC后端进行通信。

gRPC-Web 使Web应用能够通过类似于Envoy的代理访问gRPC后端。Envoy是Istio的默认代理,因此,我们可以利用Istio的 EnvoyFilter 构件来创建无缝连接的云原生应用。

构建无缝集成的gRPC-Web和Istio的云原生应用教程 构建无缝集成的gRPC-Web和Istio的云原生应用教程

介绍

在这篇文章中,我将引导你构建一个简单的Web应用,使用emoji替换用户输入文本中的关键字,并使用gRPC-Web和Istio与gRPC后端进行通信。

以下是我们创建emoji应用的步骤大纲:

  1. 使用 Protobuf 定义协议格式;
  2. 编译Protobuf定义文件,来生成 Go 和JavaScript文件;
  3. 构建并测试基于Go的gRPC服务,该服务使用emoji替换输入文本中的关键字;
  4. 使用gRPC-Web为emoji服务创建Web界面;
  5. 配置EnvoyFilter并通过Istio部署后端;
  6. 部署Web应用程序并测试我们的emoji服务。

架构

让我们进一步理解emoji服务的最终架构是什么样子。

构建无缝集成的gRPC-Web和Istio的云原生应用教程

简而言之,只要用户提供一些文本,Web应用就会利用gRPC-Web库向Istio Gatway发送HTTP请求。然后,Istio网关将HTTP请求路由到emoji服务旁运行的Proxy sidecar,后者使用Envoy的 gRPC-Web filter将HTTP调用转换成gRPC调用。

定义协议格式

首先,让我们使用Protobuf定义协议格式。

syntax = "proto3";

package emoji;

service EmojiService {
  rpc Emojize (EmojizeRequest) returns (EmojizeReply);
}

message EmojizeRequest {
  string text = 1;
}

message EmojizeReply {
  string emojized_text = 1;
}

我们定义一个名为 EmojiServiceservice ,处理名为 Emojizerpc 调用,该调用接受 EmojizeRequest 对象参数并返回一个 EmojizeReply 实例。

EmojizeRequest 消息参数包含一个名为 textstring 类型的字段,表示用户输入的文本。同样, EmojizeReply 包含一个名为 emojized_textstring 类型的字段,表示最终输出的字符,也即服务端将emoji关键字替换为emoji表情符号的输出内容。

编译Protobuf定义文件

我们先创建一个名为 grpc-web-emoji/emoji/ 的项目目录结构,然后把前面的定义内容写入名为 emoji.proto 的文件。

然后编译emoji.proto文件并生成所需要的Go文件。

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

同样,我们也生成JavaScript文件。

$ protoc -I emoji/ emoji/emoji.proto --js_out=import_style=commonjs:emoji \
         --grpc-web_out=import_style=commonjs,mode=grpcwebtext:emoji

此时,您将获得如下所示的目录结构。

── grpc-web-emoji
   └── emoji
       ├── emoji.pb.go
       ├── emoji.proto
       ├── emoji_grpc_web_pb.js
       └── emoji_pb.js

构建和测试Go后端程序

现在让我们创建一个实现 EmojiService API的Go程序。为此,我们使用以下内容创建一个名为 main.go 的文件。

package main

import (
	"context"
	"log"
	"net"

	proto "github.com/venilnoronha/grpc-web-emoji/emoji"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
	emoji "gopkg.in/kyokomi/emoji.v1"
)

// server is used to implement the EmojiService interface
type server struct{}

// Emojize takes a input string via EmojizeRequest, replaces known keywords with
// actual emoji characters and returns it via a EmojizeReply instance.
func (s *server) Emojize(c context.Context, r *proto.EmojizeRequest)
			(*proto.EmojizeReply, error) {
	return &proto.EmojizeReply{EmojizedText: emoji.Sprint(r.Text)}, nil
}

func main() {
	// listen to TCP requests over port 9000
	lis, err := net.Listen("tcp", ":9000")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	log.Printf("listening on %s", lis.Addr())

	// register the EmojiService implementation with the gRPC server
	s := grpc.NewServer()
	proto.RegisterEmojiServiceServer(s, &server{})
	reflection.Register(s)
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

我已经使用 kyokomi/emoji 库来完成繁重的工作,即将输入文本中的关键字转换为表情符号。

启动服务后如下所示:

$ go run -v main.go
2018/11/12 10:45:12 listening on [::]:9000

我们创建一个名为emoji_client.go的客户端,来实现通过程序测试emoji服务。

package main

import (
	"log"
	"time"

	proto "github.com/venilnoronha/grpc-web-emoji/emoji"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

func main() {
	// connect to the server
	conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("could not connect to the service: %v", err)
	}
	defer conn.Close()

	// send a request to the server
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	c := proto.NewEmojiServiceClient(conn)
	resp, err := c.Emojize(ctx, &proto.EmojizeRequest{
		Text: "I like :pizza: and :sushi:!",
	})
	if err != nil {
		log.Fatalf("could not call service: %v", err)
	}
	log.Printf("server says: %s", resp.GetEmojizedText())
}

我们现在可以运行emoji服务客户端,如下所示。

$ go run emoji_client.go
2018/11/12 10:55:52 server says: I like :pizza:  and :sushi: !

瞧!gRPC版本的emoji服务如期工作了,现在是时候让Web前端启动并运行了。

使用gRPC-Web创建Web界面

首先,让我们创建一个名为 index.html 的HTML页面。该页面向用户显示一个文本编辑器,并调用一个 emojize 函数(我们稍后将定义)将用户输入发送到后端emoji服务。 emojize 函数还将消费后端服务返回的gRPC响应,并使用服务端返回的数据更新用户输入框。

<!DOCTYPE html>
<html>
<body>
  <div id="editor" contentEditable="true" hidefocus="true" onkeyup="emojize()"></div>
  <script src="dist/main.js"></script>
</body>
</html>

我们将如下所示的JavaScript代码放入名为client.js的前端文件。

const {EmojizeRequest, EmojizeReply} = require('emoji/emoji_pb.js');
const {EmojiServiceClient} = require('emoji/emoji_grpc_web_pb.js');

var client = new EmojiServiceClient('http://192.168.99.100:31380');
var editor = document.getElementById('editor');

window.emojize = function() {
  var request = new EmojizeRequest();
  request.setText(editor.innerText);

  client.emojize(request, {}, (err, response) => {
    editor.innerText = response.getEmojizedText();
  });
}

请注意, EmojiServiceClient 与后端emoji服务的连接地址是 http://192.168.99.100:31380 ,而非 http://localhost:9000 。这是因为Web应用程序无法直接与gRPC后端通信,因此,我们将通过Istio部署我们的后端emoji服务。Istio将在Minikube上运行,其IP地址为 192.168.99.100 ,默认的Istio Ingress HTTP端口为 31380

现在,我们需要一些库来生成 index.html 中引用的 dist/main.js 文件。为此,我们使用如下的 npm package.json 配置。

{
  "name": "grpc-web-emoji",
  "version": "0.1.0",
  "description": "gRPC-Web Emoji Sample",
  "devDependencies": {
    "@grpc/proto-loader": "^0.3.0",
    "google-protobuf": "^3.6.1",
    "grpc": "^1.15.0",
    "grpc-web": "^1.0.0",
    "webpack": "^4.16.5",
    "webpack-cli": "^3.1.0"
  }
}

此时,我们使用如下命令来安装库并生成 dist/main.js

$ npm install
$ npx webpack client.js

通过Istio部署后端服务

我们现在可以将后端emoji服务打包到一个容器,并通过Istio进行部署。我们需要安装gRPC-Web EnvoyFilter ,以便将后端gRPC服务的调用在gRPC和HTTP间转换。

我们使用如下内容的 Dockerfile 构建Docker image。

FROM golang:1.11 as builder
WORKDIR /root/go/src/github.com/venilnoronha/grpc-web-emoji/
COPY ./ .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -v -o emoji-service main.go

FROM scratch
WORKDIR /bin/
COPY --from=builder /root/go/src/github.com/venilnoronha/grpc-web-emoji/emoji-service .
ENTRYPOINT [ "/bin/emoji-service" ]
CMD [ "9000" ]
EXPOSE 9000

我们可以如下所示build image,并将其推送到Docker Hub:

$ docker build -t vnoronha/grpc-web-emoji .
$ docker push vnoronha/grpc-web-emoji

接下来,我们定义Kubernetes ServiceDeployment 配置,如下所示,并命名为 backend.yaml

apiVersion: v1
kind: Service
metadata:
  name: backend
  labels:
    app: backend
spec:
  ports:
  - name: grpc-port
    port: 9000
  selector:
    app: backend
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: backend
        version: v1
    spec:
      containers:
      - name: backend
        image: vnoronha/grpc-web-emoji
        imagePullPolicy: Always
        ports:
        - containerPort: 9000

注意,一旦我们通过Istio部署此服务,由于 Service ports name 中的 grpc- 前缀,Istio会将其识别为gRPC服务。

由于我们希望将gRPC-Web filter安装在 backend sidecar代理上,因此我们需要在部署 backend 服务之前安装它。 EnvoyFilter 配置如下所示,我们将其命名为 filter.yaml

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: grpc-web-filter
spec:
  workloadLabels:
    app: backend
  filters:
  - listenerMatch:
      listenerType: SIDECAR_INBOUND
      listenerProtocol: HTTP
    insertPosition:
      index: FIRST
    filterType: HTTP
    filterName: "envoy.grpc_web"
    filterConfig: {}

接下来,我们需要定义一个Istio Gateway 来将HTTP流量路由到后端服务。为此,我们将以下配置写入名为 gateway.yaml 的文件。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: backend
spec:
  host: backend
  subsets:
  - name: v1
    labels:
      version: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: vs
spec:
  hosts:
  - "*"
  gateways:
  - gateway
  http:
  - match:
    - port: 80
    route:
    - destination:
        host: backend
        port:
          number: 9000
        subset: v1
    corsPolicy:
      allowOrigin:
        - "*"
      allowMethods:
        - POST
        - GET
        - OPTIONS
        - PUT
        - DELETE
      allowHeaders:
        - grpc-timeout
        - content-type
        - keep-alive
        - user-agent
        - cache-control
        - content-type
        - content-transfer-encoding
        - custom-header-1
        - x-accept-content-transfer-encoding
        - x-accept-response-streaming
        - x-user-agent
        - x-grpc-web
      maxAge: 1728s
      exposeHeaders:
        - custom-header-1
        - grpc-status
        - grpc-message
      allowCredentials: true

注意,为了能让gRPC-Web正常工作,我们在这里定义了一个复杂的 corsPolicy

我们现在可以按以下顺序简单地部署上述配置。

$ kubectl apply -f filter.yaml
$ kubectl apply -f <(istioctl kube-inject -f backend.yaml)
$ kubectl apply -f gateway.yaml

backend pod启动之后,我们可以验证gRPC-Web filter在sidecar代理中的配置是否正确,如下所示:

$ istioctl proxy-config listeners backend-7bf6c8f67c-8lbm7 --port 9000 -o json
...
    "http_filters": [
        {
            "config": {},
            "name": "envoy.grpc_web"
        },
...

部署和测试Web前端

我们现在已经到了实验的最后阶段。我们通过 Python 启动一个HTTP服务,来为我们的Web应用提供服务。

$ python2 -m SimpleHTTPServer 8080
Serving HTTP on 0.0.0.0 port 8080 ...

让我们前往emoji web页面 http://localhost:8080 .

如果一切顺利,你将拥有一个功能完整的基于gRPC-Web的Web应用,如下所示。

构建无缝集成的gRPC-Web和Istio的云原生应用教程

如果你在Chrome等浏览器上打开开发者工具,你将会看到如下所示的gRPC-Web HTTP请求。

构建无缝集成的gRPC-Web和Istio的云原生应用教程

结论

gRPC-Web提供了一种将gRPC服务的优势带给Web应用的好方法。它目前需要一个中间代理,如Istio数据平面(即Envoy代理),以便将数据在HTTP和gRPC之间转换。然而,一旦我们准备好了基础架构,开发人员就可以无缝使用gRPC构建Web应用。

参考


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

An Introduction to the Analysis of Algorithms

An Introduction to the Analysis of Algorithms

Robert Sedgewick、Philippe Flajolet / Addison-Wesley Professional / 1995-12-10 / CAD 67.99

This book is a thorough overview of the primary techniques and models used in the mathematical analysis of algorithms. The first half of the book draws upon classical mathematical material from discre......一起来看看 《An Introduction to the Analysis of Algorithms》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具