istio 除了自带拥有丰富的流量管理和控制功能外,还有强大的扩展能力,我们可以基于Lua或swam轻松扩展服务网格的功能,实现我们的需求,这里以lua为例子给大家介绍如何给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.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 Filter 说明

Lua 脚本方法说明:

每个函数都接收一个句柄,该句柄有不同的定义方法:

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说明

istio源码编译(sidecar写一个自定义扩展程序)(1)

Envoy 配置说明

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

istio源码编译(sidecar写一个自定义扩展程序)(2)

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

参考文献,