6.7 容器存储与网络

一句话

容器只有两种存储身份——临时的根文件系统(overlay,删容器即丢)与外挂的数据卷(与容器生命周期解耦);也只有两张网络——Pod 网络(CNI,一个 Pod 一个 IP)与 Service 网络(虚拟 IP + 转发规则);把这两组搞混,就会得到 “数据丢在容器层” 与 “服务发现靠记 IP” 两类事故。

存储:先问 “这份数据要活多久”

挂载类型 生命周期 数据在哪 适合
容器可写层 容器 节点磁盘(overlay upperdir) 临时文件;不适合任何要保留的数据
emptyDir Pod 节点磁盘(或 medium: Memory 用 tmpfs) 同 Pod 内容器共享、缓存、临时落盘
hostPath 节点 节点目录 读取节点信息(如 /var/log);生产数据禁用
PVC(CSI) 独立于 Pod 云盘 / NFS / 分布式存储 数据库、消息队列、上传文件
tmpfs / Memory Pod 内存 敏感临时数据、极低延迟缓存

判断句式:这份数据要不要活过容器?要 → PVC;只在 Pod 内共享 → emptyDir;只是临时文件 → 可写层(但要意识到它会占节点磁盘)。

可写层的三个代价

  1. copy-up:写一个文件要先把整份从只读层拷上来(机制见 6.2 镜像与分层、写时复制)。
  2. ephemeral storage 配额:可写层 + 日志 + emptyDir 共用节点临时存储额度,超了会被驱逐(Evicted)。
  3. 随容器消失:docker rm / 重建 Pod 即丢,没有任何恢复路径。

日志是最常见的 “无意识写可写层”:应用把日志写文件,文件在容器层,节点磁盘被写满 —— 正确做法是写 stdout(由 kubelet 轮转)或挂 emptyDir 并设 sizeLimit。

访问模式:最容易背错的一张表

模式 含义 常见误用
ReadWriteOnce(RWO) 单节点读写(不是单 Pod) 以为 “一个 Pod 独占”,实际同节点多 Pod 可同时挂
ReadOnlyMany(ROX) 多节点只读 用在要求强一致的读多写少场景,忽略缓存
ReadWriteMany(RWX) 多节点读写 需要文件系统支持(NFS/CephFS);块设备给不了
ReadWriteOncePod(RWOP) 单 Pod 独占 新集群才有,老集群不可用

“RWO 就是独占” 是最贵的误解:两个同节点 Pod 挂同一个 RWO 卷时,文件锁与写入冲突仍然会发生。

CSI 的两个时间成本

  • attach/detach:Pod 漂移到新节点时,云盘要先从旧节点摘、在新节点挂,通常 30 秒–2 分钟;这会让 “节点故障后 Pod 恢复” 变慢,是 RTO 的一部分。
  • 扩容:allowVolumeExpansion 打开后可以原地扩,但缩容几乎都不支持——规划时按 “只增不减” 设计。

性能与选型(块 / 文件 / 对象、IOPS 随容量)见 10.1 块存储 / 文件存储 / 对象存储与 10.3 云盘性能模型(IOPS / 吞吐 / 时延)(待补);备份与快照见 10.4 快照与备份。

网络:两张网,别混

网络 谁提供 地址 解决什么
Pod 网络 CNI 插件 每个 Pod 一个 IP(集群内可路由) Pod 之间怎么通
Service 网络 kube-proxy / eBPF / 云 LB 虚拟 IP(ClusterIP),不落在任何网卡上 服务怎么被稳定访问

三层访问路径:外部 → Service(LB/NodePort)→ Pod IP → 容器端口。排错时先说清 “卡在哪一层”,否则会在 “Service 不通” 与 “Pod 不通” 之间反复假设。

Pod 网络模型:一个 Pod 一个 IP

  • 同节点:veth pair + 网桥(或 eBPF 直接转发)。
  • 跨节点:overlay(VXLAN / IPIP 封装)或 underlay(BGP 宣告 Pod 网段,如 Calico 的 BGP 模式)。
  • MTU 坑:VXLAN 封装占 50 字节,1500 的物理 MTU 下 Pod MTU 通常要设 1450;不设会出现 “小包通、大包丢” 的诡异现象(握手成功、传文件卡死)。注意别拿本机 Docker Desktop 当参照:实测它默认网络的 com.docker.network.driver.mtu 是 65535(Linux VM 内部环路),生产网卡不是这个数,按物理 MTU 减封装开销算。
CNI 插件 模式 特点 适合
Flannel VXLAN / host-gw 简单、够用 中小集群、快速起步
Calico BGP / IPIP / eBPF 网络策略强、可 underlay 需要 NetworkPolicy 的生产集群
Cilium eBPF 高性能、可观测、L7 策略 大规模、想摆脱 iptables
云厂商 CNI VPC 原生 Pod 直接用 VPC IP,与 LB/安全组天然集成 公有云托管 K8s

Service:四种暴露方式

类型 用途 代价
ClusterIP 集群内访问 无(默认)
NodePort 临时对外 / 调试 端口范围有限、暴露节点、无健康检查
LoadBalancer 生产对外 每个 Service 一个 LB,成本随服务数增长
Headless(clusterIP: None) 有状态服务直连 Pod 客户端自己做发现与负载均衡

生产入口通常是 “一个 Ingress / 网关 + 多个 ClusterIP”,而不是 “每个服务一个 LoadBalancer”。四层与七层的分工见 8.9 四层与七层负载均衡,南北向与东西向的区分见 47.5 南北向与东西向流量。

NetworkPolicy:默认全通

Kubernetes 的默认行为是 Pod 之间全通。NetworkPolicy 是 “白名单”,且需要 CNI 支持(Flannel 默认不支持,Calico/Cilium 支持):

写法 效果 注意
podSelector: {} + policyTypes: [Ingress] 该命名空间所有 Pod 拒绝入站 必须先放行 DNS 与探针,否则 “全拒” 会连自身健康检查一起拒掉
只写 Ingress 出站仍然全通 出站管控要单独写 Egress
按 namespace 放行 常见的多环境隔离 标签拼写错误会静默放行/拒绝

命令:把“存储与网络”查清楚

# 存储:这个容器到底挂了什么、写在哪一层
docker inspect -f '{{json .Mounts}}' <container> | python3 -m json.tool
docker system df -v                 # 镜像/容器/卷各占多少(卷最容易漏)

# 网络:容器在哪个网络、拿到什么 IP、能不能通
docker network ls
docker network inspect bridge -f '{{json .IPAM.Config}}'
docker exec <container> sh -c 'ip -4 addr show eth0; cat /etc/resolv.conf'
# K8s:临时卷与持久卷的区别就写在 spec 里
volumes:
  - name: cache
    emptyDir: {sizeLimit: 1Gi}      # 随 Pod 生命周期,删 Pod 即丢
  - name: data
    persistentVolumeClaim: {claimName: pg-data}   # 独立生命周期,RWO 通常只能单节点挂

反模式

反模式 后果
数据库数据目录不挂卷 重建即丢数据;copy-up 还会放大 IO
用 hostPath 存业务数据 Pod 漂移后数据留在旧节点,新节点读不到
以为 RWO 是 “单 Pod 独占” 同节点多 Pod 并发写同一卷,文件损坏
日志写容器内文件 节点磁盘写满,Pod 被驱逐
用 NodePort 当生产入口 无健康检查、端口冲突、暴露节点 IP
客户端硬编码 Pod IP Pod 重建后 IP 变化,调用全挂
不设 MTU 小包通、大包不通,排查耗时以天计
以为 NetworkPolicy 默认拒绝 集群实际全通,隔离形同虚设
Service 数量爆炸(每服务一个 LB) 云账单暴涨,入口配置难以治理

三条铁律

🔑 数据不落容器层:要活过容器就挂 PVC;只在 Pod 内共享用 emptyDir。

🔑 RWO 是 “单节点” 不是 “单 Pod”;要独占用 RWOP,要并发写用 RWX。

🔑 默认全通:Pod 网络与 NetworkPolicy 都默认放行;隔离必须显式写,且要放行 DNS 与探针。

自检

Q1 一个 StatefulSet 的 Pod 从节点 A 漂到节点 B,恢复用了 90 秒,其中 60 秒花在存储上。怎么解释与优化?

▸ 答案:60 秒是 CSI detach/attach:云盘必须先脱离旧节点、再挂到新节点,典型 30 秒–2 分钟。优化方向:① 用支持 “多挂载/快速重挂” 的存储类型;② 让调度尽量回到原节点(nodeAffinity + 本地卷);③ 把 RTO 目标按 “含存储挂载时间” 重算,而不是只算进程启动。这是 RTO 里最容易被漏掉的一段。

Q2 为什么两个 Pod 同时挂同一个 RWO 卷会出问题,尽管 “它们都在同一个节点”?

▸ 答案:因为 RWO 保证的是单节点挂载,不是单写入者。文件系统本身没有分布式锁语义(块设备上尤其明显),两个进程并发写同一文件会产生覆盖与元数据损坏。需要独占就用 ReadWriteOncePod,需要并发写就用支持 ReadWriteMany 的文件系统,并让应用自己处理锁与冲突。

Q3 集群里 “小请求正常、传大文件卡死”,最可能是什么问题?

▸ 答案:MTU 不匹配。overlay 封装(VXLAN 50 字节)后 Pod MTU 若仍为 1500,大包会被分片或丢弃,表现为 TCP 握手成功(小包)、传数据时卡住或超时。验证:ping -M do -s 1472 <pod-ip>(1472 + 28 = 1500)逐步降包长试;修法是把 CNI 的 MTU 设为物理 MTU 减去封装开销(常见 1450)。

Q4 团队说 “我们做了网络隔离,因为配置了 NetworkPolicy”。这句话可能哪里不对?

▸ 答案:三处可能:① CNI 不支持(如默认 Flannel),策略被忽略;② 只写了 Ingress 没写 Egress,出站仍全通;③ 默认行为是全通,策略写错选择器就静默不生效。验证方式是实际从 Pod 里 curl/nc 另一个命名空间的 Pod,而不是看 YAML 是否存在。

Q5 生产入口该用 NodePort、LoadBalancer 还是 Ingress?

▸ 答案:默认选 Ingress(或网关)+ ClusterIP:一个入口复用,TLS 与路由集中管理,成本最低。LoadBalancer 适合 “少量、需要独立公网 IP 的服务” 或云托管 LB 的集成场景;NodePort 只适合调试与临时暴露——它没有健康检查、端口范围有限、还暴露节点 IP。选择依据是 “入口要治理多少个服务、TLS 在哪终结、成本按什么计”。

跨篇相关:可写层与 copy-up 的机制见 6.2 镜像与分层、写时复制;无状态化是挂卷与横向扩展的前提见 37.2 无状态化;块 / 文件 / 对象存储与云盘性能见 10.1 块存储 / 文件存储 / 对象存储、10.3 云盘性能模型 (IOPS / 吞吐 / 时延)(待补);快照与备份见 10.4 快照与备份;Pod 网络与 Service 的编排层细节见 7.7 存储卷与持久化、7.8 网络模型 (CNI / NetworkPolicy)(待补);四层与七层的分工见 8.9 四层与七层负载均衡;南北向与东西向流量见 47.5 南北向与东西向流量;TCP 重传与 MTU 的底层机制见 8.1 TCP/IP 与三次握手。