给你的istio sidecar写一个自定义扩展程序

基于 lua 编写 istio 扩展包

我们知道 istio 支持 lua 和 wasm 两种扩展能力,lua 作为脚本语言,相信写过游戏或 nginx 插件的都了解他,这里以 Lua 为例子,介绍下 istio 的 sidecar 如何编写一个插件。

首先说下目标,我们希望编译一个只作用于带app=python-web-v1label的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.3.249.149:80...
* Connected to python-web-v1.rcmd-tt.svc (10.3.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-v1istio-test-for-python-web服务的日志都可以看到请求日志。返回的response是python-web-v1的。

Lua Filter 说明

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 中合法。

Enovy Filter说明

EnvoyFilter

Envoy 配置说明

在说明 envoy filter 之前,我们先来简单介绍下 envoy:

  • Listener(监听器):监听器是命名网地址(例如,端口、unix domain socket等),可以被下游客户端连接。Envoy 暴露一个或者多个监听器给下游主机连接。
  • Cluster(集群):集群是指 Envoy 连接到的逻辑上相同的一组上游主机。Envoy 通过服务发现来发现集群的成员。可以选择通过主动健康检查来确定集群成员的健康状态。Envoy 通过负载均衡策略决定将请求路由到哪个集群成员。

envoy架构.png

我们看一个标准的 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(监听器过滤器),它会在过滤器链之前执行,用于操纵连接的元数据。

参考文献

shikanon wechat
欢迎您扫一扫,订阅我滴↑↑↑的微信公众号!