Portal de actualizaciones de los nodos
Ver qué actualizaciones faltan en cada nodo y lanzarlas a todos, de forma segura.
| Pieza | Qué hace |
|---|---|
| Grafana → panel Actualizaciones de los nodos | Qué falta en cada nodo (seguridad, normales, reinicio, versiones de Tailscale, k3s y kernel) y la lista de paquetes |
nodo/nodo-mantenimiento |
En cada nodo: revisa, actualiza o reinicia. Cada 6 h deja las métricas para Grafana |
playbooks/ |
Ansible: lanza las acciones en todos los nodos, de uno en uno |
scripts/mantenimiento.sh |
Lanza los playbooks desde el master-1, en un contenedor |
| Semaphore UI → http://203.0.113.121:3010 | Los mismos playbooks con botones, historial y 2FA (semaphore/, scripts/portal.sh) |
Portal web (Semaphore)
http://203.0.113.121:3010, solo por Tailscale. Proyecto Nodos del clúster, en Task Templates:
| Botón | Qué hace |
|---|---|
| 1 · Revisar ahora | Qué falta en cada nodo. No instala nada |
| 2 · Simular actualización | Qué se instalaría en cada nodo. No instala ni reinicia |
| 3 · Actualizar sistema | Instala, nodo a nodo, con drain y reinicio si hace falta |
| 4 · Actualizar Tailscale | Solo Tailscale, nodo a nodo |
| 5 · Reiniciar los que lo piden | Con drain, de uno en uno |
| 6 · Actualizar k3s | Pide la versión (v1.X.Y+k3sN). Masters primero, de uno en uno con snapshot de etcd; después workers. Parche sin drain; cambio de rama (como mucho una) con drain |
Al pulsar ▶ se elige Nodos: all, workers, masters o un nodo concreto. Se ve la salida en directo, y en History queda quién lanzó qué, cuándo y con qué resultado. Solo corre una tarea a la vez.
./scripts/portal.sh instalar # la primera vez (pide usuario y contraseña del admin)
./scripts/portal.sh botones # crea o completa los botones (pide usuario, contraseña y el código 2FA de la app)
./scripts/portal.sh repo # tras mergear cambios en ansible/: el portal solo ejecuta main
./scripts/portal.sh claves # tras rotar la clave SSH o el kubeconfig
./scripts/portal.sh estado | registros | parar | arrancar
- Corre en Docker en el master-1, fuera de k3s, con la red del host (los nodos solo aceptan la clave desde
192.0.2.112) y escuchando solo en la IP de Tailscale. - Imagen
semaphoreui/semaphore:v2.19.16fijada por huella, más kubectl con sha256. Contenedor de solo lectura, sin capacidades y conno-new-privileges. Base de datos SQLite en un volumen de Docker. - Ejecuta una copia de main en
/opt/portal-nodos/repo, no la rama en la que esté/root/k3s: solo lo revisado y mergeado. - Secretos (cookies, cifrado) en
/root/.config/semaphore/semaphore.env(0600). La contraseña del admin se borra del fichero en cuanto se crea el usuario. - 2FA: actívalo en la web, en tu usuario, nada más instalar.
- Telegram (opcional): descomenta
SEMAPHORE_TELEGRAM_*ensemaphore.envy./scripts/portal.sh arrancar. Avisa cuando una tarea falla.
Uso desde la terminal
cd /root/k3s
./scripts/mantenimiento.sh revisar # qué falta (no instala nada)
./scripts/mantenimiento.sh actualizar --check # simulación
./scripts/mantenimiento.sh actualizar # todo, nodo a nodo
./scripts/mantenimiento.sh actualizar -e nodos=workers # solo un grupo o un nodo
./scripts/mantenimiento.sh actualizar -e reiniciar=false # sin reiniciar
./scripts/mantenimiento.sh tailscale # solo Tailscale
./scripts/mantenimiento.sh reiniciar # los que piden reinicio
./scripts/mantenimiento.sh k3s -e version=v1.34.12+k3s1 --check # k3s: simulación
./scripts/mantenimiento.sh k3s -e version=v1.34.12+k3s1 # k3s: masters y luego workers
Cómo actualiza (actualizar), nodo a nodo y en el orden del inventario (workers, masters, el master-1 el último):
- Comprueba que todos los nodos están Ready y que la API responde. Si no, para.
- Termina cualquier actualización anterior que se cortara (
dpkg --configure -ayapt-get -f install) e instala las actualizaciones (apt-get upgrade --with-new-pkgs, conservando la configuración local). - Si el nodo pide reinicio: lo saca del clúster (
drain, respetando los PodDisruptionBudget), lo reinicia, espera a que vuelva Ready y lo devuelve (uncordon). Si algo falla, lo devuelve igualmente y para todo. - Pasa al siguiente. Los masters también van de uno en uno, así etcd nunca pierde el quórum.
El master-1 se actualiza, pero no se reinicia solo, porque desde él se lanza todo: el playbook avisa y lo reinicias a mano.
El
drainpuede no terminar si un nodo tiene la última réplica sana de un volumen de Longhorn (políticablock-if-contains-last-replica) o si un PodDisruptionBudget no deja mover un pod. Tras 10 minutos se rinde, devuelve el nodo y para. El mensaje dice qué pod lo bloquea.
Qué se corta al reiniciar un nodo
- Volúmenes
local-path: no se pueden mover de nodo, así que sus servicios se paran hasta que el nodo vuelve. El playbook los lista antes del drain. Si el nodo no volviera, el corte duraría hasta arreglarlo. Con servicio real hoy están en master-2 (Jenkins y Kafka) e master-1 (Postgres central, Postgres de Jira y Pi-hole); la lista completa está en almacenamiento. emptyDir: el drain borra su contenido (--delete-emptydir-data). Afecta a Prometheus y Grafana, que hoy no tienen volumen persistente: se pierde el histórico de métricas o lo hecho a mano en Grafana.- Masters: antes de reiniciar uno, se comprueba que la red hasta él es estable (30 conexiones a la API sin fallos). Si falla alguna, para: con la red inestable, la VIP podría saltar o etcd quedarse sin quórum (ver operación).
Seguridad
Quien controle el portal puede actualizar y reiniciar todos los nodos, así que está limitado al máximo:
- Usuario
ansibleen cada nodo: sin contraseña (bloqueada) y con una sola clave, que solo vale desde el master-1 (from="192.0.2.112") y sin reenvío de puertos, agente ni X11. - sudo limitado: solo
nodo-mantenimiento revisar|actualizar|tailscale|reiniciar, órdenes exactas. No tiene root libre (preparar_nodo.shlo comprueba al instalar). nodo-mantenimientono acepta otros argumentos ni lee variables del entorno.- En el clúster, la ServiceAccount
mantenimiento-nodos(RBAC) solo puede hacer cordon, uncordon y drain y ver los nodos. No lee Secrets ni crea ni borra cargas. - Contenedor del lanzador: Ansible, kubectl e imagen base fijados por huella. Se ejecuta en solo lectura, sin capacidades y con
no-new-privileges. - Las claves viven en
/root/.config/mantenimiento/(0700, solo root), fuera de git. - Las huellas SSH de los nodos se comprueban (
known_hosts). Si un nodo cambia de huella, Ansible no se conecta.
Puesta en marcha
1. En los nodos
Todos a la vez (recomendado), como root en el master-1 con el kubeconfig de administrador:
./scripts/preparar_nodos.sh # también para actualizar el script del nodo
./scripts/preparar_nodos.sh --quitar
Lanza un pod privilegiado temporal en cada nodo (DaemonSet) que ejecuta preparar_nodo.sh en el propio nodo (nsenter), muestra el resultado de cada uno y lo borra todo. No da permisos nuevos: crear pods privilegiados ya exige ser administrador del clúster. El portal no puede hacerlo, a propósito: si pudiera reinstalar su propio script tendría root en todos los nodos.
Uno a uno, por SSH (lo de antes; ya no funciona desde el 2026-10-28 porque el scp entraba al master-1 con contraseña y ahora el SSH es solo con clave: usar preparar_nodos.sh):
Los ficheros están en el escritorio del master-1 (/home/admin/Escritorio/mantenimiento-nodo/). En cada nodo, con tu usuario:
scp -r admin@192.0.2.112:Escritorio/mantenimiento-nodo . && sudo bash mantenimiento-nodo/preparar_nodo.sh
Termina con sudo limitado OK, sin sudo libre OK y métricas escritas OK. Se puede repetir para actualizar nodo-mantenimiento, y --quitar lo deshace todo.
2. En el clúster (una vez)
kubectl apply -f ansible/kubernetes/mantenimiento-rbac.yaml
sudo ./ansible/kubernetes/crear_kubeconfig.sh
kubectl apply -f ansible/kubernetes/grafana-actualizaciones.yaml
helm -n monitoring upgrade prometheus prometheus-community/kube-prometheus-stack \
--version 80.13.3 --reuse-values -f ansible/kubernetes/node-exporter-textfile.yaml
El último paso activa el colector textfile de node-exporter. Reinicia Grafana, que hoy no tiene volumen persistente: se pierde lo que no venga de configuración (usuarios o paneles creados a mano).
3. En el master-1 (una vez): clave y huellas
install -d -m 700 /root/.config/mantenimiento
cp /root/.ssh/mantenimiento_ed25519 /root/.config/mantenimiento/
for ip in 192.0.2.{128,164,197,139,129,112}; do
ssh-keyscan -T 8 -t ed25519 "$ip" 2>/dev/null | grep -v '^#'
done > /root/.config/mantenimiento/known_hosts
Compara las huellas con las que ya conoce root (ssh-keygen -lf y ssh-keygen -F <ip>) antes de fiarte de ellas.
Métricas
nodo-mantenimiento escribe /var/lib/node_exporter/textfile/actualizaciones.prom:
| Métrica | Etiquetas |
|---|---|
nodo_actualizaciones_pendientes |
nodo, tipo = seguridad / normal |
nodo_actualizacion_paquete |
nodo, paquete, version, seguridad |
nodo_reinicio_pendiente |
nodo |
nodo_actualizaciones_automaticas |
nodo (unattended-upgrades) |
nodo_version_info |
nodo, componente = tailscale / k3s / kernel / sistema, version |
nodo_actualizaciones_revisado_timestamp_seconds |
nodo |
El panel se genera con kubernetes/generar_panel.py. Para cambiarlo, edita el script y vuelve a generar el YAML.
Fuera del portal
- k3s (1.34 → 1.35…) no se actualiza desde aquí todavía. Es más delicado y va con su propio procedimiento (operación).
- Docker en el master-1: si se actualiza, la demo del ERP se corta unos segundos.