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; }