云原生存储与网络方案选型:从 CSI 到 CNI 的架构决策与落地实践

一、存储与网络------云原生架构中"看不见的承重墙"
在 Kubernetes 集群中,存储和网络是最容易被忽视的底层基础设施。应用开发者关注 Pod 能不能跑起来、服务能不能访问,却很少追问:PV 挂载的 IOPS 够不够、CNI 的网络策略是否把跨命名空间流量堵死了、存储卷的快照恢复能不能满足 RTO 要求。直到生产环境出现训练任务 I/O 等待、跨节点 Pod 通信延迟飙升、数据卷迁移导致服务中断,这些"承重墙"的问题才会暴露。
存储和网络选型不是"选一个能用的就行"------不同的工作负载对 IOPS、吞吐、延迟、一致性有着截然不同的要求。AI 训练需要高吞吐的并行文件系统,数据库需要低延迟的块存储,日志采集需要弹性扩展的对象存储。网络层面,Service Mesh 的 Sidecar 代理会引入额外延迟,eBPF 方案绕过内核协议栈但调试困难,选择哪个方案直接影响服务间通信的性能上限。
二、存储与网络的底层机制剖析
2.1 CSI 存储架构
CSI(Container Storage Interface)将存储供给拆分为两个阶段:
Controller 阶段:CSI Controller 负责卷的创建/删除/快照。以 Ceph RBD 为例,Controller 调用 Ceph MON 创建 RBD Image,映射为 Kubernetes PV。这个阶段只处理元数据操作,不涉及数据读写。
Node 阶段 :CSI Node Plugin 运行在每个节点上,负责将卷挂载到 Pod 的文件系统命名空间。对于块存储,Node Plugin 执行 rbd map 将 RBD Image 映射为本地块设备,再 mount 到 Pod 目录。对于文件存储,Node Plugin 直接 mount -t ceph 挂载 CephFS 路径。
关键差异在于:块存储(RBD)经过内核块设备层,支持 fsync 保证数据持久性,但单卷只能被单节点挂载;文件存储(CephFS)支持多节点同时读写,但元数据服务可能成为瓶颈。
2.2 CNI 网络架构
传统 CNI(如 Calico iptables 模式)的数据包路径:Pod → veth pair → 网桥 → iptables 规则匹配 → 路由 → 物理网卡。每次跨 Pod 通信都要经过完整的内核协议栈和 iptables 链式匹配,当集群规模超过 1000 节点时,iptables 规则数量膨胀导致匹配延迟显著增加。
eBPF 方案(如 Cilium)绕过 iptables,在 socket 层直接将数据包从源 Pod 的 socket 重定向到目标 Pod 的 socket,跳过中间的协议栈处理。这在同节点 Pod 通信场景下,延迟可降低 30-50%。
三、生产级存储与网络方案实现
3.1 Ceph RBD + CephFS 混合存储方案
yaml
# StorageClass:块存储(数据库、消息队列等低延迟场景)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ceph-rbd-sc
provisioner: rbd.csi.ceph.com
parameters:
clusterID: ceph-cluster-01
pool: kubernetes-rbd
imageFormat: "2"
imageFeatures: layering
csi.storage.k8s.io/provisioner-secret-name: ceph-secret
csi.storage.k8s.io/node-stage-secret-name: ceph-secret
reclaimPolicy: Retain # 生产环境禁止动态删除,防止误操作
allowVolumeExpansion: true # 支持在线扩容
mountOptions:
- discard # 支持 TRIM,自动回收已删除数据的存储空间
---
# StorageClass:文件存储(AI 训练数据共享、日志汇聚等高吞吐场景)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: cephfs-sc
provisioner: cephfs.csi.ceph.com
parameters:
clusterID: ceph-cluster-01
fsName: kubernetes-cephfs
pool: cephfs-data
csi.storage.k8s.io/provisioner-secret-name: ceph-secret
csi.storage.k8s.io/node-stage-secret-name: ceph-secret
reclaimPolicy: Retain
3.2 本地 PV 方案:AI 训练的高性能存储选择
go
package storage
import (
"context"
"fmt"
"os"
"path/filepath"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// LocalPVManager 管理本地持久卷的创建与清理
// 核心思路:AI训练需要NVMe级别的IOPS,网络存储无法满足
type LocalPVManager struct {
basePath string // 本地存储根目录,如 /mnt/nvme
nodeName string // 当前节点名称
storageCap resource.Quantity // 节点可用存储容量
}
// CreateLocalPV 为指定训练任务创建本地PV
// 使用Kubernetes Local Persistent Volume机制,保证卷与节点绑定
func (m *LocalPVManager) CreateLocalPV(ctx context.Context,
taskID string, size resource.Quantity) (*corev1.PersistentVolume, error) {
volPath := filepath.Join(m.basePath, taskID)
// 创建存储目录,设置权限
if err := os.MkdirAll(volPath, 0755); err != nil {
return nil, fmt.Errorf("创建存储目录失败: %w", err)
}
pv := &corev1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("local-pv-%s", taskID),
},
Spec: corev1.PersistentVolumeSpec{
Capacity: corev1.ResourceList{
corev1.ResourceStorage: size,
},
// Local PV必须指定nodeAffinity,确保Pod调度到正确的节点
NodeAffinity: &corev1.VolumeNodeAffinity{
Required: &corev1.NodeSelector{
NodeSelectorTerms: []corev1.NodeSelectorTerm{
{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: "kubernetes.io/hostname",
Operator: corev1.NodeSelectorOpIn,
Values: []string{m.nodeName},
},
},
},
},
},
},
AccessModes: []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce, // 本地存储仅支持单节点读写
},
PersistentVolumeReclaimPolicy: corev1.Retain,
StorageClassName: "local-nvme-sc",
PersistentVolumeSource: corev1.PersistentVolumeSource{
Local: &corev1.LocalVolumeSource{
Path: volPath,
FSType: ptrToString("ext4"),
},
},
},
}
return pv, nil
}
func ptrToString(s string) *string { return &s }
3.3 Cilium eBPF 网络策略配置
yaml
# CiliumNetworkPolicy:基于身份的细粒度网络隔离
# 相比传统NetworkPolicy,支持L7层规则(HTTP方法、DNS查询等)
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: ai-training-egress
namespace: ai-platform
spec:
endpointSelector:
matchLabels:
app: training-job
egress:
# 允许访问Ceph MON节点(存储IO)
- toEndpoints:
matchLabels:
app: ceph-mon
toPorts:
- ports:
- port: "6789"
protocol: TCP
# 允许访问外部模型仓库,但限制HTTP方法为GET
- toFQDNs:
- matchName: "huggingface.co"
toPorts:
- ports:
- port: "443"
protocol: TCP
rules:
http:
- method: GET
# 允许DNS解析
- toEndpoints:
matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
四、方案选型的 Trade-offs 分析
存储选型对比:
| 维度 | Ceph RBD | CephFS | Local PV |
|---|---|---|---|
| IOPS | 中等(网络开销) | 低(元数据瓶颈) | 极高(本地NVMe) |
| 多节点共享 | 不支持 | 支持 | 不支持 |
| 数据持久性 | 副本冗余 | 副本冗余 | 单点(依赖节点可靠性) |
| 运维复杂度 | 高(Ceph集群维护) | 高 | 低 |
| 适用场景 | 数据库、消息队列 | 训练数据共享 | AI训练临时数据 |
网络选型对比:
| 维度 | Calico iptables | Calico eBPF | Cilium eBPF |
|---|---|---|---|
| 规则匹配延迟 | 高(O(n)链式匹配) | 低(eBPF Map查找) | 低(eBPF Map查找) |
| L7 策略支持 | 不支持 | 不支持 | 支持(HTTP/gRPC/DNS) |
| 可观测性 | 依赖 tcpdump | 基础指标 | Hubble 全景可观测 |
| 内核版本要求 | 无特殊要求 | ≥ 4.18 | ≥ 4.19(部分特性需 5.x) |
| 运维复杂度 | 低 | 中 | 高 |
关键边界条件:
- Local PV 的 Pod 调度被绑定到特定节点,当该节点故障时数据不可恢复。因此 Local PV 仅适用于可重建的临时数据(如训练 checkpoint),不适用于持久化业务数据
- Ceph 集群的 PG(Placement Group)数量直接影响 IOPS 分布。PG 数量过少导致数据倾斜,过多增加 MON 负载。经验公式:每个 OSD 约 100-200 个 PG
- Cilium 的 eBPF 程序在内核态执行,调试困难。当网络策略导致流量异常时,缺乏传统 iptables 的
--log机制,需要依赖 Hubble 可观测平台排查
五、总结
云原生存储与网络的选型,本质上是在性能、可靠性和运维成本之间做权衡。存储层面,核心决策路径是:需要多节点共享选 CephFS,需要低延迟选 Ceph RBD,需要极致 IOPS 选 Local PV。网络层面,小规模集群用 Calico iptables 足够,大规模集群或需要 L7 策略时切换到 Cilium eBPF。
落地建议分三步推进:第一步,先用最简方案(Calico + Ceph RBD)跑通业务;第二步,根据监控数据定位瓶颈------如果是存储 I/O 瓶颈,评估是否需要 Local PV 加速;如果是网络延迟瓶颈,评估 eBPF 方案的收益;第三步,逐步替换,每次只变更一个变量,确保问题可归因。避免一步到位引入全套复杂方案,否则故障排查时无法定位根因。