This guide explains how to expose an etcd cluster's client endpoint externally using a cloud provider LoadBalancer, and how to disable it when no longer needed.
Before proceeding, verify your environment meets these requirements:
kubectl v1.21+ installed and configured with cluster accessKubeBlocks creates a single headless service for each etcd cluster:
kubectl get service -n demo | grep etcd-cluster
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
etcd-cluster-etcd-headless ClusterIP None <none> 2379/TCP,2380/TCP 5m
| Port | Name | Description |
|---|---|---|
| 2379 | client | Client connections |
| 2380 | peer | Raft peer communication |
The headless service provides DNS-based discovery: each member is reachable at <pod-name>.etcd-cluster-etcd-headless.<namespace>.svc.cluster.local:2379.
For in-cluster access, this headless service is sufficient. External exposure is only needed when clients outside the Kubernetes cluster need to connect.
This creates a single LoadBalancer that routes to all etcd members for client connections:
apiVersion: apps.kubeblocks.io/v1
kind: Cluster
metadata:
name: etcd-cluster-lb
namespace: demo
spec:
terminationPolicy: Delete
# Cluster-level service for client access
services:
- name: client
serviceName: client
spec:
type: LoadBalancer
ports:
- port: 2379
targetPort: 2379
componentSelector: etcd
componentSpecs:
- name: etcd
componentDef: etcd
serviceVersion: 3.6.1
replicas: 3
resources:
limits:
cpu: "0.5"
memory: "0.5Gi"
requests:
cpu: "0.5"
memory: "0.5Gi"
volumeClaimTemplates:
- name: data
spec:
storageClassName: ""
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
Apply it:
kubectl apply -f https://raw.githubusercontent.com/apecloud/kubeblocks-addons/refs/heads/main/examples/etcd/cluster-with-lb.yaml
For cross-region or multi-cluster deployments where etcd peers need external connectivity, use per-pod LoadBalancer services:
componentSpecs:
- name: etcd
services:
- name: peer
serviceType: LoadBalancer
podService: true # creates one LoadBalancer per pod
This is included in cluster-with-lb.yaml. Each pod gets its own external IP for peer communication.
Cloud Provider Annotations
Add cloud-specific annotations to the services entry:
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
service.beta.kubernetes.io/aws-load-balancer-internal: "false"
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "false"
annotations:
cloud.google.com/l4-rbs: "enabled"
annotations:
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type: "internet"
kubectl get service -n demo -l app.kubernetes.io/instance=etcd-cluster-lb
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
etcd-cluster-lb-etcd-client LoadBalancer 172.20.95.145 a3f9688f...elb.ap-southeast-1.amazonaws.com 2379:30411/TCP 68s
etcd-cluster-lb-etcd-headless ClusterIP None <none> 2379/TCP,2380/TCP 68s
The LoadBalancer DNS name may take 2–5 minutes to become resolvable after provisioning.
Once the DNS resolves, connect using etcdctl:
etcdctl endpoint health --endpoints=<EXTERNAL-IP>:2379
Expected response: <EXTERNAL-IP>:2379 is healthy
Or test with netcat:
echo "" | nc <EXTERNAL-IP> 2379
kubectl delete cluster etcd-cluster-lb -n demo
kubectl delete ns demo