etcd es un almacén clave-valor distribuido y con consistencia fuerte que usa el algoritmo de consenso Raft. Es la base de datos de Kubernetes y de muchos otros sistemas que necesitan guardar configuración o coordinar procesos. En este tutorial desplegarás un clúster etcd de tres nodos en Ubuntu 24.04 con TLS mutuo entre nodos y clientes, lo gestionarás con etcdctl y dejarás programadas copias de seguridad diarias con un temporizador de systemd.

Requisitos previos

Para seguir esta guía necesitas:

  • Tres servidores con Ubuntu 24.04 LTS (x86_64), por ejemplo tres VPS de CubePath, con al menos 2 GB de RAM y disco SSD o NVMe. etcd es muy sensible a la latencia de escritura en disco.
  • Un usuario no root con privilegios sudo en cada servidor.
  • Una red privada entre los tres nodos con baja latencia. En los ejemplos se usan estas direcciones, sustitúyelas por las tuyas:
NombreIP privada
etcd-0110.0.1.10
etcd-0210.0.1.11
etcd-0310.0.1.12
  • Acceso SSH desde etcd-01 a los otros dos nodos para copiar certificados con scp.

Un clúster de tres nodos tolera la caída de uno. Usa siempre un número impar de miembros (3 o 5).

Paso 1: Instalar los binarios de etcd

Ubuntu incluye un paquete etcd-server, pero suele ir varias versiones por detrás. Usarás los binarios oficiales publicados en GitHub. Consulta la última versión estable en la página de releases de etcd y ajusta la variable. Ejecuta este paso en los tres nodos:

ETCD_VER=v3.6.4
cd /tmp
curl -fsSLO "https://github.com/etcd-io/etcd/releases/download/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz"
curl -fsSLO "https://github.com/etcd-io/etcd/releases/download/${ETCD_VER}/SHA256SUMS"
sha256sum --check --ignore-missing SHA256SUMS
etcd-v3.6.4-linux-amd64.tar.gz: OK

Extrae el archivo e instala los tres binarios (etcd, el cliente etcdctl y la utilidad offline etcdutl):

tar xzf "etcd-${ETCD_VER}-linux-amd64.tar.gz"
sudo install -m 0755 "etcd-${ETCD_VER}-linux-amd64/etcd" "etcd-${ETCD_VER}-linux-amd64/etcdctl" "etcd-${ETCD_VER}-linux-amd64/etcdutl" /usr/local/bin/
etcd --version
etcd Version: 3.6.4
Git SHA: ...
Go Version: ...
Go OS/Arch: linux/amd64

Crea el usuario de sistema y los directorios de datos y configuración:

sudo useradd --system --home-dir /var/lib/etcd --shell /usr/sbin/nologin etcd
sudo install -d -o etcd -g etcd -m 0700 /var/lib/etcd
sudo install -d -o etcd -g etcd -m 0750 /etc/etcd/pki

Paso 2: Crear la CA y los certificados TLS

Con TLS mutuo, cada nodo y cada cliente se identifica con un certificado firmado por una CA propia del clúster. Genera todo en etcd-01, en un directorio de trabajo:

mkdir -p ~/etcd-pki
cd ~/etcd-pki
openssl genrsa -out ca.key 4096
openssl req -x509 -new -key ca.key -sha256 -days 3650 -subj "/CN=etcd-ca" -out ca.crt

Genera un certificado por nodo. Cada certificado incluye la IP privada del nodo, su nombre y 127.0.0.1 en el campo subjectAltName, y sirve tanto para escuchar como servidor como para conectarse a los otros nodos:

while read -r name ip; do
  openssl genrsa -out "${name}.key" 2048
  openssl req -new -key "${name}.key" -subj "/CN=${name}" -out "${name}.csr"
  printf 'subjectAltName=DNS:%s,IP:%s,IP:127.0.0.1\nextendedKeyUsage=serverAuth,clientAuth\n' "$name" "$ip" > "${name}.ext"
  openssl x509 -req -in "${name}.csr" -CA ca.crt -CAkey ca.key -CAcreateserial \
    -days 825 -sha256 -extfile "${name}.ext" -out "${name}.crt"
done <<EOF
etcd-01 10.0.1.10
etcd-02 10.0.1.11
etcd-03 10.0.1.12
EOF

Genera también un certificado de cliente para administrar el clúster con etcdctl:

openssl genrsa -out admin.key 2048
openssl req -new -key admin.key -subj "/CN=admin" -out admin.csr
printf 'extendedKeyUsage=clientAuth\n' > admin.ext
openssl x509 -req -in admin.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 825 -sha256 -extfile admin.ext -out admin.crt

Comprueba que el certificado de un nodo contiene las direcciones correctas:

openssl x509 -in etcd-02.crt -noout -ext subjectAltName
X509v3 Subject Alternative Name:
    DNS:etcd-02, IP Address:10.0.1.11, IP Address:127.0.0.1

Copia a cada uno de los otros nodos la CA, su certificado y el certificado de administración. No copies ca.key: guárdala fuera del servidor en un lugar seguro, ya que permite emitir certificados válidos para el clúster.

scp ca.crt etcd-02.crt etcd-02.key admin.crt admin.key [email protected]:~/
scp ca.crt etcd-03.crt etcd-03.key admin.crt admin.key [email protected]:~/

En cada nodo, instala los archivos en /etc/etcd/pki con nombres uniformes. Sustituye etcd-01 por el nombre del nodo en el que estés (en etcd-01 ejecútalo desde ~/etcd-pki, en los demás desde tu directorio personal):

sudo install -o etcd -g etcd -m 0644 ca.crt /etc/etcd/pki/ca.crt
sudo install -o etcd -g etcd -m 0644 etcd-01.crt /etc/etcd/pki/server.crt
sudo install -o etcd -g etcd -m 0600 etcd-01.key /etc/etcd/pki/server.key
sudo install -o root -g root -m 0600 admin.crt admin.key /etc/etcd/pki/

Por último, deja el certificado de administración accesible para tu usuario, que es el que ejecutará etcdctl:

install -d -m 0700 ~/.etcd
install -m 0600 ca.crt admin.crt admin.key ~/.etcd/

Paso 3: Configurar etcd en cada nodo

etcd lee su configuración de variables de entorno con el prefijo ETCD_. Crea el archivo de entorno:

sudo nano /etc/etcd/etcd.env

Este es el contenido para etcd-01. En los otros nodos cambia ETCD_NAME y todas las URL que contienen 10.0.1.10 por el nombre y la IP de ese nodo; ETCD_INITIAL_CLUSTER es idéntico en los tres:

ETCD_NAME="etcd-01"
ETCD_DATA_DIR="/var/lib/etcd"

ETCD_LISTEN_PEER_URLS="https://10.0.1.10:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://10.0.1.10:2380"
ETCD_LISTEN_CLIENT_URLS="https://10.0.1.10:2379,https://127.0.0.1:2379"
ETCD_ADVERTISE_CLIENT_URLS="https://10.0.1.10:2379"
ETCD_LISTEN_METRICS_URLS="http://127.0.0.1:2381"

ETCD_INITIAL_CLUSTER="etcd-01=https://10.0.1.10:2380,etcd-02=https://10.0.1.11:2380,etcd-03=https://10.0.1.12:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-1"

ETCD_CERT_FILE="/etc/etcd/pki/server.crt"
ETCD_KEY_FILE="/etc/etcd/pki/server.key"
ETCD_TRUSTED_CA_FILE="/etc/etcd/pki/ca.crt"
ETCD_CLIENT_CERT_AUTH="true"
ETCD_PEER_CERT_FILE="/etc/etcd/pki/server.crt"
ETCD_PEER_KEY_FILE="/etc/etcd/pki/server.key"
ETCD_PEER_TRUSTED_CA_FILE="/etc/etcd/pki/ca.crt"
ETCD_PEER_CLIENT_CERT_AUTH="true"

ETCD_AUTO_COMPACTION_MODE="periodic"
ETCD_AUTO_COMPACTION_RETENTION="1h"

Puntos importantes:

  • El puerto 2379 atiende a los clientes y el 2380 la comunicación entre nodos.
  • ETCD_CLIENT_CERT_AUTH y ETCD_PEER_CLIENT_CERT_AUTH obligan a presentar un certificado firmado por la CA; sin él, la conexión se rechaza.
  • ETCD_LISTEN_METRICS_URLS publica las métricas de Prometheus y el endpoint /health solo en local y sin TLS, para poder monitorizar sin certificados.
  • La compactación automática descarta cada hora las revisiones antiguas de las claves, lo que evita que la base de datos crezca sin límite.
  • ETCD_INITIAL_CLUSTER* solo se usa en el primer arranque; con el directorio de datos ya creado, etcd lo ignora.

Protege el archivo:

sudo chown root:etcd /etc/etcd/etcd.env
sudo chmod 0640 /etc/etcd/etcd.env

Paso 4: Crear el servicio systemd

Crea la unidad systemd en los tres nodos:

sudo nano /etc/systemd/system/etcd.service
[Unit]
Description=etcd key-value store
Documentation=https://etcd.io/docs/
Wants=network-online.target
After=network-online.target

[Service]
Type=notify
User=etcd
Group=etcd
EnvironmentFile=/etc/etcd/etcd.env
ExecStart=/usr/local/bin/etcd
Restart=on-failure
RestartSec=5s
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Abre los puertos de etcd solo para la red privada. Si UFW no está activo todavía, permite antes SSH:

sudo ufw allow OpenSSH
sudo ufw allow from 10.0.1.0/24 to any port 2379:2380 proto tcp
sudo ufw enable

Paso 5: Arrancar el clúster

Con Type=notify, etcd solo avisa a systemd de que está listo cuando el clúster tiene quórum, por lo que el primer nodo esperaría a los demás. Usa --no-block para que el comando no se quede bloqueado y ejecútalo en los tres nodos:

sudo systemctl daemon-reload
sudo systemctl enable --now --no-block etcd

Tras unos segundos, comprueba el estado en cada nodo:

sudo systemctl status etcd
● etcd.service - etcd key-value store
     Loaded: loaded (/etc/systemd/system/etcd.service; enabled; preset: enabled)
     Active: active (running) since ...

Si el servicio no arranca, revisa los mensajes con sudo journalctl -u etcd -e. Los errores más habituales son rutas de certificados mal escritas o una IP que no coincide con el subjectAltName.

Paso 6: Gestionar el clúster con etcdctl

Configura etcdctl mediante variables de entorno para no repetir los certificados en cada comando. Añádelas a tu ~/.bashrc:

nano ~/.bashrc
export ETCDCTL_ENDPOINTS="https://10.0.1.10:2379,https://10.0.1.11:2379,https://10.0.1.12:2379"
export ETCDCTL_CACERT="$HOME/.etcd/ca.crt"
export ETCDCTL_CERT="$HOME/.etcd/admin.crt"
export ETCDCTL_KEY="$HOME/.etcd/admin.key"

Carga las variables y comprueba la salud de los tres endpoints:

source ~/.bashrc
etcdctl endpoint health
https://10.0.1.10:2379 is healthy: successfully committed proposal: took = 8.1ms
https://10.0.1.11:2379 is healthy: successfully committed proposal: took = 9.4ms
https://10.0.1.12:2379 is healthy: successfully committed proposal: took = 9.0ms

Lista los miembros:

etcdctl member list --write-out=table
+------------------+---------+---------+------------------------+------------------------+------------+
|        ID        | STATUS  |  NAME   |       PEER ADDRS       |      CLIENT ADDRS      | IS LEARNER |
+------------------+---------+---------+------------------------+------------------------+------------+
| 3a57933972cb5131 | started | etcd-01 | https://10.0.1.10:2380 | https://10.0.1.10:2379 |      false |
| 8e9e05c52164694d | started | etcd-02 | https://10.0.1.11:2380 | https://10.0.1.11:2379 |      false |
| c1b2e4f7a0d39e6b | started | etcd-03 | https://10.0.1.12:2380 | https://10.0.1.12:2379 |      false |
+------------------+---------+---------+------------------------+------------------------+------------+

etcdctl endpoint status --write-out=table muestra además qué nodo es el líder y el tamaño de la base de datos de cada miembro.

Prueba las operaciones básicas. Escribe una clave, léela y lee todas las que comparten prefijo:

etcdctl put /config/app/log_level info
etcdctl get /config/app/log_level
etcdctl get /config/ --prefix
OK
/config/app/log_level
info
/config/app/log_level
info

Para ver cómo reaccionan los clientes a los cambios, abre una segunda sesión y observa el prefijo con etcdctl watch /config/ --prefix. Cada put o del que hagas en la primera sesión aparecerá al instante.

Comprueba también que un cliente sin certificado queda rechazado:

curl -s --cacert ~/.etcd/ca.crt https://10.0.1.10:2379/version

La conexión falla durante el handshake TLS porque el servidor exige certificado de cliente. Con --cert ~/.etcd/admin.crt --key ~/.etcd/admin.key añadidos, la misma petición devuelve la versión en JSON.

Paso 7: Programar copias de seguridad

Un snapshot de etcd contiene todo el estado del clúster y se puede tomar en caliente desde un solo miembro. Crea un script de copia en etcd-01:

sudo nano /usr/local/sbin/etcd-backup
#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR="/var/backups/etcd"
RETENTION_DAYS=7
SNAPSHOT="${BACKUP_DIR}/etcd-$(date +%Y%m%d-%H%M%S).db"

mkdir -p "$BACKUP_DIR"
chmod 0700 "$BACKUP_DIR"

/usr/local/bin/etcdctl \
  --endpoints="https://127.0.0.1:2379" \
  --cacert=/etc/etcd/pki/ca.crt \
  --cert=/etc/etcd/pki/admin.crt \
  --key=/etc/etcd/pki/admin.key \
  snapshot save "$SNAPSHOT"

/usr/local/bin/etcdutl snapshot status "$SNAPSHOT" --write-out=table

find "$BACKUP_DIR" -name 'etcd-*.db' -mtime +"$RETENTION_DAYS" -delete

Hazlo ejecutable y pruébalo:

sudo chmod 0750 /usr/local/sbin/etcd-backup
sudo /usr/local/sbin/etcd-backup
Snapshot saved at /var/backups/etcd/etcd-20260925-101500.db
+----------+----------+------------+------------+
|   HASH   | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| 5e1f0a2b |       14 |          9 |      25 kB |
+----------+----------+------------+------------+

Programa la ejecución diaria con systemd. Crea la unidad de servicio:

sudo nano /etc/systemd/system/etcd-backup.service
[Unit]
Description=etcd snapshot backup

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/etcd-backup

Y el temporizador:

sudo nano /etc/systemd/system/etcd-backup.timer
[Unit]
Description=Daily etcd snapshot backup

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target

Actívalo y comprueba la próxima ejecución:

sudo systemctl daemon-reload
sudo systemctl enable --now etcd-backup.timer
systemctl list-timers etcd-backup.timer

Copia los snapshots fuera del servidor (almacenamiento de objetos u otro host): una copia que vive en el mismo disco que los datos no protege frente a la pérdida del nodo.

Paso 8: Restaurar el clúster desde un snapshot

La restauración crea un clúster nuevo con los datos del snapshot. Se hace en todos los nodos con el mismo archivo. Copia el snapshot a los tres nodos y, primero, detén etcd en todos:

sudo systemctl stop etcd

En cada nodo, aparta el directorio de datos actual (el restore necesita que el destino no exista) y restaura. Ajusta --name y --initial-advertise-peer-urls al nodo en el que estés:

sudo mv /var/lib/etcd /var/lib/etcd.old
sudo etcdutl snapshot restore /var/backups/etcd/etcd-20260925-101500.db \
  --name etcd-01 \
  --initial-cluster "etcd-01=https://10.0.1.10:2380,etcd-02=https://10.0.1.11:2380,etcd-03=https://10.0.1.12:2380" \
  --initial-cluster-token etcd-cluster-1 \
  --initial-advertise-peer-urls https://10.0.1.10:2380 \
  --data-dir /var/lib/etcd
sudo chown -R etcd:etcd /var/lib/etcd
sudo chmod 0700 /var/lib/etcd

Arranca etcd en los tres nodos y verifica que los datos están de vuelta:

sudo systemctl start --no-block etcd
etcdctl endpoint health
etcdctl get /config/ --prefix

Cuando confirmes que todo funciona, puedes borrar /var/lib/etcd.old.

Paso 9: Monitorización y mantenimiento

El endpoint de métricas configurado en el paso 3 permite comprobar la salud del nodo sin certificados:

curl -s http://127.0.0.1:2381/health
{"health":"true","reason":""}

Las métricas en formato Prometheus están en http://127.0.0.1:2381/metrics. Las más útiles para alertar son etcd_server_has_leader (debe valer 1), etcd_server_leader_changes_seen_total (debe crecer muy poco), etcd_disk_wal_fsync_duration_seconds y etcd_disk_backend_commit_duration_seconds:

curl -s http://127.0.0.1:2381/metrics | grep -E '^etcd_server_(has_leader|leader_changes_seen_total)'

La compactación automática libera revisiones antiguas, pero el espacio en disco solo se recupera al desfragmentar. Hazlo de un nodo en cada paso, porque el miembro queda bloqueado mientras dura:

etcdctl defrag --endpoints=https://10.0.1.10:2379

Si la base de datos alcanza su cuota (2 GB por defecto), etcd activa una alarma NOSPACE y deja de aceptar escrituras. Compruébalo con:

etcdctl alarm list

Tras compactar y desfragmentar, desactiva la alarma con etcdctl alarm disarm.

Para saber si el disco es lo bastante rápido, la documentación de etcd recomienda medir la latencia de fdatasync con fio en el mismo sistema de archivos que /var/lib/etcd. El percentil 99 debería estar por debajo de 10 ms:

sudo apt install -y fio
sudo mkdir -p /var/lib/etcd-fio-test
sudo fio --rw=write --ioengine=sync --fdatasync=1 --directory=/var/lib/etcd-fio-test --size=22m --bs=2300 --name=etcd-disk-test
sudo rm -rf /var/lib/etcd-fio-test

En la salida, busca la sección fsync/fdatasync/sync_file_range y el valor 99.00th.

Solución de problemas

  • x509: certificate is valid for ..., not 10.0.1.11: el certificado del nodo no incluye esa IP. Regenera el certificado con el subjectAltName correcto y reinicia etcd.
  • request cluster ID mismatch o el nodo no se une: el nodo tiene datos de un clúster anterior. Detén etcd, aparta /var/lib/etcd y vuelve a arrancarlo con la configuración inicial correcta.
  • Cambios de líder frecuentes: casi siempre es latencia de disco o de red. Revisa etcd_disk_wal_fsync_duration_seconds y la prueba de fio, y evita compartir el disco con cargas intensivas.
  • database space exceeded: la base de datos llegó a la cuota. Compacta, desfragmenta y ejecuta etcdctl alarm disarm; si el volumen de datos es legítimo, aumenta ETCD_QUOTA_BACKEND_BYTES.

Conclusión

Tienes un clúster etcd de tres nodos con TLS mutuo, compactación automática, copias de seguridad diarias programadas con systemd y un procedimiento de restauración probado. Como siguientes pasos, envía las métricas del puerto 2381 a tu sistema de monitorización, copia los snapshots a un almacenamiento externo y activa la autenticación por usuarios y roles de etcd (etcdctl user y etcdctl role) si varias aplicaciones van a compartir el clúster.