MoE 系列 - 如何使用 Golang 扩展 Envoy [一]
背景
MoE,MOSN on Envoy 是 MOSN 团队提出的技术架构,经过近两年的发展,在蚂蚁内部已经得到了很好的验证;并且去年我们也将底层的 Envoy Go 七层扩展贡献了 Envoy 官方,MOSN 也初步支持了使用 Envoy 作为网络底座的能力。
准备写一系列的文章,逐一介绍这里面的技术,本文是开篇,重点介绍 MoE 中的基础技术,Envoy Go 扩展。
FAQ
开始前,先回答几个基本的问题:
-
MoE 与 Envoy Go 扩展的区别
MoE 是技术架构,Envoy Go 扩展是连接 MOSN 和 Envoy 的基础技术
-
Envoy Go 扩展,与用 Go 来编译 Wasm 有什么区别
Envoy Go 支持 Go 语言的所有特性,包括 Goroutine,Go Wasm 则只能使用少量的 Go 语言特性,尤其是没有 Goroutine 的支持
-
Go 是静态链接到 Envoy 么?
不是的,Go 扩展编译成为 so,Envoy 动态加载 so,不需要重新编译 Envoy
-
Envoy Go 支持流式处理么?
支持的。
由于 Go 扩展提供的是底层的 API,非常的灵活,使用上相对会稍微复杂一些;如果只想简单的使用,可以使用 MOSN 的 filter,后面我们也会介绍。
需求
我们先实现一个小需求,来实际体会一下:
对请求需要进行验签,大致是从 URI 上的某些参数,以及私钥计算一个 token
,然后和 header 中的 token
进行对比,对不上就返回 403。
很简单的需求,仅仅作为示例,主要是体验一下过程。
代码实现
完整的代码可以看 envoy-go-filter-example 这个仓库
这里摘录最核心的两个函数:
const secretKey = "secret"
func verify(header api.RequestHeaderMap) (bool, string) {
token, ok := header.Get("token")
if ok {
return false, "missing token"
}
path, _ := header.Get(":path")
hash := md5.Sum([]byte(path + secretKey))
if hex.EncodeToString(hash[:]) != token {
return false, "invalid token"
}
return true, ""
}
func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType {
if ok, msg := verify(header); !ok {
f.callbacks.SendLocalReply(403, msg, map[string]string{}, 0, "bad-request")
return api.LocalReply
}
return api.Continue
}
DecodeHeaders
是扩展 filter
必须实现的方法,我们就是在这个阶段对请求 header
进行校验。
verfiy
是校验函数,这里的 RequestHeaderMap
是 Go 扩展提供的 interface
,我们可以通过它来读写 header,其他都是常见的 Go 代码写法。
编译
编译很简单,与常见的 Go 编译一样,这里我们使用 Golang 官方的 docker 镜像来编译:
docker run --rm -v `pwd`:/go/src/go-filter -w /go/src/go-filter \
-e GOPROXY=https://goproxy.cn \
golang:1.19 \
go build -v -o libgolang.so -buildmode=c-shared .
Go 编译还是很快的,只需要几秒钟,当前目录下,就会产生一个 libgolang.so 的文件。
反观 Envoy 的编译速度,一次全量编译,动辄几十分钟,上小时的,这幸福感提升了不止一个档次。
运行
我们可以使用 Envoy 官方提供的镜像来运行,如下示例:
docker run --rm -v `pwd`/envoy.yaml:/etc/envoy/envoy.yaml \
-v `pwd`/libgolang.so:/etc/envoy/libgolang.so \
-p 10000:10000 \
envoyproxy/envoy:contrib-dev \
envoy -c /etc/envoy/envoy.yaml
只需要把上一步编译的 libgolang.so
和 envoy.yaml
挂载进去就可以了。
值得一提的是,我们需要在 envoy.yaml 配置中启用 Go 扩展,具体是这段配置:
http_filters:
- name: envoy.filters.http.golang
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config
library_id: example
library_path: /etc/envoy/libgolang.so
plugin_name: example-1
跑起来之后,我们测试一下:
$ curl 'http://localhost:10000/'
missing token
$ curl -s 'http://localhost:10000/' -H 'token: c64319d06364528120a9f96af62ea83d' -I
HTTP/1.1 200 OK
符合期望,是不是很简单呢
后续
什么?这个示例太简单?
是的,这里主要是体验下开发流程,下篇我们再介绍更高级的玩法:
Go 接受来自 Envoy 侧的配置,异步 Goroutine,以及与 Istio 配合的用法。