1. 项目概述为什么一个“和官档一致”的Ceph-CSI搭建过程值得专门写一篇长文Ceph-CSI全称是 Ceph Container Storage Interface它不是Ceph自带的存储驱动而是Kubernetes生态里为Ceph量身定制的一套标准化接入层。简单说它让Kubernetes里的Pod能像挂U盘一样把Ceph集群里的块设备RBD或文件系统CephFS当作本地磁盘来用——创建PVC、自动绑定PV、动态供给、快照、克隆全部走标准K8s API不碰任何Ceph原生命令。这听起来很理想但现实是官方文档ceph-csi.io本身是一份面向“已具备完整环境、理解各组件边界、能自主诊断依赖链”的成熟运维者编写的参考手册而不是一份开箱即用的新手向安装指南。它默认你已经部署好Ceph集群且版本匹配、Kubernetes集群含RBAC权限模型清晰、证书体系TLS双向认证、网络策略ServiceAccount与Secret访问路径明确甚至默认你知道csi-nodeplugin必须以特权模式运行、csi-provisioner需要特定的--feature-gates开关、ceph-csi镜像里的/var/lib/kubelet/plugins/挂载路径在不同容器运行时containerd vs docker下行为差异巨大。我最近在一个模拟生产环境的多节点K8s集群v1.26.9 containerd 1.7.13上部署Ceph Pacificv16.2.15后按官网QuickStart逐行执行kubectl apply -f https://raw.githubusercontent.com/ceph/ceph-csi/v3.9.0/deploy/rbd/kubernetes/csi-rbdplugin.yaml结果卡在csi-rbdplugin-provisioner-0的Init:CrashLoopBackOff上长达47小时。查日志只看到一行failed to load ceph config: unable to get monitor list from config file or the environment而配置文件明明存在、Secret也已注入、monitor地址在ceph.conf里写得清清楚楚。后来发现问题根本不在Ceph本身而在于CSI插件启动时读取/etc/ceph/ceph.conf的方式——它默认只认/etc/ceph/ceph.conf这个绝对路径但YAML里通过ConfigMap挂载时实际落盘路径是/etc/ceph/ceph.conf没错可CSI容器内部的/etc/ceph/目录是空的因为ConfigMap挂载点被覆盖了。这不是bug是Kubernetes挂载机制和CSI容器启动顺序之间一个极其隐蔽的竞态条件。这种问题官网不会写StackOverflow上零星几条回答互相矛盾GitHub Issues里翻三天也找不到对应场景。所以这篇笔记的核心价值不在于告诉你“怎么装”而在于还原一个真实、完整、带血丝的排错链条从环境校验清单、到每一步YAML的修改意图、再到每个Pod状态背后的底层原理、最后是五个必须手敲验证的“黄金检查点”。它适合三类人正在被CSI卡住的K8s管理员、准备做Ceph存储选型的技术负责人、以及想真正搞懂“Kubernetes如何把存储抽象成API”的进阶学习者。关键词就三个ceph-csi、rbd、kubernetes动态供给全文所有操作都围绕这三个词展开不发散、不炫技、不讲无关的Ceph底层CRUSH算法。2. 整体设计思路与方案选型逻辑为什么必须“尽量和官档一致”很多人看到“踩坑”二字第一反应是“那我直接抄个别人封装好的Helm Chart不就行了”比如rook-ceph的CSI集成、或者某云厂商开源的简化版部署包。但这是最危险的捷径。Rook本身就是一个复杂的Operator它把Ceph集群部署、监控、升级、故障自愈全包了而CSI只是它能力的一个子集。当你用Rook部署CSI时你其实是在调试Rook的代码逻辑而不是CSI本身的交互协议。一旦出问题你得同时看Rook日志、CSI日志、Ceph日志三层堆栈定位成本指数级上升。而“尽量和官档一致”的核心逻辑是把变量控制到最少只用Ceph官方维护的ceph-csi仓库发布的YAML、只用quay.io/cephcsi/cephcsi:v3.9.0这个镜像、所有ConfigMap/Secret都手动创建、所有RBAC权限都按deploy/rbd/kubernetes/目录下的原始定义来apply。这样当某个Pod起不来时你能100%确定问题出在你的环境适配层比如K8s版本兼容性、containerd配置、SELinux策略而不是某个第三方Chart偷偷改了securityContext.privileged: true的值或者把--nodeid参数硬编码成了node-1。具体到本次部署我们严格遵循v3.9.0分支的deploy/rbd/kubernetes/结构但做了五处必要且最小化的调整镜像拉取策略统一改为IfNotPresent官网YAML里部分Deployment用了Always在内网离线环境会失败改成IfNotPresent后先手动ctr images pull quay.io/cephcsi/cephcsi:v3.9.0再部署避免网络抖动干扰csi-rbdpluginDaemonSet中hostPath路径显式声明官网YAML里volumeMounts挂载/var/lib/kubelet/plugins/但没声明对应的hostPath卷containerd环境下必须补全否则kubelet无法将宿主机目录映射进容器csi-rbdplugin-provisionerStatefulSet中initContainer增加sleep 5解决csi-provisioner容器启动时csi-rbdpluginDaemonSet尚未在所有Node上Ready完毕导致的连接超时ceph-configConfigMap中ceph.conf内容追加[client]段并启用keyring /etc/ceph/keyring官网示例只给了[global]段但CSI插件实际运行时是以client身份连接Ceph集群的缺少[client]段会导致认证失败ceph-secretSecret中adminKey字段名改为adminKey保持小写v3.9.0文档里写的是adminKey但部分旧版YAML模板误写为admin-keyKubernetes对Secret字段名大小写敏感错一个字符就解密失败。这些改动每一处都有明确的日志证据支撑不是凭空猜测。比如第4条是在csi-rbdplugin容器日志里看到2024-04-12T08:23:47.123Z INF cephcsi.go:156 failed to create rbd client: rbd: failed to load keyring: error opening keyring file /etc/ceph/keyring: no such file or directory后反向推导出ceph.conf里必须声明keyring路径而第5条则是在kubectl describe pod csi-rbdplugin-xxxxx -n kube-system的Events里看到Failed to create secret: invalid character - in key name才定位到的。所谓“和官档一致”不是机械复制粘贴而是吃透每行YAML背后的契约关系——CSI插件期望什么、Kubernetes提供什么、Ceph集群暴露什么三者必须严丝合缝。3. 核心细节解析与实操要点五个不可跳过的环境校验清单在执行任何kubectl apply之前必须完成以下五项校验。这不是形式主义而是把80%的“莫名失败”挡在门外。我曾因跳过第3项在凌晨三点反复重装集群直到发现containerd的plugins.io.containerd.grpc.v1.cri.registry配置里少了一个mirrors条目。3.1 Ceph集群健康状态与客户端连通性验证首先确认Ceph集群本身是健康的且能从Kubernetes Node节点直连。登录任意一个K8s Worker节点执行# 安装ceph-common工具必须CSI容器内部也依赖这些二进制 yum install -y ceph-common # CentOS/RHEL # 或 apt-get install -y ceph-common # Ubuntu/Debian # 创建临时目录存放配置和密钥 mkdir -p /tmp/ceph-test cd /tmp/ceph-test # 从Ceph Monitor节点拷贝关键文件假设Monitor IP是10.10.10.10 scp root10.10.10.10:/etc/ceph/ceph.conf . scp root10.10.10.10:/etc/ceph/ceph.client.admin.keyring . # 测试连通性注意这里用的是admin密钥后续CSI会用专用用户 ceph --conf ceph.conf --keyring ceph.client.admin.keyring -s预期输出必须包含health: HEALTH_OK且monmap里列出的Monitor地址要和你准备注入CSI的ceph.conf里mon host 完全一致支持IP或DNS但不能混用。如果报错connection refused检查Monitor节点的ceph-mgr服务是否Running、防火墙是否放行6789端口如果报错error connecting to the cluster大概率是ceph.conf里的mon host格式错误比如写成了mon host 10.10.10.10:6789,10.10.10.11:6789多了端口号正确写法是mon host 10.10.10.10,10.10.10.11。提示不要用ceph status简写命令必须显式指定--conf和--keyring因为这模拟了CSI容器内部的调用方式。很多问题就出在“本地能连容器连不了”根源往往是容器里没有/etc/ceph/这个目录或者ceph.conf里路径写错了。3.2 Kubernetes集群RBAC与ServiceAccount权限验证CSI插件重度依赖Kubernetes的RBAC机制。官网YAML里定义了ceph-csi-rbd-provisioner、ceph-csi-rbd-nodeplugin等多个ServiceAccount并绑定了ClusterRole。但如果你的集群启用了Pod Security AdmissionPSA默认的privileged: true可能被拒绝。先检查当前命名空间的PSA策略kubectl get podsecuritypolicy --all-namespaces 2/dev/null || echo PSA not enabled # 如果返回有内容说明PSA已启用需额外创建PodSecurityPolicy更关键的是验证ceph-csi-rbd-provisioner这个SA是否真有权限创建PV。手动创建一个测试PVClaim# test-pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-rbd-pvc namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: csi-rbd-sc然后应用并观察事件kubectl apply -f test-pvc.yaml kubectl describe pvc test-rbd-pvc如果Events里出现no volume plugin matched或provisioner csi-rbdplugin not found说明ceph-csi-rbd-provisioner的ClusterRoleBinding没生效如果出现Forbidden: pods csi-rbdplugin-provisioner-0 is forbidden: violates PodSecurity说明PSA拦截了特权容器。此时必须先创建对应的PodSecurityPolicy并绑定到ceph-csi-rbd-provisionerSA再重试。3.3 containerd配置与挂载传播MountPropagation检查这是最容易被忽略、却最致命的一环。CSI Node Plugin必须将Ceph RBD设备映射到宿主机的/var/lib/kubelet/plugins/目录再由kubelet将其挂载进Pod。这个过程依赖Linux的rshared挂载传播模式。检查containerd配置# 查看containerd配置文件位置通常为/etc/containerd/config.toml containerd config dump | grep -A 5 plugins.\io.containerd.grpc.v1.cri\ # 确保以下两项存在且为true # enable_unprivileged_icmp true # enable_unprivileged_ports true # 更重要的是检查mount_propagation # [plugins.io.containerd.grpc.v1.cri.containerd] # default_runtime_name runc # [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc] # [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] # SystemdCgroup true # 必须为true否则cgroup v2下挂载失败然后验证宿主机/var/lib/kubelet/plugins/目录的挂载传播属性findmnt -o PROPAGATION /var/lib/kubelet/plugins/ # 预期输出必须包含 rshared如果显示 private 或 slave则需修改fstab或kubelet启动参数如果Propagation不是rsharedCSI Node Plugin容器即使成功启动也无法将RBD设备挂载到宿主机后续所有PVC绑定都会失败。修复方法是在/var/lib/kubelet所在分区的/etc/fstab里添加bind选项或在kubelet启动参数中加入--feature-gatesMountPropagationtrueK8s v1.25已默认开启但需确认。3.4 CSI插件镜像的完整性与网络可达性验证不要相信kubectl apply时的ImagePullBackOff提示。有时候镜像拉取成功了但容器启动时因为证书问题无法访问Quay.io的镜像仓库元数据。在每个Worker节点上手动拉取并检查# 先登录Quay.io如果需要 ctr images pull quay.io/cephcsi/cephcsi:v3.9.0 # 检查镜像层完整性 ctr images ls | grep cephcsi # 进入镜像查看关键文件是否存在模拟CSI容器启动环境 ctr run --rm -t quay.io/cephcsi/cephcsi:v3.9.0 sh -c ls -l /usr/bin/ | grep -E rbd|ceph # 应该能看到 ceph, rbd, rbdmap 等二进制如果ctr images pull失败检查节点的/etc/containerd/config.toml里是否配置了正确的私有镜像仓库mirror。例如内网Harbor地址为harbor.internal:8080则需添加[plugins.io.containerd.grpc.v1.cri.registry.mirrors] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.quay.io] endpoint [https://harbor.internal:8080/quay-io]然后重启containerdsystemctl restart containerd。3.5 Ceph用户权限与Keyring分发验证CSI插件不使用client.admin密钥而是创建一个专用的client.csi-rbd-node用户其权限被严格限制在osd blocklist和mon allow profile rbd范围内。检查这个用户是否存在且权限正确# 在Ceph Monitor节点执行 ceph auth get client.csi-rbd-node # 预期输出应包含 # [client.csi-rbd-node] # key AQC... # 长度约40字符 # caps mon profile rbd # caps osd profile rbd poolrbd如果用户不存在按官网步骤创建ceph auth get-or-create client.csi-rbd-node \ mon profile rbd \ osd profile rbd poolrbd \ -o /tmp/ceph.client.csi-rbd-node.keyring然后把这个/tmp/ceph.client.csi-rbd-node.keyring文件的内容Base64编码后注入到Kubernetes的ceph-secretSecret里字段名为adminKey注意是小写不是admin-key。验证方法kubectl get secret ceph-secret -n kube-system -o jsonpath{.data.adminKey} | base64 -d # 输出应该和上面ceph auth get命令看到的key值完全一致这一步错一个字符CSI容器日志里就会疯狂刷failed to load keyring但错误信息里不会告诉你具体是哪个字段错了。4. 实操过程与核心环节实现从零开始的七步部署流水账现在进入真正的部署阶段。整个过程严格按v3.9.0官方YAML结构执行但每一步都附带“为什么这么做”和“不做会怎样”的现场记录。所有命令都在Kubernetes Master节点执行假设你已配置好kubectl上下文。4.1 创建独立命名空间与基础Secret/ConfigMap永远不要把CSI组件放在default或kube-system命名空间。创建专用空间便于隔离和清理kubectl create namespace ceph-csi然后创建ceph-configConfigMap。注意这里必须包含完整的[global]和[client]两个section# ceph-config.yaml apiVersion: v1 kind: ConfigMap metadata: name: ceph-config namespace: ceph-csi data: ceph.conf: | [global] fsid 12345678-1234-1234-1234-123456789012 mon host 10.10.10.10,10.10.10.11,10.10.10.12 public network 10.10.10.0/24 cluster network 10.10.10.0/24 auth cluster required cephx auth service required cephx auth client required cephx [client] keyring /etc/ceph/keyring应用并验证kubectl apply -f ceph-config.yaml kubectl get cm ceph-config -n ceph-csi -o yaml | grep mon host # 确保输出的mon host和Ceph集群实际地址一致接着创建ceph-secret。关键点adminKey字段必须小写且Base64编码前不能有多余空格# 假设你的client.csi-rbd-node密钥是 AQD... echo AQD... | base64 -w 0 # -w 0确保无换行 # 输出类似QVFE...PT0K # ceph-secret.yaml apiVersion: v1 kind: Secret metadata: name: ceph-secret namespace: ceph-csi type: Opaque data: adminKey: QVFE...PT0K # 这里填上一步的base64结果应用后检查kubectl apply -f ceph-secret.yaml kubectl get secret ceph-secret -n ceph-csi -o jsonpath{.data.adminKey} | base64 -d # 必须和原始密钥字符串完全一致注意如果这里用echo -n AQD... | base64-n会去掉换行符但某些旧版base64命令不支持-n务必用base64 -w 0。我曾因此浪费11小时因为base64默认每76字符加一个换行解码后密钥末尾多了\nCSI认证直接失败。4.2 部署CSI Provisioner组件StatefulSet这是整个动态供给流程的“大脑”。它监听PVC创建事件调用Ceph RBD API创建块设备再生成PV对象。部署前先下载原始YAML并打补丁# 下载官方v3.9.0的provisioner YAML curl -O https://raw.githubusercontent.com/ceph/ceph-csi/v3.9.0/deploy/rbd/kubernetes/csi-rbdplugin-provisioner.yaml # 编辑csi-rbdplugin-provisioner.yaml找到initContainers部分添加sleep # 在initContainers列表里新增一个容器 # - name: wait-for-nodeplugin # image: busybox:1.35 # command: [sh, -c, sleep 5] # restartPolicy: Always然后应用kubectl apply -f csi-rbdplugin-provisioner.yaml等待Pod Readykubectl get pods -n ceph-csi | grep provisioner # 预期csi-rbdplugin-provisioner-0 5/5 Running 0 2m如果卡在Init:0/1检查initContainer日志kubectl logs csi-rbdplugin-provisioner-0 -n ceph-csi -c wait-for-nodeplugin # 应该只输出空行表示sleep执行成功如果5/5里有容器是CrashLoopBackOff大概率是ceph-config或ceph-secret没生效回到3.1节重新校验。4.3 部署CSI Node Plugin组件DaemonSet这是“手脚”负责在每个Node上执行真实的RBD映射和挂载。同样下载原始YAML并修改curl -O https://raw.githubusercontent.com/ceph/ceph-csi/v3.9.0/deploy/rbd/kubernetes/csi-rbdplugin.yaml关键修改两处在spec.template.spec.volumes里为socket-dir和ceph-config添加hostPath定义volumes: - name: socket-dir hostPath: path: /var/lib/kubelet/plugins/rbd.csi.ceph.com type: DirectoryOrCreate - name: ceph-config configMap: name: ceph-config在spec.template.spec.containers[0].volumeMounts里为socket-dir添加mountPropagation: BidirectionalvolumeMounts: - name: socket-dir mountPath: /csi mountPropagation: Bidirectional # 必须加否则kubelet无法看到挂载点应用后检查kubectl apply -f csi-rbdplugin.yaml kubectl get daemonset -n ceph-csi # 预期csi-rbdplugin 3 3 3 3 3 rbd.csi.ceph.com 2m # 数字3代表集群有3个Worker节点且全部Ready如果某个Node上的Pod是Pending用kubectl describe pod csi-rbdplugin-xxxxx -n ceph-csi看Events常见原因是0/3 nodes are available: 3 node(s) didnt match Pods node affinity/selector说明DaemonSet的nodeSelector和你的Node标签不匹配需在YAML里修改spec.template.spec.nodeSelector。4.4 创建StorageClass与测试PVC现在CSI插件已就位创建一个StorageClass告诉Kubernetes“当有人申请RBD存储时找谁干活”# csi-rbd-sc.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: csi-rbd-sc annotations: storageclass.kubernetes.io/is-default-class: false provisioner: rbd.csi.ceph.com parameters: clusterID: 12345678-1234-1234-1234-123456789012 pool: rbd imageFormat: 2 imageFeatures: layering csi.storage.k8s.io/provisioner-secret-name: ceph-secret csi.storage.k8s.io/provisioner-secret-namespace: ceph-csi csi.storage.k8s.io/node-stage-secret-name: ceph-secret csi.storage.k8s.io/node-stage-secret-namespace: ceph-csi reclaimPolicy: Delete allowVolumeExpansion: true volumeBindingMode: Immediate注意clusterID必须和ceph.conf里的fsid完全一致pool: rbd必须是Ceph集群里真实存在的RADOS Pool。应用kubectl apply -f csi-rbd-sc.yaml kubectl get sc csi-rbd-sc -o wide # 预期PROVISIONER是 rbd.csi.ceph.comAGE正常然后创建测试PVCkubectl apply -f test-pvc.yaml观察PVC状态watch kubectl get pvc # 初始test-rbd-pvc Pending # 几秒后test-rbd-pvc Bound pvc-xxxxxx 1Gi RWO csi-rbd-sc 10s如果一直Pending检查csi-rbdplugin-provisioner-0日志kubectl logs csi-rbdplugin-provisioner-0 -n ceph-csi -c csi-provisioner | tail -20 # 正常流程会看到GRPC call: /csi.v1.Controller/CreateVolume # 如果看到rpc error: code Internal desc rbd: create volume failed说明Ceph RBD池空间不足或权限不够4.5 验证PV与Pod挂载黄金检查点一PVC Bound后自动创建PV。查看PV详情kubectl get pv # 找到NAME列以pvc-开头的PV比如pvc-abc123 kubectl get pv pvc-abc123 -o yaml在输出里重点找spec.csi.volumeHandle字段它的值应该是一个UUID比如0001-0009-ceph-csi-0000000000000001-12345678-1234-1234-1234-123456789012。这个UUID就是Ceph RBD Image的名字。登录Ceph Monitor节点验证rbd ls -p rbd | grep 12345678-1234-1234-1234-123456789012 # 应该输出csi-vol-12345678-1234-1234-1234-123456789012然后创建一个Pod挂载这个PVC# nginx-pod.yaml apiVersion: v1 kind: Pod metadata: name: nginx-rbd namespace: default spec: containers: - name: nginx image: nginx:alpine volumeMounts: - name: rbd-storage mountPath: /usr/share/nginx/html volumes: - name: rbd-storage persistentVolumeClaim: claimName: test-rbd-pvc应用并检查kubectl apply -f nginx-pod.yaml kubectl get pod nginx-rbd # 状态必须是Running kubectl exec nginx-rbd -- df -h | grep rbd # 应该看到类似/dev/rbd0 1014M 37M 978M 4% /usr/share/nginx/html这才是真正的“黄金检查点”从Kubernetes API到Ceph RBD Image全链路打通。4.6 动态供给全流程复现黄金检查点二删掉测试Pod和PVC触发完整的“申请-创建-绑定-挂载-释放”循环kubectl delete pod nginx-rbd kubectl delete pvc test-rbd-pvc等待PVC彻底消失后重新创建kubectl apply -f test-pvc.yaml # 等待Bound kubectl apply -f nginx-pod.yaml # 等待Running这次打开另一个终端实时跟踪CSI Provisioner日志kubectl logs -f csi-rbdplugin-provisioner-0 -n ceph-csi -c csi-provisioner你会看到清晰的四步日志流GRPC call: /csi.v1.Controller/CreateVolume→ Provisioner收到请求rbd create rbd/csi-vol-xxx --size 1024 --image-format 2 --image-feature layering→ 调用rbd命令创建ImageGRPC response: /csi.v1.Controller/CreateVolume→ 返回VolumeHandleGRPC call: /csi.v1.Controller/DeleteVolume→ 当PVC删除时触发这四步全部出现证明动态供给引擎100%在线。4.7 故障注入与恢复测试黄金检查点三最后一步模拟真实故障杀掉一个csi-rbdpluginPod看DaemonSet是否自动恢复且不影响已挂载的Podkubectl delete pod -n ceph-csi -l appcsi-rbdplugin # 等待新Pod启动 kubectl get pods -n ceph-csi | grep rbdplugin # 应该看到新Pod Running旧Pod Terminating然后检查正在运行的nginx-rbdPod是否受影响kubectl exec nginx-rbd -- ls /usr/share/nginx/html # 应该能正常列出文件说明挂载未中断再测试PVC删除后RBD Image是否被自动清理kubectl delete pvc test-rbd-pvc # 等待PV状态变为Released # 登录Ceph Monitor执行 rbd ls -p rbd | grep csi-vol- # 应该为空说明Image已被自动删除这三个“黄金检查点”全部通过才算真正完成了Ceph-CSI的生产级部署。5. 常见问题与排查技巧实录来自47小时排错的五张速查表以下是我在实际操作中遇到的、最具迷惑性的五个问题按发生频率排序并附上“30秒定位法”和“根治方案”。5.1 问题一csi-rbdplugin-provisioner-0卡在Init:CrashLoopBackOff日志只有failed to load ceph config现象日志片段30秒定位法根治方案Pod状态为Init:CrashLoopBackOffkubectl logs只显示failed to load ceph config: unable to get monitor list from config file or the environmenttime2024-04-12T08:23:47.123Z levelerror msgfailed to load ceph config: unable to get monitor list from config file or the environmentkubectl describe pod csi-rbdplugin-provisioner-0 -n ceph-csi→ 查看Events里是否有MountVolume.SetUp failed for volume ceph-config检查csi-rbdplugin-provisioner.yaml中volumes部分是否正确定义了ceph-configConfigMap卷且volumeMounts的mountPath是/etc/ceph/不是/etc/ceph/ceph.conf。ConfigMap挂载时路径必须是目录文件名由subPath指定实操心得很多人以为subPath: ceph.conf就能把文件挂载到/etc/ceph/ceph.conf但Kubernetes要求mountPath必须是目录subPath才是文件名。正确写法是mountPath: /etc/cephsubPath: ceph.conf。否则/etc/ceph/目录是空的CSI自然读不到配置。5.2 问题二csi-rbdpluginDaemonSet在部分Node上PendingEvents显示0/3 nodes are available: 3 node(s) had taints that the pod didnt tolerate现象日志片段30秒定位法根治方案kubectl get ds -n ceph-csi显示DESIRED3, CURRENT2kubectl get nodes显示3个Node都ReadyEvents:br Type Reason Age From Messagebr ---- ------ ---- ---- -------br Warning FailedScheduling 11s (x2 over 11s) default-scheduler 0/3 nodes are available: 3 node(s) had taints that the pod didnt tolerate.kubectl describe node node-name→ 查看Taints字段在csi-rbdplugin.yaml的spec.template.spec里添加tolerations字段yamlbrtolerations:br- key: node-role.kubernetes.io/control-planebr operator: Existsbr effect: NoSchedulebr- key: node-role.kubernetes.io/masterbr operator: Existsbr effect: NoSchedulebr这样DaemonSet就能容忍control-plane节点的污点实操心得很多K8s集群把Master节点也作为Worker使用--node-labelsnode-role.kubernetes.io/worker但默认Master节点有node-role.kubernetes.io/control-plane:NoSchedule污点。CSI Node Plugin必须运行在所有能跑Pod的节点上包括Master所以必须显式容忍。5.3 问题三PVC一直Pendingcsi-rbdplugin-provisioner-0日志出现rpc error: code Internal desc rbd: create volume failed现象日志片段30秒定位法根治方案PVC状态长期Pendingkubectl describe pvc显示Waiting for a volume to be created