New to KubeDB? Please start here.
KubeDB - Milvus Distributed Cluster
This tutorial will show you how to use KubeDB to provision a Distributed Milvus cluster, where the Milvus roles run as separate, independently scalable workloads.
Before You Begin
You need a Kubernetes cluster with the KubeDB operator installed (
--set global.featureGates.Milvus=true).Milvus external dependencies must be available:
- Object storage (
my-release-miniosecret) — mandatory. - An etcd operator — KubeDB provisions an internal etcd cluster for metadata when
spec.metaStorageis omitted.
- Object storage (
Create the
demonamespace:$ kubectl create ns demo namespace/demo created
Note: The yaml files used in this tutorial are stored in docs/guides/milvus/quickstart/yamls folder in GitHub repository kubedb/docs.
Distributed Topology
A distributed Milvus is composed of five roles:
| Role | Purpose | Persistent storage |
|---|---|---|
mixcoord | Coordinator (root/data/query/index coordination) | No |
proxy | Client-facing gateway (gRPC 19530) | No |
datanode | Data persistence | No |
querynode | Query execution | No (scratch only) |
streamingnode | Streaming / WAL | Yes |
Only streamingnode carries a persistent volume. This is why distributed storage operations (volume expansion, storage migration, storage autoscaling) target streamingnode.
Create a Distributed Milvus
In the manifest below, only streamingnode is specified under spec.topology.distributed. The operator defaults the other four roles (mixcoord, datanode, querynode, proxy) automatically.
distributed.yaml
apiVersion: kubedb.com/v1alpha2
kind: Milvus
metadata:
name: milvus-cluster
namespace: demo
spec:
version: "2.6.11"
objectStorage:
configSecret:
name: "my-release-minio"
topology:
mode: Distributed
distributed:
streamingnode:
storageType: Durable
storage:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
monitor:
agent: prometheus.io/operator
prometheus:
serviceMonitor:
labels:
release: prometheus
interval: 10s
tls:
issuerRef:
name: milvus-issuer
kind: Issuer
apiGroup: "cert-manager.io"
external:
mode: mTLS
internal:
mode: TLS
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.6.19/docs/guides/milvus/quickstart/yamls/distributed.yaml
milvus.kubedb.com/milvus-cluster created
Wait for the Cluster to be Ready
Distributed Milvus takes longer than standalone — allow time for all components and the internal etcd to settle.
$ kubectl get milvuses.kubedb.com -n demo milvus-cluster -w
NAME VERSION STATUS AGE
milvus-cluster 2.6.11 Provisioning 20s
milvus-cluster 2.6.11 Ready 3m
Verify the Created Resources
PetSets — one per role
$ kubectl get petset -n demo -l app.kubernetes.io/instance=milvus-cluster
NAME AGE
milvus-cluster-datanode 2m57s
milvus-cluster-mixcoord 2m58s
milvus-cluster-proxy 2m54s
milvus-cluster-querynode 2m56s
milvus-cluster-streamingnode 2m55s
The four roles other than streamingnode were created even though only streamingnode was specified — they are the defaulted distributed components.
$ kubectl get pods -n demo -l app.kubernetes.io/instance=milvus-cluster
NAME READY STATUS RESTARTS AGE
milvus-cluster-datanode-0 1/1 Running 0 2m58s
milvus-cluster-mixcoord-0 1/1 Running 0 2m59s
milvus-cluster-proxy-0 1/1 Running 0 2m55s
milvus-cluster-querynode-0 1/1 Running 0 2m57s
milvus-cluster-streamingnode-0 1/1 Running 0 2m55s
Services
A primary client service (milvus-cluster, gRPC 19530, backed by the proxy), a metrics stats service (milvus-cluster-stats), and a headless governing service per role (9091) are created:
$ kubectl get svc -n demo -l app.kubernetes.io/instance=milvus-cluster
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
milvus-cluster ClusterIP 10.43.221.1 <none> 19530/TCP 3m
milvus-cluster-datanode ClusterIP None <none> 9091/TCP 3m
milvus-cluster-mixcoord ClusterIP None <none> 9091/TCP 3m
milvus-cluster-querynode ClusterIP None <none> 9091/TCP 3m
milvus-cluster-stats ClusterIP 10.43.95.57 <none> 9091/TCP 3m
milvus-cluster-streamingnode ClusterIP None <none> 9091/TCP 3m
Storage — only on streamingnode
There is exactly one Milvus PVC, for the streamingnode:
$ kubectl get pvc -n demo -l app.kubernetes.io/instance=milvus-cluster
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-milvus-cluster-streamingnode-0 Bound pvc-... 1Gi RWO local-path 2m55s
Auth, TLS, and AppBinding
As with standalone, KubeDB auto-generates the auth secret (milvus-cluster-auth, user root), the TLS certificate secrets, the rendered configuration secret, and an AppBinding. Because TLS is enabled, the AppBinding scheme is https:
$ kubectl get secret -n demo | grep milvus-cluster
milvus-cluster-auth kubernetes.io/basic-auth 2 3m
milvus-cluster-client-cert kubernetes.io/tls 4 3m
milvus-cluster-d7497a Opaque 2 3m
milvus-cluster-server-cert kubernetes.io/tls 3 3m
$ kubectl get appbinding milvus-cluster -n demo -o jsonpath='{.spec.clientConfig.service}'
{"name":"milvus-cluster","path":"/","port":19530,"scheme":"https"}
These base manifests already include Prometheus Operator monitoring and TLS — see the monitoring and TLS guides.
Cleaning up
$ kubectl patch -n demo milvus.kubedb.com milvus-cluster -p '{"spec":{"deletionPolicy":"WipeOut"}}' --type="merge"
$ kubectl delete milvus.kubedb.com milvus-cluster -n demo
$ kubectl delete ns demo
Next Steps
- Monitor your Milvus cluster.
- Horizontally scale the distributed roles.
- Detail concepts of Milvus object.
- Want to hack on KubeDB? Check our contribution guidelines.































