This is the sixth day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

introduce

This article describes how to quickly set up gRPC agent through RK-boot.

What is a gRPC agent?

The gRPC agent accepts gRPC requests and forwards them to other gRPC services based on user policies. There are few application scenarios, such as forwarding requests to different gRPC services based on environment parameters.

The installation

go get github.com/rookie-ninja/rk-boot
Copy the code

Quick start

GRPC agents that are started with RK-boot have a limitation. Only requests sent in code can be proxied. The request in the form of grpc-gateway or grpcurl is not supported.

Currently, RK-boot supports three policies.

  • HeaderBased: Identifies the agent destination based on the Metadata value in the gRPC request.
  • PathBased: Determines the proxy destination by the request path.
  • Ip-based: determines the proxy destination based on the remote IP address.

In the example below, we start three services.

  • Proxy (8080) : the proxy service, according to the headerBased strategy, forwarding rk API. V1. RkCommonService. Healthy test request to the service.
  • Test (8081) : tests the gRPC service of the domain and accepts requests from the proxy.
  • Client: the proxy service sends rk API. V1. RkCommonService. Healthy requests.

1. Create proxy/boot.yaml & proxy/main.go

The proxy service does not implement any gRPC method. If the Metadata requested by the gRPC contains domain:test, the gRPC forwards the Metadata.

By default, the proxy picks an address from proxy.rules.dest to forward.

  • boot.yaml
---
grpc:
  - name: greeter                        # Required
    port: 8080                           # Required
    enabled: true                        # Required
    proxy:
      enabled: true                      # Optional, enable proxy server
      rules:
        - type: headerBased              # Optional, options:[headerBased, pathBased, ipBased]
          headerPairs: ["domain:test"]   # Optional, header pairs separated by colon(:)
          dest: ["localhost:8081"]       # Optional, destinations
    interceptors:
      loggingZap:
        enabled: true
Copy the code
  • main.go
// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main

import (
	"context"
	"github.com/rookie-ninja/rk-boot"
)

// Application entrance.
func main(a) {
	// Create a new boot instance.
	boot := rkboot.NewBoot()

	// Bootstrap
	boot.Bootstrap(context.Background())

	// Wait for shutdown sig
	boot.WaitForShutdownSig(context.Background())
}
Copy the code

2. Create test/boot.yaml & test/main.go

Listen on port 8081.

Start CommonService service, receiving the Proxy agent over the rk API. V1. RkCommonService. Healthy requests.

  • boot.yaml
---
grpc:
  - name: greeter                     # Required
    port: 8081                        # Required
    enabled: true                     # Required
    commonService:
      enabled: true                   # Optional, default: false
    interceptors:
      loggingZap:
        enabled: true
Copy the code
  • main.go
// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main

import (
	"context"
	"github.com/rookie-ninja/rk-boot"
)

// Application entrance.
func main(a) {
	// Create a new boot instance.
	boot := rkboot.NewBoot()

	// Bootstrap
	boot.Bootstrap(context.Background())

	// Wait for shutdown sig
	boot.WaitForShutdownSig(context.Background())
}
Copy the code

3.client/main.go

Add domain:test to the sent request metadata and let the proxy request domain:test.

// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main

import (
	"context"
	"fmt"
	api "github.com/rookie-ninja/rk-grpc/boot/api/third_party/gen/v1"
	"github.com/rookie-ninja/rk-grpc/interceptor/context"
	"github.com/rookie-ninja/rk-grpc/interceptor/log/zap"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	"log"
)

// In this example, we will create a simple gRpc client and enable RK style logging interceptor.
func main(a) {
	/ / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	// ********** Enable interceptors *************
	/ / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	opts := []grpc.DialOption{
		grpc.WithChainUnaryInterceptor(
			rkgrpclog.UnaryClientInterceptor(),
		),
		grpc.WithInsecure(),
		grpc.WithBlock(),
	}

	// 1: Create grpc client
	conn, client := createCommonServiceClient(opts...)
	defer conn.Close()

	// 2: Wrap context, this is required in order to use bellow features easily.
	ctx := rkgrpcctx.WrapContext(context.Background())
	// Add header to make proxy request to test server
	ctx = metadata.AppendToOutgoingContext(ctx, "domain"."test")

	// 3: Call server
	ifresp, err := client.Healthy(ctx, &api.HealthyRequest{}); err ! =nil {
		rkgrpcctx.GetLogger(ctx).Fatal("Failed to send request to server.", zap.Error(err))
	} else {
		rkgrpcctx.GetLogger(ctx).Info(fmt.Sprintf("[Message]: %s", resp.String()))
	}
}

func createCommonServiceClient(opts ... grpc.DialOption) (*grpc.ClientConn, api.RkCommonServiceClient) {
	// 1: Set up a connection to the server.
	conn, err := grpc.DialContext(context.Background(), "localhost:8080", opts...)
	iferr ! =nil {
		log.Fatalf("Failed to connect: %v", err)
	}

	// 2: Create grpc client
	client := api.NewRkCommonServiceClient(conn)

	return conn, client
}
Copy the code

4. Folder structure

$tree. ├ ─ ─ client │ ├ ─ ─. Mod │ └ ─ ─ main. Go ├ ─ ─ the proxy │ ├ ─ ─ the boot. The yaml │ ├ ─ ─. Mod │ ├ ─ ─. Sum │ └ ─ ─ main. Go └ ─ ─ ├── Go. └─ main. Go directories, 9 filesCopy the code

5. Start proxy, test

$ go run proxy/main.go
$ go run test/main.go
Copy the code

6. Verify

Start the client/main. Go

  • The client side log
The 2021-11-13 T02: behold. 598 + 0800 INFO client/main go: 45 [Message] : fields:{key:"healthy" value:{bool_value:true}} ------------------------------------------------------------------------ EndTime = 2021-11-13 T02: behold. 597875 + 08:00 startTime = 2021-11-13 T02: behold. 59247 + 08:00 elapsedNano = 5405233 timezone = CST ids={"eventId":"7ffe293d-24f4-42b3-9325-64bde414912c"} app={"appName":"rk","appVersion":"","entryName":"grpc","entryType":"grpc"} Env = {" arch ", "amd64", "az" : "*", "domain" : "*", "hostname" : "lark. Local", "localIP" : "10.8.0.2", "OS" : "Darwin", "realm" : "*", "region ":" * "} payloads={"grpcMethod":"Healthy","grpcService":"rk.api.v1.RkCommonService","grpcType":"unaryClient","remoteIp":"localhos t","remotePort":"8080"} error={} counters={} pairs={} timing={} remoteAddr=localhost:8080 operation=/rk.api.v1.RkCommonService/Healthy resCode=OK eventStatus=Ended EOECopy the code
  • The proxy client log

Proxy grpcType is streamServer.

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- endTime = 2021-11-13 T02: behold. 597337 + 08:00 StartTime = 2021-11-13 T02: behold. 593252 + 08:00 elapsedNano = 4085300 timezone = CST ids={"eventId":"f32f7895-a4b2-4c8c-bedc-1d4733db78a7"} app={"appName":"rk-demo","appVersion":"master-2c9c6fd","entryName":"greeter","entryType":"GrpcEntry"} Env = {" arch ", "amd64", "az" : "*", "domain" : "*", "hostname" : "lark. Local", "localIP" : "10.8.0.2", "OS" : "Darwin", "realm" : "*", "region ":" * "} payloads={"grpcMethod":"Healthy","grpcService":"rk.api.v1.RkCommonService","grpcType":"streamServer","gwMethod":"","gwPa Th ":" ", "gwScheme" : ""," gwUserAgent ":" "} error = {} counters = {} pairs = {} timing = {} remoteAddr = 127.0.0.1:58441 operation=/rk.api.v1.RkCommonService/Healthy resCode=OK eventStatus=Ended EOECopy the code
  • The test client log

GrpcType on the test side is unaryServer.

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- endTime = 2021-11-13 T02: behold. 596967 + 08:00 StartTime = 2021-11-13 T02: behold. 59692 + 08:00 elapsedNano = 47149 timezone = CST ids={"eventId":"6eb9b10a-153a-4955-85b0-f227e3ec54b2"} app={"appName":"rk-demo","appVersion":"master-2c9c6fd","entryName":"greeter","entryType":"GrpcEntry"} Env = {" arch ", "amd64", "az" : "*", "domain" : "*", "hostname" : "lark. Local", "localIP" : "10.8.0.2", "OS" : "Darwin", "realm" : "*", "region ":" * "} payloads={"grpcMethod":"Healthy","grpcService":"rk.api.v1.RkCommonService","grpcType":"unaryServer","gwMethod":"","gwPat H ":" ", "gwScheme" : ""," gwUserAgent ":" "} error = {} counters = {} pairs = {" healthy ":" true "} timing = {} remoteAddr = 10.8.0.2:58443 operation=/rk.api.v1.RkCommonService/Healthy resCode=OK eventStatus=Ended EOECopy the code

PathBased strategy

You can modify the boot.yaml file to modify the agent policy.

---
grpc:
  - name: greeter                                         # Required
    port: 8080                                            # Required
    enabled: true                                         # Required
    proxy:
      enabled: true                                       # Optional, enable proxy server
      rules:
        - type: pathBased                                 # Optional, options:[headerBased, pathBased, ipBased]
          paths: ["/rk.api.v1.RkCommonService/Healthy"]   # Optional, gRPC method, support golang regex.
          dest: ["localhost:8081"]                        # Optional, destinations
Copy the code

IpBased strategy

You can modify the boot.yaml file to modify the agent policy.

Proxy.rules. ips supports CIDR.

---
grpc:
  - name: greeter                    # Required
    port: 8080                       # Required
    enabled: true                    # Required
    proxy:
      enabled: true                  # Optional, enable proxy server
      rules:
        - type: ipBased              # Optional, options:[headerBased, pathBased, ipBased]
          ips: ["127.0.0.0/24"]      # Optional, remote Ips, support CIDR.
          dest: ["localhost:8081"]   # Optional, destinations
Copy the code