跟我一起玩微服务网关
本文是个人在学习书籍《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
注意:
- docker下的kong没有arm64的镜像,所以M1芯片无法使用docker方式进行部署,即便指定amd64环境也会报qemu相关错误。
- {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