我们知道 istio 支持 lua 和 wasm 两种扩展能力,lua 作为脚本语言,相信写过游戏或 nginx 插件的都了解他,这里以 Lua 为例子,介绍下 istio 的 sidecar 如何编写一个插件。
首先说下目标,我们希望编译一个只作用于带app=python-web-v1
label的sidecar,将所有到他的流量请求都转发一份到istio-test-for-python-web.rcmd-tt.svc.cluster.local
服务,使用EnvoyFilter
进行扩展:
案例源代码
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: example-istio-lua
namespace: rcmd-tt
spec:
workloadSelector: # envoyfilter的作用域
labels:
app: python-web-v1
configPatches:
# 编写一个lua脚本在filter链上拦截处理处理连接请求
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND #"ANY", "SIDECAR_INBOUND", "SIDECAR_OUTBOUND", "GATEWAY"
listener:
portNumber: 80
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value: # lua filter specification
name: envoy.lua
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
inlineCode: |
function envoy_on_request(request_handle)
-- Make an HTTP call to an upstream host with the following headers, body, and timeout.
local headers, body = request_handle:httpCall(
"istio-plugin-example",
{
[":method"] = "GET",
[":path"] = "/send-the-next-server",
[":authority"] = "istio-test-for-python-web",
[":host"] = "istio-test-for-python-web"
},
"",
1000)
end
function envoy_on_response(response_handle)
response_handle:headers():add("mytag", "hello-world-girl")
end
- applyTo: CLUSTER
match:
context: SIDECAR_OUTBOUND
patch:
operation: ADD
value: # cluster specification
name: "istio-plugin-example"
type: STRICT_DNS
connect_timeout: 0.5s
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: istio-plugin-example
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
protocol: TCP
address: "istio-test-for-python-web.rcmd-tt.svc.cluster.local" # 自定义的一个下游服务
port_value: 80
首先,通过app: istio-test
限制了下发的 istio-sidcar 服务。
发送一个请求给istio-test-for-python-web
服务:
curl -vv http://python-web-v1.rcmd-tt.svc/
* Trying 10.247.249.149:80...
* Connected to python-web-v1.rcmd-tt.svc (10.247.249.149) port 80 (#0)
> GET / HTTP/1.1
> Host: python-web-v1.rcmd-tt.svc
> User-Agent: curl/7.71.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< date: Tue, 31 May 2022 13:07:46 GMT
< server: istio-envoy
< content-length: 77
< content-type: application/json
< x-envoy-upstream-service-time: 1
< mytag: hello-world-girl
< x-envoy-decorator-operation: python-web-v1.rcmd-tt.svc.cluster.local:80/*
我们看python-web-v1
和istio-test-for-python-web
服务的日志都可以看到请求日志。返回的response是python-web-v1
的。
Lua 脚本方法说明:
envoy_on_request
函数在请求路径上被调用,envoy_on_response
脚本则在响应路径上被调用。每个函数都接收一个句柄,该句柄有不同的定义方法:
function envoy_on_request(request_handle)
end
function envoy_on_response(response_handle)
end
request_handle 和 response_handle 两个 handle 句柄。句柄方法:
headers()
headers = handle:headers()
返回一个头对象,返回流的头。只要它们还没有被发送到头链中的下一个过滤器,就可以被修改。例如,它们可以在一个 httpCall() 或者 body() 调用返回后被修改。如果头在任何其他情况下被修改,脚本将失败。
body()
body = handle:body()
返回一个缓存对象,返回流的正文。这个调用将造成 Envoy 退出脚本直到整个正文被缓存。注意,所有缓存必须遵从适当的流控策略。Envoy 将不会缓存比连接管理器允许的更多的数据。
log()
handle:logTrace(message)
handle:logDebug(message)
handle:logInfo(message)
handle:logWarn(message)
handle:logErr(message)
handle:logCritical(message)
打印一条消息。message 是被保存的字符串。
httpCall()
headers, body = handle:httpCall(cluster, headers, body, timeout)
一个 HTTP 调用,返回一个header和body。cluster 是一个字符串,映射为一个配置好的集群管理器。headers 一个要发送的键值对的表,header中的:method
,:path
和 :authority
头必须被设置。body 是一个可选的要发送的正文数据的字符串。timeout 是一个整数,指定以微秒为单位的调用超时。
metadata()
metadata = handle:metadata()
返回当前条目元数据。元数据需要在过滤器名下指定,例如envoy.lua。
handle:streamInfo():dynamicMetadata()
可以用来存储和传递数据
respond()
handle:respond(headers, body)
立即响应,这个调用仅在请求流 request_handle 中合法。
在说明 envoy filter 之前,我们先来简单介绍下 envoy:
我们看一个标准的 envoy 静态配置文件:
static_resources:
listeners: // 监听器
- address:
socket_address:
address: 0.0.0.0
port_value: 8000
filter_chains: // 过滤链表
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: service
domains:
- "*"
routes:
- match:
prefix: "/service"
route:
cluster: local_service
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters: // 集群
- name: local_service
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: local_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8080
admin:
address:
socket_address:
address: 0.0.0.0
port_value: 8081
每个监听器都可以配置多个 Filter Chains(过滤器链),监听器会根据 filter_chain_match
中的匹配条件将流量转交到对应的过滤器链,其中每一个过滤器链都由一个或多个Network filters
(网络过滤器)组成。Listener filters
(监听器过滤器),它会在过滤器链之前执行,用于操纵连接的元数据。