Kubernetes 网络模型¶
作者:JiangChong | 撰写时间:2026年07月
✅ 学完本文你将能够: ✅ 理解 K8s 扁平网络模型和四大网络问题的解决方案 ✅ 区分 Flannel、Calico、Cilium 等 CNI 插件的适用场景 ✅ 掌握 Service 的五种类型和负载均衡原理 ✅ 使用 Ingress 和 NetworkPolicy 实现高级流量管理和安全隔离
前置阅读: Kubernetes 核心架构,建议了解基本网络概念(IP/端口/DNS)。
0. 本文概览¶
02、03 篇学会了部署应用和管理存储,但有一件事一直没解决:怎么从外面访问你的 Pod? Pod IP 是动态变化的,而且外部网络根本 ping 不到 10.244.x.x。
网络是连接「集群内部」和「外部世界」的桥梁。本文按这条主线走:
Pod 怎么上网? → CNI 插件(4:给 Pod 配网卡)
Pod IP 会变,怎么稳定访问? → Service(5:固定入口 + 负载均衡)
怎么从外面进来? → Ingress(6:HTTP 路由 + 域名)
怎么限制谁访问谁? → NetworkPolicy(7:Pod 级防火墙)
Kind 用户注意: 本文的 Service 实操都可以在你的单节点 Kind 集群上跑,不需要多节点。
1. 网络基础知识(如果已了解可跳过)¶
以下是最基本的网络概念,理解它们能帮助你后续阅读。如果这些对你来说已经是常识,直接跳到下一节。
- IP 地址:每台设备在网络中的"门牌号"。就像快递员通过门牌号找到你家一样,网络数据通过 IP 地址找到对应的设备。例如
10.1.1.2。 - 端口(Port):一台设备上的"房门号"。IP 地址把你带到设备上后,端口号决定数据交给哪个程序处理。例如 Vertica 数据库通常用端口 5433。
- DNS:把人类好记的名字(如
google.com)翻译成 IP 地址(如142.250.70.46)的"电话簿"。 - NAT(网络地址转换):把私有 IP(如
192.168.x.x)映射成公网 IP 的技术。常见的路由器就是这样,让家里多台设备共用一个公网 IP 上网。不做 NAT 意味着 Pod 直接把真实 IP 拿出来通信,不做地址转换。 - 路由表:设备上的一张"地图",告诉数据包"去某个 IP 应该走哪条路"。比如去
10.1.2.0/24网段走 Node 2。
2. 为什么 K8s 网络比传统网络更复杂¶
在传统部署中,服务器的 IP 是固定的。你可以在配置文件中写好 192.168.1.10:5433,几年都不用改。
但在 Kubernetes 中:
- Pod 重启后 IP 会变:每次 Pod 重新创建,K8s 都会给它分配一个新的 IP 地址。你不能把 IP 写死在配置里。
- Pod 可以在任意节点上运行:同一个应用的两个副本可能运行在不同的物理节点上,它们需要通过节点网络互相通信。
- 需要稳定的服务端点:用户需要一个不变的入口来访问一组动态变化的 Pod。这就是 Service 的由来(后面会详细讲)。
简单说:传统网络是静态的、手动配置的;K8s 网络是动态的、自动管理的。
3. 四大网络问题¶
Kubernetes 网络模型需要解决四个核心问题:
- 容器间通信:同一 Pod 内多个容器通过 localhost 通信
- Pod 间通信:不同 Pod 之间直接通过 IP 通信,无需 NAT
- Pod 与 Service 间通信:通过 Service 抽象访问一组 Pod
- 外部访问:从集群外部访问 Service
K8s 网络模型的核心约定:每个 Pod 拥有独立的 IP 地址,所有 Pod 之间可以直接通信(扁平网络),无需 NAT 转换。
4. CNI — 容器网络接口¶
CNI(Container Network Interface)是 K8s 网络插件的标准接口。可以把它理解为"K8s 的网卡驱动程序"——不同厂商提供不同的实现,但都遵循同一个接口规范。
CNI 插件负责最基本的网络操作:
ADD:把一个新的 Pod 接入网络,分配 IP、配置网络接口DEL:从网络移除 PodCHECK:检查 Pod 的网络连接是否正常VERSION:报告 CNI 版本
4.1 Flannel — 最简方案(推荐入门)¶
Flannel 是 K8s 社区最早、最简单的 CNI 插件。它采用 VXLAN 覆盖网络(overlay) 技术。
什么是覆盖网络(overlay)? 可以理解为在物理网络之上"铺设"一层虚拟网络。就像隧道——地铁在地下穿行,地面上的交通完全感知不到它的存在。Flannel 在节点之间建立这样的"隧道",Pod 的流量通过隧道到达另一个节点的 Pod,底层物理网络不需要做任何特殊配置。
Flannel 的优点:配置极其简单,几乎开箱即用。缺点是不支持 NetworkPolicy(后面会讲它的作用),性能有少量损耗(因为多了一层封装和解封装)。
4.2 Calico — 生产环境的稳妥选择¶
Calico 采用纯三层路由方案,不依赖隧道封装,性能更好。它把每个 Pod 的 IP 路由信息直接写入节点的路由表,流量以原生方式转发。如果你需要 NetworkPolicy 和更好的性能,Calico 是最主流的生产环境选择。
4.3 Cilium — 高性能与安全¶
Cilium 基于 eBPF(Linux 内核的一项技术)实现网络和安全功能。它不经过传统的 iptables,性能极高,并且能实现 L7(应用层)的网络安全策略。适合大规模集群和安全敏感场景。
4.4 选型建议¶
| 插件 | 模式 | 网络策略 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|---|
| Flannel | VXLAN 覆盖网络 | 不支持 | 中等 | 低 | 小规模、快速上手 |
| Calico | BGP 路由 / IPIP | 支持(丰富全面) | 高 | 中 | 生产环境、需网络策略 |
| Cilium | eBPF | 支持(L3-L7 全覆盖) | 极高 | 高 | 大规模、安全敏感场景 |
| Weave Net | 覆盖网络(快速数据路径) | 支持 | 中等 | 中 | 多云/混合环境 |
- 开发/测试环境 → Flannel 最省心
- 生产环境、需要 NetworkPolicy → Calico 是最稳妥的选择
- 关注性能、安全性、可观测性 → Cilium(eBPF 技术栈是趋势)
- K8s on AWS → AWS VPC CNI(原生 VPC IP,性能最好)
4.5 深入了解:Calico BGP 模式¶
以下内容涉及路由协议,初次接触时可能不容易理解。可以先跳过,在了解 K8s 网络整体框架后再回来看。
Calico 的核心组件是 Felix 代理,运行在每个节点上,负责维护路由表和防火墙规则(iptables/ipset)。
节点之间通过 BGP(Border Gateway Protocol) 交换路由信息。BGP 是互联网骨干网络使用的路由协议,Calico 把它"借"来用在小规模的容器网络上。
Node 1 (10.0.1.10) Node 2 (10.0.1.20)
pod: 10.1.1.2 pod: 10.1.2.3
│ │
▼ ▼
路由表: 路由表:
10.1.1.0/24 via local 10.1.2.0/24 via local
10.1.2.0/24 via 10.0.1.20 10.1.1.0/24 via 10.0.1.10
│ │
└─────────── BGP 路由交换 ────────────┘
当 Node 1 上的 Pod 要访问 Node 2 上的 Pod 时,数据包走 Linux 内核的三层路由直接转发,没有 VXLAN 封装的开销。这就是 Calico 性能更好的原因。
BGP 路由守护程序通常使用 BIRD 实现。一个典型的 BIRD 配置包含:
5. Service — Pod 访问抽象¶
前面说过,Pod 是非持久化的,会因滚动更新、扩缩容、节点故障等原因不断创建和销毁,其 IP 地址也随之变化。Service 提供了一套稳定的虚拟 IP(VIP)和 DNS 名称来访问一组 Pod。
你可以把 Service 想象成公司总机号码——拨打总机,它会自动转接到当前可用的客服人员(Pod)。客服人员可能换了一批又一批,但总机号码不变。
5.1 动手实操:Service 如何解决 Pod IP 变化¶
# 1. 创建 Deployment + Service
kubectl create deployment nginx-svc --image=nginx:1.25 --replicas=2
# deployment.apps/nginx-svc created
kubectl expose deployment nginx-svc --port=80 --type=ClusterIP
# service/nginx-svc exposed
# 2. 看 Pod IP 和 Service IP
kubectl get pods -o wide -l app=nginx-svc
# NAME READY STATUS RESTARTS AGE IP[Pod IP] NODE NOMINATED NODE READINESS GATES
# nginx-svc-c798cf8f-6r47z 1/1 Running 0 26s 10.244.0.21 k8s-playground-control-plane <none> <none>
# nginx-svc-c798cf8f-h5c5t 1/1 Running 0 26s 10.244.0.20 k8s-playground-control-plane <none> <none>
kubectl get svc nginx-svc
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# nginx-svc ClusterIP 10.96.166.95 <none> 80/TCP 78s ← Service IP(固定不变)
# 3. 验证:通过 Service IP 访问 → 自动负载均衡到两个 Pod
kubectl run test --image=nginx:1.25 --rm -it --restart=Never -- curl -s 10.96.166.95
# --rm: 临时 Pod 跑完 curl 自动删除,不留垃圾。--restart=Never: 只跑一次,不重启
# 返回 nginx 欢迎页 ← 你通过不变的 Service IP 访问到了动态的 Pod!
# 4. 验证:删 Pod → Pod IP 变了 → Service IP 不变 → 还能访问
SVC_IP=$(kubectl get svc nginx-svc -o jsonpath='{.spec.clusterIP}')
POD_NAME=$(kubectl get pods -l app=nginx-svc -o jsonpath='{.items[0].metadata.name}')
kubectl delete pod $POD_NAME
# pod "nginx-svc-c798cf8f-6r47z" deleted from default namespace
kubectl get pods -o wide -l app=nginx-svc # Pod IP 变了(新 Pod 替换)
# NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
# nginx-svc-c798cf8f-h5c5t 1/1 Running 0 15m 10.244.0.20 k8s-playground-control-plane <none> <none>
# nginx-svc-c798cf8f-lcbrg 1/1 Running 0 12s 10.244.0.23 k8s-playground-control-plane <none> <none> <-- New Pod
kubectl get svc nginx-svc # Service IP 不变
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# nginx-svc ClusterIP 10.96.166.95 <none> 80/TCP 15m
kubectl run test --image=nginx:1.25 --rm -it --restart=Never -- curl -s $SVC_IP
# 还能访问!Pod 都换了但 Service 入口没变
清理:
kubectl delete deploy nginx-svc && kubectl delete svc nginx-svc
这就是 Service 的核心价值——给动态变化的 Pod 一个固定不变的访问入口。
5.2 Service 类型¶
以下 YAML 定义了 K8s 的 5 种 Service 类型。从上到下依次是:ClusterIP(集群内部可达)、NodePort(节点端口暴露)、LoadBalancer(云负载均衡器)、ExternalName(映射到外部域名)、Headless(无 VIP,直连 Pod IP 列表)。
# ClusterIP(默认)— 只在集群内部可达
apiVersion: v1
kind: Service
metadata:
name: vertica-main
spec:
type: ClusterIP
ports:
- name: vertica
port: 5433
targetPort: 5433
selector:
vertica.com/subcluster: main
---
# NodePort — 通过节点的固定端口暴露
apiVersion: v1
kind: Service
metadata:
name: vertica-external
spec:
type: NodePort
ports:
- port: 5433
nodePort: 32001 # 范围 30000-32767
selector:
vertica.com/subcluster: external
---
# LoadBalancer — 云提供商负载均衡器(AWS ELB、GCP LB)
apiVersion: v1
kind: Service
metadata:
name: vertica-lb
spec:
type: LoadBalancer
ports:
- port: 5433
targetPort: 5433
---
# ExternalName — 映射到集群外部 DNS
apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ExternalName
externalName: db.external.example.com # 返回 CNAME 记录
---
# Headless Service — 无 VIP,直接返回 Pod IP 列表
apiVersion: v1
kind: Service
metadata:
name: vertica-headless
spec:
clusterIP: None # 关键标记
ports:
- port: 5433
selector:
app: vertica
五种类型怎么选:
| 类型 | 访问范围 | 典型场景 |
|---|---|---|
| ClusterIP | 集群内部 | 后端服务调用、微服务间通信。Vertica 子集群内部连接、应用连接数据库。最常用,也是默认类型 |
| NodePort | 集群外部(通过节点 IP:端口) | 开发调试时从笔记本直接连 K8s 里的数据库、没有云 LB 的裸金属环境 |
| LoadBalancer | 外部(通过云 LB 的公网/内网 IP) | 生产环境对外暴露服务。AWS/GCP 自动创建云 LB,自动分配公网 IP |
| ExternalName | 集群内部 → 外部服务 | 集群内的应用访问集群外的数据库(如 RDS),用内部 DNS 名包装外部地址 |
| Headless | 直连 Pod | StatefulSet 需要 Pod 间直接通信(如 Vertica 节点互访),不经过 VIP 转发 |
5.3 Headless Service 与 StatefulSet¶
Headless Service(clusterIP: None)不分配虚拟 IP。DNS 查询不会返回一个 VIP,而是直接返回所有就绪 Pod 的 IP 列表。
为什么需要 Headless? 普通 ClusterIP Service 是「总机转接」——你拨总机,总机帮你转到某个 Pod,你不需要知道具体哪个 Pod 接了电话。但有状态应用(如 Vertica)恰好相反——每个节点都需要知道其他每个节点的确切 IP,直接通信建立集群。
在 03 篇的 StatefulSet 实操(nginx-sts-0, nginx-sts-1, nginx-sts-2)中,配上 Headless Service 后每个 Pod 获得固定的 DNS:
nginx-sts-0.nginx-hl.default.svc.cluster.local → 10.244.0.x → 10.244.0.14
nginx-sts-1.nginx-hl.default.svc.cluster.local → 10.244.0.y → 10.244.0.25
nginx-sts-2.nginx-hl.default.svc.cluster.local → 10.244.0.z → 10.244.0.18
Pod 删了重建,名字不变 → DNS 不变 → 即使 IP 变了,其他 Pod 通过 DNS 名还能找到它。
验证一下:
# 接 03 篇的 StatefulSet 实验(如果已清理,重新 apply sts-demo.yaml)
kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never -- nslookup nginx-sts-0.nginx-hl.default.svc.cluster.local
# Server: 10.96.0.10 ← CoreDNS 的 ClusterIP
# Address: 10.244.0.14 ← nginx-sts-0 的 Pod IP!不是 VIP,是 Pod 真实 IP
# 对比:普通 Service 的 nslookup 会返回 10.96.x.x(VIP),不会返回 Pod IP
# Server: 10.96.0.10
# Address: 10.96.0.10:53
# Name: nginx-sts-0.nginx-hl.default.svc.cluster.local
# Address: 10.244.0.14
# All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.
# If you don't see a command prompt, try pressing enter.
# warning: couldn't attach to pod/dns-test, falling back to streaming logs: unable to upgrade connection: container dns-test not found in pod dns-test_default
# Server: 10.96.0.10
# Address: 10.96.0.10:53
# Name: nginx-sts-0.nginx-hl.default.svc.cluster.local
# Address: 10.244.0.14
# pod "dns-test" deleted from default namespace
验证 Headless 的效果——看一眼 CLUSTER-IP 列就清楚了:
kubectl get svc
# NAME TYPE CLUSTER-IP PORT(S)
# nginx-hl ClusterIP None ← Headless:没有 VIP
# nginx-svc ClusterIP 10.96.166.95 ← 普通 ClusterIP:有 VIP
nginx-hl 的 CLUSTER-IP: None 就是 clusterIP: None 的效果。nslookup 它时 CoreDNS 没有 VIP 可返回,直接给 Pod IP。
顺便看一下 kube-dns(CoreDNS 自己)也是有 VIP 的普通 Service——nslookup 输出的 Server: 10.96.0.10 就是它的地址:
kubectl get svc -n kube-system kube-dns
# NAME TYPE CLUSTER-IP PORT(S)
# kube-dns ClusterIP 10.96.0.10 53/UDP,53/TCP
对比普通 Service 和 Headless Service:
| ClusterIP Service | Headless Service | |
|---|---|---|
| DNS 返回什么 | VIP(10.96.x.x) | Pod IP 列表 |
| 流量路径 | VIP → kube-proxy → Pod | 直连 Pod |
| 适用场景 | 无状态 Web 服务 | 数据库集群、消息队列 |
| Pod 间是否需要知道彼此 IP | 不需要 | 需要 |
这一点对于 Vertica 等有状态数据库非常关键——每个 Pod 需要固定的网络标识来建立集群通信。
5.4 服务发现:从环境变量到 DNS¶
Pod 怎么知道 Service 的地址?两种方式——环境变量(早期)和 DNS(现代)。
环境变量(不推荐): Pod 启动时 K8s 自动注入类似 NGINX_SVC_SERVICE_HOST=10.96.166.95 的环境变量。致命缺点:Pod 创建后才存在的 Service 不会被注入(Pod 启动时 Service 还没建,环境变量就是空的)。
CoreDNS(标准方式): K8s 自带 DNS 服务,不需要任何配置。你可以通过 DNS 名访问任何 Service,无视创建顺序。
kubectl get pods -n kube-system | grep coredns
# coredns-xxxxx 1/1 Running ← K8s 内置的 DNS 服务器
# coredns-589f44dc88-cv9b4 1/1 Running 0 5h57m ← K8s 内置的 DNS 服务器
# coredns-589f44dc88-q8s89 1/1 Running 0 5h57m ← K8s 内置的 DNS 服务器
# etcd-k8s-playground-control-plane 1/1 Running 0 5h58m
# kindnet-99phc 1/1 Running 0 5h58m
# kube-apiserver-k8s-playground-control-plane 1/1 Running 0 5h58m
# kube-controller-manager-k8s-playground-control-plane 1/1 Running 0 5h58m
# kube-proxy-74dfk 1/1 Running 0 5h58m
# kube-scheduler-k8s-playground-control-plane 1/1 Running 0 5h58m
每个 Pod 的 /etc/resolv.conf 自动指向 CoreDNS,所以 Pod 内直接用 DNS 名就能解析 Service。
kubectl exec nginx-sts-0 -- cat /etc/resolv.conf
# search default.svc.cluster.local svc.cluster.local cluster.local
# nameserver 10.96.0.10
# options ndots:5
动手对比:IP vs DNS 名 — 目的是证明「用名字好过用 IP」:
# 用 5.1 的 nginx-svc 试
kubectl create deployment nginx-svc --image=nginx:1.25 --replicas=2
kubectl expose deployment nginx-svc --port=80 --type=ClusterIP
# 方式一:用 IP(Pod 重建就失效)
SVC_IP=$(kubectl get svc nginx-svc -o jsonpath='{.spec.clusterIP}') # 10.96.166.95
kubectl run test --image=nginx:1.25 --rm -it --restart=Never -- curl -s $SVC_IP # ✅
# 方式二:用 DNS 名(永远有效,Pod 重建也没关系)
kubectl run test --image=nginx:1.25 --rm -it --restart=Never -- curl -s nginx-svc
# ✅ 同 Namespace 直接用 Service 名就行,不需要写全 FQDN
# DNS 全格式(跨 Namespace 必须写全)
# <service>.<namespace>.svc.cluster.local
# 同 Namespace 简写:<service>
每个 Pod 的 /etc/resolv.conf 自动指向 CoreDNS:
kubectl exec nginx-sts-0 -- cat /etc/resolv.conf
# nameserver 10.96.0.10 ← CoreDNS 的地址
# search default.svc.cluster.local svc.cluster.local cluster.local
清理:
kubectl delete deploy nginx-svc && kubectl delete svc nginx-svc
# DNS 解析格式(仅供参考)
<service>.<namespace>.svc.cluster.local
# 例如
vertica-main.my-namespace.svc.cluster.local:5433
nginx-sts-0.nginx-hl.default.svc.cluster.local → 10.244.0.x → 10.244.0.14
nginx-sts-1.nginx-hl.default.svc.cluster.local → 10.244.0.y → 10.244.0.25
nginx-sts-2.nginx-hl.default.svc.cluster.local → 10.244.0.z → 10.244.0.18
# 同 Namespace 可以简写
vertica-main:5433
5.5 Service 负载均衡原理¶
你访问 10.96.166.95:80 时,数据包并没有一个叫 10.96.166.95 的真实设备——这是 kube-proxy 在节点上用 iptables 规则虚拟出来的。真实流程:
curl 10.96.166.95:80
→ iptables 看到目标 IP 是 10.96.166.95
→ 查规则:这个 VIP 对应 Pod [10.244.0.20, 10.244.0.23]
→ 随机选 10.244.0.20 → 转发
验证负载均衡确实在随机分发: 连刷几次,看 nginx 日志里的 Pod 名在变化:
kubectl create deployment nginx-svc --image=nginx:1.25 --replicas=2
kubectl expose deployment nginx-svc --port=80
# 连续访问 3 次
for i in 1 2 3; do
kubectl run t-$i --image=nginx:1.25 --restart=Never -- curl -s nginx-svc
done
# pod/t-1 created
# pod/t-2 created
# pod/t-3 created
kubectl delete pod t-1 t-2 t-3 --wait=false # 清理临时 Pod
# pod "t-1" deleted from default namespace
# pod "t-2" deleted from default namespace
# pod "t-3" deleted from default namespace
# 逐个看每个 Pod 的日志——两个都有请求记录,证明负载均衡生效
kubectl get pods -l app=nginx-svc -o name | while read pod; do
echo "=== $pod ===" && kubectl logs $pod --tail=3
done
# === pod/nginx-svc-c798cf8f-h5c5t ===
# 10.244.0.43 - - [30/Jul/2026:09:17:29 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-" ← h5c5t 处理了 2 个
# 10.244.0.44 - - [30/Jul/2026:09:17:29 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-" ← h5c5t 处理了 2 个
# === pod/nginx-svc-c798cf8f-lcbrg ===
# 10.244.0.42 - - [30/Jul/2026:09:17:29 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-" ← lcbrg 处理了 1 个
kube-proxy 是规则的维护者: 运行在每个节点上,Watch API Server 上 Service 和 EndpointSlice 的变化。Pod 挂了 → EndpointSlice 更新 → kube-proxy 更新 iptables 规则 → 流量不再发到死 Pod。
两种模式:
- iptables(默认):随机选 Pod,简单可靠。规则量大时更新慢
- IPVS:支持 round-robin、least-connection 等算法,大规模集群性能更好
清理:
kubectl delete deploy nginx-svc && kubectl delete svc nginx-svc
5.6 Session Affinity(会话亲和性)¶
问题: 刚才的负载均衡测试证明了每次请求可能打到不同 Pod。如果你的应用在 Pod 上存了登录状态(session),跳来跳去就会导致用户反复登录。Session Affinity 让同一客户端 IP 始终路由到同一个 Pod。
默认是 None(随机),加上 sessionAffinity: ClientIP 后 kube-proxy 会给每个客户端 IP 记录一个「上次转发到哪个 Pod」,后续请求优先走同一个。
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 会话保持时间(默认 3 小时)
动手验证: 同一次 curl 连刷 3 次,看是不是都打到同一个 Pod:
1、不开启时:请求分散到两个 Pod
MY_IP=$(kubectl get pod nginx-svc-c798cf8f-h5c5t -o jsonpath='{.status.podIP}') # 10.244.0.20
POD_NAME=$(kubectl get pods -l app=nginx-svc -o jsonpath='{.items[0].metadata.name}') # nginx-svc-c798cf8f-h5c5t
# 从同一个 Pod 内连刷 3 次
kubectl exec $POD_NAME -- sh -c 'for i in 1 2 3; do curl -s nginx-svc > /dev/null; done'
kubectl get pods -l app=nginx-svc -o name | while read p; do echo "=== $p ===" && kubectl logs $p --tail=3; done
# 不开启时:请求分散到两个 Pod
# === pod/nginx-svc-c798cf8f-h5c5t ===
# 10.244.0.1 - - [30/Jul/2026:09:30:56 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-" ← h5c5t 处理了 1 个
# === pod/nginx-svc-c798cf8f-lcbrg ===
# 10.244.0.20 - - [30/Jul/2026:09:30:55 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-" ← lcbrg 处理了 2 个
# 10.244.0.20 - - [30/Jul/2026:09:30:56 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-" ← lcbrg 处理了 2 个
# 1. 开启
kubectl patch svc nginx-svc -p '{"spec":{"sessionAffinity":"ClientIP"}}'
# service/nginx-svc patched
# 2. 从同一个 Pod 连刷 3 次
POD_NAME=$(kubectl get pods -l app=nginx-svc -o jsonpath='{.items[0].metadata.name}') # nginx-svc-c798cf8f-h5c5t
kubectl exec $POD_NAME -- sh -c 'for i in 1 2 3; do curl -s nginx-svc > /dev/null; done'
# 3. 看分布——这次全在同一个 pod/nginx-svc-c798cf8f-lcbrg @ 30/Jul/2026:09:41:38
kubectl get pods -l app=nginx-svc -o name | while read p; do echo "=== $p ===" && kubectl logs $p --tail=3; done
# === pod/nginx-svc-c798cf8f-h5c5t ===
# 10.244.0.43 - - [30/Jul/2026:09:17:29 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
# 10.244.0.44 - - [30/Jul/2026:09:17:29 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
# 10.244.0.1 - - [30/Jul/2026:09:26:41 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
# 10.244.0.1 - - [30/Jul/2026:09:26:41 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
# 10.244.0.1 - - [30/Jul/2026:09:30:56 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-" ← h5c5t 处理了 0 个
# === pod/nginx-svc-c798cf8f-lcbrg ===
# 10.244.0.20 - - [30/Jul/2026:09:30:55 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
# 10.244.0.20 - - [30/Jul/2026:09:30:56 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
# 10.244.0.20 - - [30/Jul/2026:09:41:38 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-" ← lcbrg 处理了 3 个
# 10.244.0.20 - - [30/Jul/2026:09:41:38 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-" ← lcbrg 处理了 3 个
# 10.244.0.20 - - [30/Jul/2026:09:41:38 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-" ← lcbrg 处理了 3 个
# 4. 恢复
kubectl patch svc nginx-svc -p '{"spec":{"sessionAffinity":"None"}}'
# service/nginx-svc patched
Vertica 不需要 Session Affinity。 Vertica 的连接状态由数据库本身管理(每个连接有独立的 session),不依赖 K8s 的粘性路由。但一些老的 Web 应用确实需要它。
6. Ingress — 七层入口¶
为什么有了 Service 还需要 Ingress? Service 是 L4(TCP/IP 层)的——它只能按 IP:端口转发,不能看 HTTP 请求内容。如果你有三个 Service(
/api、/monitor、/),想通过同一个域名vertica.example.com的不同路径访问不同的 Service,Service 做不到,Ingress 可以。
用户请求: https://vertica.example.com/monitor
→ Ingress Controller(看 HTTP Host + Path)
→ /monitor 匹配 → 转发到 vertica-monitor Service
→ /api 匹配 → 转发到 vertica-api Service
→ / → 转发到 vertica-web Service
Ingress 规则只定义「什么请求转给谁」,实际的转发由 Ingress Controller 执行——它是集群里跑的一个反向代理(通常是 nginx)。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vertica-web
spec:
ingressClassName: nginx # 使用哪个 Ingress Controller
tls: # HTTPS 证书
- hosts:
- vertica.example.com
secretName: vertica-tls # 证书存在 Secret 里
rules: # 路由规则
- host: vertica.example.com # 匹配这个域名
http:
paths:
- path: /monitor # 路径是 /monitor
pathType: Prefix
backend:
service:
name: vertica-monitor # → 转给这个 Service
port:
number: 80
注意: Ingress 只是 YAML 规则,必须安装 Ingress Controller 才能真正生效。Kind 可以通过
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml安装 nginx-ingress。
常见 Ingress Controller:
| 实现 | 特点 |
|---|---|
| nginx-ingress | 社区最主流,基于 Nginx |
| Traefik | 自动服务发现,自带 Dashboard |
| AWS Load Balancer Controller | AWS ALB/NLB 原生集成 |
| Contour | 基于 Envoy 代理 |
2024+ 更新: Ingress API 已被冻结。新项目建议优先评估 Gateway API(K8s 下一代入口网关标准),但 Ingress 目前仍然是绝大多数生产集群的主力。
7. NetworkPolicy — Pod 级防火墙¶
安全问题: K8s 默认所有 Pod 之间可以互相访问——集群内任何 Pod 都能连你的数据库 Pod 的 5433 端口。这在生产环境是不可接受的。NetworkPolicy 就是在 Pod 前面加一道防火墙。
NetworkPolicy 决定了「谁」可以访问「哪些 Pod」的「哪些端口」。注意:不是所有 CNI 都支持——Flannel 和 Kind 默认的 kindnet 不支持,需要 Calico 或 Cilium。
# 规则:只允许 app=my-application 的 Pod 访问 app=vertica 的 5433 端口
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: restrict-vertica-access
namespace: vertica
spec:
podSelector: # 受保护的 Pod(app=vertica)
matchLabels:
app: vertica
policyTypes:
- Ingress # 控制入站流量
ingress:
- from: # 允许谁进来
- podSelector: # 允许这些 Pod
matchLabels:
app: my-application # → app=my-application
- namespaceSelector: # 也允许这个 Namespace 的所有 Pod
matchLabels:
kubernetes.io/metadata.name: monitoring # K8s 自动打的标签,值就是 NS 名;除了 app=my-application 的 Pod 之外,整个 monitoring 命名空间的所有 Pod 也可以访问 Vertica 的 5433 端口。
ports: # 允许的端口
- protocol: TCP
port: 5433 # → 只能是 5433,其他端口一律拒绝
默认行为: 某个 Pod 没有被任何 NetworkPolicy 选中 → 全部放行。一旦被某条 NetworkPolicy 的 podSelector 选中,就按该策略的规则执行——未列出的来源和端口全部拒绝。
零信任模型——先全部禁止、再逐条放行:
restrict-vertica-access 自身就是零信任——ingress 里只列出了两条放行规则,其他一切来源和端口自动拒绝,不需要额外兜底策略。
其他 Pod → vertica NS 中的 app=vertica Pod → restrict-vertica-access 判断
app=my-application → vertica:5433 → ✅ ingress 第一条匹配,放行
monitoring NS 所有 Pod → vertica:5433 → ✅ ingress 第二条匹配,放行
app=unknown → vertica:5433 → ❌ 没有匹配的 ingress 规则
app=my-application → vertica:8080 → ❌ 端口不在允许列表中
NetworkPolicy 的核心逻辑:列出了才是允许,没列出就是禁止。
8. Vertica on K8s 网络实践¶
把本文学的网络概念套到 Vertica 部署上——每一层都有自己的角色:
| 网络层 | 本文概念 | Vertica 怎么用 |
|---|---|---|
| Pod 网络 | CNI(4) | Vertica 节点间扁平网络直连,对延迟敏感 → 推荐 Calico,不用 Flannel 的 VXLAN 封装 |
| 内部访问 | ClusterIP Service(5.2) | 每个子集群自动创建 ClusterIP Service,应用通过 <sc-name>:5433 连接 |
| 外部访问 | NodePort / LoadBalancer(5.2) | 从集群外用客户端连 Vertica:patch 子集群为 NodePort 或 LoadBalancer |
| Pod 间通信 | Headless Service(5.3) | StatefulSet + Headless 给每个 Pod 固定的 DNS,Vertica 节点通过 DNS 互相发现 |
| 服务发现 | CoreDNS(5.4) | Pod 内用 vertica-main.default.svc.cluster.local 解析到 Service IP |
| 安全 | NetworkPolicy(7) | 限制只有应用 Pod 能访问 Vertica 的 5433 端口 |
# 外部访问:将子集群 sc 暴露为 NodePort
kubectl patch vdb verticadb-sample --type=merge \
--patch '{"spec":{"subclusters":[{"name":"sc","serviceType":"NodePort","nodePort":32001}]}}'
更详细的 K8s 网络配置请参考 Vertica Eon on K8s 生产部署实战。
9. 总结¶
| 层次 | 组件 | 作用 |
|---|---|---|
| Pod 网络 | CNI 插件 | 为 Pod 分配 IP、配置网络 |
| Service | ClusterIP/NodePort/LB | 稳定的 IP 和 DNS 访问可变的 Pod 集合 |
| 入口 | Ingress + Controller | HTTP/HTTPS 路由和 TLS 终结 |
| 安全 | NetworkPolicy | Pod 间网络隔离和访问控制 |
| 服务发现 | CoreDNS | 基于 DNS 的 Service 发现 |
→ 下一篇文章: Kubernetes RBAC 权限与安全 — 掌握 K8s 的认证授权和安全策略。