En la lección anterior levantamos clústeres desechables en el portátil y descubrimos, casi de pasada, que kind usa kubeadm por dentro: aquel campo kubeadmConfigPatches del fichero de configuración no era casualidad. Ahora vamos a la herramienta directamente.
kubeadm es la herramienta oficial del proyecto Kubernetes para arrancar un clúster conforme sobre máquinas que tú controlas. Es la base sobre la que están construidas casi todas las distribuciones, y entenderla te da algo que ninguna otra vía te da: ver con tus propios ojos dónde están los certificados, dónde viven los pods estáticos del plano de control y qué ocurre exactamente cuando un nodo se une al clúster. Aunque Rutas Norte S.L. acabe eligiendo un clúster gestionado (10-06), este conocimiento es el que te permitirá depurar un plano de control roto y aprobar la certificación CKA (12-01).
Contenido
- Qué es kubeadm y qué no es
- Autogestionado frente a gestionado: cuándo tiene sentido
- Preparación de las máquinas
- Instalación de containerd
- Instalación de kubeadm, kubelet y kubectl
kubeadm initcon fichero de configuración- Qué crea exactamente kubeadm en el nodo
- Instalar el CNI y por qué los nodos están NotReady
- Unir nodos de trabajo
- Plano de control en alta disponibilidad
- Gestión de certificados
- Actualización de versión
- Copia de seguridad y restauración de etcd
kubeadm resety limpieza- Errores comunes y consejos
- Ejercicios
- Conclusión
- Qué es kubeadm y qué no es
kubeadm hace una sola cosa bien: dadas unas máquinas ya preparadas, arranca en ellas un plano de control y une nodos de trabajo, produciendo un clúster que pasa las pruebas de conformidad de Kubernetes.
| kubeadm SÍ hace | kubeadm NO hace |
|---|---|
| Generar toda la jerarquía de certificados (CA, apiserver, etcd, kubelet) | Aprovisionar máquinas virtuales o físicas |
| Escribir los manifiestos de los pods estáticos del plano de control | Instalar el runtime de contenedores |
| Arrancar etcd (apilado) o conectarse a uno externo | Instalar el complemento de red (CNI) |
| Configurar el kubelet y generar sus kubeconfig | Configurar el sistema operativo (swap, sysctl, cortafuegos) |
| Emitir tokens de unión y unir nodos | Instalar Ingress, monitorización, almacenamiento |
| Actualizar el plano de control de versión | Gestionar copias de seguridad de etcd |
| Renovar certificados | Ofrecer alta disponibilidad del balanceador de la API |
Esa lista de la derecha es la clave. Mucha gente ejecuta kubeadm init, ve el mensaje de éxito, y luego se sorprende de que kubectl get nodes diga NotReady. No hay ningún error: falta el CNI, y ese trabajo no es de kubeadm.
flowchart TB
subgraph tuyo["Responsabilidad tuya"]
A[Máquinas y red]
B[Sistema operativo:<br/>swap, sysctl, cortafuegos]
C[containerd]
E[CNI: Calico, Cilium...]
F[Complementos: Ingress,<br/>almacenamiento, monitorización]
G[Copias de etcd, parches,<br/>rotación de nodos]
end
subgraph kubeadm["Responsabilidad de kubeadm"]
D[Certificados, plano de control,<br/>tokens, unión de nodos,<br/>actualizaciones]
end
A --> B --> C --> D --> E --> F --> G
style kubeadm fill:#e8f4ff
style tuyo fill:#fff4e8
- Autogestionado frente a gestionado: cuándo tiene sentido
Antes de escribir una sola orden, la pregunta honesta: ¿debería Rutas Norte S.L. operar su propio clúster?
| Criterio | kubeadm (autogestionado) | Gestionado (10-06) |
|---|---|---|
| Coste del plano de control | Tus máquinas (3 nodos de control mínimo para HA) | 0-75 €/mes por clúster, según proveedor |
| Quién arregla un etcd corrupto a las 3 de la mañana | Tu equipo | El proveedor |
| Actualizaciones de versión | Manuales, nodo a nodo, con ventana de mantenimiento | Un botón, aunque sigue exigiendo planificación |
| Control sobre banderas del apiserver | Total | Limitado |
| Ejecutar en máquinas propias o en un centro de datos privado | Sí | No (salvo variantes híbridas) |
| Requisitos normativos de soberanía del dato | Cumplibles | Depende del proveedor y la región |
| Personal necesario | Al menos 2 personas con conocimiento profundo y guardia | Una persona a tiempo parcial |
| Tiempo hasta el primer clúster productivo | Semanas | Horas |
Advertencia seria: operar un clúster propio en producción exige un equipo dedicado. No es una tarea que se pueda añadir a la lista de otro puesto. Necesitas cobertura para incidentes fuera de horario, un procedimiento probado de restauración de etcd, una política de parches del sistema operativo, y alguien que entienda los certificados cuando caduquen. Si tu organización no puede sostener eso, un clúster gestionado no es una comodidad: es una decisión de gestión de riesgo.
Cuándo kubeadm sí es la respuesta correcta: centro de datos propio o hardware específico (GPU, baja latencia, cumplimiento normativo); necesidad de configuraciones del plano de control que ningún gestionado permite; ahorro de coste con muchos nodos; y aprendizaje y certificación, porque el CKA (12-01) evalúa exactamente esto.
Para Rutas Norte montaremos un clúster con kubeadm en un laboratorio para aprender y practicar, con la conclusión razonada de producción reservada para 10-06.
- Preparación de las máquinas
Partimos de tres máquinas Ubuntu 24.04 LTS en el laboratorio de Rutas Norte:
| Nombre | Función | vCPU | RAM | IP |
|---|---|---|---|---|
rn-control-1 |
Plano de control | 2 | 4 GB | 10.10.0.11 |
rn-worker-1 |
Nodo de trabajo | 2 | 4 GB | 10.10.0.21 |
rn-worker-2 |
Nodo de trabajo | 2 | 4 GB | 10.10.0.22 |
Requisitos mínimos oficiales: 2 CPU y 2 GB de RAM por nodo de control, nombre de host único, dirección MAC única y product_uuid único (sudo cat /sys/class/dmi/id/product_uuid). Si dos máquinas clonadas comparten product_uuid, el clúster puede confundirlas: regenéralo desde el hipervisor.
3.1 Desactivar el swap
Todos los pasos siguientes se ejecutan en las tres máquinas.
sudo swapoff -a # ahora
sudo sed -i '/ swap / s/^/#/' /etc/fstab # y de forma permanente
free -h # comprobar que Swap está a 0¿Por qué hay que desactivar el swap? Porque rompe el modelo de recursos que estudiamos en 03-04 y 03-05. El planificador decide dónde va un pod según la memoria solicitada y disponible; el kubelet expulsa pods cuando el nodo tiene presión de memoria, siguiendo las clases de QoS. Si hay swap, un pod que supera su límite de memoria no muere: empieza a paginar a disco, se vuelve mil veces más lento, sus sondas de liveness empiezan a fallar de forma errática y el nodo entero se degrada sin que ninguna métrica lo explique. Es un fallo mucho peor que un OOMKilled limpio.
Kubernetes 1.30 tiene soporte experimental de swap (NodeSwap), pero está en beta con muchas advertencias. Para un clúster de producción, swap desactivado.
3.2 Módulos del kernel
# Declarar los módulos que deben cargarse en cada arranque
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
# Cargarlos ahora sin reiniciar
sudo modprobe overlay
sudo modprobe br_netfilter
# Verificar
lsmod | grep -E 'overlay|br_netfilter'Qué hace cada uno:
overlay: el sistema de ficheros por capas que usa containerd para montar las imágenes de contenedor. Sin él, el runtime no puede crear contenedores.br_netfilter: permite que el tráfico que atraviesa un puente de red de Linux (el que conecta los contenedores del nodo) sea visible paraiptables. Sin él, kube-proxy escribe reglas que nunca se aplican al tráfico entre pods, y los Services simplemente no funcionan.
3.3 Parámetros sysctl
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system # aplicar
sysctl net.ipv4.ip_forward net.bridge.bridge-nf-call-iptables # verificarip_forward = 1: el nodo debe reenviar paquetes entre interfaces. Sin esto, un pod dern-worker-1no puede hablar con uno dern-worker-2.bridge-nf-call-iptables = 1: activa lo quebr_netfilterhace posible.
Si sysctl te dice que el parámetro no existe, es que br_netfilter no está cargado. El orden importa.
3.4 Cortafuegos y puertos
| Nodo | Puerto/rango | Protocolo | Componente | Quién accede |
|---|---|---|---|---|
| Control | 6443 | TCP | kube-apiserver | Todos los nodos, kubectl, balanceador |
| Control | 2379-2380 | TCP | etcd (cliente y par) | Solo nodos de control |
| Control | 10250 | TCP | kubelet API | Plano de control (logs, exec) |
| Control | 10257 | TCP | kube-controller-manager | Local |
| Control | 10259 | TCP | kube-scheduler | Local |
| Trabajo | 10250 | TCP | kubelet API | Plano de control |
| Trabajo | 10256 | TCP | kube-proxy (health) | Balanceadores |
| Ambos | 30000-32767 | TCP | Rango de NodePort | Según necesidad |
| Ambos | Según CNI | UDP/TCP | Red de pods | Todos los nodos |
Sobre el último: cada CNI usa lo suyo. Calico con VXLAN necesita UDP 4789; con IP-in-IP necesita el protocolo IP 4; Cilium con VXLAN, UDP 8472. Consulta la documentación del CNI que elijas.
Para el laboratorio basta con abrir esos puertos con ufw y permitir todo el tráfico de la red interna: sudo ufw allow from 10.10.0.0/24. Verifica desde otro nodo con nc -zv 10.10.0.11 6443.
Consejo de laboratorio: si estás aprendiendo y algo no conecta, desactiva temporalmente el cortafuegos (sudo ufw disable) para descartar que sea el culpable. En producción, jamás; ahí se documenta cada regla.
- Instalación de containerd
Kubernetes no ejecuta contenedores directamente: habla con un runtime a través de la interfaz CRI. Desde 1.24, Docker no es un runtime válido de forma directa; containerd es la opción estándar.
# 1. Repositorio de Docker (containerd se distribuye desde ahí)
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 2. Instalar SOLO containerd (no el motor de Docker)
sudo apt-get update && sudo apt-get install -y containerd.io4.1 SystemdCgroup: el error más habitual
Aquí está el fallo número uno de los clústeres con kubeadm. containerd trae por defecto una configuración con SystemdCgroup = false, mientras que el kubelet de Kubernetes 1.30 usa el controlador de cgroups systemd. Si no coinciden, el clúster arranca aparentemente bien y luego los pods se reinician de forma aleatoria bajo presión de memoria, porque hay dos gestores de cgroups peleándose por el mismo nodo.
# 1. Generar la configuración por defecto (containerd viene sin config.toml completo)
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml > /dev/null
# 2. Cambiar SystemdCgroup de false a true
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# 3. VERIFICAR que el cambio se aplicó (no te fíes) y reiniciar
grep SystemdCgroup /etc/containerd/config.toml # debe decir: SystemdCgroup = true
sudo systemctl restart containerd && sudo systemctl enable containerdSi el grep no devuelve nada o devuelve false, la ruta de la opción ha cambiado en tu versión. Edita el fichero a mano y busca la sección [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options].
4.2 Comprobar que containerd responde a CRI
sudo apt-get install -y cri-tools
printf 'runtime-endpoint: unix:///run/containerd/containerd.sock\n' | sudo tee /etc/crictl.yaml
sudo crictl info | head -20Si esto responde, el runtime está listo. crictl es tu herramienta de depuración a nivel de nodo: crictl ps, crictl images, crictl logs. Cuando el apiserver está caído, kubectl no sirve de nada y crictl es lo único que tienes.
- Instalación de kubeadm, kubelet y kubectl
Los tres se instalan desde el repositorio oficial de Kubernetes, que desde 1.28 está segmentado por versión menor. Esto es importante: el repositorio de v1.30 solo contiene parches de 1.30, lo que evita saltos accidentales de versión menor.
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | \
sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] \
https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' | \
sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
# Instalar una versión de parche CONCRETA, no la última
sudo apt-get install -y kubelet=1.30.4-1.1 kubeadm=1.30.4-1.1 kubectl=1.30.4-1.15.1 Fijar las versiones con apt-mark hold
sudo apt-mark hold kubelet kubeadm kubectl
apt-mark showhold # comprobar
# Habilitar el kubelet (aún fallará y reintentará: es normal, no hay
# configuración hasta que se ejecute kubeadm init o join)
sudo systemctl enable --now kubeletPor qué el hold es imprescindible: sin él, un apt upgrade rutinario de mantenimiento del sistema actualizaría el kubelet en un nodo cualquiera. Ese kubelet nuevo podría ser incompatible con el plano de control, o simplemente reiniciarse en mitad del día y expulsar todos los pods de ese nodo. Las actualizaciones de Kubernetes son un procedimiento planificado (apartado 12), nunca un efecto secundario.
kubeadm init con fichero de configuración
kubeadm init con fichero de configuraciónPodrías arrancar el clúster con una ristra de banderas (kubeadm init --pod-network-cidr=... --control-plane-endpoint=... --apiserver-cert-extra-sans=...), y funcionaría. Pero el problema es evidente después de todo lo que llevamos de curso: no está versionado, nadie recuerda qué banderas se usaron hace un año, y al añadir un nodo de control hay que repetirlas exactamente. La forma profesional es un fichero de configuración, guardado junto a los manifiestos.
# kubeadm/rn-cluster.yaml
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint: # cómo anuncia ESTE nodo el apiserver
advertiseAddress: "10.10.0.11"
bindPort: 6443
nodeRegistration:
name: "rn-control-1"
criSocket: "unix:///run/containerd/containerd.sock"
taints: # taint estándar del plano de control (06-05)
- { key: "node-role.kubernetes.io/control-plane", effect: "NoSchedule" }
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: "v1.30.4"
clusterName: "rutas-norte-lab"
# Punto de entrada ESTABLE. Se declara desde el primer momento aunque
# hoy solo haya un nodo: cambiarlo después obliga a regenerar certificados.
controlPlaneEndpoint: "api-k8s.rutasnorte.example:6443"
networking:
# DEBE coincidir con lo que configures en Calico/Cilium, y NO puede
# solaparse con la red física (10.10.0.0/24) ni con serviceSubnet.
podSubnet: "10.244.0.0/16"
serviceSubnet: "10.96.0.0/12"
dnsDomain: "cluster.local"
apiServer:
# Nombres e IP válidos del certificado del apiserver. Acceder por un
# nombre no listado da "certificate is valid for ..., not ...".
certSANs: ["api-k8s.rutasnorte.example", "10.10.0.10", "10.10.0.11",
"localhost", "127.0.0.1"]
extraArgs: # registro de auditoría (08-06)
audit-log-path: "/var/log/kubernetes/audit.log"
audit-log-maxage: "30"
etcd:
local: { dataDir: "/var/lib/etcd" } # etcd apilado, pod estático aquí
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: "systemd" # DEBE coincidir con SystemdCgroup=true
# Recursos reservados al sistema y al kubelet, que NO se ofrecen al
# planificador. Sin esto, un nodo saturado se queda sin memoria para sshd.
systemReserved: { cpu: "200m", memory: "256Mi" }
kubeReserved: { cpu: "200m", memory: "256Mi" }
evictionHard: { memory.available: "200Mi", nodefs.available: "10%" }
serverTLSBootstrap: trueEjecutamos:
# Primero, descargar las imágenes por adelantado (evita timeouts
# durante el init si la red es lenta)
sudo kubeadm config images pull --config kubeadm/rn-cluster.yaml
# Arrancar
sudo kubeadm init --config kubeadm/rn-cluster.yaml --upload-certs--upload-certs sube los certificados del plano de control a un Secret temporal del clúster, cifrado, para que otros nodos de control puedan unirse sin copiarlos a mano. Caduca a las 2 horas.
Salida (muy abreviada):
[certs] Generating "ca" certificate and key
[certs] apiserver serving cert is signed for DNS names [api-k8s.rutasnorte.example
kubernetes kubernetes.default rn-control-1] and IPs [10.96.0.1 10.10.0.11 10.10.0.10]
[control-plane] Creating static Pod manifest for "kube-apiserver"
[etcd] Creating static Pod manifest for local etcd
[apiclient] All control plane components are healthy after 12.503 seconds
Your Kubernetes control-plane has initialized successfully!
You should now deploy a pod network to the cluster.
You can now join any number of control-plane nodes:
kubeadm join api-k8s.rutasnorte.example:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:1a2b3c... \
--control-plane --certificate-key 8f3c1a...
Then you can join any number of worker nodes:
kubeadm join api-k8s.rutasnorte.example:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:1a2b3c...Guarda esa salida. Las dos órdenes join del final son las que necesitas en los apartados 9 y 10.
# Configurar kubectl para tu usuario
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
kubectl get nodesNotReady. Es correcto, y lo explicamos en el apartado 8.
6.1 Campos que más importan
| Campo | Qué hace | Qué pasa si lo pones mal |
|---|---|---|
podSubnet |
Rango de IP de los pods | Si se solapa con la red física o con serviceSubnet, el enrutado se rompe de formas muy difíciles de depurar |
serviceSubnet |
Rango de las IP virtuales de los Services | Igual; además la IP .1 de este rango es la del Service kubernetes |
controlPlaneEndpoint |
Nombre estable del plano de control | Sin él, no puedes añadir nodos de control después sin regenerar certificados |
certSANs |
Nombres válidos del certificado del apiserver | Acceder por un nombre no listado da error de certificado |
kubernetesVersion |
Versión a instalar | Si no coincide con los binarios instalados, init avisa |
cgroupDriver |
Gestor de cgroups del kubelet | Si no coincide con containerd, pods inestables bajo presión |
criSocket |
Socket del runtime | Con varios runtimes instalados, kubeadm no sabe cuál usar |
Sobre controlPlaneEndpoint: aunque hoy solo tengas un nodo de control y no un balanceador, decláralo con un nombre DNS desde el principio. Puedes hacer que ese nombre apunte hoy a 10.10.0.11 y mañana a la IP virtual del balanceador. Si no lo haces, el certificado y todos los kubeconfig apuntarán a la IP del nodo, y montar HA más tarde exige regenerar certificados en todo el clúster.
- Qué crea exactamente kubeadm en el nodo
Este apartado conecta directamente con la arquitectura que vimos en 01-02. Ahora vamos a verla en el disco.
7.1 Pods estáticos del plano de control
-rw------- 1 root root 2405 Sep 12 10:02 etcd.yaml
-rw------- 1 root root 3891 Sep 12 10:02 kube-apiserver.yaml
-rw------- 1 root root 3320 Sep 12 10:02 kube-controller-manager.yaml
-rw------- 1 root root 1463 Sep 12 10:02 kube-scheduler.yamlEsto es un pod estático: el kubelet vigila ese directorio y arranca cualquier pod que encuentre en él, sin pasar por el apiserver. Es la solución al problema del huevo y la gallina: ¿cómo arranca el apiserver, si para crear un pod hace falta un apiserver? Respuesta: no se crea como un pod normal, lo lee el kubelet directamente del disco.
Consecuencias prácticas muy importantes:
- Si editas
/etc/kubernetes/manifests/kube-apiserver.yaml, el kubelet detecta el cambio y reinicia el apiserver automáticamente en segundos. Es la forma de añadir una bandera a mano. - Si borras ese fichero, el apiserver desaparece y pierdes el clúster. Copia de seguridad antes de tocar nada.
kubectl delete pod kube-apiserver-rn-control-1 -n kube-systemno lo elimina de verdad: el kubelet lo recrea, porque la fuente de verdad es el fichero.- Si el apiserver no arranca,
kubectlno funciona. Depúralo concrictl ps -aycrictl logs, o conjournalctl -u kubelet.
# Los pods estáticos aparecen como objetos "espejo" en la API,
# con el nombre del nodo como sufijo: esa es su marca distintiva.
kubectl get pods -n kube-system -l tier=control-planeetcd-rn-control-1 1/1 Running 0 3m
kube-apiserver-rn-control-1 1/1 Running 0 3m
kube-controller-manager-rn-control-1 1/1 Running 0 3m
kube-scheduler-rn-control-1 1/1 Running 0 3m7.2 Certificados
apiserver.crt apiserver.key apiserver-etcd-client.crt apiserver-etcd-client.key
apiserver-kubelet-client.crt apiserver-kubelet-client.key ca.crt ca.key etcd/
front-proxy-ca.crt front-proxy-ca.key front-proxy-client.crt front-proxy-client.key
sa.key sa.pub| Fichero | Función |
|---|---|
ca.crt / ca.key |
Autoridad certificadora raíz del clúster. Es el secreto más valioso: quien la tiene puede emitir credenciales de administrador |
apiserver.crt |
Certificado de servidor del apiserver, con los certSANs |
apiserver-kubelet-client.* |
Con qué se identifica el apiserver al hablar con los kubelets (kubectl logs, exec) |
apiserver-etcd-client.* |
Con qué se identifica el apiserver ante etcd |
etcd/ |
CA y certificados propios de etcd (servidor, par, sanidad) |
front-proxy-* |
Para el agregador de API (métricas personalizadas de 09-01) |
sa.key / sa.pub |
Par de claves con el que se firman los tokens de las ServiceAccounts (03-06) |
Para inspeccionar los nombres válidos de un certificado: sudo openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A2 'Subject Alternative Name'.
7.3 Ficheros kubeconfig
En /etc/kubernetes/*.conf hay uno por componente: controller-manager.conf, scheduler.conf, kubelet.conf (credenciales de este nodo, con rotación automática) y dos de administración:
admin.conf: el que copiaste a~/.kube/config. Desde 1.29 pertenece al grupokubeadm:cluster-admins, sujeto a RBAC (08-01).super-admin.conf: salta el RBAC por completo (system:masters). Es el rompecristales cuando has roto el RBAC y no puedes ni arreglarlo. No lo repartas.
7.4 Datos de etcd
En /var/lib/etcd/member/ (con sus directorios snap y wal) vive el estado completo de tu clúster: todos los objetos, todos los Secrets, todo. Es lo que hay que respaldar (apartado 13).
- Instalar el CNI y por qué los nodos están NotReady
Type Status Reason Message
Ready False KubeletNotReady container runtime network not
ready: NetworkReady=false
reason:NetworkPluginNotReady
message:Network plugin returns
error: cni plugin not initializedEl mensaje es explícito. El kubelet se niega a declararse Ready mientras no exista un complemento de red que sepa asignar IP a los pods. Recuerda de 04-01: Kubernetes define el contrato (todo pod tiene una IP, todos los pods se ven sin NAT) pero no lo implementa. Eso es trabajo del CNI.
Verás también CoreDNS en Pending, y es normal: CoreDNS es un pod y necesita red para arrancar.
Instalamos Calico (el detalle de los CNI está en 04-01; aquí solo lo necesario):
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/tigera-operator.yaml
cat <<EOF | kubectl apply -f -
apiVersion: operator.tigera.io/v1
kind: Installation
metadata: { name: default }
spec:
calicoNetwork:
ipPools:
# DEBE coincidir con networking.podSubnet del fichero de kubeadm
- { cidr: 10.244.0.0/16, encapsulation: VXLANCrossSubnet, natOutgoing: Enabled }
EOF
kubectl get nodes -w # NotReady -> Ready en menos de un minutoY CoreDNS arranca solo. La secuencia completa es:
sequenceDiagram
participant K as kubeadm init
participant Kl as kubelet
participant A as apiserver
participant C as CNI (Calico)
K->>Kl: escribe pods estáticos + config
Kl->>A: arranca apiserver, etcd, scheduler, cm
Kl->>A: registra el nodo (NotReady: sin red)
Note over A: CoreDNS queda Pending
K-->>C: (tú aplicas los manifiestos del CNI)
C->>Kl: instala el binario CNI en /opt/cni/bin
Kl->>A: NetworkReady=true → nodo Ready
A->>A: CoreDNS se planifica y arranca
- Unir nodos de trabajo
En rn-worker-1 y rn-worker-2, ya preparados con los apartados 3, 4 y 5:
sudo kubeadm join api-k8s.rutasnorte.example:6443 \
--token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:1a2b3c4d5e6f...[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...
This node has joined the cluster.Qué significan los dos parámetros:
--token: una credencial temporal (caduca a las 24 horas) que autoriza al nodo a pedir su certificado. Es un Secret de tipobootstrap.kubernetes.io/tokenenkube-system.--discovery-token-ca-cert-hash: el hash SHA-256 de la clave pública de la CA del clúster. Sirve para que el nodo verifique al apiserver, no al revés. Sin él, un atacante podría suplantar el plano de control y capturar el token. Nunca uses--discovery-token-unsafe-skip-ca-verificationfuera de un laboratorio.
9.1 Generar un token nuevo cuando caduca
Es la situación más habitual: quieres añadir un nodo tres semanas después y el token original ya no vale.
# En un nodo de control: una sola orden que imprime todo lo necesario
sudo kubeadm token create --print-join-commandkubeadm join api-k8s.rutasnorte.example:6443 --token 7t8u9i.qwertyuiopasdfgh \
--discovery-token-ca-cert-hash sha256:1a2b3c4d5e6f...Órdenes relacionadas:
sudo kubeadm token list # tokens vivos y caducidad
sudo kubeadm token create --ttl 2h --print-join-command
sudo kubeadm token delete 7t8u9i.qwertyuiopasdfgh
# Recalcular el hash de la CA a mano, si perdiste la salida del init
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \
openssl rsa -pubin -outform der 2>/dev/null | \
openssl dgst -sha256 -hex | sed 's/^.* //'9.2 Etiquetar los nodos
kubeadm no pone la etiqueta de rol en los nodos de trabajo por seguridad (un kubelet no puede autoasignarse etiquetas de rol). Se hace desde el plano de control:
kubectl label node rn-worker-1 node-role.kubernetes.io/worker=
kubectl label node rn-worker-2 node-role.kubernetes.io/worker=
# Y las etiquetas de topología de Rutas Norte (09-05)
kubectl label node rn-worker-1 topology.kubernetes.io/zone=lab-a
kubectl label node rn-worker-2 topology.kubernetes.io/zone=lab-b
kubectl get nodes # los tres Ready, con sus roles
- Plano de control en alta disponibilidad
Con un solo nodo de control, si rn-control-1 cae, el clúster sigue sirviendo tráfico (los pods siguen corriendo, kube-proxy sigue enrutando) pero pierdes toda capacidad de gestión: no hay reprogramación de pods, ni escalado, ni despliegues, ni HPA. Y si el disco de etcd se pierde, pierdes el clúster entero.
10.1 El punto de entrada estable
Todos los nodos de control publican el mismo puerto 6443. Necesitas algo delante que reparta:
flowchart TB
K[kubectl / kubelets<br/>de los nodos] --> LB["api-k8s.rutasnorte.example:6443<br/>(IP virtual 10.10.0.10)"]
LB --> C1[rn-control-1<br/>:6443]
LB --> C2[rn-control-2<br/>:6443]
LB --> C3[rn-control-3<br/>:6443]
C1 -.-> E[(etcd apilado<br/>quórum de 3)]
C2 -.-> E
C3 -.-> E
Opciones: HAProxy + keepalived con una IP virtual flotante (lo más común en centro de datos propio), el balanceador de capa 4 de la nube si estás en máquinas virtuales, o kube-vip como pod estático si no quieres infraestructura externa. Lo que no vale es un DNS con varios registros A: el cliente cachea y no detecta caídas.
Configuración mínima de HAProxy:
# /etc/haproxy/haproxy.cfg
frontend k8s-api
bind *:6443
mode tcp
default_backend k8s-control-plane
backend k8s-control-plane
mode tcp
option tcp-check
balance roundrobin
server rn-control-1 10.10.0.11:6443 check fall 3 rise 2
server rn-control-2 10.10.0.12:6443 check fall 3 rise 2
server rn-control-3 10.10.0.13:6443 check fall 3 rise 2Es TCP puro (modo tcp), no HTTP: la conexión al apiserver es TLS de extremo a extremo y el balanceador no debe terminarla. Con check, si un nodo deja de responder en 6443, HAProxy deja de enviarle tráfico automáticamente.
10.2 etcd apilado frente a etcd externo
| Aspecto | etcd apilado | etcd externo |
|---|---|---|
| Dónde corre | Como pod estático en cada nodo de control | En máquinas dedicadas |
| Máquinas necesarias | 3 (control + etcd juntos) | 3 de control + 3 de etcd = 6 |
| Configuración | Automática con kubeadm | Manual: certificados, unidades systemd |
| Aislamiento de fallos | Perder un nodo pierde un control y un miembro de etcd | Independientes |
| Rendimiento bajo carga | etcd compite con el apiserver por E/S y CPU | etcd tiene su disco para él |
| Recomendación | Por defecto, para la mayoría | Clústeres muy grandes o con requisitos estrictos |
etcd necesita quórum: 1 miembro tolera 0 fallos, 2 miembros también toleran 0 (¡peor que 1!), 3 toleran 1, 4 toleran 1, y 5 toleran 2. Por eso el número de nodos de control es siempre impar: 1 (laboratorio), 3 (producción normal) o 5 (clústeres grandes).
Para etcd externo, se declara en el fichero de kubeadm:
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
etcd:
external:
endpoints:
- https://10.10.0.31:2379
- https://10.10.0.32:2379
- https://10.10.0.33:2379
caFile: /etc/kubernetes/pki/etcd/ca.crt
certFile: /etc/kubernetes/pki/apiserver-etcd-client.crt
keyFile: /etc/kubernetes/pki/apiserver-etcd-client.key10.3 Unir nodos de control adicionales
# En rn-control-2 y rn-control-3 (ya preparados con 3, 4 y 5)
sudo kubeadm join api-k8s.rutasnorte.example:6443 \
--token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:1a2b3c... \
--control-plane \
--certificate-key 8f3c1a...La --certificate-key viene de --upload-certs. Si han pasado más de 2 horas, se regenera:
[upload-certs] Using certificate key:
f0e1d2c3b4a5968778695a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4dPara verificar la salud del clúster de etcd, kubectl -n kube-system exec etcd-rn-control-1 -- etcdctl ... endpoint status --cluster -w table debe mostrar los tres miembros, con la misma versión, tamaño de base de datos parecido y exactamente uno con IS LEADER = true.
- Gestión de certificados
Aquí está la trampa que sorprende a más equipos: los certificados que genera kubeadm caducan al año. Un clúster montado en marzo deja de funcionar en marzo del año siguiente, sin previo aviso, con un mensaje así:
Kubernetes renueva automáticamente los certificados durante kubeadm upgrade. Como muchos equipos actualizan al menos una vez al año, nunca lo ven. El que no actualiza, se lleva el susto.
11.1 Comprobar la caducidad
CERTIFICATE EXPIRES RESIDUAL TIME
admin.conf Sep 12, 2026 10:02 UTC 364d
apiserver Sep 12, 2026 10:02 UTC 364d
apiserver-etcd-client Sep 12, 2026 10:02 UTC 364d
apiserver-kubelet-client Sep 12, 2026 10:02 UTC 364d
etcd-server, etcd-peer, scheduler.conf, controller-manager.conf ... 364d
CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME
ca, etcd-ca, front-proxy-ca Sep 10, 2035 10:02 UTC 9yLas CA duran 10 años; los certificados que firman, 1 año.
Recomendación de operación: no lo dejes al calendario humano. El apiserver expone apiserver_client_certificate_expiration_seconds_bucket, sobre la que se puede montar una alerta en Alertmanager (07-04). Más simple y más fiable todavía: un CronJob (06-03) o una tarea del sistema que ejecute kubeadm certs check-expiration semanalmente y avise si queda menos de un mes.
11.2 Renovar
# EN CADA NODO DE CONTROL
sudo cp -r /etc/kubernetes/pki /root/pki-respaldo-$(date +%F) # copia primero
sudo kubeadm certs renew all # o solo uno: kubeadm certs renew apiserver
# Reiniciar los pods estáticos: basta con sacarlos y devolverlos
sudo mkdir -p /tmp/manifests-parados
sudo mv /etc/kubernetes/manifests/*.yaml /tmp/manifests-parados/
sleep 20
sudo mv /tmp/manifests-parados/*.yaml /etc/kubernetes/manifests/
# Actualizar tu kubeconfig personal, que también se renovó
sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
sudo kubeadm certs check-expiration && kubectl get nodesNota sobre el kubelet: su certificado se rota solo, automáticamente, si rotateCertificates: true (por defecto). Los certificados que hay que renovar a mano son los del plano de control.
- Actualización de versión
12.1 La regla del salto de una versión menor
Solo puedes saltar una versión menor cada vez. De 1.28 a 1.30 no se puede ir directo: hay que hacer 1.28 → 1.29 → 1.30. Cada salto es un procedimiento completo.
Además, la política de desfase (version skew) exige:
| Componente | Desfase permitido respecto al apiserver |
|---|---|
| kube-controller-manager, kube-scheduler | Hasta 1 menor por debajo |
| kubelet | Hasta 3 menores por debajo |
| kube-proxy | Hasta 3 menores por debajo |
| kubectl | 1 por encima o 1 por debajo |
De ahí el orden obligatorio: primero el plano de control, después los nodos de trabajo. Nunca al revés.
12.2 Antes de empezar
Tres cosas, por este orden: copia de seguridad de etcd (apartado 13, no negociable), leer las notas de la versión, y comprobar que nada usa APIs que desaparecen.
apiserver_requested_deprecated_apis{group="flowcontrol.apiserver.k8s.io",
removed_release="1.32",resource="flowschemas",version="v1beta3"} 1Cualquier línea aquí es trabajo que hay que hacer antes de actualizar. Herramientas como kubent (kube-no-trouble) o pluto escanean tus manifiestos buscando lo mismo.
12.3 Primer nodo de control
# --- EN rn-control-1 ---
# 1. Liberar el hold y actualizar SOLO kubeadm
sudo apt-mark unhold kubeadm
sudo apt-get update
# Cambiar el repositorio a la nueva versión menor
sudo sed -i 's|v1.30|v1.31|' /etc/apt/sources.list.d/kubernetes.list
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | \
sudo gpg --dearmor --yes -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
sudo apt-get update
sudo apt-get install -y kubeadm=1.31.1-1.1
sudo apt-mark hold kubeadm
kubeadm versionCOMPONENT CURRENT TARGET
kube-apiserver v1.30.4 v1.31.1
kube-controller-manager v1.30.4 v1.31.1
kube-scheduler v1.30.4 v1.31.1
kube-proxy v1.30.4 v1.31.1
CoreDNS v1.11.1 v1.11.3
etcd 3.5.14 3.5.15
Components that must be upgraded manually: kubelet en los 3 nodos.
You can now apply the upgrade by executing: kubeadm upgrade apply v1.31.1# 3. Aplicar. Actualiza los manifiestos de los pods estáticos y
# RENUEVA LOS CERTIFICADOS de paso.
sudo kubeadm upgrade apply v1.31.1# 4. Drenar el nodo: mover sus pods a otro sitio y no aceptar nuevos.
# Respeta los PodDisruptionBudgets de 09-05.
kubectl drain rn-control-1 --ignore-daemonsets --delete-emptydir-data
# 5. Actualizar kubelet y kubectl
sudo apt-mark unhold kubelet kubectl
sudo apt-get install -y kubelet=1.31.1-1.1 kubectl=1.31.1-1.1
sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload
sudo systemctl restart kubelet
# 6. Devolver el nodo al servicio
kubectl uncordon rn-control-1
kubectl get nodesNAME STATUS ROLES AGE VERSION
rn-control-1 Ready control-plane 3d v1.31.1
rn-worker-1 Ready worker 3d v1.30.4
rn-worker-2 Ready worker 3d v1.30.412.4 Resto de nodos, uno a uno
Para los demás nodos de control y para los de trabajo, el procedimiento es el mismo del apartado anterior con una sola diferencia: se usa sudo kubeadm upgrade node en vez de upgrade apply.
# En cada nodo restante, de uno en uno:
sudo apt-mark unhold kubeadm && sudo apt-get install -y kubeadm=1.31.1-1.1 && sudo apt-mark hold kubeadm
sudo kubeadm upgrade node # <-- la diferencia
kubectl drain <nodo> --ignore-daemonsets --delete-emptydir-data --timeout=300s
sudo apt-mark unhold kubelet kubectl
sudo apt-get install -y kubelet=1.31.1-1.1 kubectl=1.31.1-1.1
sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload && sudo systemctl restart kubelet
kubectl uncordon <nodo>
kubectl get nodes # confirmar Ready y v1.31.1 ANTES de pasar al siguienteUno a uno, verificando entre medias. Si drain se bloquea, es un PodDisruptionBudget haciendo su trabajo: postgres-reservas no puede quedarse sin réplicas. Investiga con kubectl get pdb -A antes de forzar nada.
- Copia de seguridad y restauración de etcd
En 05-06 hicimos copias de los datos de las aplicaciones con Velero. Esto es otra cosa: es la copia de la definición del clúster. Todos los Deployments, Secrets, ConfigMaps, RBAC, CRDs... todo vive en etcd.
Sin copia de etcd, un fallo del disco del nodo de control te obliga a recrear el clúster desde cero y volver a aplicar todos los manifiestos. Con GitOps (10-05) eso sería recuperable, pero perderías todo lo que no esté en Git.
13.1 Hacer la copia
sudo apt-get install -y etcd-client
# En un nodo de control
sudo ETCDCTL_API=3 etcdctl snapshot save /var/respaldos/etcd-$(date +%F-%H%M).db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
# Verificar (¡una copia no verificada no es una copia!)
sudo etcdutl --write-out=table snapshot status /var/respaldos/etcd-2026-09-12-1042.db+----------+----------+------------+------------+
| HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| 4f2a91bc | 184920 | 1847 | 28 MB |
+----------+----------+------------+------------+Esa misma orden en /etc/cron.d/respaldo-etcd, a las 03:15 diarias y encadenada con un find /var/respaldos -name 'etcd-*.db' -mtime +14 -delete, cubre la automatización con retención de dos semanas.
Tres cosas imprescindibles: copia también /etc/kubernetes/pki/ (el snapshot sin la CA es inútil), sácalas del nodo (una copia en el disco que se va a estropear no es una copia) y cífralas, porque contienen todos los Secrets de rutas-norte-pro, incluidas las credenciales de postgres-reservas y las de la pasarela de pagos: recuerda de 03-02 que los Secrets están en base64, no cifrados.
13.2 Restaurar
Procedimiento de emergencia, para cuando el plano de control es irrecuperable:
# 1. Parar el plano de control sacando los pods estáticos
sudo mkdir -p /tmp/manifests-parados
sudo mv /etc/kubernetes/manifests/*.yaml /tmp/manifests-parados/
sudo crictl ps # esperar a que no quede nada
# 2. Apartar los datos actuales (NO borrarlos: por si acaso)
sudo mv /var/lib/etcd /var/lib/etcd-roto-$(date +%F)
# 3. Restaurar el snapshot en un directorio nuevo
sudo etcdutl snapshot restore /var/respaldos/etcd-2026-09-12-1042.db \
--data-dir=/var/lib/etcd --name=rn-control-1 \
--initial-cluster=rn-control-1=https://10.10.0.11:2380 \
--initial-advertise-peer-urls=https://10.10.0.11:2380
# 4. Devolver los pods estáticos y verificar
sudo mv /tmp/manifests-parados/*.yaml /etc/kubernetes/manifests/
sleep 60 && kubectl get nodes && kubectl get pods -APuntos críticos:
- En un clúster HA, hay que restaurar en los tres nodos de control, con el snapshot idéntico y cada uno con su
--namey sus URL. Si los datos difieren, etcd no formará quórum. - El estado vuelve al momento de la copia. Todo lo creado después se pierde.
- Los pods que estaban corriendo siguen corriendo (el kubelet no se ha enterado), pero pueden aparecer objetos "fantasma": pods que existen en el nodo pero no en etcd, o al revés. Después de restaurar, revisa
kubectl get pods -Acontracrictl psen cada nodo.
Ensaya la restauración. Una copia de seguridad que nunca se ha restaurado no es una copia de seguridad: es un fichero. Hazlo en el laboratorio, cronométrate, y escribe el procedimiento en un runbook (11-06).
kubeadm reset y limpieza
kubeadm reset y limpiezaCuando algo sale mal y quieres volver a empezar, o retirar un nodo:
# 1. Desde el plano de control: drenar y quitar el nodo del clúster
kubectl drain rn-worker-2 --ignore-daemonsets --delete-emptydir-data
kubectl delete node rn-worker-2
# 2. En el propio nodo: deshacer lo que hizo kubeadm
sudo kubeadm reset -f[reset] Deleting contents of directories: [/etc/kubernetes/manifests
/var/lib/kubelet /etc/kubernetes/pki]
The reset process does not clean CNI configuration. To do so, you must
remove /etc/cni/net.d
The reset process does not reset or clean up iptables rules.reset es honesto sobre lo que no limpia. Hay que rematarlo a mano:
sudo rm -rf /etc/cni/net.d /opt/cni/bin/calico* # configuración del CNI
sudo iptables -F && sudo iptables -t nat -F # reglas de kube-proxy
sudo iptables -t mangle -F && sudo iptables -X
sudo ipvsadm -C 2>/dev/null || true # si usaba modo IPVS
for i in cni0 flannel.1 vxlan.calico; do # interfaces virtuales
sudo ip link delete "$i" 2>/dev/null || true
done
rm -rf $HOME/.kubeSi te saltas los pasos 3, 4 y 5, el siguiente kubeadm init o join en esa máquina puede parecer que funciona y luego dar fallos de red inexplicables: reglas viejas de iptables enrutando a IP de pods que ya no existen. Es una de las causas más frecuentes de «reinstalé y sigue fallando».
Errores Comunes y Consejos
1. SystemdCgroup = false en containerd. Es el error número uno. El clúster arranca, todo parece bien, y días después los pods se reinician bajo presión de memoria sin explicación. Verifica siempre con grep SystemdCgroup /etc/containerd/config.toml y reinicia containerd después de cambiarlo.
2. Olvidar swapoff -a en /etc/fstab. Lo desactivas, funciona, reinicias la máquina un mes después y el kubelet no arranca. Comenta la línea en /etc/fstab siempre.
3. podSubnet que se solapa con la red física. Si tu laboratorio usa 10.10.0.0/24 y declaras podSubnet: 10.0.0.0/8, el enrutado se rompe de formas que parecen magia negra. Elige rangos que no colisionen con nada: ni con la red de nodos, ni con serviceSubnet, ni con la red corporativa.
4. El rango del CNI distinto de podSubnet. Declaras 10.244.0.0/16 en kubeadm e instalas Calico con su valor por defecto 192.168.0.0/16. Los pods obtienen IP del rango del CNI, pero el controller-manager asigna bloques del otro. Deben coincidir.
5. No declarar controlPlaneEndpoint desde el principio. Añadir alta disponibilidad después obliga a regenerar certificados en todo el clúster. Declara un nombre DNS desde el primer día, aunque apunte a una sola IP.
6. Certificados caducados al año. Real y muy común. Alerta en el calendario, o mejor, en la monitorización.
7. Saltar dos versiones menores, o actualizar los nodos antes que el plano de control. kubeadm upgrade apply v1.32.0 desde 1.30 es rechazado; y un kubelet 1.31 contra un apiserver 1.30 está fuera de la política de desfase. Una menor cada vez, plano de control primero.
8. kubectl drain bloqueado y forzarlo con --force. Lo que te bloquea suele ser un PDB (09-05) protegiendo postgres-reservas o redis-cache. Forzarlo puede provocar pérdida de datos. Investiga antes con kubectl get pdb -A.
9. Copias de etcd sin /etc/kubernetes/pki. El snapshot sin la CA no te devuelve un clúster funcional. Copia ambos, juntos, cifrados y fuera del nodo.
10. kubeadm reset sin limpiar iptables y CNI. Restos que provocan fallos de red en la reinstalación. Ejecuta la limpieza completa del apartado 14.
11. No fijar apt-mark hold. Un apt upgrade rutinario que actualice el kubelet en producción es un incidente esperando a ocurrir.
Ejercicios
Ejercicio 1: script de preparación de nodo
Escribe un script preparar-nodo.sh idempotente (que se pueda ejecutar dos veces sin romper nada) que deje una máquina Ubuntu lista para kubeadm join: swap desactivado de forma permanente, módulos y sysctl, containerd con SystemdCgroup=true verificado, y kubeadm/kubelet/kubectl 1.30.4 fijados. El script debe fallar con un mensaje claro si alguna verificación no pasa.
Ejercicio 2: fichero de configuración para HA
Escribe el fichero de configuración de kubeadm para el clúster rutas-norte-lab con estos requisitos: tres nodos de control detrás de api-k8s.rutasnorte.example (IP virtual 10.10.0.10), podSubnet 172.16.0.0/16, etcd apilado, auditoría activada, y el kubelet reservando 500m de CPU y 512Mi de memoria para el sistema. Explica por qué elegiste 172.16.0.0/16 y qué comprobarías antes.
Ejercicio 3: plan de actualización y recuperación
Redacta el procedimiento completo, en orden y con las órdenes exactas, para actualizar el clúster de 1.30.4 a 1.31.1 con un nodo de control y dos de trabajo, incluyendo la copia de seguridad previa. Añade el criterio de "punto sin retorno" y qué harías si el apiserver no arranca tras upgrade apply.
Soluciones
Solución 1
#!/usr/bin/env bash
# preparar-nodo.sh — idempotente
set -euo pipefail
VER="1.30.4-1.1"
sudo swapoff -a
sudo sed -i '/\sswap\s/ s/^\([^#]\)/#\1/' /etc/fstab # no recomenta lo ya comentado
[[ $(swapon --show | wc -l) -eq 0 ]] || { echo "ERROR: swap activo"; exit 1; }
printf 'overlay\nbr_netfilter\n' | sudo tee /etc/modules-load.d/k8s.conf >/dev/null
sudo modprobe overlay; sudo modprobe br_netfilter
printf 'net.bridge.bridge-nf-call-iptables=1\nnet.bridge.bridge-nf-call-ip6tables=1\nnet.ipv4.ip_forward=1\n' \
| sudo tee /etc/sysctl.d/k8s.conf >/dev/null
sudo sysctl --system >/dev/null
[[ $(sysctl -n net.ipv4.ip_forward) == 1 ]] || { echo "ERROR: ip_forward"; exit 1; }
command -v containerd >/dev/null || sudo apt-get install -y containerd.io
sudo mkdir -p /etc/containerd
[[ -s /etc/containerd/config.toml ]] || \
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
grep -q 'SystemdCgroup = true' /etc/containerd/config.toml || { echo "ERROR: cgroup"; exit 1; }
sudo systemctl restart containerd && sudo systemctl enable containerd
sudo apt-mark unhold kubelet kubeadm kubectl 2>/dev/null || true
sudo apt-get install -y kubelet="$VER" kubeadm="$VER" kubectl="$VER"
sudo apt-mark hold kubelet kubeadm kubectl
sudo systemctl enable --now kubeletClaves de idempotencia: el sed que no recomenta líneas ya comentadas, el [[ -s ]] antes de regenerar la configuración de containerd, y el unhold antes de instalar.
Solución 2
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: "v1.30.4"
clusterName: "rutas-norte-lab"
controlPlaneEndpoint: "api-k8s.rutasnorte.example:6443"
networking:
podSubnet: "172.16.0.0/16"
serviceSubnet: "10.96.0.0/12"
apiServer:
certSANs: ["api-k8s.rutasnorte.example", "10.10.0.10",
"10.10.0.11", "10.10.0.12", "10.10.0.13"]
extraArgs:
audit-log-path: "/var/log/kubernetes/audit.log"
audit-policy-file: "/etc/kubernetes/audit-policy.yaml"
audit-log-maxage: "30"
extraVolumes: # montar la política en el pod estático
- { name: audit-policy, hostPath: /etc/kubernetes/audit-policy.yaml,
mountPath: /etc/kubernetes/audit-policy.yaml, readOnly: true, pathType: File }
etcd:
local: { dataDir: "/var/lib/etcd" }
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: "systemd"
systemReserved: { cpu: "500m", memory: "512Mi" }
kubeReserved: { cpu: "200m", memory: "256Mi" }
evictionHard: { memory.available: "200Mi", nodefs.available: "10%" }Por qué 172.16.0.0/16: es rango privado (RFC 1918) que no colisiona con la red del laboratorio (10.10.0.0/24) ni con serviceSubnet (10.96.0.0/12), y da 65 536 IP, suficientes. Comprobaciones previas: que 172.16/16 no se use en la red corporativa ni en la VPN, y configurar el CNI con ese mismo CIDR.
Solución 3
# --- FASE 0: copia previa (punto de retorno) ---
sudo etcdctl snapshot save /var/respaldos/pre-131.db --endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
sudo tar czf /var/respaldos/pki-pre-131.tgz /etc/kubernetes/pki
sudo etcdutl --write-out=table snapshot status /var/respaldos/pre-131.db # verificar
kubectl get --raw /metrics | grep apiserver_requested_deprecated_apis # APIs obsoletas
# --- FASE 1: rn-control-1 ---
sudo apt-mark unhold kubeadm
sudo sed -i 's|v1.30|v1.31|' /etc/apt/sources.list.d/kubernetes.list
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | \
sudo gpg --dearmor --yes -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
sudo apt-get update && sudo apt-get install -y kubeadm=1.31.1-1.1 && sudo apt-mark hold kubeadm
sudo kubeadm upgrade plan
sudo kubeadm upgrade apply v1.31.1 # <-- PUNTO SIN RETORNO
kubectl drain rn-control-1 --ignore-daemonsets --delete-emptydir-data
sudo apt-mark unhold kubelet kubectl && sudo apt-get install -y kubelet=1.31.1-1.1 kubectl=1.31.1-1.1
sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload && sudo systemctl restart kubelet
kubectl uncordon rn-control-1 && kubectl get nodes
# --- FASE 2 y 3: rn-worker-1, luego rn-worker-2 (uno a uno) ---
# repositorio + kubeadm + `sudo kubeadm upgrade node` + drain + kubelet + uncordonPunto sin retorno: kubeadm upgrade apply, porque migra el esquema de etcd. Antes, basta con revertir los paquetes; después, la vuelta atrás exige restaurar el snapshot.
Si el apiserver no arranca: sudo crictl ps -a | grep apiserver, sudo crictl logs <id> y journalctl -u kubelet -f. Causas típicas: una bandera nueva no soportada o un certificado mal generado. Si no se resuelve en la ventana de mantenimiento, restaurar pre-131.db y pki-pre-131.tgz con el procedimiento del apartado 13.2 y revertir los paquetes a 1.30.4.
Conclusión
Ya sabes montar un clúster de Kubernetes desde cero sobre máquinas propias. Lo esencial:
- kubeadm arranca un clúster conforme; no aprovisiona máquinas ni instala el CNI. Esa frontera explica el 90 % de las dudas iniciales, incluido el nodo
NotReady. - La preparación de las máquinas —swap,
overlayybr_netfilter,ip_forward, puertos, y sobre todoSystemdCgroup = trueen containerd— es donde nacen los fallos más difíciles de diagnosticar. - Un fichero de configuración versionado (
ClusterConfiguration,InitConfiguration,KubeletConfiguration) es infinitamente mejor que una ristra de banderas;controlPlaneEndpointycertSANsbien puestos desde el primer día te ahorran regenerar certificados más tarde. - kubeadm deja en el nodo pods estáticos en
/etc/kubernetes/manifests, los certificados en/etc/kubernetes/pkiy los kubeconfig: la arquitectura de 01-02, hecha ficheros. - Los nodos se unen con token y hash de la CA; el token caduca a las 24 horas y se regenera con
kubeadm token create --print-join-command. La alta disponibilidad exige un punto de entrada estable y un número impar de miembros de etcd, normalmente 3 apilados. - Los certificados caducan al año, las actualizaciones van una versión menor cada vez con el plano de control antes que los nodos de trabajo, y las copias de etcd (snapshot +
pki, cifradas, fuera del nodo, con restauración ensayada) son la diferencia entre un mal día y el fin de la empresa.
Y la advertencia que hay que repetir: operar esto en producción exige un equipo dedicado con guardia. En 10-06 veremos qué te ahorra un clúster gestionado y a qué precio.
Pero antes tenemos que resolver el problema con el que empezó este módulo. Ya sabemos crear clústeres, locales y propios; lo que sigue sin resolverse son los 120 ficheros YAML duplicados de Rutas Norte. En la siguiente lección, Helm, conoceremos el gestor de paquetes de Kubernetes: charts, valores, plantillas y releases. Es la herramienta con la que ya instalamos cert-manager (04-05) y kube-prometheus-stack (07-03) sin explicarla; toca entenderla de verdad y usarla para empaquetar la plataforma Rutas Norte en un único chart con un fichero de valores por entorno.
Curso de Kubernetes
Módulo 1: Introducción a Kubernetes
- ¿Qué es Kubernetes?
- Arquitectura de Kubernetes
- Conceptos y Terminología Clave
- Configuración de un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objetos, Manifiestos YAML y el Modelo Declarativo
- El Proyecto del Curso: la Plataforma Rutas Norte
Módulo 2: Componentes Principales de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualizaciones, Rollbacks y Estrategias de Despliegue
- Servicios
- Namespaces
- Etiquetas, Selectores y Anotaciones
Módulo 3: Gestión de Configuración y Secretos
- ConfigMaps
- Secrets
- Variables de Entorno
- Cuotas y Límites de Recursos
- LimitRanges y Clases de Calidad de Servicio (QoS)
- ServiceAccounts y Acceso a la API desde los Pods
Módulo 4: Redes en Kubernetes
- Redes de Clúster
- Tipos de Servicios
- DNS Interno y Descubrimiento de Servicios
- Controladores de Ingress
- TLS y Gestión de Certificados con cert-manager
- Políticas de Red
Módulo 5: Almacenamiento en Kubernetes
- Volúmenes
- Volúmenes Persistentes
- Reclamaciones de Volúmenes Persistentes
- Clases de Almacenamiento
- Aprovisionamiento Dinámico, Expansión y Snapshots
- Copias de Seguridad y Restauración de Datos
Módulo 6: Conceptos Avanzados de Kubernetes
- StatefulSets
- DaemonSets
- Trabajos y CronJobs
- Init Containers, Sidecars y Patrones Multi-Contenedor
- Planificación: Afinidad, Taints y Tolerations
- Definiciones de Recursos Personalizados (CRDs)
- Operadores y el Patrón Controlador
Módulo 7: Monitoreo y Registro
- Verificaciones de Salud y Sondas
- Servidor de Métricas y kubectl top
- Monitoreo con Prometheus
- Visualización y Alertas con Grafana y Alertmanager
- Registro Centralizado con Elasticsearch, Fluentd y Kibana (EFK)
- Depuración de Aplicaciones y Eventos del Clúster
Módulo 8: Seguridad en Kubernetes
- Control de Acceso Basado en Roles (RBAC)
- Contextos de Seguridad y Endurecimiento del Contenedor
- Políticas de Seguridad de Pods y Pod Security Standards
- Seguridad de Red
- Seguridad de Imágenes
- Auditoría, Escaneo y Gestión de Vulnerabilidades
Módulo 9: Escalado y Rendimiento
- Autoescalado Horizontal de Pods
- Autoescalado Vertical de Pods
- Autoescalado de Clúster
- Escalado por Eventos y Métricas Personalizadas con KEDA
- Alta Disponibilidad: PodDisruptionBudgets y Topología
- Ajuste de Rendimiento
Módulo 10: Ecosistema y Herramientas de Kubernetes
- Minikube y Entornos Locales con kind
- Kubeadm
- Helm
- Kustomize
- GitOps con Argo CD y Flux
- Kubernetes Gestionado: EKS, AKS y GKE
Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real
- Despliegue de una Aplicación Web
- Ejecución de Aplicaciones con Estado
- CI/CD con Kubernetes
- Estrategias de Despliegue: Blue-Green y Canary
- Gestión Multi-Clúster
- Operación en Producción: Incidencias, Runbooks y Costes
