写点什么

使用插件扩展服务网格

作者:Flomesh
  • 2023-02-07
    北京
  • 本文字数:7566 字

    阅读完需:约 25 分钟

使用插件扩展服务网格

在 Flomesh 服务网格 osm-edge 最新发布的 1.3.0 版本 中,我们推出了一项重要特性:插件(Plugin)。这项功能旨在为开发者提供途径对服务网格的功能进行扩展,今天这篇文章便为大家介绍一下该特性。

背景

向左走,向右走?



如今的服务网格貌似向着两个方向发展,一个是类似 Istio 的提供大量开箱即用功能,功能非常丰富;另一个是 Linkerd、Flomesh 等秉持着简单之上的原则,提供满足用户的最小功能集。二者无所谓优劣:前者功能丰富但不可避免代理额外的开销,不只是资源占用,还有学习、维护上的成本;后者学习、使用简单,资源占用少,但提供的功能无法满足用户对功能的追求。


在首届服务网格峰会中,几乎所有分享中都提及的内容是 扩展性,相信这也是已经采用或者观望中的用户非常关注的部分(对此感兴趣的同学,可以参考我的那篇 服务网格峰会回顾:服务网格的发展趋势)。即使像 Istio 这种提供了丰富的功能,用户仍需要对网格的功能进行扩展。


“魚,我所欲也;熊掌,亦我所欲也。” 有没有什么方案可以做到鱼与熊掌兼得呢?


不难想到,理想的方案是 最小功能集的低成本开销 + 灵活的扩展性。服务网格的核心在数据面,灵活的扩展性对 sidecar 代理的体质有着非常高的要求。这也正是 Flomesh 服务网格选择 可编程代理 Pipy 作为 sidecar 代理的原因。

可编程代理 Pipy

Pipy 是面向云、边缘和 IoT 的可编程网络代理。具有灵活多变、快、小、可编程、开放的特性。灵活多变的特性,也使其可以应用于多种场景。


Pipy 的核心使用了模块化设计,提供了大量可复用的小模块(过滤器)。这些过滤器可以组装成管道,网络数据流经这些管道并被处理。对外屏蔽了底层的细节,提供了类似拼图的编程方式来实现业务目标。


这里需要一提的是 Pipy 的脚本(实现功能逻辑的编程代码)可以通过网络动态下发到 Pipy 实例,做到无需编译无需重启为代理扩展新的功能。

Flomesh 服务网格的方案

osm-edge 提供了三个新的 CRD 来提供扩展性:


  • Plugin:插件,包含了新增功能的代码逻辑,用于扩展功能。osm-edge 默认提供的功能也是通过插件方式存在的,但并不是以 Plugin 资源的形式提供。安装 osm-edge 时通过 helm values 文件对这些插件进行调整。更多内容,可以参考 Helm values.yaml 内置插件列表

  • PluginChain:插件链,插件的执行是循序执行的,通过插件链在组装插件的执行顺序。系统提供了 4 个插件链:inbound-tcpinbound-httpoutbound-tcpoutbound-http。分别对应着入站和出站流量的 4 层、7 层处理阶段。

  • PluginConfig:插件配置,提供插件逻辑运行所需的配置,会以 JSON 的方式下发到边车代理。


有关插件 CRD 的详细说明,可以参考 Plugin API 文档,也将在下面的演示中结合具体示例进行解释。

演示

在这个演示中,我们将会为服务网格扩展 IAM(Identity and Access Management)功能,来提升服务的安全性:服务 A 访问服务 B 时会携带获得的令牌,服务 B 收到请求后通过身份验证服务来验证令牌,根据验证结果决定是否提供服务。


这里需要用到两个 Plugin:为服务 A 的请求注入令牌的 token-injector;对访问服务 B 的请求进行身份验证的 token-verifyer。二者分别处理 outbound 和 inbound 的流量。


与之对应的是两个 PluginChaintoken-injector-chaintoken-verifier-chain



安装集群

export INSTALL_K3S_VERSION=v1.23.8+k3s2curl -sfL https://get.k3s.io | sh -s - --disable traefik --disable servicelb  --write-kubeconfig-mode 644 --write-kubeconfig ~/.kube/config
复制代码

下载并安装 osm-edge

system=$(uname -s | tr [:upper:] [:lower:])arch=$(dpkg --print-architecture)release=v1.3.0curl -L https://github.com/flomesh-io/osm-edge/releases/download/${release}/osm-edge-${release}-${system}-${arch}.tar.gz | tar -vxzf -./${system}-${arch}/osm versioncp ./${system}-${arch}/osm /usr/local/bin/
复制代码


export osm_namespace=osm-system export osm_mesh_name=osm 
osm install \ --mesh-name "$osm_mesh_name" \ --osm-namespace "$osm_namespace"
复制代码

部署示例应用

kubectl create namespace curlosm namespace add curlkubectl apply -n curl -f https://raw.githubusercontent.com/flomesh-io/osm-edge-docs/main/manifests/samples/curl/curl.yaml
kubectl create namespace httpbinosm namespace add httpbinkubectl apply -n httpbin -f https://raw.githubusercontent.com/flomesh-io/osm-edge-docs/main/manifests/samples/httpbin/httpbin.yaml
sleep 2kubectl wait --for=condition=ready pod -n curl -l app=curl --timeout=90skubectl wait --for=condition=ready pod -n httpbin -l app=httpbin --timeout=90s
curl_pod=`kubectl get pod -n curl -l app=curl -o jsonpath='{.items..metadata.name}'`httpbin_pod=`kubectl get pod -n httpbin -l app=httpbin -o jsonpath='{.items..metadata.name}'`
复制代码


查看两个应用目前的插件链内容,这里 内置的插件 位于 modules 目录中。这些内置插件是服务网格原生提供的功能,并不是通过插件功能配置的,但是可以通过插件功能进行覆盖。


osm proxy get config_dump -n curl $curl_pod | jq '.Chains."outbound-http"'[  "modules/outbound-http-routing.js",  "modules/outbound-metrics-http.js",  "modules/outbound-tracing-http.js",  "modules/outbound-logging-http.js",  "modules/outbound-circuit-breaker.js",  "modules/outbound-http-load-balancing.js",  "modules/outbound-http-default.js"]
osm proxy get config_dump -n httpbin $httpbin_pod | jq '.Chains."inbound-http"'[ "modules/inbound-tls-termination.js", "modules/inbound-http-routing.js", "modules/inbound-metrics-http.js", "modules/inbound-tracing-http.js", "modules/inbound-logging-http.js", "modules/inbound-throttle-service.js", "modules/inbound-throttle-route.js", "modules/inbound-http-load-balancing.js", "modules/inbound-http-default.js"]
复制代码


测试下应用间的通信。


kubectl exec $curl_pod -n curl -c curl -- curl -Is http://httpbin.httpbin:14001/getHTTP/1.1 200 OKserver: gunicorn/19.9.0date: Sun, 05 Feb 2023 05:42:51 GMTcontent-type: application/jsoncontent-length: 304access-control-allow-origin: *access-control-allow-credentials: trueconnection: keep-alive
复制代码

部署身份认证服务

部署独立的身份认证服务,对请求进行认证,返回 200 或者 401。这里我们硬编码了有效令牌 2f1acc6c3a606b082e5eef5e54414ffb


kubectl create namespace auth
kubectl apply -n auth -f - <<EOFapiVersion: apps/v1kind: Deploymentmetadata: labels: app: ext-auth name: ext-authspec: replicas: 1 selector: matchLabels: app: ext-auth strategy: {} template: metadata: creationTimestamp: null labels: app: ext-auth spec: containers: - command: - pipy - -e - |2-
pipy({ _acceptTokens: ['2f1acc6c3a606b082e5eef5e54414ffb'], _allow: false, })
// Pipeline layouts go here, e.g.: .listen(8079) .demuxHTTP().to($ => $ .handleMessageStart( msg => ((token = msg?.head?.headers?.['x-iam-token']) => _allow = token && _acceptTokens?.find(el => el == token) )() ) .branch(() => _allow, $ => $.replaceMessage(new Message({ status: 200 })), $ => $.replaceMessage(new Message({ status: 401 })) ) ) image: flomesh/pipy:latest name: pipy resources: {}---apiVersion: v1kind: Servicemetadata: labels: app: ext-auth name: ext-authspec: ports: - port: 8079 protocol: TCP targetPort: 8079 selector: app: ext-authEOF
复制代码

启用插件策略

默认情况下并未启用插件策略,需要修改 网格配置 来开启。


export osm_namespace=osm-systemkubectl patch meshconfig osm-mesh-config -n "$osm_namespace" -p '{"spec":{"featureFlags":{"enablePluginPolicy":true}}}' --type=merge
复制代码

声明插件

插件 token-injector


  • metadata.name: 插件名称,也是插件脚本名称。比如这个插件将保存为 token-injector.js 存储在代码库的 plugins 目录中。

  • spec.pipyscriptPipyJS 脚本 内容,是功能逻辑代码,保存在脚本文件 plugins/token-injector.js 中。在脚本中可以只用系统内置的上下文元数据(context metadata)。碍于篇幅,这里提供元数据变量列表的截图。


  • spec.priority:插件的优先级,可选值 0-65535。数值越大,优先级越高,在插件链中的位置越靠前。这里的值是 115,通过查看 Helm values.yaml 内置插件列表 可知,将位于 modules/outbound-circuit-breaker.jsmodules/outbound-http-load-balancing.js 之间,在熔断逻辑处理之后、负载均衡器转发到上游之前执行。


kubectl apply -f - <<EOFkind: PluginapiVersion: plugin.flomesh.io/v1alpha1metadata:  name: token-injectorspec:  priority: 115  pipyscript: |+    (    pipy({      _pluginName: '',      _pluginConfig: null,      _accessToken: null,    })    .import({        __service: 'outbound-http-routing',    })    .pipeline()    .onStart(        () => void (            _pluginName = __filename.slice(9, -3),            _pluginConfig = __service?.Plugins?.[_pluginName],            _accessToken = _pluginConfig?.AccessToken        )    )    .handleMessageStart(        msg => _accessToken && (msg.head.headers['x-iam-token'] = _accessToken)    )    .chain()    )EOF
复制代码


插件 token-verifier


kubectl apply -f - <<EOFkind: PluginapiVersion: plugin.flomesh.io/v1alpha1metadata:  name: token-verifierspec:  priority: 115  pipyscript: |+    (    pipy({        _pluginName: '',        _pluginConfig: null,        _accessToken: null,        _valid: false,    })    .import({        __service: 'inbound-http-routing',    })    .pipeline()    .onStart(        () => void (            _pluginName = __filename.slice(9, -3),            _pluginConfig = __service?.Plugins?.[_pluginName],            _accessToken = _pluginConfig?.AccessToken        )    )    .handleMessageStart(        msg => _valid = (_accessToken && msg.head.headers['accesstoken'] === _accessToken)    )    .branch(        () => _valid, (            $ => $.chain()        ), (            $ => $.replaceMessage(            new Message({ status: 403 }, 'token verify failed')            )        )    )    )EOF
复制代码

设置插件链

插件链 token-injector-chain


  • metadata.name:插件链资源名称 token-injector-chain

  • spec.chains

  • name:所处的插件链名称,4 个插件链之一,这里是 outbound-http 也就是出站流量的 HTTP 协议处理阶段。

  • plugins:要插入到插件链的插件列表,这里将 token-injector 插入到插件链中。

  • spec.selectors:插件链作用的目标,使用的是 Kubernetes 标签选择器 方案。

  • podSelector:pod 选择器,选择标签 app=curl 的 pod。

  • namespaceSelector:命名空间选择器,选择命名空间被网格纳管的命名空间,即 openservicemesh.io/monitored-by=osm


kubectl apply -n curl -f - <<EOFkind: PluginChainapiVersion: plugin.flomesh.io/v1alpha1metadata:  name: token-injector-chainspec:  chains:    - name: outbound-http      plugins:        - token-injector  selectors:    podSelector:      matchLabels:        app: curl      matchExpressions:        - key: app          operator: In          values: ["curl"]    namespaceSelector:      matchExpressions:        - key: openservicemesh.io/monitored-by          operator: In          values: ["osm"]EOF
复制代码


插件链 token-verifier-chain


kubectl apply -n httpbin -f - <<EOFkind: PluginChainapiVersion: plugin.flomesh.io/v1alpha1metadata:  name: token-verifier-chainspec:  chains:    - name: inbound-http      plugins:        - token-verifier  selectors:    podSelector:      matchLabels:        app: httpbin    namespaceSelector:      matchExpressions:        - key: openservicemesh.io/monitored-by          operator: In          values: ["osm"]EOF
复制代码


应用了插件链配置后,此时再查看两个应用的插件链。从结果可以看到两个位于 plugins 目录中的插件,我们声明的插件通过插件链的配置已经配置两个应用中了。


osm proxy get config_dump -n curl $curl_pod | jq '.Chains."outbound-http"'[  "modules/outbound-http-routing.js",  "modules/outbound-metrics-http.js",  "modules/outbound-tracing-http.js",  "modules/outbound-logging-http.js",  "modules/outbound-circuit-breaker.js",  "plugins/token-injector.js",  "modules/outbound-http-load-balancing.js",  "modules/outbound-http-default.js"]
osm proxy get config_dump -n httpbin $httpbin_pod | jq '.Chains."inbound-http"'[ "modules/inbound-tls-termination.js", "modules/inbound-http-routing.js", "modules/inbound-metrics-http.js", "modules/inbound-tracing-http.js", "modules/inbound-logging-http.js", "modules/inbound-throttle-service.js", "modules/inbound-throttle-route.js", "plugins/token-verifier.js", "modules/inbound-http-load-balancing.js", "modules/inbound-http-default.js"]
复制代码


应用配置了插件之后,由于没有为插件进行配置,应用 curl 仍然可以访问 httpbin


kubectl exec $curl_pod -n curl -c curl -- curl -Is http://httpbin.httpbin:14001/getHTTP/1.1 200 OKserver: gunicorn/19.9.0date: Sun, 05 Feb 2023 06:34:33 GMTcontent-type: application/jsoncontent-length: 304access-control-allow-origin: *access-control-allow-credentials: trueconnection: keep-alive
复制代码

设置插件配置

我们先应用插件 token-verifier 的配置,这里配置了身份认证服务 ext-auth.auth:8079 和需要进行认证的请求 /get


  • spec.config 插件配置的内容,将被转换成 JSON 格式。比如这里为 token-verifier 插件应用的配置最终以下面 JSON 的形式存在:


kubectl apply -n httpbin -f - <<EOFkind: PluginConfigapiVersion: plugin.flomesh.io/v1alpha1metadata:  name: token-verifier-configspec:  config:    Verifier: 'ext-auth.auth:8079'    Paths:      - "/get"  plugin: token-verifier  destinationRefs:    - kind: Service      name: httpbin      namespace: httpbinEOF
复制代码


这时应用 curl 是无法访问 httbin/get 路径,因为还没有为 curl 配置访问令牌。


kubectl exec $curl_pod -n curl -c curl -- curl -Is http://httpbin.httpbin:14001/getHTTP/1.1 401 Unauthorizedcontent-length: 13connection: keep-alive
复制代码


但访问 /headers 路径并不需要令牌。


kubectl exec $curl_pod -n curl -c curl -- curl -Is http://httpbin.httpbin:14001/headersHTTP/1.1 200 OKserver: gunicorn/19.9.0date: Sun, 05 Feb 2023 06:37:05 GMTcontent-type: application/jsoncontent-length: 217access-control-allow-origin: *access-control-allow-credentials: trueconnection: keep-alive
复制代码


接下来应用插件 token-injector 的配置,为应用的请求配置访问令牌 2f1acc6c3a606b082e5eef5e54414ffb。这个令牌,也是身份认证服务中硬编码的有效令牌。


kubectl apply -n curl -f - <<EOFkind: PluginConfigapiVersion: plugin.flomesh.io/v1alpha1metadata:  name: token-injector-configspec:  config:    AccessToken: '2f1acc6c3a606b082e5eef5e54414ffb'  plugin: token-injector  destinationRefs:    - kind: Service      name: httpbin      namespace: httpbinEOF
复制代码


此时,再次访问 httpbin/get 路径,请求通过认证后被 httpbin 接受。


kubectl exec $curl_pod -n curl -c curl -- curl -Is http://httpbin.httpbin:14001/getHTTP/1.1 200 OKserver: gunicorn/19.9.0date: Sun, 05 Feb 2023 06:39:54 GMTcontent-type: application/jsoncontent-length: 360access-control-allow-origin: *access-control-allow-credentials: trueconnection: keep-alive
复制代码

总结

这篇文章,我们通过实际的场景来演示了 osm-edge 在 1.3.0 中的插件功能。插件功能的推出,为网格功能的扩展带来了方便快捷的机制。通过插件功能、Pipy 的可编程特性和多协议的支持,我们可以轻松为网格定制各种功能。


有了灵活扩展的能力,加上低成本的开销,相信服务网格会有更广阔的天地!



引用链接

[1] osm-edge: https://github.com/flomesh-io/osm-edge

[2] 1.3.0 版本: https://github.com/flomesh-io/osm-edge/releases/tag/v1.3.0

[3] 可编程代理 Pipy: https://github.com/flomesh-io/pipy

[4] Helm values.yaml 内置插件列表: https://github.com/flomesh-io/osm-edge/blob/45b05bd39dc0e8d1c28460622a4be2f92abdf28f/charts/osm/values.yaml#L84

[5] Plugin API 文档: https://osm-edge-docs.flomesh.io/docs/api_reference/plugin/

[6] 网格配置: https://osm-edge-docs.flomesh.io/docs/api_reference/config/v1alpha2/

[7] PipyJS 脚本: https://flomesh.io/pipy/docs/en/reference/pjs

[8] Helm values.yaml 内置插件列表: https://github.com/flomesh-io/osm-edge/blob/45b05bd39dc0e8d1c28460622a4be2f92abdf28f/charts/osm/values.yaml#L84

[9] Kubernetes 标签选择器: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/

发布于: 2 小时前阅读数: 3
用户头像

Flomesh

关注

微信订阅号:flomesh 2022-04-07 加入

一站式云原生应用流量管理供应商 官网:https://flomesh.io

评论

发布
暂无评论
使用插件扩展服务网格_插件_Flomesh_InfoQ写作社区