Wasm 让使用不同编程语言开发 Envoy 扩展成为可能。
作者:Yaroslav Skopets Yaroslav Skopets 是一名 Tetrate 工程师和 Envoy 贡献者,他专注于推进 Wasm 对 Envoy proxy 的支持。
说一说来龙去脉,Envoy 是一个非常注重规模化业务的底层网络组件,令人激动且功能强大。然而它在用户体验方面一直很欠缺。
当用户开始使用一个新工具时,必然会从 “如何在自己的环境中安装” 这一问题开始。而 Envoy 之前并没有给出答案。
为了填补这一空白,我们 启动了[3] GetEnvoy 项目并且 推出了[4] getenvoy CLI,作为提供给用户的组件。见 https://getenvoy.io
下一个关于 Envoy 的常见需求是 “如何扩展”。
截止目前,如果想扩展或定制 Envoy,你将不得不 “越界” 成为实质上的 Envoy 开发者。
幸运的是,这种情况即将改变。一种名为 WebAssembly(Wasm)[5] 的新技术即将纳入 Envoy。Wasm 让使用不同编程语言开发 Envoy 扩展成为可能。更重要的是,能以完全动态的方式部署这些扩展。
GetEnvoy 扩展工具包[6] 的目的在于帮助有扩展 Envoy 需求的开发者,在短时间内完成扩展开发并启动运行。
作为开发者,你很可能想:
•从工作中的典型示例入手•从开始就建立有效的开发工作流•利用最佳实践,自动避免常见陷阱
GetEnvoy 扩展工具包将帮助您解决以上所有问题!
让我们使用 Rust 开发一个 Envoy HTTP Filter 来试试 GetEnvoy 扩展工具包吧!
安装[7] getenvoy CLI,例如:
$ curl -L https://getenvoy.io/cli | bash -s -- -b /usr/local/bin
安装[8] Docker。
检查
运行命令:
$ getenvoy --version
应该看到类似于以下的输出:
getenvoy version 0.2.0
运行
$ docker --version
应该看到类似于以下的输出:
Docker version 19.03.8, build afacb8b
如果想在交互模式下构建一个新扩展,运行命令:
$ getenvoy extension init
此外,如果想跳过向导,需要在命令行提供参数,比如:
$ getenvoy extension init \ --category envoy.filters.http \ --language rust \ --name me.filters.http.my_http_filter \ my_http_filter
检查
运行命令:
$ tree -a my_http_filter
应该看到类似于以下的输出:
my_http_filter├── .cargo│ └── config├── .getenvoy│ └── extension│ └── extension.yaml├── .gitignore├── Cargo.toml├── README.md├── src│ ├── config.rs│ ├── factory.rs│ ├── filter.rs│ ├── lib.rs│ └── stats.rs└── wasm └── module ├── Cargo.toml └── src └── lib.rs
运行命令:
$ getenvoy extension build
应该看到类似于以下的输出:
Updating crates.io indexDownloaded envoy-sdk v0.1.0...Compiling envoy-sdk v0.1.0...Finished dev [unoptimized + debuginfo] target (s) in 23.57sCopying *.wasm file to 'target/getenvoy/extension.wasm'
检查
运行命令:
$ tree target/getenvoy/
应该看到类似于以下的输出:
target/getenvoy└── extension.wasm
运行命令:
$ getenvoy extension test
检查
应该看到类似于以下的输出:
running 1 testtest tests::should_initialize ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
让我们用较困难的方式完成这部分工作。与其使用一条简单的自动化命令,不如手动完成每一步。
需要去下载和扩展开发版本相同的 Envoy
运行命令:
$ cat .getenvoy/extension/extension.yaml
应该看到类似于以下的输出:
…# Runtime the extension is being developed against.runtime: envoy: version: wasm:1.15
为了下载该版本的 Envoy,运行命令:
$ getenvoy fetch wasm:1.15
应该看到类似于以下的输出:
fetching wasm:1.15/darwin[Fetching Envoy] 100%
运行命令:
$ getenvoy extension examples add
检查一下,运行命令:
$ tree .getenvoy/extension/examples
应该看到类似于以下的输出:
.getenvoy/extension/examples└── default ├── README.md ├── envoy.tmpl.yaml ├── example.yaml └── extension.json
运行命令:
$ cat .getenvoy/extension/examples/default/envoy.tmpl.yaml
应该看到类似于以下的输出:
...http_filters:- name: envoy.filters.http.wasm typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm config: configuration: {{.GetEnvoy.Extension.Config}} name: {{.GetEnvoy.Extension.Name}} root_id: {{.GetEnvoy.Extension.Name}} vm_config: vm_id: {{.GetEnvoy.Extension.Name}} runtime: envoy.wasm.runtime.v8 code: {{.GetEnvoy.Extension.Code}}- name: envoy.filters.http.router...
注意,示例 Envoy 配置中包含了占位符 {{…}},这些占位符会由 getenvoy CLI 解决。
运行命令:
$ getenvoy extension run
应该看到类似于以下的输出:
info Envoy command: [$HOME/.getenvoy/builds/wasm/1.15/darwin/bin/envoy -c /tmp/getenvoy_extension_run732371719/envoy.tmpl.yaml]...[info][main] [external/envoy/source/server/server.cc:339] admin address: 127.0.0.1:9901...[info][config] [external/envoy/source/server/listener_manager_impl.cc:700] all dependencies initialized. starting workers[info][main] [external/envoy/source/server/server.cc:575] starting main dispatch loop
此时 Envoy 已启动,扩展也可以使用了。
检查
为了测试 HTTP Filter 扩展,运行命令:
$ curl -i http://0.0.0.0:10000
在 Envoy 的输出中,应该看到类似于以下的输出:
my_http_filter: #2 new http exchange starts at 2020-07-01T18:22:51.623813+00:00 with config:my_http_filter: #2 observing request headersmy_http_filter: #2 -> :authority: 0.0.0.0:10000my_http_filter: #2 -> :path: /my_http_filter: #2 -> :method: GETmy_http_filter: #2 -> user-agent: curl/7.64.1my_http_filter: #2 -> accept: */*my_http_filter: #2 -> x-forwarded-proto: httpmy_http_filter: #2 -> x-request-id: 8902ca62-75a7-40e7-9b2e-cd7dc983b091my_http_filter: #2 http exchange complete
由于现在你知道了运行扩展的背后都发生了什么,下次就可以用以下命令简单启动扩展:
$ getenvoy extension run
让我们为扩展添加一个新特性:在代理的 HTTP 响应中注入一个额外的标头。
首先,更新扩展配置以保存注入的标头的名称(添加的行后添加了注释):
在 src/config.rs 文件中
/// Configuration for a Sample HTTP Filter.#[derive (Debug, Default, Deserialize)]pub struct SampleHttpFilterConfig {#[serde (default)]pub response_header_name: String, //added code 添加的代码}
接着,添加 on_response_headers 方法到 SampleHttpFilter 中:
在 src/filter.rs 文件中
/// Called when HTTP response headers have been received. 当 HTTP 响应头被接收时调用////// Use `filter_ops` to access and mutate response headers. 使用 filter_ops 访问和变异响应头fn on_response_headers ( &mut self, _num_headers: usize, _end_of_stream: bool, filter_ops: &dyn http::ResponseHeadersOps,) -> Result<http::FilterHeadersStatus> {if !self.config.response_header_name.is_empty () { filter_ops.set_response_header ( &self.config.response_header_name, "injected by WebAssembly extension" )?; } Ok (http::FilterHeadersStatus::Continue)}
最后,在默认的示例设置中更新扩展配置。
在.getenvoy/extension/examples/default/extension.json 文件中
{"response_header_name":"my-header"}
检查
为了确认变更,重启示例设置:
$ getenvoy extension run
发出以下示例请求:
$ curl -i localhost:10000
应该看到类似于下面的输出:
HTTP/1.1 200 OKcontent-length: 22content-type: text/plaindate: Tue, 07 Jul 2020 18:36:23 GMTserver: envoyx-envoy-upstream-service-time: 0my-header: injected by WebAssembly extensionHi from mock service!
注意到一条额外的标头被注入到响应中。
Envoy 大力支持对新行为的可观察性。
让我们更新扩展以暴露关于其新行为的度量。具体来说,提供一个计数器,显示被额外的标头注入了的 HTTP 响应的数量。
编辑源码如下(添加的行后加入了注释):
在 src/stats.rs 文件中
use envoy::host::stats::Counter;/// Sample stats.pub struct SampleHttpFilterStats {requests_total: Box<dyn Counter>, responses_injected_total: Box<dyn Counter>, //added code 新增行} impl SampleHttpFilterStats { pub fn new (requests_total: Box<dyn Counter>, responses_injected_total: Box<dyn Counter>, //added code 新增行 ) -> Self { SampleHttpFilterStats { requests_total, responses_injected_total, //added code 新增行 } } pub fn requests_total (&self) -> &dyn Counter {&*self.requests_total} pub fn responses_injected_total (&self) -> &dyn Counter { //added code 新增行 &*self.responses_injected_total }}
在 src/factory.rs 中
/// Creates a new factory.pub fn new (clock: &'a dyn Clock, stats: &dyn Stats) -> Result<Self> { let stats = SampleHttpFilterStats::new (stats.counter ("examples.http_filter.requests_total")?, stats.counter ("examples.http_filter.responses_injected_total")?, //added code 新增行 ); // Inject dependencies on Envoy host APIs Ok (SampleHttpFilterFactory {config: Rc::new (SampleHttpFilterConfig::default ()), stats: Rc::new (stats), clock, })}
在 src/filter.rs 中
/// Called when HTTP response headers have been received.////// Use `filter_ops` to access and mutate response headers.fn on_response_headers ( &mut self, _num_headers: usize, _end_of_stream: bool, filter_ops: &dyn http::ResponseHeadersOps,) -> Result<http::FilterHeadersStatus> {if !self.config.response_header_name.is_empty () { filter_ops.set_response_header ( &self.config.response_header_name, "injected by WebAssembly extension", )?; self.stats.responses_injected_total ().inc ()?; //added code } Ok (http::FilterHeadersStatus::Continue)}
检查
重启示例设置,发出示例请求并检查 Envoy 的指标:
$ getenvoy extension run
$ curl -i localhost:10000$ curl -i localhost:10000$ curl -s localhost:9901/stats | grep responses_injected_total
应该看到类似于以下的输出:
examples.http_filter.responses_injected_total: 2
我们对使用 GetEnvoy 扩展工具包[9] 的开发流程的简单介绍到此结束。
目前为止,我们展示了使用 GetEnvoy 开发 Envoy 扩展是多么容易。
结合 getenvoy CLI[10] 的便利和 Envoy Rust SDK[11] 的指引,你可以轻松胜任这些工作。
除了上面演示的 HTTP Filter[12] 扩展外,你还可以使用该工具包开发其他类型的 Envoy 扩展,比如 Network Filter[13] 和 Access Logger[14]。
在未来的几个月里,我们将为 GetEnvoy 添加一些新特性。
一方面,我们将把重点转移到扩展用户体验上,为用户提供能够轻松发现和使用扩展的方法。
另一方面,我们将继续改善开发者流程的用户体验。对更多编程语言和更多扩展类型的支持将会到来。
敬请关注 GetEnvoy 的进一步更新!请与我们分享你在 Rust 中的 Envoy 扩展!
[3]
启动了: https://www.getenvoy.io/blog/announcing-the-getenvoy-project/
[4]
推出了: https://www.getenvoy.io/blog/introducing-the-getenvoy-cli/
[5]
WebAssembly(Wasm): https://webassembly.org/
[6]
GetEnvoy 扩展工具包: https://www.getenvoy.io/reference/getenvoy_extension_toolkit_reference/
[7]
安装: https://www.getenvoy.io/install/
[8]
安装: https://docs.docker.com/engine/install/
[9]
GetEnvoy 扩展工具包: https://www.getenvoy.io/reference/getenvoy_extension_toolkit_reference/
[10]
getenvoy CLI: https://www.getenvoy.io/
[11]
Envoy Rust SDK: https://docs.rs/envoy-sdk/
[12]
HTTP Filter: https://docs.rs/envoy-sdk/0.1.0/envoy_sdk/extension/filter/http/index.html
[13]
Network Filter: https://docs.rs/envoy-sdk/0.1.0/envoy_sdk/extension/filter/network/index.html
[14]
Access Logger: https://docs.rs/envoy-sdk/0.1.0/envoy_sdk/extension/access_logger/index.html
申请加入云原生社区 Envoy SIG 微信群。