Mover el control plane de K3s a otro nodo (deborah → oliver)¶
Procedimiento para pasar el control plane (el servidor de K3s que guarda el estado del clúster y atiende la
API) de deborah a oliver, y dejar a deborah como worker. Se hace en una ventana de mantenimiento,
con una persona al teclado: el playbook se detiene en varios puntos de pausa para que confirmes cada etapa.
No está planificado
El control plane se queda en deborah. Este procedimiento queda documentado por si algún día hace falta
cambiarlo de nodo.
Por qué se podría mover¶
deborah es el equipo con más memoria (31 GB), pero también ejecuta Ollama, es el DNS de la LAN (red local) y
tiene una eMMC (memoria flash soldada) lenta. Un modelo cargado compite con el control plane por memoria.
oliver es un nodo ligero (etiqueta workload-tier=light) con una carga estable, y deborah queda libre para
los modelos y las cargas pesadas.
El clúster no se reconstruye
Longhorn (el almacenamiento) guarda su metadata dentro del estado del clúster: si se pierde, los volúmenes quedan huérfanos. Por eso este procedimiento traslada el estado existente; no reinstala desde cero. Sin backup verificado no se empieza.
Cómo se hace el traslado¶
Hoy K3s corre con un solo servidor y SQLite (un archivo). La documentación oficial de K3s no describe cómo llevar
ese archivo a otro nodo (y el token incluye el hash de la CA, la autoridad certificadora del clúster). Lo que sí está
documentado es convertir el almacén de datos a etcd embebido (la base de datos distribuida de Kubernetes, integrada en K3s), unir otro servidor con el mismo token y quedar con
un solo servidor. El playbook
playbook-k3s-move-controlplane.yml
hace exactamente eso, por etapas, cada una con su etiqueta (tag):
| Etapa | Etiqueta | Qué hace | API |
|---|---|---|---|
| 1 | preflight |
Verificaciones: nodos Ready, Longhorn sin volúmenes degradados, Argo CD sincronizado |
arriba |
| 2 | backup |
Backup cifrado del estado, guardado fuera del nodo (detiene K3s unos minutos en el origen) y generación de un agent-token distinto del token de servidor | cae un rato |
| 3 | convert |
Convierte deborah a etcd (--cluster-init) con los flags de seguridad y repunta los agentes al nombre k3s.homelab.local |
cae un rato |
| 4 | join |
Drena oliver, quita su agent y lo une como segundo servidor |
arriba |
| 5 | dns |
k3s.homelab.local pasa a apuntar a oliver |
arriba |
| 6 | retire |
Deja a oliver como único miembro de etcd y a deborah como agent |
cae un rato |
| 7 | verify |
Verificación final, resumen de pasos manuales y borrado seguro (shred) del backup en claro y de /root/k3s-move-* en los nodos |
arriba |
El rollback (--tags rollback) nunca corre solo.
Durante la ventana hay dos miembros de etcd
Entre las etapas 4 y 6 el clúster tiene dos miembros de etcd (deborah y oliver). Con dos miembros no hay
tolerancia a fallos: si uno cae, el otro pierde el quórum y la API deja de responder. No toques los nodos por
fuera del playbook durante ese intervalo.
Prerrequisitos¶
- Una ventana en la que la API pueda quedar sin responder varios minutos en las etapas 2, 3 y 6: Argo CD
no sincroniza y
kubectlno responde; las cargas ya en marcha siguen corriendo, salvo los pods dedeborah, que se drenan en la etapa 6. - Acceso por SSH a
deborahyoliver(Entrar por SSH) y acceso físico adeborah. - Longhorn sano y Argo CD sincronizado (el
preflightlo comprueba; se puede saltar lo de Argo CD conk3s_move_allow_unsynced, pero no conviene). -
playbook-bind-dns.ymlya aplicado, con el registrok3sen la zonahomelab.local: la variablek3s_api_endpoint(k3s.homelab.local) reemplaza a la IP fija del control plane en los agents, el kubeconfig y los SAN (nombres alternativos del certificado de la API). - La CA del HomeLab en los nodos (rol
opkssh), porque los servidores nuevos confían en Dex para el login de personas. - Un método de cifrado para el backup (contiene el token y los certificados del clúster): una clave pública de
age(k3s_move_backup_age_recipient) o una contraseña paragpg(k3s_move_backup_passphrase, mejor por variable de entorno). Sin ninguno de los dos el playbook aborta, salvo que lo fuerces con-e k3s_move_backup_unencrypted=true(no recomendado). -
olivercon unos 5 GB de memoria libre. Si tiene cargas pesadas, muévelas antes.
Paso a paso¶
Desde ansible/:
-
Ensayo, sin cambiar nada:
-
Ejecución real. Con las pausas activas (por defecto), el playbook te pide confirmar antes del backup, antes de convertir a etcd, antes de unir a
oliver, antes de repuntar el DNS y antes de retirar el origen:Variables útiles:
k3s_move_target(el destino, por defectooliver),k3s_move_backup_dir(dónde se guarda el backup, por defecto~/k3s-move-backupsen tu equipo),k3s_move_backup_age_recipientok3s_move_backup_passphrase(cifrado),k3s_move_agent_token(si quieres fijar el agent-token en vez de generarlo),k3s_move_drain_source,k3s_move_rotate_secrets,k3s_move_allow_unsyncedyk3s_move_confirm=falsepara no interactivo (no recomendado). -
Puntos de pausa.
- Tras el backup: abre el archivo y comprueba su suma antes de seguir.
- Antes de convertir: es el paso que no se deshace sin el rollback.
- Tras unir a
oliver:kubectl get nodesdebe mostrar dos servidores. - Tras repuntar el DNS:
dig +short k3s.homelab.local @192.168.20.5debe devolver la IP deoliver.
Si algo no te convence, para
Cada pausa admite abortar con Ctrl+C. Si el playbook se interrumpe después de la etapa 3, no reanudes con
--tags sueltos: las etapas 4 y 6 necesitan el token que se obtiene en la 3, dentro de la misma ejecución.
Al terminar, el playbook no modifica el inventario: lo haces tú, como se explica más abajo. Los agents dejan de usar
el token de servidor y pasan a un agent-token propio, que queda en <k3s_move_backup_dir>/agent-token.
Con la mudanza también quedan aplicados los endurecimientos del servidor nuevo: banderas oidc-* hacia Dex (login
de personas, con --oidc-username-prefix=oidc: y --oidc-groups-prefix=oidc:), --secrets-encryption (cifrado de Secrets en reposo) y el instalador de K3s verificado por suma de
control en vez de curl | sh. El certificado de administrador queda solo para emergencias. Ver
Modelo de autenticación.
Verificación¶
| Qué | Cómo |
|---|---|
| Nodos | kubectl get nodes -o wide: oliver con rol control-plane, y los tres en Ready |
| Argo CD | kubectl -n argocd get applications: todas Synced y Healthy |
| Longhorn | UI o kubectl -n longhorn-system get volumes.longhorn.io: sin volúmenes degradados |
| Kong | curl -kI https://argocd.homelab.local responde |
| DNS | dig +short k3s.homelab.local @192.168.20.5 devuelve la IP de oliver |
| Pods | kubectl get pods -A sin pods atascados (Pending, CrashLoopBackOff); los que vivían en deborah se reprograman |
Qué cambia en el inventario después¶
El playbook no toca estos archivos; edítalos a mano al terminar (y haz commit tú):
inventory.ini:deborahpasa de[k3s_server]a[k3s_agent]yoliverpasa de[k3s_agent]a[k3s_server](y suk3s_role).group_vars/all.yml:k3s_api_ippasa a192.168.20.7(la IP deoliver) yk3s_move_doneatrue.host_vars/deborah.ymlyhost_vars/oliver.yml: las etiquetas y las opciones de K3s siguen ak3s_move_done;deborahconservaworkload-tier=heavyyoliversigue siendolight.- Vuelve a aplicar
playbook-bind-dns.ymlpara que la zona quede alineada conk3s_api_ip. - Planes del SUC (System Upgrade Controller, el actualizador automático de K3s;
k3s-upgrade/): seleccionan el control plane por la etiquetanode-role.kubernetes.io/control-plane, así que no cambian y ahora actualizan primero aoliver.
Otras guías que hablan del control plane: Sacar o reemplazar un nodo, Agregar un nodo, Actualizar K3s y la Fase 3.
Rollback¶
Restaura el SQLite original en deborah desde el backup y devuelve a oliver a agent:
# o -e k3s_move_backup_passphrase=...
ansible-playbook -i inventory.ini playbook-k3s-move-controlplane.yml --tags rollback \
-e k3s_move_backup_stamp=<AAAAMMDDTHHMMSS> \
-e k3s_move_backup_age_identity=<archivo de identidad age>
La marca es la parte de fecha del nombre del archivo de backup en el directorio de backups. El rollback son cuatro
pasos, en este orden: 1) el DNS vuelve a apuntar a deborah y se espera unos 300 segundos a que se propague (si
no, el agent reinstalado en oliver intentaría unirse a sí mismo por k3s.homelab.local); 2) se restaura el
origen; 3) oliver vuelve a agent; 4) los demás agents recuperan el token original. Se pierde todo cambio
del clúster posterior al backup: decide con calma antes de revertir. Si ya habías editado el inventario, vuelve a
dejarlo como estaba.
Pasos manuales al terminar¶
-
Kubeconfig. Regenera el de tu equipo, que apuntaba a la IP vieja:
-
Guarda los secretos de la mudanza en 1Password. El agent-token (
<k3s_move_backup_dir>/agent-token) y la clave o contraseña con la que cifraste el backup. El backup en claro y los archivos/root/k3s-move-*de los nodos se borran solos al final (shred); lo único que queda es el backup cifrado en tu equipo, que es el que sirve para el rollback. - Revisa Argo CD, Longhorn y Kong con la tabla de Verificación.
-
Permisos del login con Dex. El repo
gitopstrae el ClusterRoleBindingoidc-devops-view(Applicationsecurity-rbac, ya activa), que da el ClusterRoleview(solo lectura) al grupooidc:maceosoft:devops. Es inofensivo hasta que haya tokens OIDC.Por qué solo lectura y no
cluster-adminEntrar con Dex no debe dar control total del clúster por defecto: el cambio de estado pasa por Git y Argo CD, y el certificado de administrador queda para emergencias. Si una persona necesita escribir en un namespace, amplía con un
RoleBindingque enlace el ClusterRoleedital mismo grupo solo en ese namespace, en lugar de ampliar el ClusterRoleBinding. -
Opcional, riesgo de Calico. Comprueba que una cuenta de servicio común no pueda crear políticas globales (ver Comprobaciones opcionales).
Login de personas con Dex (kubelogin)¶
Con las banderas oidc-* activas, puedes entrar a la API con tu cuenta de GitHub en lugar del certificado de
administrador. Instala el plugin kubelogin (kubectl oidc-login) y pide un token:
kubectl oidc-login get-token \
--oidc-issuer-url=https://argocd.homelab.local/api/dex \
--oidc-client-id=kubernetes \
--oidc-extra-scope=email,groups
Se abre el navegador; el cliente de Dex kubernetes es público (PKCE) y recibe la respuesta en
http://localhost:8000 (o, de respaldo, 18000): esos puertos tienen que estar libres en tu equipo. Para usarlo
siempre, configura un usuario en tu kubeconfig con exec hacia ese comando.
Riesgos¶
- Es un único control plane (sin HA, alta disponibilidad): si
olivercae, el clúster pierde su API hasta restaurar. Las cargas ya iniciadas siguen corriendo. - Pasar de SQLite a etcd es un cambio de almacén de datos: por eso el backup y el rollback son parte del procedimiento, no un extra.
Ver también Sacar o reemplazar un nodo, Actualizar K3s y la Fase 3 — K3s.