本文是个人在学习书籍《Go语言高并发与微服务实战》第9章内容过程中,而动手做的小实验,涉及consul、go-kit、kong、jwt、zipkin相关知识,仅供参考。

简易网关的Go实现

代码示例

【网关代码】 gateway/main.go

package main

import (
	"flag"
	"fmt"
	"github.com/go-kit/kit/log"
	"github.com/hashicorp/consul/api"
	"math/rand"
	"net/http"
	"net/http/httputil"
	"os"
	"os/signal"
	"strings"
	"syscall"
)

func main() {

	// 创建环境变量
	var (
		consulHost = flag.String("consul.host", "127.0.0.1", "consul server ip address")
		consulPort = flag.String("consul.port", "8500", "consul server port")
	)
	flag.Parse()

	//创建日志组件
	var logger log.Logger
	{
		logger = log.NewLogfmtLogger(os.Stderr)
		logger = log.With(logger, "ts", log.DefaultTimestampUTC)
		logger = log.With(logger, "caller", log.DefaultCaller)
	}

	// 创建consul api客户端
	consulConfig := api.DefaultConfig()
	consulConfig.Address = "http://" + *consulHost + ":" + *consulPort
	consulClient, err := api.NewClient(consulConfig)
	if err != nil {
		logger.Log("err", err)
		os.Exit(1)
	}

	//创建反向代理
	proxy := NewReverseProxy(consulClient, logger)

	errc := make(chan error)
	go func() {
		c := make(chan os.Signal)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
		errc <- fmt.Errorf("%s", <-c)
	}()

	//开始监听
	go func() {
		logger.Log("transport", "HTTP", "addr", "9090")
		errc <- http.ListenAndServe(":9090", proxy)
	}()

	// 开始运行,等待结束
	logger.Log("exit", <-errc)
}

// NewReverseProxy 创建反向代理处理方法
func NewReverseProxy(client *api.Client, logger log.Logger) *httputil.ReverseProxy {

	//创建Director
	director := func(req *http.Request) {

		//查询原始请求路径
		reqPath := req.URL.Path
		if reqPath == "" {
			return
		}
		//按照分隔符'/'对路径进行分解,获取服务名称serviceName
		pathArray := strings.Split(reqPath, "/")
		serviceName := pathArray[1]

		//调用consul api查询serviceName的服务实例列表
		result, _, err := client.Catalog().Service(serviceName, "", nil)
		if err != nil {
			logger.Log("ReverseProxy failed", "query service instance error", err.Error())
			return
		}

		if len(result) == 0 {
			logger.Log("ReverseProxy failed", "no such service instance", serviceName)
			return
		}

		//重新组织请求路径,去掉服务名称部分
		destPath := strings.Join(pathArray[2:], "/")

		//随机选择一个服务实例
		tgt := result[rand.Int()%len(result)]
		logger.Log("service id", tgt.ServiceID)

		//设置代理服务地址信息
		req.URL.Scheme = "http"
		req.URL.Host = fmt.Sprintf("%s:%d", tgt.ServiceAddress, tgt.ServicePort)
		req.URL.Path = "/" + destPath
	}
	return &httputil.ReverseProxy{Director: director}

}

【服务代码】 可参考往期文章:《微服务注册发现de小实验》

在此为了加深go-kit的层级划分,特此尝试扩展一个extra服务,改动点如下。

main.go 增加endpoint的初始化

    extraEndpoint := MakeExtraEndpoint(svc)

	//把算术运算Endpoint和健康检查Endpoint封装至StringEndpoints
	endpts := StringEndpoints{
		StringEndpoint:      endpoint,
		HealthCheckEndpoint: healthEndpoint,
		ExtraEndpoint:       extraEndpoint,
	}

endpoints.go 结构体变更,并增加扩展方法

// CalculateEndpoint define endpoint
type StringEndpoints struct {
	StringEndpoint      endpoint.Endpoint
	HealthCheckEndpoint endpoint.Endpoint
	ExtraEndpoint       endpoint.Endpoint
}

type ExtraRequest struct {
	Extra string `json:"extra"`
}
type ExtraResponse struct {
	Extra string `json:"extra"`
}

// MakeExtraEndpoint 自己测试扩充一个Endpoint
func MakeExtraEndpoint(svc Service) endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) (response interface{}, err error) {
		req := request.(ExtraRequest)
		extra := svc.Extra(req.Extra)
		return ExtraResponse{extra}, nil
	}
}

service.go 增加接口定义,并实现扩展服务

// Service Define a service interface
type Service interface {
	// Concat a and b
	Concat(a, b string) (string, error)

	// a,b pkg string value
	Diff(a, b string) (string, error)

	// Extra Service demo
	Extra(extra string) string

	// HealthCheck check service health status
	HealthCheck() bool
}

// 扩展的一个Service
func (s StringService) Extra(extra string) string {
	return extra + "." + time.Now().String()
}

transports.go 扩展一个路由,并增加decode方法

func MakeHttpHandler(ctx context.Context, endpoints StringEndpoints, logger log.Logger) http.Handler {

    // 扩展一个路由
	r.Methods("GET").Path("/extra/{extra}").Handler(kithttp.NewServer(
		endpoints.ExtraEndpoint,
		decodeExtraRequest,
		encodeStringResponse,
		options...,
	))
	return r
}

// 扩展一个解码方法
func decodeExtraRequest(_ context.Context, r *http.Request) (interface{}, error) {
	vars := mux.Vars(r)

	extra, ok := vars["extra"]
	if !ok {
		return nil, ErrorBadRequest
	}

	return ExtraRequest{
		Extra: extra,
	}, nil
}

运行结果

// 编译服务
cd string-service
go build -o string-service *.go

// 编译网关
cd gateway
go build -o gateway *.go

// 启动consul
consul agent -dev

// 启动服务1
./string-service -consul.host localhost -consul.port 8500 -service.host 127.0.0.1 -service.port 8000

// 启动服务2
./string-service -consul.host localhost -consul.port 8500 -service.host 127.0.0.1 -service.port 8001

// 启动网关
./gateway -consul.host localhost -consul.port 8500

// CURL请求测试
curl localhost:9090/string-service/op/Diff/abc/bcd -X POST

// 扩展的服务也正常
curl localhost:9090/string-service/extra/abcdefg
# 请求端
{"result":"bc","error":null}

# 网关端
ts=2021-08-04T07:16:50.78641Z caller=main.go:55 transport=HTTP addr=9090
ts=2021-08-04T07:18:56.531668Z caller=main.go:95 serviceid=string-service8aa99dfb-23ac-4618-9f55-d8e877808dc9
ts=2021-08-04T07:19:49.585397Z caller=main.go:95 serviceid=string-servicef6eed43e-26fb-420f-8fd8-384844a1aab9
ts=2021-08-04T07:19:51.449128Z caller=main.go:95 serviceid=string-servicef6eed43e-26fb-420f-8fd8-384844a1aab9

网关Kong的初体验

Kong Gateway是为微服务优化的开源、轻量级API网关,基于 OpenResty(Nginx+Lua)研发,提供了诸如身份认证,权限控制,流量控制,日志等一系列API相关的组件,拥有无与伦比的延迟、性能和可伸缩性。

环境&安装

mac环境可参考此文档:https://docs.konghq.com/install/macos/

Docker环境可按照如下步骤搭建:

docker pull kong/kong-gateway:2.4.1.0-alpine
docker tag kong/kong-gateway:2.4.1.0-alpine kong-ee
docker network create kong-ee-net

docker run -d --name kong-ee-database \
  --network=kong-ee-net \
  -p 5432:5432 \
  -e "POSTGRES_USER=kong" \
  -e "POSTGRES_DB=kong" \
  -e "POSTGRES_PASSWORD=kong" \
  postgres:9.6

docker run --rm --network=kong-ee-net \
  -e "KONG_DATABASE=postgres" \
  -e "KONG_PG_HOST=kong-ee-database" \
  -e "KONG_PG_PASSWORD=kong" \
  -e "KONG_PASSWORD=kong" \
  kong-ee kong migrations bootstrap
  
docker run -d --name kong-ee --network=kong-ee-net \
  -e "KONG_DATABASE=postgres" \
  -e "KONG_PG_HOST=kong-ee-database" \
  -e "KONG_PG_PASSWORD=kong" \
  -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
  -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
  -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
  -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
  -e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \
  -e "KONG_ADMIN_GUI_URL=http://{HOST_NAME}:8002" \
    -p 8000:8000 \
    -p 8443:8443 \
    -p 8001:8001 \
    -p 8444:8444 \
    -p 8002:8002 \
    -p 8445:8445 \
    -p 8003:8003 \
    -p 8004:8004 \
    kong-ee

环境搭建好后可用指令检查服务是否正常
curl -X GET --url http://localhost:8001/services
也可浏览器访问如下地址验证
http://{HOST_NAME}:8002/default/dashboard

注意:

  1. docker下的kong没有arm64的镜像,所以M1芯片无法使用docker方式进行部署,即便指定amd64环境也会报qemu相关错误。
  2. {HOST_NAME} 根据个人实际情况填写,如果只在本机使用可写127.0.0.1,容器内同network可使用容器name,如果对外提供服务则填写机器自身的IP地址。

新增服务和路由

两种方式:

  • 管理后台直接操作
  • 使用kong的api进行操作

附:api的官方文档 https://docs.konghq.com/enterprise/2.4.x/admin-api/#route-object

指令方式操作如下

// 新增服务
curl -X POST \
  --url "http://{HOST_NAME}:8001/services" \
  --data "name=yison-blog" \
  --data "url=http://yyeer.com/about"

// 新增路由
curl -X POST \
  --url "http://{HOST_NAME}:8001/services/yison-blog/routes" \
  --data "name=yison-blog.about" \
  --data "hosts[]={HOST_NAME}:8000" \
  --data "paths[]=/about"

安装&使用JWT插件

指令方式操作如下

// 安装插件
curl -X POST \
  --url "http://{HOST_NAME}:8001/routes/c4a868a5-262c-4ef2-89ce-205088de56a2/plugins" \
  --data "name=jwt"

// 新增消费者
curl -X POST \
  --url "http://{HOST_NAME}:8001/consumers" \
  --data "username=yison"  

// 生成消费者密钥
curl -X POST \
  --url "http://{HOST_NAME}:8001/consumers/yison/jwt"

密钥示例:

{
    "rsa_public_key": null,
    "id": "61f80e92-0c63-4a18-afd5-e342f51930bd",
    "secret": "1hRGbvkH4cGzm17CzfcwwBrMXmjpe2V9",
    "created_at": 1627382073,
    "tags": null,
    "consumer": {
        "id": "57ead688-da98-481f-b7c8-e4bea7e4eaf8"
    },
    "algorithm": "HS256",
    "key": "DibULfaLzE0KZ29uJ28MTD9cCQjEuV8J"
}

prometheus插件

这里为方便体验,直接用后台操作插件新增和配置就行了。

配置完成后,检查metrics是否正常:curl http://{HOST_NAME}:8001/metrics

部署prometheus+grafana服务:

// 容器启动prometheus
docker run --network=kong-ee-net --name prometheus -d -p 9090:9090 prom/prometheus

// 修改配置文件,监听8001端口
docker exec -it prometheus /bin/sh
vi /etc/prometheus/prometheus.yml
`- targets: ['localhost:9090','{HOST_NAME}:8001'] `

// 重新启动,加载新的配置文件
docker restart prometheus

// 容器启动grafana服务
docker run -d --network=kong-ee-net --name=grafana -p 3000:3000 grafana/grafana

运行效果:

zipkin插件

部署zipkin服务:

docker pull openzipkin/zipkin
docker run --network=kong-ee-net --name zipkin -d -p 9411:9411 openzipkin/zipkin

Kong手动安装zipkin插件,然后配置http endpoint地址: http://{HOST_NAME}:9411/api/v2/spans

运行效果:

总结

  • 首先使用go做了一个简易的网关
  • 然后动手实操了高性能Kong网关,并体验了一些实用的插件
  • 通过这样的方式,了解网关的作用以及其运行方式,加深了对微服务的理解

关于我

name: yison.li
blog: http://yyeer.com
github: https://github.com/yisonli