
1. 项目背景业务场景本地生活电商的技术栈正在从 Docker Compose 手工运维迁移到 Kubernetes。MongoDB 的部署方式也要从运维手敲 docker run升级为 K8s 管理的 StatefulSet。但团队心里没底——MongoDB 是有状态服务Pod 挂了数据怎么办PVC 要怎么配置才能保证磁盘性能复制集的节点之间怎么在 K8s 网络中互相发现滚动升级时节点退出会不会丢数据更头疼的是运维部门要求所有 K8s 中的服务必须有健康检查和资源限制。但 MongoDB 的探针怎么写——db.runCommand(ping)探活简单但ping 通了但性能极差怎么检测CPU 限制设太小 MongoDB 在启动恢复时会被 OOM kill设太大浪费 K8s Node 的资源。要不要用 MongoDB Community Operator 还是手写 YAML痛点有状态服务在 K8s 上运行坑点密集——PVC 绑定到特定 AZ 后 Pod 漂移到其他 Node 时无法挂盘反亲和配置遗漏导致主节点和从节点跑在同一台物理机上——宿主机宕机导致整个复制集不可用MongoDB 需要直连 Pod IP 进行复制集内部通信而 K8s Service 的负载均衡反而会破坏复制集的拓扑发现。2. 项目设计小胖盯着 K8s YAML 一脸困惑大师我们把 MongoDB 从 Docker Compose 搬到了 K8s结果重启一个 Pod 后复制集全乱了为什么 K8s Service 反而坏了事大师因为 MongoDB 的复制集节点之间需要直连 IP 通信——每个节点在自己的rs.conf()里记录了其他节点的 host:port。如果这些 host 是 K8s Service 的 ClusterIPmongod 发现对方不是真正的节点地址心跳和选主都乱了。技术映射MongoDB 的复制集使用节点间直连通信P2P不适合通过 K8s Service 的负载均衡进行。正确的做法是使用Headless Service StatefulSet——每个 Pod 有稳定的 DNS 名称如mongo-0.mongo-svc.default.svc.cluster.localPod IP 变化但 DNS 名称不变。小胖StatefulSet 和 Deployment 有啥区别大师StatefulSet 三个核心特性让它适合 MongoDB特性StatefulSetDeploymentPod 标识稳定的序号名mongo-0, mongo-1, mongo-2随机后缀mongo-7f4b5c-abc网络标识每个 Pod 有固定的 DNS 名DNS 无稳定性保证存储每个 Pod 绑定各自的 PVC独立磁盘共享 PVC 或临时存储启动/终止顺序0→1→2有序并行技术映射StatefulSet 的稳定标识是 MongoDB 复制集的基础——节点在复制集配置中以 DNS 名注册DNS 名永不变化即使 Pod IP 变了也能正常恢复通信。小白那 PVC 和 StorageClass 呢性能影响大吗大师非常大。MongoDB 的 WiredTiger 存储引擎对磁盘 IOPS 和延迟敏感——如果 PVC 用的是普通的 HDD 存储类写入延迟 10ms你的复制延迟就会飙升。生产环境必须选 SSD 存储类且 IOPS 至少 3000云厂商的 gp3/ESSD PL1 起步。小白那 K8s 探针怎么写db.runCommand(ping)够吗大师ping 探针只检查进程是否活着liveness不检查是否可用。需要分层Liveness Probemongosh --eval db.runCommand(ping).ok——进程是否存活。存活探测失败 → K8s 重启 Pod。Readiness Probe判断节点是否准备好接受流量。对 Primaryrs.isMaster().ismaster true对 Secondaryrs.isMaster().secondary true且复制延迟 10s。未就绪 → 从 Service 中摘除。Startup Probe节点刚启动时可能在 Initial Sync需要数分钟此时 liveness 和 readiness 都不应太快判定失败。startup probe 给首次启动更长的宽限期如 300s。技术映射Liveness 管要不要重启Readiness 管能不能接流量Startup 管刚启动给够时间。大师总结K8s 部署 MongoDB 的核心——StatefulSet Headless Service解决复制集节点发现反亲和配置防止节点集中在一个宿主机分层探针startup/readiness/liveness制定资源限制内存 工作集 1-2GB 系统开销CPU 不 hard-limit。3. 项目实战3.1 环境准备需要 K8s 集群Minikube / Kind / 云 K8s。本章提供 YAML 配置和部署流程。# 本地测试用 Kind 创建集群kind create cluster--namemongodb-lab3.2 分步实现步骤一StorageClass PVC 模板目标定义 SSD 存储类和 StatefulSet 的 PVC 模板。# storageclass.yamlapiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:mongodb-ssdprovisioner:kubernetes.io/aws-ebs# AWS EBS云环境# 本地测试用:# provisioner: rancher.io/local-path # k3s / kindparameters:type:gp3iopsPerGB:100volumeBindingMode:WaitForFirstConsumerreclaimPolicy:RetainallowVolumeExpansion:true# headless-service.yamlapiVersion:v1kind:Servicemetadata:name:mongo-svclabels:app:mongodbspec:clusterIP:None# Headless Serviceports:-port:27017name:mongoselector:app:mongodb步骤二StatefulSet 定义目标部署 3 节点 MongoDB 复制集。# statefulset.yamlapiVersion:apps/v1kind:StatefulSetmetadata:name:mongospec:serviceName:mongo-svcreplicas:3podManagementPolicy:Parallel# 并行启动加快部署selector:matchLabels:app:mongodbtemplate:metadata:labels:app:mongodbspec:# 反亲和每个 Pod 尽量在不同 Node 上affinity:podAntiAffinity:preferredDuringSchedulingIgnoredDuringExecution:-weight:100podAffinityTerm:labelSelector:matchLabels:app:mongodbtopologyKey:kubernetes.io/hostnamecontainers:-name:mongodimage:mongo:8.0command:-mongod---replSet-myRS---bind_ip_allports:-containerPort:27017name:mongoresources:requests:memory:2Gicpu:1limits:memory:4Gi# 不设 CPU limit 硬限制避免被 throttleenv:-name:MONGO_INITDB_ROOT_USERNAMEvalueFrom:secretKeyRef:name:mongo-secretkey:username-name:MONGO_INITDB_ROOT_PASSWORDvalueFrom:secretKeyRef:name:mongo-secretkey:passwordvolumeMounts:-name:mongo-datamountPath:/data/dbstartupProbe:exec:command:-mongosh---eval-db.runCommand(ping).okinitialDelaySeconds:30periodSeconds:10failureThreshold:30# 30 * 10 300s 启动宽限期livenessProbe:exec:command:-mongosh---eval-db.runCommand(ping).okinitialDelaySeconds:60periodSeconds:30readinessProbe:exec:command:-mongosh---eval-|try { if (rs.status().myState 1 || rs.status().myState 2) { quit(0); } } catch (e) { quit(1); } quit(1);initialDelaySeconds:60periodSeconds:10volumeClaimTemplates:-metadata:name:mongo-datalabels:app:mongodbspec:accessModes:[ReadWriteOnce]storageClassName:mongodb-ssdresources:requests:storage:50Gi---apiVersion:v1kind:Secretmetadata:name:mongo-secrettype:OpaquestringData:username:adminpassword:K8sMongoAdmin2026!步骤三初始化复制集目标在 Pod 启动后通过 Job 初始化复制集配置。# init-rs-job.yamlapiVersion:batch/v1kind:Jobmetadata:name:mongo-init-rsspec:template:spec:restartPolicy:OnFailurecontainers:-name:init-rsimage:mongo:8.0command:-mongosh---host-mongo-0.mongo-svc:27017--u-admin--p-K8sMongoAdmin2026!---authenticationDatabase-admin---eval-|try { rs.initiate({ _id: myRS, members: [ { _id: 0, host: mongo-0.mongo-svc:27017, priority: 2 }, { _id: 1, host: mongo-1.mongo-svc:27017, priority: 1 }, { _id: 2, host: mongo-2.mongo-svc:27017, priority: 1 } ] }); print(复制集初始化成功); } catch(e) { if (e.message e.message.includes(already initialized)) { print(复制集已初始化跳过); } else { throw e; } }restartPolicy:OnFailure步骤四滚动升级与故障恢复验证# 1. 滚动升级——逐个 Pod 更新镜像kubectlsetimage statefulset/mongomongodmongo:8.0.1 kubectl rollout status statefulset/mongo# 滚动升级过程中# - K8s 一次只重启一个 Pod# - 被重启的 Pod 如果之前是 Primary会触发 stepDown 重新选举# - 重启后 Pod 自动重新加入复制集、追 Oplog# 2. 模拟故障——删除一个 Podkubectl delete pod mongo-1# 观察# - StatefulSet 自动重建 mongo-1# - 新 Pod 启动后自动重新加入复制集从其他节点拉取数据# - 如果 mongo-0 是 Primary——不受影响# - 如果 mongo-1 是 Primary——触发选举mongo-0 或 mongo-2 当选新 Primary# 查看复制集状态kubectlexecmongo-0 -- mongosh--evalrs.status().members.forEach(m print(m.name : m.stateStr))# 3. PVC 数据持久性验证# 删除整个 StatefulSet保留 PVCkubectl delete statefulset mongo--cascadeorphan# 重新创建 StatefulSet用相同的名称和 PVC 模板kubectl apply-fstatefulset.yaml# 数据通过 PVC 被重新挂载到同名 Pod 上——启动后数据完整步骤五MongoDB Operator vs 手工 YAML目标对比两种部署方式的优劣。维度手工 YAML (StatefulSet)MongoDB Community Operator初始配置需要手写 YAMLOpsManager CR 声明式升级手动kubectl set imageOperator 自动滚动升级备份需自建 CronJobOpsManager 集成备份节点扩容手动改 replicas reconfigCR 中改 membersTLS需自管理证书Operator 自动签发学习成本低标准 K8s 知识中需学 CR 规范灵活性高中受限于 Operator 的支持范围推荐小规模 10 节点用手工 YAML 足够灵活稳定大规模生产集群用 Operator 来处理证书轮转、自动扩容、备份恢复等复杂运维。步骤六资源限制与内核调优# 在 StatefulSet 的 pod spec 中添加# 1. 内核参数调优initContainers:-name:sysctl-tuningimage:busybox:1.36securityContext:privileged:truecommand:-sh--c-|sysctl -w vm.swappiness1 sysctl -w vm.zone_reclaim_mode0 # 禁用透明大页MongoDB 官方强烈建议 if [ -f /sys/kernel/mm/transparent_hugepage/enabled ]; then echo never /sys/kernel/mm/transparent_hugepage/enabled echo never /sys/kernel/mm/transparent_hugepage/defrag fivolumeMounts:-name:sysmountPath:/sys# 2. NodeSelector 将 MongoDB Pod 调度到 SSD 节点# nodeSelector:# disktype: ssd# 3. 为 MongoDB 设置合理的 --wiredTigerCacheSizeGB# 在 command 中添加# - --wiredTigerCacheSizeGB# - 2.5 # 4GB 内存容器留 1-1.5GB 给系统和其他开销3.3 完整代码清单文件用途mongodb-lab/k8s/storageclass.yamlSSD StorageClassmongodb-lab/k8s/headless-service.yamlHeadless Servicemongodb-lab/k8s/statefulset.yamlStatefulSet PVC 模板 探针mongodb-lab/k8s/secret.yaml管理员密码 Secretmongodb-lab/k8s/init-rs-job.yaml复制集初始化 Jobmongodb-lab/k8s/rolling-upgrade.sh滚动升级脚本mongodb-lab/k8s/disaster-recovery.sh故障恢复验证脚本3.4 测试验证# 1. 部署到 K8skubectl apply-fmongodb-lab/k8s/# 2. 等待 Pod 就绪kubectlwait--forconditionready pod-lappmongodb--timeout300s# 3. 验证复制集kubectlexecmongo-0 -- mongosh--quiet-uadmin-pK8sMongoAdmin2026!\--evalrs.status().members.forEach(m print(m.name m.stateStr))# 4. 验证 Headless Service DNSkubectlexecmongo-0 --nslookupmongo-svc# 应返回 3 个 Pod IP# 5. 写入测试kubectlexecmongo-0 -- mongosh--quiet-uadmin-pK8sMongoAdmin2026!\--eval use test_k8s; db.messages.insertOne({msg:K8s部署成功, ts: new Date()}); print(db.messages.findOne().msg); # 6. PVC 持久性测试kubectl delete statefulset mongo--cascadeorphan kubectl apply-fstatefulset.yaml kubectlexecmongo-0 -- mongosh--quiet-uadmin-pK8sMongoAdmin2026!\--evalprint(db.getSiblingDB(test_k8s).messages.findOne().msg)# 期望输出: K8s部署成功# 7. 清理# kubectl delete -f mongodb-lab/k8s/4. 项目总结4.1 K8s 部署 MongoDB 关键决策决策点推荐方案理由工作负载类型StatefulSet稳定标识 独立 PVC服务发现Headless Service每个 Pod 独立 DNS 名存储SSD StorageClass PVC 模板IOPS 和延迟保障调度反亲和 (preferred)避免单点故障探针startup 300s liveness 30s readiness 10s适应 Initial Sync 的长时间窗口资源限制CPU 不设硬限, Memory 设 1.5-2x 工作集避免 OOM/cgroup throttle安全Secret 存密码, K8s RBAC不硬编码在 YAML 中4.2 适用场景K8s 部署 MongoDB 适用统一基础设施——团队已有 K8s 集群用 StatefulSet 统一管理。中小型规模——3-5 节点复制集 少量分片。开发/测试环境——快速拉起、销毁、重建。自动化运维——Operator 提供自动备份、升级、扩容。不适用场景超大规模集群百 TB 级——裸机部署能更精细调优内核参数和磁盘。极致的延迟要求P99 1ms——K8s 网络 overlay 和容器化的额外延迟。4.3 注意事项注意事项说明不要用recreate部署策略会一次性删掉所有 Pod 再重建→复制集全部宕机Headless Service 的 DNS 查询StatefulSet Pod 的 DNS 名 pod-name.service-name.namespace.svc.cluster.localPVC 的reclaimPolicy: Retain删除 StatefulSet 时 PVC 不会自动删除数据保留滚动升级时的 stepDown升级到 Primary Pod 时会触发 stepDown确保升级期间写入不中断podManagementPolicy: OrderedReadyvsParallelOrdered 循序启动 0→1→2Parallel 同时启动快但 0 号节点启动较慢可能选主不稳定4.4 常见踩坑经验故障案例一K8s Service 的 ClusterIP 负载均衡破坏复制集某团队把 MongoDB 的 Headless Service 错配成了 ClusterIP Service——mongod 之间通过 Service IP 通信时K8s 的 iptables 随机路由到任意一个 Pod导致心跳请求错位、节点认为其他节点挂了反复选举导致复制集抖动。解决使用clusterIP: None的 Headless Service每个 Pod 独立 DNS 名。故障案例二CPU limit 设太死导致 MongoDB 被 throttle某团队把 CPU limit 设为 2 核MongoDB 默认启动时用满所有核做恢复Pod 尝试使用超过 2 核被 K8s cgroup throttle恢复速度从 2 分钟剧降到 20 分钟——readiness probe 超时Pod 被不断重启。解决不设 CPU hard limit只设 request 预留或者把 limit 设得足够大如 8 核。故障案例三PVC 跨 AZ 绑定后 Pod 调度失败云环境中有 3 个可用区ZonePVC 创建时被绑定到 Zone-A 的磁盘上。当 Zone-A 的 Node 宕机K8s 尝试在 Zone-B 上重新调度 Pod——但 PVC 在 Zone-A 无法挂载到 Zone-B 的 Node 上Pod 永远 Pending。解决使用支持跨 AZ 的存储类如 AWS EFS/Portworx或在每个 AZ 部署独立的复制集而非让 K8s 调度器跨 AZ 迁移有状态 Pod。4.5 思考题在 K8s 上部署分片集群mongos config server 多个 shard每个组件应该用什么 K8s 工作负载提示mongos 是无状态的config server 和 shard 是复制集如果在 K8s StatefulSet 中设置了podManagementPolicy: OrderedReady而 PVC 的storageClassName对应的后端正处于维护不可用状态——mongo-0 的 PVC 无法绑定mongo-1 和 mongo-2 会启动吗为什么答案将在第 31 章末尾揭晓上一章思考题答案Oplog 窗口 48 小时 每天增量 50GB → 需要的 Oplog 磁盘空间 50GB × 2 100GB加上安全余量建议 120-150GB。带宽 100MB/s 传输 50GB ≈ 512 秒 ≈ 8.5 分钟——超过 5 分钟的 RPO 目标。改进压缩 Oplog 增量压缩比约 2-3x传输量可降到 20GB 3.4 分钟或升级带宽到 200MB/s。恢复后集合文档数为 0 而生产正常——最可能的原因是恢复时mongorestore带了--drop参数清空了集合但 BSON 文件的文档数据没有被成功导入。原因可能是BSON 文件损坏、备份时用了错误的条件查询--query、或备份的 BSON 文件路径指向了一个空的 dump 目录。也可能是集合名大小写不匹配Windows 和 Linux 的文件系统差异。延伸阅读与资源MongoDB 实战进阶与内核修炼python入门Rquests从菜鸟脚本到企业级SDK的网络实战圣经Milvus向量数据库实战修炼从 0 到 1精通向量检索与生产落地后端工程师的 AI 转型第一课Ollama 与私有化大模型实战10倍开发者的 Dify 魔法书从零构建全栈 AI 应用后端工程师转型AI第一课-Ollama 与私有化大模型实战大型语言模型(LLM) vLLM 高性能推理落地实战Agent开发之LlamaIndex 实战修炼与源码进阶大语言模型Transformers 实战修炼与源码剖析