广告位 · 页头(后台-模块管理-新建模块后替换此区域)

如何基于Kubernetes完成Milvus分布式部署与集群配置?

如果只是做个向量检索的原型验证,Milvus单机版用Docker跑起来确实方便。但生产上数据量一旦达到亿级甚至十亿级,单节点的内存、CPU和磁盘都撑不住,更别说节点宕机时整个服务不可用。这时候就需要把Milvus的各个组件拆开,借助Kubernetes来编排。本文将围绕Kubernetes环境下的Milvus分布式部署展开,从架构理解、集群准备、Helm配置到故障排查,给出可操作的具体步骤。

如何基于Kubernetes完成Milvus分布式部署与集群配置?

一、Milvus分布式架构与依赖组件

Milvus 2.x 的分布式模式并不是简单的把单机程序复制几份,而是按照功能边界拆分成了多个独立服务。主要分为四类角色:Root Coordinator、Data Coordinator、Query Coordinator和Index Coordinator,它们分别负责元数据管理、数据写入调度、查询调度以及索引构建调度。除了这些Coordinator,真正干活的还有Data Node、Query Node和Index Node。这些组件之间不通过直连通信,而是借助消息队列和元数据中心来解耦。

要让这一堆组件在Kubernetes里协同起来,有三个外部依赖必须先准备好。第一个是Etcd,用来保存Milvus元数据和分布式锁信息,所有Coordinator启动时都会去Etcd注册自己并获取配置。第二个是对象存储,MinIO是最常见的自建选择,主要存储向量数据文件、索引文件和binlog。第三个是消息队列,Milvus官方推荐Pulsar,它承担了数据流在各个节点间的转发。需要注意的是,虽然Milvus也支持Kafka,但生产环境为了减少兼容性问题,建议优先用Pulsar。如果把这三样东西单独部署在Kubernetes集群之外,可以降低K8s集群的存储压力,但为了资源隔离和统一管理,将它们作为Helm Chart的一部分一起部署也完全可行。

理解了组件依赖之后,部署顺序就清楚了:先保证存储和消息队列可用,再启动Coordinator,最后拉起Data Node和Query Node。任何一步顺序颠倒,都会导致Pod反复重启并报连接超时错误。例如在Etcd还没就绪时先启动Root Coordinator,日志里会不停出现类似 connection refused 的记录,新手很容易在这个阶段被绕晕。

二、Kubernetes集群前置准备

Kubernetes集群本身需要满足一些基本条件。节点数量建议至少三个工作节点,这样可以把Milvus的组件分散到不同物理节点上,避免某个节点宕机后全部服务一起挂掉。每个工作节点至少8核CPU和32GB内存,如果只是测试环境可以放宽到4核16GB,但Query Node会频繁因为内存不足被OOM Killer干掉。磁盘方面,如果使用本地存储,节点上需要提前创建好对应的挂载路径;如果使用云厂商的存储类,需要确认StorageClass支持动态扩容和ReadWriteOnce模式。

版本兼容性是需要特别注意的地方。Milvus官方Helm Chart对Kubernetes版本有明确要求,一般来说K8s 1.19以上都能跑,但太新的版本偶尔会出现API弃用导致的Chart安装失败。除了Kubernetes本身,Helm建议使用3.8以上版本,因为低版本Helm对某些CRD的处理不够稳定。此外,如果集群启用了Pod Security Admission或网络策略,需要给Milvus命名空间放行对应的端口,例如Etcd的2379端口、Pulsar的6650和8080端口、MinIO的9000端口。

一个经常被忽略的环节是存储类的性能。Milvus的向量索引文件读写对IOPS要求并不低,尤其是Data Node在写入binlog时会产生大量小文件。如果使用的是云盘普通存储,可能会出现写入延迟过高,导致数据插入速度比预期慢好几倍。建议至少使用SSD类型的存储类,并且将对象存储的bucket提前创建好。对于MinIO,如果跑在K8s内部,需要单独分配足够的PVC,避免与Milvus数据卷争抢磁盘空间。

# 创建一个独立的MinIO StorageClass示例
kubectl create namespace milvus
kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: minio-data
  namespace: milvus
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 200Gi
EOF

上面的命令假设集群里已经存在名为 fast-ssd 的StorageClass。如果使用云厂商托管Kubernetes,通常可以直接用默认的SSD存储类,但需要确认最小容量限制。另外,Milvus Helm Chart也会自动创建PVC,所以在自定义values.yaml时,可以为不同组件指定不同的storageClass,例如将Etcd和Pulsar放在高性能盘上,将日志数据放在普通盘上。

三、Helm部署与values.yaml配置详解

安装Milvus最简单的方式是使用官方Helm仓库。先添加仓库并更新索引,然后就可以通过一条命令完成整个集群的部署。不过默认的values.yaml只适合快速体验,生产环境一定要针对性地调整副本数和资源配额。下面是一份经过精简的配置示例,重点关注几个核心字段。

# values-prod.yaml 关键片段
cluster:
  enabled: true
etcd:
  replicaCount: 3
  persistence:
    size: 20Gi
    storageClass: fast-ssd
pulsar:
  enabled: true
  components:
    broker: 3
    bookkeeper: 3
    zookeeper: 3
  storage:
    size: 100Gi
    storageClass: fast-ssd
minio:
  mode: distributed
  replicas: 4
  persistence:
    size: 200Gi
    storageClass: fast-ssd
rootCoordinator:
  replicas: 2
  resources:
    requests:
      cpu: 2
      memory: 4Gi
    limits:
      cpu: 4
      memory: 8Gi
dataNode:
  replicas: 3
  resources:
    requests:
      cpu: 4
      memory: 8Gi
    limits:
      cpu: 8
      memory: 16Gi
queryNode:
  replicas: 3
  resources:
    requests:
      cpu: 4
      memory: 8Gi
    limits:
      cpu: 8
      memory: 16Gi
indexNode:
  replicas: 2
  resources:
    requests:
      cpu: 4
      memory: 8Gi
    limits:
      cpu: 8
      memory: 16Gi

上面配置中,cluster.enabled必须为true,否则即使部署了多个组件,Milvus仍会以单机模式运行。etcd.replicaCount设为3是为了保证元数据高可用,Etcd使用Raft协议,三个副本可以容忍一个节点故障。Pulsar的broker、bookkeeper和zookeeper也分别设为3副本,这是Pulsar集群的最低推荐配置。MinIO的distributed模式要求至少4个副本,否则MinIO会拒绝启动。

资源配额部分是很多人容易忽略的坑。如果只设置了requests而没有设置limits,Kubernetes默认不会对CPU做硬限制,但内存一旦超限会被容器运行时直接杀掉。Query Node在执行高并发搜索时内存峰值可能达到请求值的两倍以上,所以limits给到requests的两倍是比较稳妥的做法。此外,三个Data Node和三个Query Node的副本数并不是固定值,需要根据实际数据规模和查询并发量来调整。比如写入吞吐要求高,可以增加Data Node数量;查询延迟敏感,就多开几个Query Node。

执行安装命令时,建议使用独立的release名称和命名空间,避免与其他应用混淆。安装完成后不要急着插入数据,先通过kubectl get pods确认所有Pod都处于Running状态,并且READY列显示为1/1或预期副本数。如果某些Pod长时间处于Pending,大概率是资源不足或者存储类没有正确绑定。下面的命令可以快速查看Pod状态和事件。

helm repo add milvus https://milvus-io.github.io/milvus-helm/
helm repo update
helm install milvus-prod milvus/milvus \
  -n milvus \
  -f values-prod.yaml
kubectl get pods -n milvus -o wide
kubectl describe pod -n milvus milvus-prod-datanode-0

如果一切正常,你应该能看到类似 milvus-prod-rootcoord-0、milvus-prod-datanode-0以及milvus-prod-querynode-0这样的Pod名称。需要注意的是,Milvus的Helm Chart对某些组件使用了StatefulSet,所以Pod名称带有序号后缀,这是正常现象,不要误以为是多余的副本。

四、常见故障排查与性能调优

初次部署Milvus集群时,最容易遇到的是Pod反复重启问题。如果Root Coordinator日志里出现 connect to etcd timeout,首先要确认Etcd服务是否真的可用。可以用下面的命令进到Etcd Pod里做个健康检查。如果Etcd本身正常,再检查Root Coordinator的环境变量是否正确指向了Etcd地址。在Milvus Helm Chart里,这些地址通常通过ConfigMap注入,手动修改很容易写错。

kubectl exec -it -n milvus milvus-prod-etcd-0 -- sh
# 在容器内执行
etcdctl endpoint health

另一个高频问题是Query Node加载Collection时报内存不足。即使你已经在values.yaml里设置了内存limits,Milvus内部还会对查询向量分配额外的临时缓冲区。如果Collection的segment数量过多或者索引类型选择不当,Query Node的内存消耗会急剧上升。解决办法可以从两方面入手:一是调整Segment大小和索引参数,减少单个Query Node需要加载的数据量;二是增加Query Node副本数,但在数据分片不均衡的情况下单纯加副本效果有限。更推荐的做法是启用Milvus的负载均衡功能,让Coordinator自动把Query Node上的segment分布得更均匀。

数据持久化失效是另一个让很多人头疼的问题。典型表现是Pod重建后,之前写入的数据不见了。这通常是因为MinIO的PVC没有正确绑定,或者MinIO使用了emptyDir作为存储。检查MinIO的StatefulSet定义,确认volumeClaimTemplates中storageClassName与集群实际可用的StorageClass一致。如果MinIO跑在Kubernetes外部,还要确认Milvus的配置中minio.address和minio.bucketName是否填写正确。建议在首次写入数据后执行一次节点驱逐测试,主动删除某个Data Node Pod,观察数据是否能够自动恢复。

# 主动删除一个Data Node Pod,验证数据可靠性
kubectl delete pod -n milvus milvus-prod-datanode-1
# 观察新Pod拉起后,数据是否完整
kubectl logs -n milvus milvus-prod-datanode-1 | tail -n 50

性能调优方面,除了前面提到的副本数和资源配额,还有一个关键参数是消息队列的分区数。Milvus Helm Chart默认给每个物理topic创建的分区数较少,当写入并发很高时,Pulsar的吞吐会先成为瓶颈。你可以在values.yaml中通过extraConfigMap自定义Milvus配置,将pulsar.maxMessageSize调大,并适当增加消息分区。不过这个操作需要在部署前规划,因为修改后需要重启相关组件才能生效。对于查询性能,选择合适的索引类型比盲目增加Query Node更有效,例如百万级数据用IVF_FLAT即可,千万级以上建议用HNSW,而且HNSW的M和efConstruction参数需要根据内存预算仔细调整。

整个Kubernetes环境下的Milvus分布式部署,本质上是在做组件拆分与编排的平衡。架构拆得太细会带来运维复杂度,拆得不够又无法享受水平扩容的好处。好在官方Helm Chart已经把大部分繁琐的配置封装好了,你只需要在前置依赖、资源配额和存储上多下点功夫。按照本文给出的路径,先确保Etcd、MinIO、Pulsar健康,再调整副本数和limits,最后通过主动故障演练验证数据可靠性,基本可以避开一大半生产事故。如果后续需要接入监控,推荐直接使用Prometheus Operator采集Milvus暴露的metrics端口,配合Grafana模板可以直观地看到各节点写入和查询的延迟分布。

广告位 · 文章底部(后台-模块管理替换此区域)