chiachan
chiachan
Published on 2025-01-09 / 34 Visits
0

对kratos服务进行颜色标签过滤

解决如何对kratos服务进行颜色标签过滤

应用场景

  • 生产环境下新功能测试
  • 带标签状态的需求
  • 对来源用户进行分类服务划分

给服务打上color标签

在main.go中,metadata加入color标签即可,可以参考一下方式。从启动命令中读取

...
func init() {
	flag.StringVar(&flagconf, "conf", "../../configs", "config path, eg: -conf config.yaml")
	flag.StringVar(&color, "color", "", "color tag, eg: -color blue")
}

func newApp(logger log.Logger, gs *grpc.Server, hs *http.Server) *kratos.App {
	metadata := map[string]string{}
	if color != "" {
		metadata["color"] = color
	}
	opts := []kratos.Option{
		kratos.ID(Name + "_" + Version + "_" + id + "_" + strconv.Itoa(int(time.Now().UnixMicro()))),
		kratos.Name(Name),
		kratos.Version(Version),
		kratos.Metadata(metadata),
		kratos.Logger(logger),
		kratos.Server(
			gs,
			hs,
		),
	}
	if reg != nil {
		opts = append(opts, kratos.Registrar(reg))
	}
	return kratos.New(opts...)
}
...

服务内部调用接入

过滤器(color.go)

目录位置参考github.com/go-kratos/kratos/selector/filter

package filter

import (
	"context"

	"github.com/go-kratos/kratos/v2/selector"
)

// Color is color filter.
func Color(color string) selector.NodeFilter {
	return func(_ context.Context, nodes []selector.Node) []selector.Node {
		newNodes := make([]selector.Node, 0, len(nodes))
		for _, n := range nodes {
			if n.Metadata() == nil || n.Metadata()["color"] == "" {
				continue
			}
			if n.Metadata()["color"] == color {
				newNodes = append(newNodes, n)
			}
		}
		return newNodes
	}
}

使用方法

在newClient时,引入grpc.WithNodeFilter(filter.Color("blue"))http.WithNodeFilter(filter.Color("blue"))可以请求指定服务是"color:blue"的节点

func NewGrpcClient(dsn string) (GreeterClient, error) {
	if dsn == "" {
		if addr := os.Getenv(api.HTTPAddrEnvName); addr != "" {
			dsn = fmt.Sprintf("consul://%s", addr)
		}
	}
	dis, err := discovery.Create(dsn)
	if err != nil {
		return nil, err
	}
	conn, cErr := grpc.DialInsecure(
		context.Background(),
		grpc.WithEndpoint("discovery:///helloworld"),
		grpc.WithDiscovery(dis),
		grpc.WithTimeout(15*time.Second),
		grpc.WithMiddleware(
			tracing.Client(),
		),
		grpc.WithNodeFilter(filter.Color("blue")),
	)
	if cErr != nil {
		return nil, cErr
	}
	return NewGreeterClient(conn), nil
}

func NewHttpClient(dsn string) (GreeterHTTPClient, error) {
	dis, err := discovery.Create(dsn)
	if err != nil {
		return nil, err
	}
	conn, cErr := http.NewClient(
		context.Background(),
		http.WithMiddleware(
			tracing.Client(),
			recovery.Recovery(),
		),
		http.WithEndpoint("discovery:///helloworld"),
		http.WithDiscovery(dis),
		http.WithNodeFilter(filter.Color("blue"))
	)
	if cErr != nil {
		return nil, cErr
	}
	return NewGreeterHTTPClient(conn), nil
}

在go-kratos/gateway使用

官方提供了https://github.com/go-kratos/gateway

api/middleware/selectorFilter/v1/filter.proto

syntax = "proto3";

package gateway.middleware.selectorFilter.v1;
option go_package = "github.com/go-kratos/gateway/api/gateway/middleware/selectorFilter/v1";

message Filters {
  repeated Filter filters = 1;
}

message Filter {
  string name = 1; // 已支持 color、version;之前请求来来源的header携带X-相应标签时可触发,如:X-Color、X-Version
}

middleware/selectorFilter/selector.go

package selectorFitler

import (
	"net/http"

	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/anypb"

	config "github.com/go-kratos/gateway/api/gateway/config/v1"
	v1 "github.com/go-kratos/gateway/api/gateway/middleware/selectorFilter/v1"
	"github.com/go-kratos/gateway/app/gateway/middleware"
	"github.com/go-kratos/kratos/selector/filter" // 这里自行改
)

func init() {
	middleware.Register("selector_filter", Middleware)
}
func Middleware(c *config.Middleware) (middleware.Middleware, error) {
	return func(next http.RoundTripper) http.RoundTripper {
		return middleware.RoundTripperFunc(func(req *http.Request) (reply *http.Response, err error) {
			options := &v1.Filters{}
			if c.Options != nil {
				if err := anypb.UnmarshalTo(c.Options, options, proto.UnmarshalOptions{Merge: true}); err != nil {
					return nil, err
				}
			}
			if len(options.Filters) > 0 {
				for _, f := range options.Filters {
					switch f.Name {
					case "color":
						color := req.Header.Get("X-Color")
						if color != "" {
							middleware.WithSelectorFitler(req.Context(), filter.Color(color))
						}
					case "version":
						version := req.Header.Get("X-Version")
						if version != "" {
							middleware.WithSelectorFitler(req.Context(), filter.Version(version))
						}
					}
				}
			}
			return next.RoundTrip(req)
		})
	}, nil
}

使用方法

这里给完整的helloword.yaml文件。配置selector_filter插件,开启color过滤即可

endpoints:
  - path: /helloworld/*
    timeout: 15s
    protocol: HTTP
    backends:
      - target: 'discovery:///helloworld'
    middlewares:
      - name: circuitbreaker
        options:
          '@type': type.googleapis.com/gateway.middleware.circuitbreaker.v1.CircuitBreaker
          successRatio: { "success": 0.6, "request": "100", "bucket": "10", "window": "3s" }
          responseData:
            status_code: 503
            header:
              - key: 'Content-Type'
                value: [ 'application/json' ]
          assertCondtions:
            - { "by_status_code": "200" }
      - name: rewrite
        options:
          '@type': type.googleapis.com/gateway.middleware.rewrite.v1.Rewrite
      - name: selector_filter
        options:
          '@type': type.googleapis.com/gateway.middleware.selectorFilter.v1.Filters
          filters:
            - name: color
            - name: version