12 月 28 日,上周六有幸参与了 KubeSphere 社区和 Higress 社区联合举办的「云原生 + AI Meetup 广州站」的活动。在会上,我分享了一篇关于「从 LB Ingress 到 ZTM:集群服务暴露新思路」的主题演讲。在这里,我将分享一下演讲的内容,同时将文章标题做了小调整。
集群服务暴露的需求
集群服务暴露的需求来自 Kubernetes 服务的虚拟化和网络隔离。众所周知,Kubernetes 的 Pod 是动态的,可能会频繁的删除、重建,重新调度到不同的节点,IP 地址也会随之变化。Kubernetes 使用 Service 来提供访问 Pod 的稳定接口,实现对服务的抽象。
Service 为 Pod 提供了一个稳定的 DNS 名称和虚拟 IP 地址,而不依赖于 Pod 的临时 IP。因此在集群内部的通信,通过 Service 的 ClusterIP 访问完全不存在问题。
不过 Service 的 ClusterIP 只能在集群内部访问,外部无法直接访问。Service DNS 名称的解析,只能在集群内部进行。这种网络隔离作为网络保护机制,确保 Pod 和 Service 的访问受限于集群内部。
然而,我们在实际应用中,往往需要将服务暴露到集群外部,以便外部用户访问。这时,我们就需要额外的组件来实现集群服务的暴露。尤其是在一些高级应用场景下,如多集群、多云等,更需要一种灵活、动态的方式来暴露集群服务。
集群服务的暴露方式
Kubernetes 对集群服务的抽象,提供了多种方式,设计外部访问的有 LoadBalancer[1]、NodePort[2],以及更高级的 Ingress[3]。(Ingress 可能逐渐被 Gateway API[4] 所取代,而二者实现上基本一致。我们下面讨论的 Ingress 泛指 Kubernetes 高级入口流量管理。)
这些方式的实现方式各有不同,各有优劣,适用于不同的场景。
LoadBalancer
LoadBalancer 是常见的暴露集群服务的方式之一。在云环境中通过云提供商的负载均衡器,可以将流量分发到集群内的多个 Pod 上;在 On-Prem 环境中,可以使用开源负载均衡器。咱们这里主要讨论开源负载均衡器。
kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.43.177.15 192.168.2.1 80:32186/TCP 2m18s
在开源的服务均衡器中,有两类比较常见的实现。第一种的实现比较多,如 MetaLB[5]、青云的 OpenELB[6]、kube-vip[7] 以及 PureLB[8]。这几种的实现方式基本类似,都会在集群的每个节点上运行一个控制器 Pod。这个控制器 Pod 会监听 LoadBalancer 类型 Service 的变化,然后为其配置 VIP,并将 VIP 绑定到节点的网卡上。最后这个 VIP 会通过 ARP(二层)或者 BGP(三层)协议通知外部网络。
如果到某个 VIP 存在多条路由,通常会使用等价多路径(ECMP)路由测量将流量分发到多个节点上,以实现负载均衡。
详细内容可以参考我过往的几篇文章:
Kubernetes LoadBalancer Service 与负载均衡器 在 Kubernetes 集群中使用 MetalLB 作为 LoadBalancer(上)- Layer2 在 Kubernetes 集群中使用 MetalLB 作为 LoadBalancer(下)- BGP
第二种的实现方式相对简单不少,如 K3s 的 ServiceLB 也就是以前 Klipper[9],基于 iptables 和 HostNetwork。当监控到有 LoadBalancer 类型的 Service 创建时,ServiceLB 会为该 Service 创建一个 DS(DaemonSet),DS 在每个 Node 上创建代理容器,代理容器使用 hostNetwork 网络模式,hostPort 为 Service 的端口。
访问的入口是每个节点的 IP 地址,代理容器接收到流量后通过 iptables 转发到 Service 的 clusterIP,最终到达 Pod。
这种实现方式的优点是轻量级,不需要依赖云供应商的负载均衡器,成本低,简单易用,非常适合网络和资源受限的场景;缺点也明显,全节点监听端口,缺乏 TLS 终止等高级功能,转发需要节点网络高并发场景下成为瓶颈。
NodePort
如果说 LoadBalancer 是比较高级的方式,那么 NodePort 就是最简单直接的方式了。NodePort 是一种 Service 类型,用于在集群的每个节点上开放一个固定的端口(30000-32767),将该端口的流量转发到后端的 Pod。
针对每个 NodePort 类型的 Service,Kubernetes 会其分配一个端口。运行在每个节点上的 kube-proxy 会根据 Service 和 Pod 的信息,设置 iptables 规则。这样,当有流量到达节点的 NodePort 端口时,iptables 会先匹配到对应的 Service,然后将流量转发到后端的 Pod。
场景上,NodePort 适用于小型集群、测试环境等,不需要负载均衡,直接暴露节点 IP 和端口即可。缺点是无自动负载均衡,暴露节点 IP,安全性较低。
Ingress
Ingress 是 Kubernetes 内置的流量管理对象,定义了 HTTP 或 HTTPS 路由规则。Ingress 通过 Ingress Controller 实现,Ingress Controller 会根据 Ingress 资源的配置,动态的更新负载均衡器的配置,以实现流量的分发。慢慢地 Ingress 可能会被 Gateway API[10] 所取代,后者有着更加灵活的扩展能力。但二者实现基本类似,都是有代理和控制器组成。
所以这里我们以大家最为熟悉的 Ingress 为例。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example
spec:
rules:
- http:
paths:
- backend:
service:
name: foo
port:
number: 8080
path: /foo
- backend:
service:
name: bar
port:
number: 8080
path: /bar
Ingress 的定位是集群的单一入口,通过这个入口管理多个服务。通过基于域名、路径进行流量转发。与 LoadBalancer 和 NodePort 不同,Ingress 工作在 OSI 模型的第七层,支持 HTTP、HTTPS 等应用层协议,提供更加高级的流量管理功能,如 TLS 卸载、限流、金丝雀发布、熔断、重定向等,还可以通过更加复杂的路由规则实现更加灵活的流量控制。
场景上,Ingress 适用于复杂路由规则、HTTPS 支持等场景。缺点是配置可能相对复杂,还需要依赖 Ingress Controller。更主要的是,Ingress 的代理本身仍然需要对外暴露,需要额外的 LoadBalancer 来实现。
介绍了这几种方式后,我们看下是否还有其他的方式来暴露集群服务。
零暴露面网络 ZTM
零暴露面网络 ZTM(Zero Trust Mesh)[11] 是基于 HTTP/2 隧道的去中心化的、面向远程办公和 IoT 边缘网络等场景的开源网络基础设施软件。
ZTM 可以运行在任意的现存 IP 网络之上,包括但不限于局域网、互联网、容器网络等。ZTM 为保障应用安全提供了必要的网络基础,包括网络联通性、基于端口的访问控制、mTLS 加密的网络通道、基于证书的身份识别与访问控制、负载均衡等基础网络及安全能力。
ZTM 可以多种设备上运行,支持 CPU 架构:x86、ARM、MIPS、RISC-V、LoongArch 等,以及操作系统:Linux、Windows、macOS、FreeBSD、Android 等。
ZTM 的架构
ZTM 的架构非常简单,
ZTM Agent:部署在需要接入零信任网络的设备上,包括个人计算机、服务器或边缘设备上,用于发起加密隧道,将设备的流量安全地转发到 ZTM Hub。 ZTM Hub:分布式部署的接入点,与每个 Agent 建立加密隧道,转发来自 Agent 的请求,实现多点接入和高可用性。
通过 ZTM Hub 的连通,Agent 之间可以在已有网络(局域网、互联网、容器网络等)之上组建一个安全的零信任网络,实现设备之间的安全通信。
这个网络在 ZTM 中的术语 Mesh。Mesh 是一个逻辑网络,由多个 Agent 组成,Agent 之间通过加密隧道连接。Agent 也可以加入多个 Mesh,实现与多个网络之间的互联。
ZTM 的特性
零防火墙:全链路无需防火墙配置,管理更加简单。 零端口:没有开放端口,轻松应对各种扫描。 零运维:终端网络不需要虚拟网卡,不需要终端路由,不需要终端防火墙。 零特权:Agent 运行的用户态,不需要特权配置,更加安全。 零路由:基于服务发现的访问机制,无需复杂易错的路由配置。 零信任:基于证书的身份识别,可信设备,全链路验证身份和访问控制。
ZTM App
zt-app 框架是一个基于 ZTM 的应用开发框架,提供了一套标准化的开发接口,使开发者能够更轻松地构建去中心化的应用。zt-app 框架的设计目标是“简单、易用、安全”,为开发者提供便捷的开发工具。
ZTM 是用 PipyJS[12] 编写的,PipyJS 是一种专为 Pipy[13] 设计的定制版 JavaScript 。开发人员可以使用 PipyJS 轻松地开发 ZTM App。
在 ZTM 中内置了几大关键 App:
zt-tunnel:在点对点之间建立安全的 TCP/UDP 隧道。 zt-proxy:SOCKS/HTTP 代理,从一个端点接收流量,另一个端点转发出去。 zt-script:在端点上远程执行脚本。 zt-terminal:远程访问端点上的 shell。
隧道 zt-tunnel
零信任隧道,消除了物理距离和网络隔离的限制,可以从任何地方访问设备。zt-tunnel 有两个核心概念:
出口 Outbound:隧道的出口,位于目标设备的网络中。 入口 Inbound:隧道的入口,位于访问方的网络中。
比如这个场景,我们有两个隔离的网络,通过 ZTM 打通组成了一个 ZTM 网络。在我们的办公网络中有两台设备:Windows 设备支持远程桌面访问;Linux 设备支持 SSH 访问。我们称这两个为远端设备。
当我们需要从外面访问这两台设备时,我们只需要在办公网络的 ZTM Agent 上创建名为 tcp/rdp 和 tcp/ssh-vm 的两个出口,分别指向要访问的两台设备。
在访问侧的 ZTM Agent 上创建两个入口,分别指向上面创建的出口,并指定端口。当我们需要访问远端设备时,仅需访问本地 Agent 的地址和入口端口即可。
如果需要访问其他网络的设备,只需要其网络中运行 ZTM Agent 并连接到同一个 ZTM Hub 上,然后为设备添加出口和入口即可。
代理 zt-proxy
用 zt-tunnel 的方式访问设备时需要为每个服务都创建隧道的出口和入口,可能会比较繁琐。zt-proxy 可以简化这个过程,通过代理的方式访问设备。
zt-proxy 可以为访问方提供一个 HTTP 或 SOCKS 代理,在目标网络的 ZTM Agent 上,我们只需添加代理的目标地址(可以是 IP 网段,也可以是带有通配符的域名),完成。在访问方只需要目标的 IP 地址或者域名,通过代理即可访问目标设备了。
通过 IP 网段或者域名,可以实现设备的批量添加,非常适合多设备的场景。并且代理的方式,可以让我们像在同一个局域网中一样访问远程设备。
在了解过 ZTM 的基本概念和特性之后,我们可以看下如何使用 ZTM 来暴露集群服务。
使用 ZTM 暴露集群服务
可能有人已经想到了,我们可以使用 ZTM 来打通集群内外、甚至是多个集群的网络。
借助 ZTM Hub,我们可以在集群内外部署 ZTM Agent,打通与集群的连通性。这样即使在两个隔离的网络,我们也能通过 ZTM 的 HTTP/2 隧道到访问集群内的服务。
使用 zt-tunnel 暴露集群服务
在这个场景中,我们通过 ZTM 的 mesh 网络连通了集群内外的隔离网络。在集群内部,我们可以通过 zt-tunnel 为需要对外暴露的服务创建出口,然后在集群外部的 ZTM Agent 上创建入口,即可通过 ZTM 的隧道访问集群内的服务。
配置隧道的出入口后,我们可以通过 Agent 的地址以及配置的隧道入口的端口里访问集群内的服务 A 了,比如 curl http://192.168.1.30:8080
。
如果需要访问其他服务,只需要为其他服务创建出口和入口即可。
使用 zt-proxy 暴露集群服务
如果有大量的服务需要对外暴露,我们可以借助 zt-proxy 来简化这个过程。在连通 mesh 网络之后,在集群内部可以配置 zt-proxy 的目标为集群内服务的 DNS,如 *.svc.cluster.local
,这样将集群内的所有服务作为代理的目标,也可以通过指定暴露某个命名空间下的服务。
然后通过集群外部 Agent 的代理来访问集群内部服务了,比如 curl -x http://192.168.1.30:8080 http://svc-a.svc:8080
。
通过控制暴露服务的 DNS,我们可以控制服务的暴露范围。
Demo
为了展示 ZTM 如何暴露集群服务,准备了一个 简单的 Demo[14]。
在这个 Demo 中通过 K3d 创建了两个网络完全隔离的集群,接着通过 ZTM 连通两个集群。然后,分别演示了如何通过 ZTM 的隧道和代理来进行跨集群的服务访问。
有兴趣的朋友可以自己动手试试。
方案对比
在介绍过集群服务暴露的方式和 ZTM 后,我们可以对比一下这几种方式。
特性 | NodePort | LoadBalancer | ZTM |
---|---|---|---|
技术原理 | iptables | DNAT(BGP/ARP) | HTTP/2 反向隧道 |
访问方式 | 节点 IP + 固定端口 | 云、开源负载均衡器 + 外部 IP | 隧道、代理 |
网络依赖 | 节点需拥有固定或可访问的 IP | 外部 IP 地址、BGP/ARP 网络环境 | 无需直接暴露网络 |
适用场景 | 小型集群或测试环境,快速暴露服务 | 公有云集群,需高可用和稳定外部访问服务 | 零暴露面安全场景,远程和跨集群访问 |
易用性 | 简单直接,配置少 | 自动化配置,依赖云平台或开源负载均衡器 | 需要部署 ZTM Agent 和 Hub |
ZTM 更多应用场景
远程执行命令 zt-terminal
zt-tunnel 零信任终端,基于“设备 + 证书”身份认证和访问控制的远程命令行工具。获得授权后,一个设备可以在另一个设备上执行 shell 命令。
在远端的网络配置可以执行命令的用户。
ztm terminal config --add-user zt-user1
在本地网路中就可以访问指定设备上的 shell 了。
ztm terminal open ep-office
分布式文件共享 zt-cloud
在 mesh 网络中,可以通过 zt-cloud 来实现分布式文件共享。在某个 Agent 上发布的文件,可以通过 mesh 网络广播到其他的 Agent,实现文件的分发。
其他场景
这里列出来 ZTM 的一些其他应用场景,还有更多的场景等待大家的探索。有兴趣的朋友可以加入 ZTM 玩家交流群,后台联系我加入。
内网穿透 远程办公 安全访问云资源 远程调试 多集群网络 设备远程访问 文件共享
总结
这次分享,我们介绍了集群服务暴露的需求,以及 Kubernetes 中常见的暴露方式。然后介绍了 ZTM 的基本概念和特性,以及如何使用 ZTM 来暴露集群服务。
希望通过本次的分享,大家可以了解到 ZTM 的基本概念和特性,以及如何使用 ZTM 来现集群服务的暴露。
LoadBalancer: https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer
[2]NodePort: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport
[3]Ingress: https://kubernetes.io/docs/concepts/services-networking/ingress/
[4]Gateway API: https://kubernetes.io/docs/concepts/services-networking/gateway/
[5]MetaLB: https://metallb.io
[6]OpenELB: https://openelb.io/
[7]kube-vip: https://kube-vip.io
[8]PureLB: https://purelb.gitlab.io/purelb/
[9]Klipper: https://github.com/k3s-io/klipper-lb
[10]Gateway API: https://kubernetes.io/docs/concepts/services-networking/gateway/
[11]ZTM(Zero Trust Mesh): https://github.com/flomesh-io/ztm
[12]PipyJS: https://flomesh.io/pipy/docs/en/reference/pjs
[13]Pipy: https://github.com/flomesh-io/pipy
[14]简单的 Demo: https://github.com/flomesh-io/ztm-mcs-demo