--- title: "Cómo instalar Kubernetes con kubeadm en Ubuntu 24.04" description: "Crea un clúster de Kubernetes con kubeadm en Ubuntu 24.04: prepara los nodos, instala containerd, inicia el plano de control, añade Flannel y une los workers." category: "Docker Kubernetes" language: es source: https://cubepath.com/es/docs/docker-kubernetes/instalacion-de-kubernetes-con-kubeadm updated: 2026-09-25 --- # Cómo instalar Kubernetes con kubeadm en Ubuntu 24.04 kubeadm es la herramienta oficial de Kubernetes para crear un clúster en servidores propios: configura el plano de control, genera los certificados y facilita unir más nodos. En este tutorial montarás un clúster con un nodo de plano de control y dos workers sobre Ubuntu 24.04, usando containerd como runtime de contenedores y Flannel como red de pods. Al terminar desplegarás una aplicación de prueba accesible desde fuera del clúster. ## Requisitos previos - Tres servidores con Ubuntu 24.04 LTS, por ejemplo VPS de CubePath: uno para el plano de control (mínimo 2 vCPU y 2 GB de RAM) y dos workers (mínimo 1 vCPU y 2 GB de RAM, más según tus cargas). - Un usuario no root con privilegios `sudo` en cada servidor. - Conectividad completa entre los tres servidores. En este tutorial se usan sus direcciones IP principales; los ejemplos usan `your_cp_ip` para el plano de control y `your_worker1_ip` y `your_worker2_ip` para los workers. - Un nombre de host distinto en cada servidor (por ejemplo `k8s-cp`, `k8s-worker1` y `k8s-worker2`). Cámbialo si hace falta con `sudo hostnamectl set-hostname k8s-cp`. Este tutorial usa Kubernetes v1.36. Consulta la última versión menor estable en [kubernetes.io/releases](https://kubernetes.io/releases/) y, si es otra, sustituye `v1.36` en las URL del paso 4. > Nota: los pasos 1 a 5 se ejecutan en **los tres servidores**. Los pasos 6 y 7 solo en el plano de control, y el paso 8 en los workers. ## Paso 1: Desactivar el swap Por defecto, el kubelet no arranca si el sistema tiene swap activo, porque interfiere con la gestión de memoria de los pods. Desactívalo ahora y en el arranque: ```bash sudo swapoff -a sudo sed -i '/\sswap\s/ s/^#*/#/' /etc/fstab ``` El segundo comando comenta cualquier línea de swap en `/etc/fstab`. Comprueba que no queda swap: ```bash swapon --show free -h | grep -i swap ``` `swapon --show` no debe devolver nada, y la línea `Swap:` de `free` debe mostrar `0B`. ## Paso 2: Cargar módulos del kernel y ajustar sysctl Los contenedores y la red de pods necesitan los módulos `overlay` y `br_netfilter`, y que el kernel reenvíe paquetes IPv4. Configura los módulos para que se carguen en cada arranque y cárgalos ya: ```bash printf 'overlay\nbr_netfilter\n' | sudo tee /etc/modules-load.d/k8s.conf sudo modprobe overlay sudo modprobe br_netfilter ``` Crea el archivo de parámetros de red: ```bash sudo nano /etc/sysctl.d/k8s.conf ``` ```ini net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 ``` Aplica la configuración sin reiniciar y verifícala: ```bash sudo sysctl --system sysctl net.ipv4.ip_forward net.bridge.bridge-nf-call-iptables ``` ```output net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-iptables = 1 ``` ## Paso 3: Instalar y configurar containerd Kubernetes necesita un runtime compatible con CRI. Instalarás containerd desde el repositorio oficial de Docker, que ofrece versiones 2.x actualizadas. No hace falta instalar Docker Engine. Añade la clave y el repositorio: ```bash sudo apt update sudo apt install -y ca-certificates curl gpg sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc ``` ```bash sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null < /dev/null sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml grep SystemdCgroup /etc/containerd/config.toml ``` ```output SystemdCgroup = true ``` Reinicia containerd y comprueba que está activo: ```bash sudo systemctl restart containerd sudo systemctl enable containerd systemctl is-active containerd ``` ```output active ``` ## Paso 4: Instalar kubeadm, kubelet y kubectl Los paquetes de Kubernetes se publican en `pkgs.k8s.io`, con un repositorio por versión menor. Descarga la clave y añade el repositorio de v1.36: ```bash curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.36/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.36/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list ``` Instala los tres paquetes y bloquea su versión para que un `apt upgrade` no los actualice: en Kubernetes las actualizaciones se hacen de forma controlada con `kubeadm upgrade`. ```bash sudo apt update sudo apt install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl sudo systemctl enable kubelet ``` Comprueba las versiones: ```bash kubeadm version -o short kubelet --version ``` ```output v1.36.1 Kubernetes v1.36.1 ``` Es normal que en este momento `systemctl status kubelet` muestre el servicio reiniciándose cada pocos segundos: está esperando la configuración que generará `kubeadm`. ## Paso 5: Abrir el firewall entre los nodos Los nodos se comunican por varios puertos (API en 6443, etcd en 2379-2380, kubelet en 10250, VXLAN de Flannel en UDP 8472, entre otros). En lugar de abrirlos uno a uno a todo Internet, permite todo el tráfico entre los nodos del clúster y desde la red de pods. En **cada** servidor ejecuta: ```bash sudo ufw allow OpenSSH sudo ufw allow from your_cp_ip sudo ufw allow from your_worker1_ip sudo ufw allow from your_worker2_ip sudo ufw allow from 10.244.0.0/16 sudo ufw enable ``` `10.244.0.0/16` es el rango de IP de los pods que usará Flannel. Para gestionar el clúster con `kubectl` desde tu equipo, abre además la API solo a tu IP en el plano de control: ```bash sudo ufw allow from your_admin_ip to any port 6443 proto tcp ``` Verifica las reglas: ```bash sudo ufw status ``` > Consejo: si tus servidores tienen una red privada, úsala para el tráfico del clúster. Pasa su IP en `--apiserver-advertise-address` del paso 6, indica al kubelet de cada nodo que la use añadiendo `KUBELET_EXTRA_ARGS=--node-ip=IP_PRIVADA` en `/etc/default/kubelet`, y configura Flannel con el argumento `--iface` apuntando a esa interfaz. ## Paso 6: Inicializar el plano de control Ejecuta este paso **solo en el plano de control**. `kubeadm init` comprueba los requisitos, genera certificados, arranca etcd, la API y el resto de componentes como pods estáticos. `--pod-network-cidr` debe coincidir con el rango que usa Flannel: ```bash sudo kubeadm init --apiserver-advertise-address=your_cp_ip --pod-network-cidr=10.244.0.0/16 ``` Tarda uno o dos minutos. Si termina bien, verás: ```output Your Kubernetes control-plane has initialized successfully! ... Then you can join any number of worker nodes by running the following on each as root: kubeadm join your_cp_ip:6443 --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:1f2e... ``` Guarda el comando `kubeadm join` completo: lo usarás en el paso 8. El token caduca a las 24 horas, pero puedes generar otro cuando quieras. Configura `kubectl` para tu usuario copiando el kubeconfig de administrador: ```bash mkdir -p "$HOME/.kube" sudo cp /etc/kubernetes/admin.conf "$HOME/.kube/config" sudo chown "$(id -u):$(id -g)" "$HOME/.kube/config" ``` Comprueba el estado del nodo: ```bash kubectl get nodes ``` ```output NAME STATUS ROLES AGE VERSION k8s-cp NotReady control-plane 45s v1.36.1 ``` `NotReady` es lo esperado: falta instalar la red de pods. > Advertencia: `admin.conf` da control total sobre el clúster. No lo compartas y no lo dejes con permisos de lectura para otros usuarios. ## Paso 7: Instalar la red de pods Flannel Kubernetes no incluye red de pods; la aporta un plugin CNI. Flannel es sencillo y suficiente para empezar. Aplica su manifiesto oficial en el plano de control: ```bash kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml ``` Espera a que los pods de Flannel y CoreDNS estén en marcha: ```bash kubectl get pods -n kube-flannel kubectl get pods -n kube-system -l k8s-app=kube-dns ``` ```output NAME READY STATUS RESTARTS AGE kube-flannel-ds-7xk2p 1/1 Running 0 40s NAME READY STATUS RESTARTS AGE coredns-7c65d6cfc9-4bq8n 1/1 Running 0 3m coredns-7c65d6cfc9-vj9wd 1/1 Running 0 3m ``` El nodo pasará a `Ready`: ```bash kubectl get nodes ``` ```output NAME STATUS ROLES AGE VERSION k8s-cp Ready control-plane 3m10s v1.36.1 ``` > Nota: Flannel no aplica `NetworkPolicy`. Si necesitas políticas de red para aislar aplicaciones, usa Calico o Cilium en su lugar (con su propio procedimiento de instalación y rango de pods). ## Paso 8: Unir los nodos worker En **cada worker** ejecuta con `sudo` el comando `kubeadm join` que guardaste en el paso 6: ```bash sudo kubeadm join your_cp_ip:6443 --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:1f2e... ``` ```output This node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details. ``` Si has perdido el comando o el token ha caducado, genera uno nuevo en el plano de control: ```bash sudo kubeadm token create --print-join-command ``` De vuelta en el plano de control, comprueba que los tres nodos están listos (puede tardar un minuto mientras Flannel arranca en los workers): ```bash kubectl get nodes -o wide ``` ```output NAME STATUS ROLES AGE VERSION INTERNAL-IP k8s-cp Ready control-plane 12m v1.36.1 203.0.113.10 k8s-worker1 Ready 2m v1.36.1 203.0.113.11 k8s-worker2 Ready 90s v1.36.1 203.0.113.12 ``` ## Paso 9: Desplegar una aplicación de prueba Crea un Deployment de Nginx con dos réplicas y publícalo con un Service de tipo NodePort, que abre un puerto alto en todos los nodos: ```bash kubectl create deployment hola --image=nginx:stable --replicas=2 kubectl expose deployment hola --port=80 --type=NodePort ``` Comprueba que las réplicas se reparten entre los workers y mira qué puerto se ha asignado: ```bash kubectl get pods -l app=hola -o wide kubectl get service hola ``` ```output NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hola NodePort 10.104.51.23 80:31742/TCP 15s ``` Desde cualquier nodo, prueba el acceso por el NodePort (sustituye `31742` por el tuyo): ```bash curl -sI http://your_worker1_ip:31742 | head -1 ``` ```output HTTP/1.1 200 OK ``` Prueba también la resolución DNS interna desde un pod temporal: ```bash kubectl run prueba-dns --rm -it --image=busybox:1.36 --restart=Never -- nslookup hola.default.svc.cluster.local ``` Debe devolver la `CLUSTER-IP` del Service. Para acceder desde Internet tendrías que abrir en UFW el rango NodePort (`sudo ufw allow 30000:32767/tcp`); en producción es más habitual poner un Ingress Controller delante. Elimina la prueba: ```bash kubectl delete service hola kubectl delete deployment hola ``` ## Solución de problemas **`kubeadm init` falla en las comprobaciones previas.** Lee el mensaje `[ERROR ...]`: los habituales son swap activo (paso 1), `/proc/sys/net/ipv4/ip_forward` distinto de 1 (paso 2) o que el runtime no responde (paso 3). Corrige la causa en lugar de usar `--ignore-preflight-errors`. **El kubelet no arranca después de `kubeadm init` o `join`.** Revisa su log: ```bash sudo journalctl -u kubelet -n 50 --no-pager ``` Un error sobre el driver de cgroups indica que `SystemdCgroup` no está a `true` en containerd; corrígelo y reinicia containerd y kubelet. **Un nodo se queda en `NotReady`.** Comprueba que el pod de Flannel de ese nodo está `Running` con `kubectl get pods -n kube-flannel -o wide` y mira sus logs con `kubectl logs -n kube-flannel `. La causa más común es el firewall: el tráfico UDP 8472 entre nodos está bloqueado. **Los pods de un nodo no alcanzan a los de otro, o CoreDNS no responde.** Otra vez suele ser el firewall o que los nodos se anuncian con IP que no se ven entre sí. Revisa la columna `INTERNAL-IP` de `kubectl get nodes -o wide`. **Empezar de cero en un nodo.** `kubeadm reset` deshace lo que hicieron `init` o `join`. Después borra la configuración de red de CNI y el kubeconfig: ```bash sudo kubeadm reset -f sudo rm -rf /etc/cni/net.d "$HOME/.kube" ``` Si el nodo era un worker, elimínalo también del clúster desde el plano de control con `kubectl delete node k8s-worker1`. ## Conclusión Tienes un clúster de Kubernetes funcional con un plano de control, dos workers, containerd como runtime y Flannel como red de pods, instalado desde los repositorios oficiales y con las versiones bloqueadas. Es una buena base para aprender y para cargas que no necesiten alta disponibilidad del plano de control. Como siguientes pasos puedes: - Aprender a gestionar Pods, Deployments y Services con `kubectl` y manifiestos YAML. - Instalar un Ingress Controller y cert-manager para publicar aplicaciones con HTTPS. - Añadir almacenamiento persistente y copias de seguridad de etcd con `etcdctl snapshot save` antes de poner cargas importantes.