使用插件扩展服务网格

在这里插入图片描述

在 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+k3s2
curl -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.0
curl -L https://github.com/flomesh-io/osm-edge/releases/download/${release}/osm-edge-${release}-${system}-${arch}.tar.gz | tar -vxzf -
./${system}-${arch}/osm version
cp ./${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 curl
osm namespace add curl
kubectl apply -n curl -f https://raw.githubusercontent.com/flomesh-io/osm-edge-docs/main/manifests/samples/curl/curl.yaml

kubectl create namespace httpbin
osm namespace add httpbin
kubectl apply -n httpbin -f https://raw.githubusercontent.com/flomesh-io/osm-edge-docs/main/manifests/samples/httpbin/httpbin.yaml

sleep 2
kubectl wait --for=condition=ready pod -n curl -l app=curl --timeout=90s
kubectl 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/get
HTTP/1.1 200 OK
server: gunicorn/19.9.0
date: Sun, 05 Feb 2023 05:42:51 GMT
content-type: application/json
content-length: 304
access-control-allow-origin: *
access-control-allow-credentials: true
connection: keep-alive

部署身份认证服务

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

kubectl create namespace auth

kubectl apply -n auth -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ext-auth
  name: ext-auth
spec:
  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: v1
kind: Service
metadata:
  labels:
    app: ext-auth
  name: ext-auth
spec:
  ports:
  - port: 8079
    protocol: TCP
    targetPort: 8079
  selector:
    app: ext-auth
EOF

启用插件策略

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

export osm_namespace=osm-system
kubectl 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 - <<EOF
kind: Plugin
apiVersion: plugin.flomesh.io/v1alpha1
metadata:
  name: token-injector
spec:
  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 - <<EOF
kind: Plugin
apiVersion: plugin.flomesh.io/v1alpha1
metadata:
  name: token-verifier
spec:
  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 - <<EOF
kind: PluginChain
apiVersion: plugin.flomesh.io/v1alpha1
metadata:
  name: token-injector-chain
spec:
  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 - <<EOF
kind: PluginChain
apiVersion: plugin.flomesh.io/v1alpha1
metadata:
  name: token-verifier-chain
spec:
  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/get
HTTP/1.1 200 OK
server: gunicorn/19.9.0
date: Sun, 05 Feb 2023 06:34:33 GMT
content-type: application/json
content-length: 304
access-control-allow-origin: *
access-control-allow-credentials: true
connection: keep-alive

设置插件配置

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

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

    {
      "Plugins": {
        "token-verifier": {
          "Paths": [
            "/get"
          ],
          "Verifier": "ext-auth.auth:8079"
        }
      }
    }
    
kubectl apply -n httpbin -f - <<EOF
kind: PluginConfig
apiVersion: plugin.flomesh.io/v1alpha1
metadata:
  name: token-verifier-config
spec:
  config:
    Verifier: 'ext-auth.auth:8079'
    Paths:
      - "/get"
  plugin: token-verifier
  destinationRefs:
    - kind: Service
      name: httpbin
      namespace: httpbin
EOF

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

kubectl exec $curl_pod -n curl -c curl -- curl -Is http://httpbin.httpbin:14001/get
HTTP/1.1 401 Unauthorized
content-length: 13
connection: keep-alive

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

kubectl exec $curl_pod -n curl -c curl -- curl -Is http://httpbin.httpbin:14001/headers
HTTP/1.1 200 OK
server: gunicorn/19.9.0
date: Sun, 05 Feb 2023 06:37:05 GMT
content-type: application/json
content-length: 217
access-control-allow-origin: *
access-control-allow-credentials: true
connection: keep-alive

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

kubectl apply -n curl -f - <<EOF
kind: PluginConfig
apiVersion: plugin.flomesh.io/v1alpha1
metadata:
  name: token-injector-config
spec:
  config:
    AccessToken: '2f1acc6c3a606b082e5eef5e54414ffb'
  plugin: token-injector
  destinationRefs:
    - kind: Service
      name: httpbin
      namespace: httpbin
EOF

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

kubectl exec $curl_pod -n curl -c curl -- curl -Is http://httpbin.httpbin:14001/get
HTTP/1.1 200 OK
server: gunicorn/19.9.0
date: Sun, 05 Feb 2023 06:39:54 GMT
content-type: application/json
content-length: 360
access-control-allow-origin: *
access-control-allow-credentials: true
connection: keep-alive

总结

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

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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PipyFlomesh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值