这篇文章已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。

使用 KPNG 编写专门的 kube-proxier

这篇文章将向你展示如何使用 Kubernetes Proxy NG kpng 创建一个专门的服务 kube-proxy 风格的网络代理,而不会干扰现有的 kube-proxy。kpng 项目旨在更新默认的 Kubernetes 服务实现“kube-proxy”。kpng 的一个重要特性是它可以作为库使用,在 K8s 外部创建代理。虽然这对替换 kube-proxy 的 CNI 插件很有用,但也为任何人创建用于特殊目的的代理打开了可能性。

定义使用专用代理的服务

apiVersion: v1
kind: Service
metadata:
  name: kpng-example
  labels:
    service.kubernetes.io/service-proxy-name: kpng-example
spec:
  clusterIP: None
  ipFamilyPolicy: RequireDualStack
  externalIPs:
  - 10.0.0.55
  - 1000::55
  selector:
    app: kpng-alpine
  ports:
  - port: 6000

如果定义了 service.kubernetes.io/service-proxy-name 标签,则 kube-proxy 将忽略该服务。自定义控制器可以监视标签设置为其自身名称的服务(在此示例中为“kpng-example”),并设置专门的负载均衡。

service.kubernetes.io/service-proxy-name 标签并非新标签,但到目前为止,编写一个专门的代理相当困难。

假设专用代理的常见用途是处理 K8s 不支持的某些用例的外部流量。在这种情况下,不需要 ClusterIP,因此在此示例中我们使用“无头”服务。

使用 kpng 的专用代理

基于 kpng 的代理包括处理所有 K8s api 相关功能的 kpng 控制器和一个实现负载均衡的“后端”。后端可以与 kpng 控制器二进制文件链接,也可以是使用 gRPC 与控制器通信的单独程序。

kpng kube --service-proxy-name=kpng-example to-api

这将启动 kpng 控制器,并告诉它只监视服务代理名称为“kpng-example”的服务。“to-api”参数将为后端打开 gRPC 服务器。

你可以在集群外部自行测试。请参阅下面的示例。

现在,我们启动一个后端,该后端仅打印来自控制器的更新。

$ kubectl apply -f kpng-example.yaml
$ kpng-json | jq     # (this is the backend)
{
  "Service": {
    "Namespace": "default",
    "Name": "kpng-example",
    "Type": "ClusterIP",
    "IPs": {
      "ClusterIPs": {},
      "ExternalIPs": {
        "V4": [
          "10.0.0.55"
        ],
        "V6": [
          "1000::55"
        ]
      },
      "Headless": true
    },
    "Ports": [
      {
        "Protocol": 1,
        "Port": 6000,
        "TargetPort": 6000
      }
    ]
  },
  "Endpoints": [
    {
      "IPs": {
        "V6": [
          "1100::202"
        ]
      },
      "Local": true
    },
    {
      "IPs": {
        "V4": [
          "11.0.2.2"
        ]
      },
      "Local": true
    },
    {
      "IPs": {
        "V4": [
          "11.0.1.2"
        ]
      }
    },
    {
      "IPs": {
        "V6": [
          "1100::102"
        ]
      }
    }
  ]
}

真正的后端将使用某种机制将来自外部 IP 的流量负载均衡到端点。

编写后端

kpng-json 后端如下所示

package main
import (
        "os"
        "encoding/json"
        "sigs.k8s.io/kpng/client"
)
func main() {
        client.Run(jsonPrint)
}
func jsonPrint(items []*client.ServiceEndpoints) {
        enc := json.NewEncoder(os.Stdout)
        for _, item := range items {
                _ = enc.Encode(item)
        }
}

(是的,这就是整个程序)

真正的后端当然会复杂得多,但这说明了 kpng 如何让你专注于负载均衡。

你可以将多个后端连接到 kpng 控制器,因此在开发或调试期间,让像 kpng-json 后端这样的东西与你的真实后端并行运行会很有用。

示例

完整的示例可以在此处找到。

例如,我们实现一个“all-ip”后端。它将外部 IP 的所有流量定向到本地端点,而不管端口和上层协议如何。有一个针对此功能的 KEP,并且此示例是大大简化的版本。

要将来自外部地址的所有流量定向到本地 POD,只需要一个 iptables 规则,例如;

ip6tables -t nat -A PREROUTING -d 1000::55/128 -j DNAT --to-destination 1100::202

如你所见,地址在对后端的调用中,它要做的就是

  • 使用 Local: true 提取地址
  • ExternalIPs 设置 iptables 规则

执行此操作的脚本可能如下所示

xip=$(cat /tmp/out | jq -r .Service.IPs.ExternalIPs.V6[0])
podip=$(cat /tmp/out | jq -r '.Endpoints[]|select(.Local == true)|select(.IPs.V6 != null)|.IPs.V6[0]')
ip6tables -t nat -A PREROUTING -d $xip/128 -j DNAT --to-destination $podip

假设上面的 JSON 输出存储在 /tmp/out 中(jq 是一个很棒的程序!)。

由于这是一个示例,我们通过使用上面 kpng-json 后端的一个小的变体使其变得非常简单。不是只打印,而是调用一个程序,并将 JSON 输出作为 stdin 传递给该程序。可以独立测试后端

CALLOUT=jq kpng-callout

其中 jq 可以替换为你自己的程序或脚本。脚本可能类似于上面的示例。有关更多信息和完整示例,请参阅 https://github.com/kubernetes-sigs/kpng/tree/master/examples/pipe-exec

总结

虽然 kpng 仍处于开发的早期阶段,但这篇文章旨在展示你将来如何构建自己的专用 K8s 代理。你的应用程序唯一需要做的就是在 Service 清单中添加 service.kubernetes.io/service-proxy-name 标签。

将新功能添加到 kube-proxy 是一个繁琐的过程,而且很可能会被拒绝,因此编写专用代理可能是唯一的选择。