健身网站开发可行性分析,做网站公司属于什么行业,网站制作建立,有哪些可以建设网站的单位简介#xff1a; 我们都知道#xff0c;服务网格(ServiceMesh)可以为运行其上的微服务提供无侵入式的流量治理能力。通过配置VirtualService和DestinationRule#xff0c;即可实现流量管理、超时重试、流量复制、限流、熔断等功能#xff0c;而无需修改微服务代码。 本文所… 简介 我们都知道服务网格(ServiceMesh)可以为运行其上的微服务提供无侵入式的流量治理能力。通过配置VirtualService和DestinationRule即可实现流量管理、超时重试、流量复制、限流、熔断等功能而无需修改微服务代码。 本文所述的实践是根据请求Header实现全链路A/B测试。 1 背景介绍
我们都知道服务网格(ServiceMesh)可以为运行其上的微服务提供无侵入式的流量治理能力。通过配置VirtualService和DestinationRule即可实现流量管理、超时重试、流量复制、限流、熔断等功能而无需修改微服务代码。
流量管理的前提是一个服务存在多个版本我们可以按部署多版本的目的进行分类简述如下以方便理解余文。
traffic routing根据请求信息(Header/Cookie/Query Params)将请求流量路由到指定服务(Service)的指定版本(Deployment)的端点上(Pod[])。就是我们所说的A/B测试(A/B Testing)。traffic shifting通过灰度/金丝雀(Canary)发布将请求流量无差别地按比例路由到指定服务(Service)的各个版本(Deployment[])的端点上(Pod[])。traffic switching/mirroring通过蓝绿(Blue/Green)发布根据请求信息按比例进行流量切换以及进行流量复制。
本文所述的实践是根据请求Header实现全链路A/B测试。
1.1 功能简述
从Istio社区的文档我们很容易找到关于如何根据请求Header将流量路由到一个服务的特定版本的文档和示例。但是这个示例只能在全链路的第一个服务上生效。
举例来说一个请求要访问A-B-C三个服务这三个服务都有en版本和fr版本。我们期待
header值为user:en的请求全链路路由为A1-B1-C1header值为user:fr的请求全链路路由为A2-B2-C2
相应的VirtualService配置如下所示
http:
- name: A|B|C-routematch:- headers:user:exact: enroute:- destination:host: A|B|C-svcsubset: v1
- route:- destination:host: A|B|C-svcsubset: v2
我们通过实测可以发现只有A这个服务的路由是符合我们预期的。B和C无法做到根据Header值路由到指定版本。 这是为什么呢对于服务网格其上的微服务来说这个header是凭空出现的也就是微服务代码无感知。因此当A服务请求B服务时不会透传这个header也就是说当A请求B时这个header已经丢失了。这时这个匹配header进行路由的VirtualService配置已经毫无意义。
要解决这个问题从微服务方的业务角度看只能修改代码(枚举业务关注的全部header并透传)。但这是一种侵入式的修改而且无法灵活地支持新出现的header。
从服务网格的基础设施角度看任何header都是没有业务意义且要被透传的kv pair。只有做到这点服务网格才能实现无差别地透传用户自定义的header从而支持无侵入式全链路A/B Test功能。
那么该怎样实现呢
1.2 社区现状
前面已经说明在header无法透传的情况下单纯地配置VirtualService的header匹配是无法实现这个功能的。
但是在VirtualService中是否存在其他配置可以实现header透传呢如果存在那么单纯使用VirtualService代价是最小的。
经过各种尝试(包括精心配置header相关的set/add)我发现无法实现。原因是VirtualService对header的干预发生在inbound阶段而透传是需要在outbound阶段干预header的。而微服务workload没有能力对凭空出现的header值进行透传因此在路由到下一个服务时这个header就会丢失。 因此我们可以得出一个结论无法单纯使用VirtualService实现无侵入式全链路A/B Test进一步地说社区提供的现有配置都无法做到直接使用就能支持这个功能。
那么就只剩下EnvoyFilter这个更高级的配置了。这是我们一开始很不希望的结论。原因有两个
EnvoyFilter的配置太过复杂一般用户很难在服务网格中快速学习和使用即便我们提供示例一旦需求稍有变化示例对修改EnvoyFilter的参考价值甚微。就算使用EnvoyFilter目前Envoy内置的filter也没有直接支持这个功能的需要借助Lua或者WebAssembly(WASM)进行开发。
1.3 实现方案
接下来进入技术选型。我用一句话来概括
Lua的优点是小巧缺点是性能不理想WASM的优点是性能好缺点是开发和分发相比Lua要困难。WASM的实现主流是C和Rust其他语言的实现尚不成熟或者存在性能问题。本文使用的是Rust。
我们使用Rust开发一个WASM在outbound阶段获取用户在EnvoyFilter中定义的header并向后传递。
WASM包的分发使用Kubernetes的configmap存储Pod通过annotation中的定义获取WASM配置并加载。(为什么使用这种分发形式后面会讲。) 2 技术实现 本节所述的相关代码 https://github.com/AliyunContainerService/rust-wasm-4-envoy/tree/master/propagate-headers-filter 2.1 使用RUST实现WASM
1 定义依赖
WASM工程的核心依赖crates只有一个就是proxy-wasm这是使用Rust开发WASM的基础包。此外还有用于反序列化的包serde_json和用于打印日志的包log。Cargo.toml定义如下
[dependencies]
proxy-wasm 0.1.3
serde_json 1.0.62
log 0.4.14
2 定义构建
WASM的最终构建形式是兼容c的动态链接库Cargo.toml定义如下
[lib]
name propaganda_filter
path src/propagate_headers.rs
crate-type [cdylib]
3 Header透传功能
首先定义结构体如下head_tag_name是用户自定义的header键的名称head_tag_value是对应值的名称。
struct PropagandaHeaderFilter {config: FilterConfig,
}struct FilterConfig {head_tag_name: String,head_tag_value: String,
}
{proxy-wasm}/src/traits.rs中的trait HttpContext定义了on_http_request_headers方法。我们通过实现这个方法来完成Header透传的功能。
impl HttpContext for PropagandaHeaderFilter {fn on_http_request_headers(mut self, _: usize) - Action {let head_tag_key self.config.head_tag_name.as_str();info!(::::head_tag_key{}, head_tag_key);if !head_tag_key.is_empty() {self.set_http_request_header(head_tag_key, Some(self.config.head_tag_value.as_str()));self.clear_http_route_cache();}for (name, value) in self.get_http_request_headers() {info!(::::H[{}] - {}: {}, self.context_id, name, value);}Action::Continue}
}
第3-6行是获取配置文件中用户自定义的header键值对如果存在就调用set_http_request_header方法将键值对写入当前header。
第7行是对当前proxy-wasm实现的一个workaround如果你对此感兴趣可以阅读如下参考
https://github.com/istio/istio/issues/30545#issuecomment-783518257https://github.com/proxy-wasm/spec/issues/16https://www.elvinefendi.com/2020/12/09/dynamic-routing-envoy-wasm.html
2.2 本地验证(基于Envoy)
1 WASM构建
使用如下命令构建WASM工程。需要强调的是wasm32-unknown-unknown这个target目前只存在于nightly中因此在构建之前需要临时切换构建环境。
rustup override set nightly
cargo build --targetwasm32-unknown-unknown --release
构建完成后我们在本地使用docker compose启动Envoy对WASM功能进行验证。
2 Envoy配置
本例需要为Envoy启动提供两个文件一个是构建好的propaganda_filter.wasm一个是Envoy配置文件envoy-local-wasm.yaml。示意如下
volumes:- ./config/envoy/envoy-local-wasm.yaml:/etc/envoy-local-wasm.yaml- ./target/wasm32-unknown-unknown/release/propaganda_filter.wasm:/etc/propaganda_filter.wasm
Envoy支持动态配置本地测试采用静态配置
static_resources:listeners:- address:socket_address:address: 0.0.0.0port_value: 80filter_chains:- filters:- name: envoy.filters.network.http_connection_manager
...http_filters:- name: envoy.filters.http.wasmtyped_config:type: type.googleapis.com/udpa.type.v1.TypedStructtype_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasmvalue:config:name: header_filterroot_id: propaganda_filterconfiguration:type: type.googleapis.com/google.protobuf.StringValuevalue: |{head_tag_name: custom-version,head_tag_value: hello1-v1}vm_config:runtime: envoy.wasm.runtime.v8vm_id: header_filter_vmcode:local:filename: /etc/propaganda_filter.wasmallow_precompiled: true
...
Envoy的配置重点关注如下3点
15行 我们在http_filters中定义了一个名称为header_filter的type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm32行 本地文件路径为/etc/propaganda_filter.wasm20-26行 相关配置的类型是type.googleapis.com/google.protobuf.StringValue值的内容是{head_tag_name: custom-version,head_tag_value: hello1-v1}。这里自定义的Header键名为custom-version值为hello1-v1。
3 本地验证
执行如下命令启动docker compose
docker-compose up --build
请求本地服务
curl -H version-tag:v1 localhost:18000
此时Envoy的日志应有如下输出
proxy_1 | [2021-02-25 06:30:09.217][33][info][wasm] [external/envoy/source/extensions/common/wasm/context.cc:1152] wasm log: ::::create_http_context head_tag_namecustom-version,head_tag_valuehello1-v1
proxy_1 | [2021-02-25 06:30:09.217][33][info][wasm] [external/envoy/source/extensions/common/wasm/context.cc:1152] wasm log: ::::head_tag_keycustom-version
...
proxy_1 | [2021-02-25 06:30:09.217][33][info][wasm] [external/envoy/source/extensions/common/wasm/context.cc:1152] wasm log: ::::H[2] - custom-version: hello1-v1
2.3 WASM的分发方式
WASM的分发是指将WASM包存储于一个分布式仓库中供指定的Pod拉取的过程。
1 Configmap Envoy的Local方式
虽然这种方式不是WASM分发的终态但是因为它较为容易理解且适合简单的场景本例最终选择了这个方案作为示例讲解。虽然configmap的本职工作不是存WASM的但是configmap和Envoy的local模式都很成熟两者结合恰能满足当前需求。
阿里云服务网格ASM产品已经提供了这种类似的方式具体可以参考 为Envoy编写WASM Filter并部署到ASM中。
要把WASM包塞到配置中首要考虑的是包的尺寸。我们使用wasm-gc进行包裁剪示意如下
ls -hl target/wasm32-unknown-unknown/release/propaganda_filter.wasm
wasm-gc ./target/wasm32-unknown-unknown/release/propaganda_filter.wasm ./target/wasm32-unknown-unknown/release/propaganda-header-filter.wasm
ls -hl target/wasm32-unknown-unknown/release/propaganda-header-filter.wasm
执行结果如下可以看到裁剪前后包的尺寸对比
-rwxr-xr-x 2 han staff 1.7M Feb 25 15:38 target/wasm32-unknown-unknown/release/propaganda_filter.wasm
-rw-r--r-- 1 han staff 136K Feb 25 15:38 target/wasm32-unknown-unknown/release/propaganda-header-filter.wasm
创建configmap
wasm_imagetarget/wasm32-unknown-unknown/release/propaganda-header-filter.wasm
kubectl -n $NS create configmap -n $NS propaganda-header --from-file$wasm_image
为指定Deployment打Patch
patch_annotations$(cat config/annotations/patch-annotations.yaml)
kubectl -n $NS patch deployment hello$i-deploy-v$j -p $patch_annotations
patch-annotations.yaml如下
spec:template:metadata:annotations:sidecar.istio.io/userVolume: [{name:wasmfilters-dir,configMap: {name:propaganda-header}}]sidecar.istio.io/userVolumeMount: [{mountPath:/var/local/lib/wasm-filters,name:wasmfilters-dir}]
2 Envoy的Remote方式
Envoy同时支持local和remote形式的资源定义。对比如下
vm_config:runtime: envoy.wasm.runtime.v8vm_id: header_filter_vmcode:local:filename: /etc/propaganda_filter.wasm
vm_config:runtime: envoy.wasm.runtime.v8code:remote:http_uri:uri: http://*.*.*.216:8000/propaganda_filter.wasmcluster: web_servicetimeout:seconds: 60sha256: da2e22*
remote方式是最接近原始Enovy的因此这种方式本来是本例的首选。但是实测过程中发现在包的hash校验上存在问题详见下方参考。并且Envoy社区的大牛周礼赞反馈我说remote不是Envoy支持WASM分发的未来方向。因此本例最终放弃这种方式。
https://stackoverflow.com/questions/65871312/how-to-set-the-sha256-hex-in-envoy-wasm-remote-confighttps://envoyproxy.slack.com/archives/C78M4KW76/p1611496672017500
3 ORAS Local方式
ORAS是OCI Artifacts项目的参考实现可显著简化OCI注册表中任意内容的存储。
使用ORAS客户端或者API/SDK的方式将具有允许的媒体类型的Wasm模块推送到注册库一个OCI兼容的注册库中然后通过控制器将Wasm Filter部署到指定工作负载对应的Pod中以Local的方式进行挂载。
阿里云服务网格ASM产品中提供了对WebAssemblyWASM技术的支持服务网格使用人员可以把扩展的WASM Filter通过ASM部署到数据面集群中相应的Envoy代理中。通过ASMFilterDeployment Controller组件 可以支持动态加载插件、简单易用、以及支持热更新等能力。具体来说ASM产品提供了一个新的CRD ASMFilterDeployment以及相关的controller组件。这个controller组件会监听ASMFilterDeployment资源对象的情况,会做2个方面的事情
创建出用于控制面的Istio EnvoyFilter Custom Resource并推送到对应的asm控制面istiod中从OCI注册库中拉取对应的wasm filter镜像并挂载到对应的workload pod中
具体可以参考基于Wasm和ORAS简化扩展服务网格功能。
后续的实践分享将会使用这种方式进行WASM的分发敬请期待。
类似地业界其他友商也在推进这种方式特别是Solo.io提供了一整套WASM的开发框架wasme基于该框架可以开发-构建-分发WASM包(OCI image)并部署到Webassembly Hub。这个方案的优点很明显完整地支持了WASM的开发到上线的生命周期。但这个方案的缺点也非常明显wasme的自包含导致了很难将其拆分并扩展到solo体系之外。
阿里云服务网格ASM团队正在与包括solo在内的业界相关团队交流如何共同推进Wasm filter的OCI规范以及相应的生命周期管理以帮助客户可以轻松扩展Envoy的功能并将其在服务网格中的应用推向了新的高度。
2.4 集群验证(基于Istio)
1 实验示例
WASM分发到Kubernetes的configmap后我们可以进行集群验证了。示例(源代码)包含3个Servicehello1-hello2-hello3每个服务包含2个版本v1/en和v2/fr。
每个Service配置了VirtualService和DestinationRule用来定义匹配Header并路由到指定版本。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:name: hello2-vs
spec:hosts:- hello2-svchttp:- name: hello2-v2-routematch:- headers:route-v:exact: hello2v2route:- destination:host: hello2-svcsubset: hello2v2- route:- destination:host: hello2-svcsubset: hello2v1
----
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:name: hello2-dr
spec:host: hello2-svcsubsets:- name: hello2v1labels:version: v1- name: hello2v2labels:version: v2
Envoyfilter示意如下
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:name: hello1v2-propaganda-filter
spec:workloadSelector:labels:app: hello1-deploy-v2version: v2configPatches:- applyTo: HTTP_FILTERmatch:context: SIDECAR_OUTBOUNDproxy:proxyVersion: ^1\\.8\\.*listener:filterChain:filter:name: envoy.filters.network.http_connection_managersubFilter:name: envoy.filters.http.routerpatch:operation: INSERT_BEFOREvalue:name: envoy.filters.http.wasmtyped_config:type: type.googleapis.com/udpa.type.v1.TypedStructtype_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasmvalue:config:name: propaganda_filterroot_id: propaganda_filter_rootconfiguration:type: type.googleapis.com/google.protobuf.StringValuevalue: |{head_tag_name: route-v,head_tag_value: hello2v2}vm_config:runtime: envoy.wasm.runtime.v8vm_id: propaganda_filter_vmcode:local:filename: /var/local/lib/wasm-filters/propaganda-header-filter.wasmallow_precompiled: true
2 验证方法
携带header的请求curl -H version:v1 http://$ ingressGatewayIp:8001/hello/xxx通过istio-ingressgateway进入全链路按header值进入服务的指定版本。这里由于header中指定了version为v2那么全链路将 为hello1 v2-hello2 v2-hello3 v2。效果如下图所示。 验证过程和结果示意如下。
for i in {1..5}; docurl -s -H route-v:v2 http://$ingressGatewayIp:$PORT/hello/eric resultecho result
done
check$(grep -o Bonjour eric result | wc -l)
if [[ $check -eq 15 ]]; thenecho pass
elseecho failexit 1
fi
result
Bonjour erichello1:172.17.68.205Bonjour erichello2:172.17.68.206Bonjour erichello3:172.17.68.182
Bonjour erichello1:172.17.68.205Bonjour erichello2:172.17.68.206Bonjour erichello3:172.17.68.182
Bonjour erichello1:172.17.68.205Bonjour erichello2:172.17.68.206Bonjour erichello3:172.17.68.182
Bonjour erichello1:172.17.68.205Bonjour erichello2:172.17.68.206Bonjour erichello3:172.17.68.182
Bonjour erichello1:172.17.68.205Bonjour erichello2:172.17.68.206Bonjour erichello3:172.17.68.182
我们看到输出信息Bonjour eric来自各个服务的fr版本说明功能验证通过。
3 性能分析
新增EnvoyFilterWASM后功能验证通过但这会带来多少延迟开销呢这是服务网格的提供者和使用者都非常关心的问题。本节将对如下两个关注点进行验证。
增加EnvoyFilterWASM后的增量延迟开销情况WASM版本和Lua版本的开销对比
3.1 Lua实现
Lua的实现可以直接写到EnvoyFilter中无需独立的工程。示例如下
patch:operation: INSERT_BEFOREvalue:name: envoy.luatyped_config:type: type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuainlineCode: |function envoy_on_request(handle)handle:logInfo([propagate header] route-v:hello3v2)handle:headers():add(route-v, hello3v2)end
3.2 压测方法
1 部署
分别在3个namespace上部署相同的Deployment/Service/VirtualService/DestinationRule在hello-abtest-lua中部署基于Lua的EnvoyFilter在hello-abtest-wasm中部署基于WASM的EnvoyFilter
hello-abtest 基准环境
hello-abtest-lua 增加EnvoyFilterLUA的环境
hello-abtest-wasm 增加EnvoyFilterWASM的环境
2 工具
本例使用hey作为压测工具。hey前身是boom用来代替ab(Apache Bench)。使用相同的压测参数分别对三个环境进行压测。示意如下
# 并发work数量
export NUM2000
# 每秒请求数量
export QPS2000
# 压测执行时常
export Duration10shey -c $NUM -q $QPS -z $Duration -H route-v:v2 http://$ingressGatewayIp:$PORT/hello/eric $SIDECAR_WASM_RESULT
请关注hey压测结果文件结果最后不能出现socket: too many open files否则影响结果。可以使用ulimit -n $MAX_OPENFILE_NUM命令配置然后再调整压测参数以确保结果的准确性。
3.3 报告
我们从三份结果报告中选取4个关键指标如下图所示 基准 WASM LUA 1000并发1000QPS持续10秒钟 平均延迟 0.6317 secs 0.6395 secs 0.7012 secs 延迟99%分布 0.9167 secs 0.9352 secs 1.1355 secs QPS 1541 1519 1390 Total 16281 16109 1390 2000并发2000QPS持续10秒钟 平均延迟 1.2078 secs 1.3290 secs 1.4593 secs 延迟99%分布 1.8621 secs 1.8354 secs 2.2116 secs QPS 1564 1421 1290 Total 17622 16009 14662
3.4 结论
相对于基准版本增加EnvoyFilter的两个版本平均延迟多出几十个到几百个毫秒增加耗时比为
wasm 1.2% (0.6395-0.6317)/0.6317和1% (1.3290-1.2078)/1.2078lua 11%(0.7012-0.6317)/0.6317和20% (1.4593-1.2078)/1.2078
WASM版本的性能明显优于LUA版本注相比LUA版本WASM的实现是一套代码多份配置。因此WASM的执行过程还比LUA多出一个获取配置变量的过程。 4 展望
4.1 如何使用
本文从技术实现角度讲述了如何实现并验证一个透传用户自定义Header的WASM从而支持无侵入式全链路A/B Test这个需求。
但是作为服务网格的使用者如果按照本文一步步去实现是非常繁琐且容易出错的。
阿里云服务网格ASM团队正在推出一种ASM插件目录的机制用户只需在插件目录中选择插件并为插件提供自定义的Header等极少数量的kv配置即可自动生成和部署相关的EnvoyFilterWASMVirtualServiceDestinationRule。
4.2 如何扩展
本例只展示了基于Header的匹配路由功能如果我们希望根据Query Params进行匹配和路由该如何扩展呢
这是ASM插件目录正在密切关注的话题最终插件目录将提供最佳实践。
以上。
作者六翁
原文链接
本文为阿里云原创内容未经允许不得转载