The most surprising thing about bootstrapping an etcd cluster statically is that it’s actually less complex than you’d think, even without a discovery service.
Let’s say you’re setting up a new Kubernetes cluster, or maybe just need a reliable distributed key-value store for something else. You’ve got three nodes, etcd-0, etcd-1, and etcd-2, all with IP addresses 192.168.1.10, 192.168.1.11, and 192.168.1.12 respectively. We want each etcd node to know about the others from the moment it starts, without relying on a separate service to tell them who’s who.
Here’s how etcd-0 would be configured to start. We’ll use the etcd binary directly, but the principles apply to systemd units or containerized deployments as well.
ETCDCTL_API=3 etcd \
--name etcd-0 \
--listen-peer-urls http://192.168.1.10:2380 \
--listen-client-urls http://192.168.1.10:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://192.168.1.10:2379 \
--initial-advertise-peer-urls http://192.168.1.10:2380 \
--initial-cluster etcd-0=http://192.168.1.10:2380,etcd-1=http://192.168.1.11:2380,etcd-2=http://192.168.1.12:2380 \
--initial-cluster-token my-etcd-cluster-1 \
--initial-cluster-state new \
--data-dir /var/lib/etcd
Let’s break down what’s happening here:
--name etcd-0: This is the unique identifier for this specific etcd member within the cluster. It must match the name used in--initial-cluster.--listen-peer-urls http://192.168.1.10:2380: This is the address where etcd will listen for connections from other etcd members for replication and cluster coordination. Port2380is standard for peer communication.--listen-client-urls http://192.168.1.10:2379,http://127.0.0.1:2379: These are the addresses where etcd will listen for requests from clients (likekubectlor your application). Port2379is standard for client communication. Including127.0.0.1allows local access.--advertise-client-urls http://192.168.1.10:2379: This is the URL that other members and clients will use to reach this etcd instance for client communication. It’s crucial that this is an IP address or hostname reachable by everyone else.--initial-advertise-peer-urls http://192.168.1.10:2380: Similar toadvertise-client-urls, but for peer-to-peer communication. This tells other etcd nodes how to connect back to this one for cluster operations.--initial-cluster etcd-0=http://192.168.1.10:2380,etcd-1=http://192.168.1.11:2380,etcd-2=http://192.168.1.12:2380: This is the heart of static bootstrapping. You explicitly list all members of the cluster and their respective peer URLs. When a new member starts withinitial-cluster-state=new, it uses this list to find and connect to its peers.--initial-cluster-token my-etcd-cluster-1: This is a unique token for this cluster. It prevents members from accidentally joining a different etcd cluster if they happen to have overlapping--initial-clusterconfigurations.--initial-cluster-state new: This tells etcd that it’s starting as a brand new cluster. If you were adding a new member to an existing cluster, you’d useexisting.--data-dir /var/lib/etcd: This is where etcd stores its state.
You would repeat this configuration on etcd-1 and etcd-2, changing --name and the IP addresses in listen-peer-urls, listen-client-urls, and advertise-client-urls accordingly. The --initial-cluster list remains identical across all members.
Once all three instances are running, you can verify the cluster status. On any of the nodes, you can run:
ETCDCTL_API=3 etcdctl --endpoints=http://192.168.1.10:2379,http://192.168.1.11:2379,http://192.168.1.12:2379 member list
This should output something like:
1b8b9c9b76c4b199, started, etcd-0, http://192.168.1.10:2380, http://192.168.1.10:2379
3c9b76c4b1991b8b, started, etcd-1, http://192.168.1.11:2380, http://192.168.1.11:2379
5b1991b8b3c9b76c, started, etcd-2, http://192.168.1.12:2380, http://192.168.1.12:2379
The key here is that the --initial-cluster parameter bootstraps the cluster by providing a fixed, known set of members and their communication endpoints. When a node starts with initial-cluster-state=new, it uses this list to discover its peers and establish communication. After the cluster has formed and the initial membership is established, etcd internally manages membership changes and discovery. The static configuration is only used for the very first boot of a new cluster.
The "discovery" part of static bootstrapping is that the discovery mechanism is simply the static list of peers provided at startup. There’s no external service; the cluster discovers itself based on the configuration you’ve given it.
The trickiest part for many is understanding how to add or remove members after the cluster is running. This is done using etcdctl member add and etcdctl member remove commands, which then require updating the configuration of the remaining nodes to reflect the new cluster membership if you’re using static configuration for subsequent restarts. However, this is beyond the scope of initial bootstrapping.
If you were to try and add a new node to this cluster statically without using etcdctl member add and then updating all other configurations, you would likely run into a situation where the new node cannot join, or worse, causes instability if it tries to form a new cluster with a subset of the existing members.