etcd 3
Overview
etcd is a CNCF graduated, strongly consistent, distributed key-value store. It backs Kubernetes, OpenShift, CoreOS Container Linux, M3DB, Patroni and dozens of other distributed systems. It uses the Raft consensus algorithm to replicate state across a cluster of nodes with guaranteed linearizable read/write ordering. It exposes a gRPC + REST API on port 2379 and ships a feature-complete CLI (etcdctl) that talks the same protocol.
The SecureAnyCloud image installs etcd 3.6.11 from the official GitHub release and runs it as the dedicated etcd system user. Data persists to /var/lib/etcd, and Role-Based Access Control (RBAC) is enabled at first boot, so the API is never reachable without credentials. The peer port (2380) stays bound to loopback because the image is single-node by design. When you scale to a 3 or 5 node Raft cluster, you re-bind the peer URL onto your private VNet.
What you get:
- etcd 3.6.11 (Apache 2.0) from the official GitHub release tarball, installed at
/usr/local/bin/etcd etcdctl3.6.11 CLI bundled at/usr/local/bin/etcdctl- Dedicated
etcdsystem user, data directory at/var/lib/etcd(root:etcd 0700) - Configuration file at
/etc/etcd/etcd.conf - Listen client URL
http://0.0.0.0:2379. Clients reach the gRPC + REST API over the network, and your NSG controls inbound access. - Listen peer URL
http://127.0.0.1:2380. It is loopback only and single-node by design (rebind for HA). - RBAC enabled at first boot, so every API call requires a valid user and password
- Per-VM SecureAnyCloud user with the readwrite role (full read/write on all keys), per-VM root user with the admin role
- etcdkeeper 0.7.8 web UI at
/opt/etcdkeeper, listening on127.0.0.1:8080, fronted by nginx on:80with HTTP Basic-Auth. It gives you a browser-based tree view, key create, edit and delete, and role and user inspection. - nginx 1.24 reverse proxy with htpasswd at /etc/nginx/etcdkeeper.htpasswd (SecureAnyCloud user, password = ETCD_PASSWORD)
etcd.service(User=etcd), the main etcd daemonetcdkeeper.service, which runs etcdkeeper with-authso it relays your credentials through to etcd RBAC at the API leveletcd-firstboot.service(Before=etcd) detects the IP through IMDS, writes/etc/etcd/etcd.confand touches/var/lib/sac/data/etcd-firstboot.done- etcd-postboot.service (After=etcd) runs etcdctl user add SecureAnyCloud and auth enable, generates htpasswd for nginx, touches /var/lib/sac/data/etcd-postboot.done, then disables itself
- Per-VM credentials log at
/stage/scripts/etcd-credentials.log(root:root 0600) carryingETCD_VERSION,ETCD_CLIENT_URL,ETCD_HEALTH_URL,ETCD_USER=sac,ETCD_PASSWORD,ETCD_ROOT_PASSWORD - 24/7 SecureAnyCloud support
Use the bundled etcdkeeper UI to browse and edit keys interactively. For scripted access and CI, use etcdctl from any host inside the VNet.
Prerequisites
You need an active Azure subscription, an SSH key, and a VNet with a subnet. Standard_B2s (4 GB RAM) is comfortable for a single-node etcd. Add these NSG inbound rules:
22/tcpfrom your management CIDR, for SSH80/tcpfrom any client CIDR that needs the etcdkeeper web UI. nginx fronts the UI with HTTP Basic-Auth, so the UI is never exposed without credentials.2379/tcpfrom any client CIDR that needs to reach the etcd API directly (Kubernetes, etcdctl, Patroni or custom apps)
Never expose 2380/tcp. It is the Raft peer port, and on this single-node image it stays bound to loopback only. The web UI itself listens on 127.0.0.1:8080 and is not directly reachable from outside. nginx is the only public entry point.
Step 1-3: Deploy + SSH (standard pattern)
ssh azureuser@<vm-ip>Service Status + Versions
sudo systemctl is-active etcd nginx etcdkeeper
sudo test -f /var/lib/sac/data/etcd-firstboot.done && echo "firstboot: complete" || echo "firstboot: missing"
sudo test -f /var/lib/sac/data/etcd-postboot.done && echo "postboot: complete" || echo "postboot: missing" /usr/local/bin/etcd --version | head -1 /usr/local/bin/etcdctl versionetcd, nginx, and etcdkeeper all report active. The two boot oneshots write a sentinel file when they finish, then exit cleanly. etcd-firstboot.done and etcd-postboot.done confirm that per-VM credential generation, RBAC bootstrap and htpasswd generation completed successfully.
The etcd binary reports etcd Version: 3.6.11 and the CLI reports etcdctl version: 3.6.11 with API version: 3.6.
Read Per-VM Credentials
sudo cat /stage/scripts/etcd-credentials.logRead two values from the file, which is root:root 0600:
- ETCD_PASSWORD is the SecureAnyCloud user's password. It is used for the etcdctl CLI, the nginx HTTP Basic-Auth prompt and the etcdkeeper login form.
- ETCD_ROOT_PASSWORD is the root user's password. You need it for snapshot save, auth status, user and role management, and the etcdkeeper Authentication dialog when you want full tree visibility.
ETCD_CLIENT_URL is the canonical client endpoint: http://127.0.0.1:2379 from the VM, or http://<vm-ip>:2379 from another VM in the VNet. ETCD_HEALTH_URL is http://127.0.0.1:2379/health.
API Health Check
curl -s http://127.0.0.1:2379/health | jqReturns {"health":"true","reason":""} once etcd is fully ready to serve traffic. The /health endpoint is unauthenticated by design. Your load balancer and Kubernetes liveness probe call it. Every other endpoint requires credentials.
Put + Get a Key from the CLI
PASS=$(sudo grep '^ETCD_PASSWORD=' /stage/scripts/etcd-credentials.log | cut -d= -f2-) etcdctl --user=sac:$PASS put /sac/example "hello world" etcdctl --user=sac:$PASS get /sac/example etcdctl --user=sac:$PASS put /test should-fail 2>&1 | tail -1 || trueThe first put returns OK. The get prints the key, then the value on the next line: /sac/example then hello world.
Keys in etcd are arbitrary byte strings, so /-prefixed paths are convention only. Values can be up to 1.5 MB each (the --max-request-bytes cap).
The third command would normally succeed. If you drop the --user= flag, etcd returns Error: etcdserver: user name is empty. This shows that RBAC is enforced and that credentials are the only thing standing between a client and the keyspace.
Login to the etcdkeeper Web UI
Browse to http://<vm-ip>/ from a workstation inside the allowed CIDR. nginx prompts with an HTTP Basic-Auth dialog. Enter SecureAnyCloud and the value of ETCD_PASSWORD from Step 5.
nginx accepts the credentials, and the etcdkeeper landing page loads with its own Authentication dialog on top. This application-level login proves you have access to the etcd RBAC API. Enter root and the value of ETCD_ROOT_PASSWORD from Step 5, then click Submit. The login flow is layered on purpose. See the Security section below.
Browse the Key Tree
After the Authentication dialog accepts your login, etcdkeeper renders the key tree in the left pane. The two keys written in Step 7, /sac/example and /sac/from-ui (added in the next step), appear under the root folder.
Click any key to load its value into the center editor. Click the disk icon at the top right to save edits back through the etcd API. The footer reports the etcd version (3.6.11), the live data-store size and the cluster member name.
Add a Key from the UI
Right-click the root folder in the tree pane and pick Create Node. The Create node dialog opens. Then:
- Set
Nameto the key path (for example/sac/from-ui). - Leave
DirasFALSE. A directory node has no value, only children. - Leave
TTLblank for a permanent key, or enter seconds for an ephemeral lease-style key. - Put the value in the
Valueeditor.
Click Submit. The key is written to etcd in a single atomic API call, and the new entry shows up in the tree pane immediately.
Watch a Key
In one SSH session, start a watch on the /sac/ key prefix.
PASS=$(sudo grep '^ETCD_PASSWORD=' /stage/scripts/etcd-credentials.log | cut -d= -f2-) etcdctl --user=sac:$PASS watch --prefix /sac/ & WATCH_PID=$! sleep 1 etcdctl --user=sac:$PASS put /sac/key1 v1 sleep 1 kill $WATCH_PID 2>/dev/null || trueThe watcher prints the change event in real time: PUT, key and value, all delivered over a single long-lived gRPC stream. Kubernetes uses the same primitive to react to resource changes. Watches survive leader elections and resume from a known revision.
Transactions and Leases
A lease is a TTL attached to one or more keys. When the lease expires, the keys are deleted automatically. This is how etcd implements ephemeral state such as service registration and distributed locks.
PASS=$(sudo grep '^ETCD_PASSWORD=' /stage/scripts/etcd-credentials.log | cut -d= -f2-)
LEASE_ID=$(etcdctl --user=sac:$PASS lease grant 60 | awk '{print $2}') etcdctl --user=sac:$PASS put /sac/ephemeral "expires in 60s" --lease=$LEASE_ID etcdctl --user=sac:$PASS lease listlease grant 60 returns a 60-second lease. The put --lease= command ties the key to that lease.
To keep the key alive, run etcdctl lease keep-alive <lease-id> in a long-running process. If you stop refreshing the lease, or call etcdctl lease revoke <lease-id>, the key disappears at the next TTL boundary.
Transactions (etcdctl txn) compose conditional reads and writes. They are the building block for leader election and distributed locks.
Snapshot Backup and Restore
A snapshot is etcd's first-class backup primitive. It is a consistent point-in-time copy of the entire keyspace.
ROOT_PASS=$(sudo grep '^ETCD_ROOT_PASSWORD=' /stage/scripts/etcd-credentials.log | cut -d= -f2-) sudo mkdir -p /var/backups/etcd
sudo etcdctl --user=root:$ROOT_PASS snapshot save /var/backups/etcd/etcd-snap-$(date +%F).db
sudo etcdctl --user=root:$ROOT_PASS --write-out=table snapshot status /var/backups/etcd/etcd-snap-$(date +%F).dbsnapshot save requires the root user (the admin role). snapshot status prints the hash, revision, total keys and total size of the snapshot. Verify that the revision moves forward with each backup.
Periodically copy /var/backups/etcd off the VM to Azure Blob Storage with az storage blob upload-batch for disaster recovery retention.
To restore, stop etcd and run etcdctl snapshot restore <backup-dir>/etcd-snap-DATE.db --data-dir=/var/lib/etcd-restored. Swap /var/lib/etcd with the restored directory, run chown -R etcd:etcd /var/lib/etcd, and start etcd again.
For a multi-node cluster, restore is more involved. Each member restores from the same snapshot with cluster-specific flags. See https://etcd.io/docs/v3.5/op-guide/recovery/.
Add a User and Role
Add a read-only role for monitoring or for a Kubernetes-style service account:
ROOT_PASS=$(sudo grep '^ETCD_ROOT_PASSWORD=' /stage/scripts/etcd-credentials.log | cut -d= -f2-) etcdctl --user=root:$ROOT_PASS role add ro etcdctl --user=root:$ROOT_PASS role grant-permission ro --prefix=true read / etcdctl --user=root:$ROOT_PASS user listuser list prints SecureAnyCloud and root, the two users provisioned at first boot. To add a new monitoring user, run etcdctl --user=root:$ROOT_PASS user add monitoring --new-user-password=<new-password>, followed by user grant-role monitoring ro.
The new user can read every key (--prefix=true read /) but cannot write. You can scope permissions by exact key, key range (--from-key) or prefix.
Multi-Node Cluster Setup
This image runs a single-node etcd by design. That suits development, CI, edge deployments and any workload that does not need HA.
For production HA, use a 3 or 5 node Raft cluster. An odd number of nodes tolerates (N-1)/2 failures: 3 nodes tolerate 1 failure, and 5 tolerate 2. To stand up the cluster, deploy three of these images. Then edit /etc/etcd/etcd.conf on each node to set:
ETCD_INITIAL_CLUSTERto the comma-separated list ofname=http://<peer-ip>:2380triplesETCD_LISTEN_PEER_URLStohttp://<this-node-private-ip>:2380(no longer loopback)ETCD_INITIAL_ADVERTISE_PEER_URLSto the same routable URLETCD_INITIAL_CLUSTER_STATEtonewon first boot,existingthereafter
Restart etcd on all three nodes within a few seconds of each other. Open 2380/tcp between the nodes in your NSG for peer-to-peer Raft traffic, and keep it closed to everyone else.
The etcdkeeper UI on each node shows that node's view of the keyspace. Once Raft converges, all three views are identical. Full reference: https://etcd.io/docs/v3.5/op-guide/clustering/.
Logs and Troubleshooting
ROOT_PASS=$(sudo grep '^ETCD_ROOT_PASSWORD=' /stage/scripts/etcd-credentials.log | cut -d= -f2-) sudo etcdctl --user=root:$ROOT_PASS --write-out=table endpoint status
sudo etcdctl --user=root:$ROOT_PASS alarm listendpoint status prints the version, DB size, leader and Raft index in a table. It is the first command to run when triaging. alarm list reports any active NOSPACE or CORRUPT alarms. A NOSPACE alarm pins the cluster read-only, and it clears with alarm disarm once you have compacted and defragmented.
Use these commands to read the logs:
sudo journalctl -u etcd -n 80 --no-pagerfor the long-running etcd daemon logsudo journalctl -u nginx -n 80 --no-pagerfor the reverse proxy (HTTP 401s for missing Basic-Auth show up here)sudo journalctl -u etcdkeeper -n 80 --no-pagerfor the web UI processsudo journalctl -u etcd-firstboot --no-pagerandsudo journalctl -u etcd-postboot --no-pagerfor the one-shot bootstrap units that ran at first boot
Security
- Layered authentication for the web UI. There are three independent gates, each with its own credential. You type the SecureAnyCloud user and ETCD_PASSWORD into the browser's HTTP Basic-Auth prompt (nginx, the edge gate). You then enter the same credentials in the etcdkeeper Authentication dialog, or root and ETCD_ROOT_PASSWORD for full visibility (the application gate). Finally, etcd RBAC enforces per-key permissions on every API call that etcdkeeper relays (the data gate). A leaked nginx password alone exposes only the HTML chrome. The keyspace stays locked behind etcd RBAC.
- RBAC enabled by default. Every API call requires a valid user and password, and the default
guest(anonymous) user is not created. - Peer port
2380/tcpbound to127.0.0.1only. The image is single-node by design, and the port is never exposed. - etcdkeeper bound to
127.0.0.1:8080only. The only public entry to the UI is nginx on:80. - Client port
2379/tcpcontrolled by NSG inbound rules. Restrict it to the CIDRs that need to call the API directly. - Per-VM ETCD_PASSWORD (SecureAnyCloud user, readwrite role) and ETCD_ROOT_PASSWORD (admin) generated at first boot.
- Credentials log
/stage/scripts/etcd-credentials.logisroot:root 0600; nginxhtpasswdat/etc/nginx/etcdkeeper.htpasswdisroot:www-data 0640. - TLS on the client port and on nginx is supported. Uncomment the TLS block in
/etc/etcd/etcd.conffor the API, and add a certificate to/etc/nginx/sites-available/etcdkeeper.conffor HTTPS in front of the UI. Supply your own certificates from Azure Key Vault, your own CA or Let's Encrypt. - Use a dedicated read-only role (Step 14) for any monitoring or audit consumer. Never hand out
rootcredentials to applications.
Support
SecureAnyCloud provides 24/7/365 expert technical support, with a guaranteed response within 24 hours and a one-hour average for critical issues. Contact sales@secureanycloud.com.
Deploy on Azure
Launch etcd 3.6.11 Distributed Key-Value Store on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com