在istio服务网格中扩展自定义功能
istio 作为当下火热的服务网格框架,其提供了丰富的功能,但是在实际的使用中,我们还是会遇到一些特殊的场景,需要我们自己基于自己的业务场景去扩展一些功能。本文就简单尝试下如何在 istio 中扩展自定义功能。
前提
本文假设你已经对 Kubernetes、istio 和 Envoy 有一定的了解,如果你还不了解,可以先阅读下面的文章:
- Kubernetes: https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/
- istio: https://istio.io/latest/docs/concepts/what-is-istio/
- Envoy: https://www.envoyproxy.io/docs/envoy/latest/intro/what_is_envoy
当然不仅限于知道这些,还需要对其有一定的实践经验,这样才能更好的理解本文的内容。当然本文也不会涉及太深,只是作为 istio 的一个入门扩展教程。
为什么要扩展功能?#背景
用通俗的话去理解 istio 的作用就是,对我们部署在 kubernetes 上的应用进行流量控制(代理),给我们提供了:流量控制、流量监控、流量安全等功能。但是在实际的使用中,我们还是会遇到一些特殊的场景,需要我们自己基于自己的业务场景去扩展一些功能,比如:ip 白名单、ip 黑名单、统一认证等。这些功能往往和具体公司的业务场景有关,因此 istio 无法直接提供这些功能,需要我们自己去扩展。
传统的扩展方式
在传统架构中,常常通过 API 网关这一组件来实现这一些扩展能力,常用的 API 网关有:kong、apisix、openresty,而扩展的原理就是插件,或者像 nginx/openresty 在请求链中的不同阶段提供了不同的 hook,我们可以基于这些 hook 来实现扩展功能。
就我个人的经历来说,网关要么自己定制开发,要么基于openresty来实现,又或者使用一些开源的网关,如:kong、apisix,在此基础上进行二次开发。不过这些方式都是在传统API网关中去实现的,随着 Service Mesh 的发展,API网关的某些功能也被 Sidecar 代理所取代,比如:流量控制、流量监控、流量安全等。目前 Mesh 发展的趋势也是进一步在 Sidecar 代理中实现更多的功能,而不是在 API 网关中实现。
Envoy 的扩展能力
envoy 提供了丰富的扩展能力,Access Logger, Access Log Filter, Clusters, Listen Filter, Network Filter, HTTP Filter 等等。这一块内容非常多,如果想要了解更多,请参考官方文档。
istio 的扩展能力
https://istio.io/latest/docs/reference/config/networking/envoy-filter/
istio 作为一个 Service Mesh 框架,其底层的代理是 Envoy,因此 istio 也继承了 Envoy 的扩展能力。istio 提供了 EnvoyFilter 这样一种自定义 istio Pilot 生成的 Envoy 配置的机制。可以修改某些字段的值、添加特定过滤器。对于特定命名空间中的给定工作负载,可以存在任意数量的 EnvoyFilter。这些 EnvoyFilter 的应用顺序如下:配置根命名空间中的所有 EnvoyFilter,然后是工作负载命名空间中的所有匹配的 EnvoyFilter。
需要谨慎的使用这个功能,因为错误的配置会影响整个网格的稳定性。
实践:实现一个请求参数改写请求头的功能
假设我们有一个业务场景,需要将请求参数中的 cvn
参数的值,改写到请求头中的 x-istio-cvn
参数中,那我们应该怎么做呢?我们可以用伪代码来简单梳理下逻辑:
function rewrite_cvn_to_header()
cvn = request.query_params.cvn
request.headers.set("x-istio-cvn", cvn)
end
编写 EnvoyFilter
从 envoy 的扩展能力中,我们可以知道,我们需要编写一个 HTTP Filter,用于在请求到达 Sidecar 代理时,对请求进行处理,将请求参数中的 cvn
参数的值,改写到请求头中的 x-client-cvn
参数中。而在 istio 中,我们需要编写一个 EnvoyFilter 资源,用于将 HTTP Filter 注入到 Envoy 中。
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: cvn-rewrite # EnvoyFilter 的名称
namespace: istio-system # EnvoyFilter 所在的命名空间, 作用于 ingressgateway
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: HTTP_FILTER # 应用于 HTTP_FILTER
match:
context: GATEWAY # 作用于 GATEWAY
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
subFilter:
name: envoy.filters.http.router
patch:
operation: INSERT_BEFORE # 在 envoy.router 之前插入
value:
name: envoy.lua
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
inlineCode: |
function envoy_on_request(request_handle)
local cvn = request_handle:headers():get(":path"):match("cvn=([%w%.%-]+)")
if cvn == nil then
cvn = "default"
end
request_handle:headers():add("x-client-cvn", cvn)
end
如果想要了解 envoy 中关于 lua 更多的扩展内容,一定要先通读下官方文档,地址在此:https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter.html#lua
部署
为了方便测试我们可以编写这么一个应用用于打印请求头:
app.py
服务代码:
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def index():
print(request.headers)
dict = {}
for key, value in request.headers:
dict[key] = value
return dict
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
Dockerfile
文件进行打包:
nerdctl.lima build -t docker.io/yeqown/istio-envoy-filter-demo:0.0.1 . --build-arg VERSION=0.0.1
如果不想自己打包,可以直接用我的镜像:docker.io/yeqown/istio-envoy-filter-demo:0.0.1
FROM python:3.8-slim-buster
WORKDIR /app
COPY . .
RUN pip install flask
EXPOSE 8080
CMD ["python", "app.py"]
k8s 资源配置 deployment.yaml
文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: istio-envoy-filter-demo
namespace: default
labels:
app: istio-envoy-filter-demo
spec:
selector:
matchLabels:
app: istio-envoy-filter-demo
replicas: 1
template:
metadata:
labels:
app: istio-envoy-filter-demo
spec:
containers:
- name: istio-envoy-filter-demo
image: docker.io/yeqown/istio-envoy-filter-demo:0.0.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: istio-envoy-filter-demo
namespace: default
spec:
selector:
app: istio-envoy-filter-demo
ports:
- name: http-port
port: 8080
targetPort: 8080
deployment.networking.yaml
istio 配置文件:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: istio-envoy-filter-demo
namespace: default
spec:
hosts:
- "*"
gateways:
- istio-envoy-filter-demo-gateway
http:
- name: "istio-envoy-filter-demo"
match:
- uri:
exact: /istio-envoy-filter-demo
rewrite:
uri: /
route:
- destination:
host: istio-envoy-filter-demo.default.svc.cluster.local
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: istio-envoy-filter-demo-gateway
namespace: default
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
将这些文件都部署后,可以通过以下的命令来检查时链路是否通畅:
INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].port}')
curl -X GET -v http://$INGRESS_HOST:$INGRESS_PORT/istio-envoy-filter-demo
# > GET /istio-envoy-filter-demo HTTP/1.1
# > Host: 10.96.133.213
# > User-Agent: curl/8.4.0
# > Accept: */*
# >
# < HTTP/1.1 200 OK
# < server: istio-envoy
# < date: Wed, 27 Dec 2023 06:56:18 GMT
# < content-type: application/json
# < content-length: 691
# < x-envoy-upstream-service-time: 1
# <
# { [691 bytes data]
# 100 691 100 691 0 0 182k 0 --:--:-- --:--:-- --:--:-- 224k
# * Connection #0 to host 10.96.133.213 left intact
# {
# "Accept": "*/*",
# "Host": "10.96.133.213",
# "User-Agent": "curl/8.4.0",
# "X-B3-Parentspanid": "914dc42200412837",
# "X-B3-Sampled": "1",
# "X-B3-Spanid": "8c40029a531c2e93",
# "X-B3-Traceid": "c992738f6d8be43e914dc42200412837",
# "X-Envoy-Attempt-Count": "1",
# "X-Envoy-Internal": "true",
# "X-Envoy-Original-Path": "/istio-envoy-filter-demo",
# "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/default;Hash=da144c5982372ecb6463dbfed384d2c4430f7ebfd5e4f5183238f19f8efb565a;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account",
# "X-Forwarded-For": "10.244.0.1",
# "X-Forwarded-Proto": "http",
# "X-Request-Id": "f94024c6-8948-9a1c-af4c-aec40c395f8a"
# }
这里我们可以注意到,请求头中除了常见的 Accept
, Host
, User-Agent
还多了一些,如:X-B3-Parentspanid
, X-Request-Id
等,这些都是 istio 为我们提供的功能,用于链路追踪,我们可以通过这些参数来追踪请求的链路。当然其中没有包含 x-client-cvn
因为我们还没有部署 EnvoyFilter。
部署并测试自定义 envoyFilter
将上面编写的 EnvoyFilter 部署到 k8s 中:
kubectl apply -f envoyfilter.yaml
将这个资源都应用到 k8s 之后,我们再次访问服务,可以看到请求头中多了一个 x-client-cvn
参数,这就是我们自定义的 EnvoyFilter 所做的事情。
curl -X GET -v http://$INGRESS_HOST:$INGRESS_PORT/istio-envoy-filter-demo?cvn=v1.2.3-hotfix
# * Connected to 10.96.133.213 (10.96.133.213) port 80
# > GET /istio-envoy-filter-demo?cvn=v1.2.3-hotfix HTTP/1.1
# > Host: 10.96.133.213
# > User-Agent: curl/8.4.0
# > Accept: */*
# >
# < HTTP/1.1 200 OK
# < server: istio-envoy
# < date: Wed, 27 Dec 2023 06:55:32 GMT
# < content-type: application/json
# < content-length: 715
# < x-envoy-upstream-service-time: 7
# <
# { [715 bytes data]
# 100 715 100 715 0 0 78857 0 --:--:-- --:--:-- --:--:-- 79444
# * Connection #0 to host 10.96.133.213 left intact
# {
# "Accept": "*/*",
# "Host": "10.96.133.213",
# "User-Agent": "curl/8.4.0",
# "X-B3-Parentspanid": "ea5a04a5505b30f2",
# "X-B3-Sampled": "1",
# "X-B3-Spanid": "579aefbe55ff1859",
# "X-B3-Traceid": "22daad86fc4471d2ea5a04a5505b30f2",
# "X-Client-Cvn": "v1.2.3-hotfix",
# "X-Envoy-Attempt-Count": "1",
# "X-Envoy-Internal": "true",
# "X-Envoy-Original-Path": "/istio-envoy-filter-demo?cvn=v1.2.3-hotfix",
# "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/default;Hash=da144c5982372ecb6463dbfed384d2c4430f7ebfd5e4f5183238f19f8efb565a;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account",
# "X-Forwarded-For": "10.244.0.1",
# "X-Forwarded-Proto": "http",
# "X-Request-Id": "c5041ac1-8b18-9999-be44-63bc6fbfa885"
# }
到这里,我们就完成了一个简单的自定义 EnvoyFilter 的功能:在网关处将查询参数 cvn
转请求头 X-Client-Cvn
,当然这只是一个简单的例子,实际的使用中,我们可以基于 Envoy 的扩展机制,实现更多的功能,比如:ip 白名单、ip 黑名单、统一认证等。
所有代码都可以在:https://github.com/yeqown/playground/tree/master/k8s/istio-envoy-filter 找到。