preparar_nodos.sh

#!/usr/bin/env bash
# Lanza ansible/nodo/preparar_nodo.sh en TODOS los nodos a la vez, sin SSH ni sudo nodo a
# nodo: un pod privilegiado temporal por nodo (DaemonSet) ejecuta el script en el propio
# nodo (nsenter en el PID 1) y después se borra todo.
#
# Uso (como root en el master-1, con el kubeconfig de administrador):
#   ./scripts/preparar_nodos.sh            instala o actualiza en los 6 nodos
#   ./scripts/preparar_nodos.sh --quitar   lo deshace en todos
#
# No da permisos nuevos: crear pods privilegiados ya exige ser administrador del clúster
# (lo que ya es root en todos los nodos). El portal (Semaphore) sigue sin poder hacerlo.
set -euo pipefail

RAIZ="$(cd "$(dirname "$0")/.." && pwd)"
NS=mantenimiento
NOMBRE=preparar-nodos
IMAGEN="python:3.13.16-slim-trixie@sha256:cf50a4f6ac4f026458b08d5222023d700ac806410dff5e9ca8728fa44f2b850f"
ARG="${1:-}"
[ -z "$ARG" ] || [ "$ARG" = --quitar ] || { sed -n '2,11p' "$0"; exit 64; }

limpiar() {
  kubectl -n "$NS" delete daemonset "$NOMBRE" --ignore-not-found --wait=false >/dev/null
  kubectl -n "$NS" delete configmap "$NOMBRE" --ignore-not-found >/dev/null
}
trap limpiar EXIT

kubectl get ns "$NS" >/dev/null 2>&1 || kubectl create ns "$NS" >/dev/null
TOTAL=$(kubectl get nodes --no-headers | wc -l)
echo "Preparando $TOTAL nodos${ARG:+ ($ARG)}…"

kubectl -n "$NS" create configmap "$NOMBRE" \
  --from-file="$RAIZ/ansible/nodo/nodo-mantenimiento" \
  --from-file="$RAIZ/ansible/nodo/preparar_nodo.sh" \
  --dry-run=client -o yaml | kubectl apply -f - >/dev/null

kubectl apply -f - >/dev/null <<YAML
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: $NOMBRE
  namespace: $NS
spec:
  selector:
    matchLabels: {app: $NOMBRE}
  template:
    metadata:
      labels: {app: $NOMBRE}
    spec:
      hostPID: true
      tolerations: [{operator: Exists}]      # todos los nodos, también worker-3 (taint app=n8n)
      automountServiceAccountToken: false
      terminationGracePeriodSeconds: 1
      containers:
        - name: preparar
          image: $IMAGEN
          securityContext: {privileged: true}
          resources:
            requests: {cpu: 10m, memory: 32Mi}
            limits: {memory: 256Mi}
          command: [bash, -c]
          args:
            - |
              set -u
              d=/host/var/tmp/preparar-nodo
              rm -rf "\$d"; mkdir -p "\$d"
              cp /ficheros/nodo-mantenimiento /ficheros/preparar_nodo.sh "\$d/"
              nsenter -t 1 -m -u -i -n -p -- bash /var/tmp/preparar-nodo/preparar_nodo.sh $ARG
              echo "PREPARAR-FIN rc=\$?"
              rm -rf "\$d"
              sleep infinity
          volumeMounts:
            - {name: ficheros, mountPath: /ficheros, readOnly: true}
            - {name: host-tmp, mountPath: /host/var/tmp}
      volumes:
        - {name: ficheros, configMap: {name: $NOMBRE}}
        - {name: host-tmp, hostPath: {path: /var/tmp, type: Directory}}
YAML

# Esperar a que todos los nodos terminen (máx. 5 min).
for _ in $(seq 60); do
  hechos=0
  for p in $(kubectl -n "$NS" get pods -l app=$NOMBRE -o name 2>/dev/null); do
    kubectl -n "$NS" logs "$p" 2>/dev/null | grep -q "PREPARAR-FIN" && hechos=$((hechos + 1))
  done
  [ "$hechos" -ge "$TOTAL" ] && break
  sleep 5
done

echo
fallos=0
for p in $(kubectl -n "$NS" get pods -l app=$NOMBRE -o name); do
  nodo=$(kubectl -n "$NS" get "$p" -o jsonpath='{.spec.nodeName}')
  log=$(kubectl -n "$NS" logs "$p" 2>/dev/null)
  rc=$(sed -n 's/^PREPARAR-FIN rc=//p' <<<"$log")
  if [ "$rc" = 0 ]; then
    echo "✓ $nodo"
    grep -E '^\s+· ' <<<"$log" | sed 's/^/  /' || true
  else
    echo "✘ $nodo (rc=${rc:-sin terminar})"
    tail -5 <<<"$log" | sed 's/^/    /'
    fallos=$((fallos + 1))
  fi
done
echo
# Un nodo sin pod (NotReady, sin programar…) también es un fallo.
vistos=$(kubectl -n "$NS" get pods -l app=$NOMBRE --no-headers | wc -l)
if [ "$TOTAL" -gt "$vistos" ]; then
  echo "✘ $((TOTAL - vistos)) nodo(s) sin pod: no se han preparado"
  fallos=$((fallos + TOTAL - vistos))
fi
[ "$fallos" -eq 0 ] && echo "✓ Los $TOTAL nodos preparados." || { echo "✘ $fallos nodo(s) con fallos"; exit 1; }