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

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):

  1. Comprueba que todos los nodos están Ready y que la API responde. Si no, para.
  2. Termina cualquier actualización anterior que se cortara (dpkg --configure -a y apt-get -f install) e instala las actualizaciones (apt-get upgrade --with-new-pkgs, conservando la configuración local).
  3. 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.
  4. 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 drain puede no terminar si un nodo tiene la última réplica sana de un volumen de Longhorn (política block-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

Seguridad

Quien controle el portal puede actualizar y reiniciar todos los nodos, así que está limitado al máximo:

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