mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3133 字
9 分钟
Kubernetes 网络
2021-11-14

一、什么是 Kubernetes 网络#

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

这三层网络各司其职又彼此依赖:Node Network 提供物理连通性,Pods Network 解决容器间直通,Service Network 在 Pod IP 变动时提供稳定的访问入口,理解它们之间的协作关系,是排查集群网络问题的基础。

  • 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 网络并非特指某种具体网络方案,它们仅表示网络是否做了逻辑性的封装。

底层的基础架构网络被称为 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 本身是成熟的分布式路由协议,天然适合大规模集群的动态拓扑。Calico 还能与数据中心现有的 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 的选择#

Calico、Flannel、Cilium 三大主流 CNI 插件的具体实现原理(Felix/Bird 的 BGP 互连、Flannel 的 VXLAN 数据路径、Cilium 的 eBPF 数据路径)在 CNI 深度解析 中逐个拆解,这里只从选型视角给一张决策表:

CNI网络模型网络策略适用场景
Flanneloverlay 为主(VXLAN),支持 host-gw不支持小规模、策略要求低、架构要简单
Calicounderlay(BGP)为主,支持 IPIP/VXLAN支持中大规模、需要 BGP 三层互连和精细策略
CiliumeBPF,可替代 kube-proxy支持(含 DNS/HTTP 层)大规模、对可观测性和性能有要求

一句话选型:规模小且不用策略选 Flannel,规模中等要策略选 Calico,规模大且要可观测性选 Cilium。三者的取舍边界和数据路径细节见 CNI 深度解析

三、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 的基础上,由云厂商的外部负载均衡器(如 AWS ELB、阿里云 SLB)分配一个外部可达的 IP,将流量转发到节点上的 NodePort。在自建机房没有云厂商 LB 时,MetalLB 通过 L2 ARP 或 BGP 宣告这个外部 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

可以通过 IngressClass 资源(spec.ingressClassName 字段,1.18+)指定使用哪个 Ingress Controller,旧的 kubernetes.io/ingress.class 注解仍可用但已是历史写法。

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

# networking.k8s.io/v1 从 1.19 起 GA,旧的 networking.k8s.io/v1beta1 已在 1.22 移除
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-nc
spec:
rules:
- host: nc.test.powercloud.com
http:
paths:
- path: /nc1
pathType: Prefix
backend:
service:
name: svc-nc-1
port:
number: 81
- path: /nc2
pathType: Prefix
backend:
service:
name: svc-nc-2
port:
number: 82

五、通过域名访问服务#

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

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

Service 类型DNS 名称解析结果
普通 Service{service}.{namespace}.svc.{domain}Cluster IP
Headless Service{service}.{namespace}.svc.{domain}后端 Pod IP 列表

可以部署自己的 DNS 实现自定义 DNS 记录,也可以利用 K8s 集群 DNS 的 rewrite 插件实现 CNAME 记录。下面是 CoreDNS ConfigMap 的一个示例,通过 rewrite name 指令把外部域名映射到集群内 Service:

kubectl edit configmap coredns -n kube-system
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
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
}

六、网络策略#

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.cn/posts/kubernetes/k8s-network/
作者
Souloss
发布于
2021-11-14
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时