mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3462 字
10 分钟
Kubernetes 网络
2023-10-23

一、什么是 Kubernetes 网络#

将 Kubernetes 的 Master 和 Worker 节点部署到一组主机节点(Node)上,称这组主机节点为 Kubernetes 集群。在 Kubernetes 集群中会运行很多由容器组成的 Pods,Pods 之间使用 CNI 提供的容器网络(Pods Network)进行通信。但容器并非永存,在节点资源不足或容器升级时都会进行 Pod 重建,此时容器网络会为 Pod 分配一个新的网络地址。为了固定容器服务提供的地址与端口,使用 Service 网络提供的 VIP 去代理真实节点的地址与端口。

综上所述,Kubernetes 容器管理平台涉及以下三种网络:

  • Node Network:即宿主机网络,需要保证三层互通。
  • Pods Network:同一个 Pod 中的容器之间通过本地回路(loopback)通信,不同 Pod 中的容器无需 NAT 能直接通信,主机网络与 Pod 网络也无需 NAT 能直接通信。
  • Service Network:为 Pods 中运行的程序提供固定的 IP 与端口,以便于集群内外的访问。
graph TB subgraph "Kubernetes 集群网络架构" subgraph "外部访问" EXT[外部客户端] end subgraph "Service 网络" SVC["Service (ClusterIP)<br/>10.96.0.1:80"] NODEPORT["NodePort<br/>节点IP:30080"] end subgraph "Pods 网络 (CNI)" POD1["Pod A<br/>10.244.1.2"] POD2["Pod B<br/>10.244.2.3"] POD3["Pod C<br/>10.244.1.4"] end subgraph "Node 网络" NODE1["Node 1<br/>192.168.1.10"] NODE2["Node 2<br/>192.168.1.11"] end end EXT --> NODEPORT --> SVC SVC --> POD1 SVC --> POD3 POD1 -->|"CNI 网络互通"| POD2 POD1 -.->|"同节点"| POD3 NODE1 --- NODE2

二、Pods 网络的实现与选择#

我们在上面描述过 Pods 网络的需求。目前实现 Pods 网络有 underlay 和 overlay 两种方案,以及两者共存的混合方案。这里的 underlay 和 overlay 网络并非特指某种具体网络方案,它们仅表示网络是否进行了逻辑性的封装。

overlay网络

底层的基础架构网络被称为 underlay 网络,这层网络在数据中心通常更加注重承载能力、高可用性等特性。

上层的逻辑性网络被称为 overlay 网络,这层网络更加注重实现多租户、流量控制与安全等高级功能。

graph TB subgraph "Overlay 网络(逻辑层)" O1["Pod Network<br/>10.244.0.0/16"] O2["Service Network<br/>10.96.0.0/12"] end subgraph "Underlay 网络(物理层)" U1["Node Network<br/>192.168.1.0/24"] U2["物理交换机"] U3["物理路由器"] end O1 -->|"VXLAN/IPIP 封装"| U1 O2 -->|"kube-proxy 规则"| U1 U1 --> U2 --> U3 style O1 fill:#e1f5fe style O2 fill:#e1f5fe style U1 fill:#fff3e0 style U2 fill:#fff3e0 style U3 fill:#fff3e0

我们的需求是实现 Pods 网络中的 Pod 能不通过 NAT 进行直接通信,以及 Pod 与 Node 不通过 NAT 的直接通信,所以我们只需了解如何在这两类网络中实现即可。

2.1 基于 underlay 网络的 Pods 互通技术#

在 underlay 网络中,我们一般通过路由实现网络互通。Flannel 就提供了一种最朴素的 underlay 网络互通方案——hostgw(主机网关)

hostgw 的原理非常直接:每台宿主机作为各自 Pod 网段的网关,集群中的其他节点通过静态路由知道如何到达该网段。例如,当 Node A 上的 Pod 要访问 Node B 上的 Pod 时,流量首先发送到 Node B 作为网关的地址,再由 Node B 的内核路由转发到对应的 Pod。这种方式简单高效,不需要额外的封装开销,性能接近物理网络。

hostgw 的局限性在于要求所有节点必须在同一个二层网络(VLAN)中,否则跨子网的路由将无法生效。此外,节点之间的路由信息需要手动维护或通过其他机制同步,在大规模集群中管理成本较高。

graph LR subgraph "Node A (10.244.1.0/24)" PA["Pod<br/>10.244.1.5"] RTA["路由表<br/>10.244.2.0/24 via Node B"] end subgraph "Node B (10.244.2.0/24)" PB["Pod<br/>10.244.2.3"] RTB["路由表<br/>10.244.1.0/24 via Node A"] end PA -->|"直接路由"| RTA RTA -->|"二层转发"| RTB RTB --> PB style PA fill:#bbdefb style PB fill:#bbdefb

Calico 通过 BGP 提供了一种全三层的 underlay 网络互通方案。Calico 为每个节点分配一个子网,并通过 BGP 协议将路由信息广播到集群中的其他节点。当 Pod 需要与另一个节点上的 Pod 通信时,流量通过主机路由表直接发送到目标节点,无需任何封装。

graph LR subgraph "Node A" A1["Pod 1<br/>10.244.1.2"] A2["veth pair"] A3["Route Table"] end subgraph "Node B" B1["Pod 2<br/>10.244.2.3"] B2["veth pair"] B3["Route Table"] end A1 --> A2 --> A3 A3 -->|"BGP Route<br/>10.244.2.0/24 via Node B"| B3 B3 --> B2 --> B1

Calico 的 BGP 模式具有以下优势:

  • 高性能:无封装开销,性能接近原生网络
  • 可扩展:BGP 是成熟的分布式路由协议,适合大规模集群
  • 灵活:支持与数据中心现有 BGP 基础设施集成

但 BGP 模式也有局限性:

  • 要求底层网络支持 BGP 协议
  • 跨子网通信需要 BGP Peer 配置

Terway 通过交换机级联的方式实现网络互通:首先创建一个虚拟交换机用于 192.168.0.0/16 网络段,然后在该交换机下接入 Node 专用网络交换机用于 192.168.0.0/19 网段,接入 Pods 专用网络交换机用于 192.168.32.0/19 网段。这样也能实现 Pods 网络的需求。为了让 Pod 可以访问 Service 时能经过宿主机的 network namespace 中的 iptables 规则,所以另外增加了一个 veth 网卡打通 Pod 和宿主机的网络,并将集群的 Service 网段指向到这个 veth 网卡上。

2.2 基于 overlay 网络的 Pods 互通技术#

在 overlay 网络中,只需 underlay 网络能三层互通,overlay 网络便可以基于这层基础网络实现任意类型的网络。

最朴素的依旧是 Flannel 提供的用户态 UDP 程序实现的 overlay 网络。

UDP overlay 的原理是将原始 IP 包封装在 UDP 包中进行传输。以 Flannel 的 UDP 后端为例,在发送端,内核将 Pod 的流量截获并转发到用户态的 flanneld 进程,该进程再将包封装成 UDP 消息发送到目标节点;在接收端,目标节点的 flanneld 接收 UDP 包,解封装后将原始 IP 包交给内核处理。

这种方式的优势在于兼容性强,只要网络能通 UDP 就能工作。但代价是性能损耗显著——每个包都需要经过用户态处理,涉及多次内存拷贝。在 Kubernetes 早期或低版本内核环境中,UDP 封装是唯一可用的选项,如今已不推荐在生产环境使用。

再者就是传统数据中心常用的 VXLAN overlay 网络(大多数网络插件都支持)。

VXLAN(Virtual Extensible LAN)是一种网络虚拟化技术,通过在三层网络上构建二层隧道来扩展 VLAN 的数量限制。相比 UDP 封装,VXLAN 由内核模块直接处理,效率高得多。VXLAN 使用 24 位的 VNI(VXLAN Network Identifier),理论上可支持 1600 万个虚拟网络,远超 VLAN 的 4094 个限制。

在 Kubernetes 中,VXLAN 通常采用「首节点封装」模式:源节点将 Pod 的流量封装成 VXLAN 包,通过三层网络发送到目标节点的目标 Pod。这一过程对 Pod 透明,Pod 以为自己直接在和同网段的其他 Pod 通信,实际上流量已经过隧道封装。

sequenceDiagram participant PodA as Pod A participant VTEPA as VTEP (Node A) participant Network as Underlay 网络 participant VTEPB as VTEP (Node B) participant PodB as Pod B PodA->>VTEPA: 原始包: 10.244.1.5 → 10.244.2.3 VTEPA->>VTEPA: VXLAN 封装<br/>外层: NodeA → NodeB<br/>VNI: 1 VTEPA->>Network: UDP 包发送 Network->>VTEPB: UDP 包接收 VTEPB->>VTEPB: VXLAN 解封装 VTEPB->>PodB: 原始包转发

还有一种增强模式被称为 geneve,它结合了 VXLAN 的框架和灵活扩展头的能力,可以携带更多元数据,但目前实际使用较少。

2.3 CNI 的选择#

2.3.1 Calico#

Calico 是一款支持网络策略的、主要基于 BGP 实现的纯三层网络解决方案,它也支持其他技术作为网络后端。

Calico 网络解决方案中,由 felix 负责编制路由和 ACL 规则以及向存储报告状态,由 bird 负责 BGP 互连和路由广播。felix 支持 etcd 和 apiserver 存储。

Calico 在 underlay 上采用 BGP 组网,使每个 Pod 都单独作为一个网络存在,用 veth pair 与宿主机互通,宿主机作为 vRouter 负责转发 Pod 的流量。由于 BGP 是动态路由协议,所以相比 Flannel 的 host-gw(主机网关)静态路由组网更容易实现三层互连以达到扩展集群的目的。

Calico 在 overlay 层支持 IPIP 模式和 VXLAN 模式:前者包头较小所以性能较高,但功能较弱;后者是传统数据中心的网络虚拟化技术,有更丰富的功能,但会损失一定的性能。

Calico 支持混合网络方案。

Calico 在可观测性方面目前也支持导出一些 Prometheus 指标。

2.3.2 Flannel#

Flannel 是最早的容器网络插件,功能较为简单,不支持网络策略,但架构清晰,能轻松定制自己的后端实现。

Flannel 在每个节点上运行了一个叫 flanneld 的二进制代理,该代理负责从 etcd 或者 apiserver 中获取预配置的网络配置信息,并且为每个节点分配子网。

Flannel 的 underlay 网络方案有 host-gw 模式,该模式仅支持所有节点工作在同一个二层网络。

Flannel 的 overlay 网络方案有 UDP、IPIP、IPsec、VXLAN 等模式。其中 UDP 隧道工作在用户空间性能低下已被淘汰,目前更常用 VXLAN 作为 overlay 网络。

Flannel 支持混合网络方案。

2.3.3 Cilium#

Cilium 是主要基于 Linux eBPF 内核模块的网络插件,它能提供更好的可观察性和安全性。

Cilium 可以完全替代 kube-proxy 实现更加高效的 Service 网络。

在可观测性方面,Cilium 提供 Prometheus 指标,并且还提供 Hubble 网络信息可视化解决方案,能够以完全透明的方式深入了解服务的通信和行为以及网络基础设施。

Cilium 的 underlay 网络方案需要提供一个额外的路由守护程序,比如 Bird、Quagga、BGPD、Zebra 等程序去维护路由。

Cilium 的 overlay 网络方案支持 Geneve、VXLAN(默认)等模式。

Cilium 也实现了 Kubernetes 网络策略,并且还支持更高级的基于 DNS 和基于 HTTP 的网络访问策略,但需要消耗更多资源。

三、Service 网络的类型与作用#

由于 Pod 在 Kubernetes 集群中是非永久性资源,滚动升级或节点资源不足等情况下都会发生重启,这会导致 Pod 的重新调度和 IP 分配。为了能相对固定地访问 Pod,Kubernetes 使用 Service 抽象了 Pod 的访问。

创建 Service 资源时,一般情况下会生成对应的 EndPoint 资源,EndPoint 记录了 Service 对应的所有 Pod 的访问地址。

一般情况下,由 kube-proxy 实现 Service 网络。kube-proxy 会监听 Service 和 EndPoint 资源的变化去修改 iptables/ipvs 规则,从而实现虚拟 IP 的效果。

graph TB subgraph "kube-proxy 工作流程" API["API Server"] KP["kube-proxy"] subgraph "规则同步" IPT["iptables/ipvs 规则"] end end subgraph "流量转发" CLIENT["客户端"] SVC["Service VIP<br/>10.96.0.1:80"] POD1["Pod 1<br/>10.244.1.2:8080"] POD2["Pod 2<br/>10.244.2.3:8080"] POD3["Pod 3<br/>10.244.1.5:8080"] end API -->|"监听 Service/Endpoint"| KP KP -->|"更新规则"| IPT CLIENT -->|"访问 VIP"| SVC SVC -->|"DNAT 转换"| IPT IPT -->|"负载均衡"| POD1 IPT -->|"负载均衡"| POD2 IPT -->|"负载均衡"| POD3 style API fill:#f3e5f5 style KP fill:#f3e5f5 style SVC fill:#e8f5e9

Service 实现了 Pod 的服务发现和负载均衡。从可访问性来看,可以将 Service 网络分为集群网络和外部接入网络。

3.1 Service 集群网络#

Service 集群网络通过 ClusterIP + Port 对 PodIP + Port 进行映射。

通过以下 YAML 可以创建一个 Service,它将:

  • 创建一个名称为 my-service 的 Service
  • 选择所有包含 MyApp 标签的 Pod
  • 目标端口为 9376
  • EndPoint 控制器会根据该 Service 的选择器自动生成 EndPoint 资源
  • kube-proxy 会根据 EndPoint 资源从 ClusterIP 池获取一个 IP 用于映射到这些 Pod 上
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
ports:
- port: 30080
targetPort: 80
selector:
name: nginx-pod

3.2 Service 外部接入网络#

从互联网访问集群有如下类型的实现:

NodePort

在每个节点上通过设置 NAT iptables 规则,将本机地址与 Pod 地址做转换,实现 Pod 的外部访问。

apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
ports:
- port: 30080
targetPort: 80
nodePort: 30001
selector:
name: nginx-pod

LoadBalancer(可以由 MetalLB 提供实现)

LoadBalancer 相比 NodePort 仅多了一步,即通过 ipvs 使单独分配的 IP 将流量引入到节点网络。

ingress-nginx/templates/controller-service.yaml
apiVersion: v1
kind: Service
metadata:
name: svc-nc-2-loadbalancer
spec:
type: LoadBalancer
ports:
- name: http
port: 80
protocol: TCP
targetPort: http
- name: https
port: 443
protocol: TCP
targetPort: https
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller

externalIPs

最简单的方法是可以为服务定义一个外部 IP 实现外网访问。需要注意的是,这个 IP 在 kube-proxy ipvs 模式中不能定义为节点 IP,否则会使节点到主节点之间的连接中断。

apiVersion: v1
kind: Service
metadata:
name: svc-nc-2-external
spec:
externalIPs:
- 172.18.40.208
ports:
- port: 8000
targetPort: 1500
protocol: TCP
selector:
app: pod-nc-2

四、通过 Ingress 聚合 Service#

通过部署 Ingress Controller 可以实现通过修改 Ingress 资源去代理多个 Service。也就是说,可以通过 Ingress 实现用一个地址暴露多个服务。

graph TB subgraph "外部流量" CLIENT["客户端"] DNS["DNS 解析"] end subgraph "Ingress 层" LB["LoadBalancer<br/>外部 IP"] IC["Ingress Controller<br/>nginx/traefik"] end subgraph "Service 层" SVC1["Service A<br/>api.example.com"] SVC2["Service B<br/>web.example.com"] SVC3["Service C<br/>admin.example.com"] end subgraph "Pod 层" PODS1["Pods (API)"] PODS2["Pods (Web)"] PODS3["Pods (Admin)"] end CLIENT --> DNS --> LB --> IC IC -->|"域名路由"| SVC1 --> PODS1 IC -->|"域名路由"| SVC2 --> PODS2 IC -->|"域名路由"| SVC3 --> PODS3 style IC fill:#fff9c4 style SVC1 fill:#c8e6c9 style SVC2 fill:#c8e6c9 style SVC3 fill:#c8e6c9

可以通过 Ingress Controller 的 kubernetes.io/ingress.class 注解实现部署多个 Ingress Controller。

Ingress Controller 也需要通过 Service 才能实现外网访问,所以 Ingress Controller 需要配合 LoadBalancer Service 使用。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-nc
spec:
rules:
- host: nc.test.powercloud.com
http:
paths:
- path: /nc1
backend:
serviceName: svc-nc-1
servicePort: 81
- path: /nc2
backend:
serviceName: svc-nc-2
servicePort: 82

五、通过域名访问服务#

Kubernetes 内置一个集群 DNS,该 DNS 支持正向查找(A Record)、端口查找(SRV 记录)、反向 IP 地址查找(PTR 记录)及其他功能。

创建 Service 资源时,集群 DNS 会自动生成记录:

Service A 记录

{service name}.{service namespace}.svc.{domain} ⇒ Cluster IP

Headless Service A 记录

{service name}.{service namespace}.svc.{domain} ⇒ 后端 Pod IP 列表

可以部署自己的 DNS 实现自定义 DNS 记录,也可以利用 K8s 集群 DNS 的 rewrite 插件实现 CNAME 记录:

kubectl edit configmap coredns -n kube-system
---
apiVersion: v1
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
# 将 kuboard-v3.kuboard.svc.cluster.local 重写为 kuboard.core.powercloud.com
# CNAME
rewrite name kuboard.core.powercloud.com kuboard-v3.kuboard.svc.cluster.local
# 集群 hosts
hosts {
{外部ip} kuboard.core.powercloud.com
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
reload
loadbalance
}
kind: ConfigMap
metadata:
creationTimestamp: "2021-07-28T03:07:22Z"
name: coredns
namespace: kube-system
resourceVersion: "8419179"
uid: e027c99a-7250-4eb7-8e85-e82d17325dac

六、网络策略#

Kubernetes 网络策略可以支持任意 Pod 到 Pod 之间的入站与出站限制。

graph LR subgraph "Namespace: production" API["API Pod<br/>app=api"] DB["Database Pod<br/>app=db"] end subgraph "Namespace: frontend" WEB["Web Pod<br/>app=web"] end subgraph "外部" EXT["外部访问"] end WEB -->|"允许访问<br/>port: 8080"| API API -->|"允许访问<br/>port: 5432"| DB EXT -.->|"被拒绝"| DB WEB -.->|"被拒绝"| DB style API fill:#c8e6c9 style DB fill:#ffccbc style WEB fill:#bbdefb

网络策略通过标签选择器定义规则,支持以下几种限制:

  • Ingress 规则:控制哪些 Pod/namespace 可以访问目标 Pod
  • Egress 规则:控制目标 Pod 可以访问哪些 Pod/namespace
  • 命名空间隔离:基于命名空间标签的网络隔离
  • IP 地址段限制:基于 CIDR 的访问控制

七、参考资料#


参考#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

Kubernetes 网络
https://blog.souloss.com/posts/kubernetes/k8s-network/
作者
Souloss
发布于
2023-10-23
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时