Construir un cluster de Kubernetes en AWS con kubeadm

¿Alguna vez te preguntaste cómo funcionan realmente los clusters de Kubernetes por debajo? En esta guía te voy a acompañar paso a paso para que construyas tu propio cluster de Kubernetes en AWS desde cero usando kubeadm. Esta experiencia práctica te va a dar una comprensión más profunda de la arquitectura de Kubernetes y te va a ayudar a apreciar lo que servicios administrados como EKS hacen detrás de escena.
Ya sea que estés preparando una certificación, armando tu homelab, o simplemente tengas curiosidad sobre la orquestación de contenedores, este tutorial paso a paso te tiene cubierto.
Lo que vas a necesitar
Tranquilo—la configuración es simple. Esto es lo que necesitás antes de arrancar:
- Cuenta de AWS: necesitás una cuenta de AWS activa con facturación habilitada. Si sos nuevo en AWS, ofrecen créditos de capa gratuita para arrancar.
- AWS CLI: la interfaz de línea de comandos de AWS en tu máquina local (conseguila acá)
- Configuración rápida:
aws configurepara autenticarte
- Configuración rápida:
- Permisos apropiados: tu cuenta de AWS debe poder:
- Crear y administrar instancias EC2
- Configurar redes VPC y grupos de seguridad
- Administrar permisos IAM
- Cierta familiaridad con:
- Comandos básicos de Linux
- Conexiones SSH
- Fundamentos de Kubernetes (aunque vamos a ir explicando sobre la marcha)
Manos a la obra
¿Listo para ensuciarte las manos? Vamos a construir este cluster paso a paso, empezando por la infraestructura y subiendo hasta tener un cluster completamente funcional. Te voy a explicar qué hace cada comando y por qué importa.
1. Preparar tu AWS CLI
Ejecutar en: tu máquina local
Configurá el AWS CLI con tus credenciales y elegí la región que vas a usar para todos los recursos de esta guía.
aws configure
aws ec2 describe-regionsElegí una región de la salida y usala de forma consistente durante el resto de la guía.
2. Construir la base de tu red en la nube
Ejecutar en: tu máquina local
Creá una VPC dedicada y una subred pública, después conectá un internet gateway y agregá una ruta por defecto para que las instancias puedan salir a internet. Actualizá la zona de disponibilidad si elegiste otra región.
VPC_ID=$(aws ec2 create-vpc \
--cidr-block 10.0.0.0/16 \
--query 'Vpc.VpcId' \
--output text)
SUBNET_ID=$(aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block 10.0.0.0/24 \
--availability-zone us-east-1a \
--query 'Subnet.SubnetId' \
--output text)
aws ec2 modify-subnet-attribute \
--subnet-id $SUBNET_ID \
--map-public-ip-on-launch
IGW_ID=$(aws ec2 create-internet-gateway \
--query 'InternetGateway.InternetGatewayId' \
--output text)
aws ec2 attach-internet-gateway \
--internet-gateway-id $IGW_ID \
--vpc-id $VPC_ID
ROUTE_TABLE_ID=$(aws ec2 describe-route-tables \
--filters "Name=vpc-id,Values=$VPC_ID" \
--query 'RouteTables[0].RouteTableId' \
--output text)
aws ec2 create-route \
--route-table-id $ROUTE_TABLE_ID \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id $IGW_ID3. Blindar la seguridad de tu cluster
Ejecutar en: tu máquina local
Creá un grupo de seguridad para el cluster que permita acceso SSH y tráfico irrestricto entre nodos. El ejemplo usa 0.0.0.0/0 para SSH; restringilo a tu IP si querés más seguridad.
SG_ID=$(aws ec2 create-security-group \
--group-name k8s-cluster \
--description 'Security group for Kubernetes cluster' \
--vpc-id $VPC_ID \
--query 'GroupId' \
--output text)
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID \
--protocol -1 \
--source-group $SG_ID4. Levantar la instancia del control plane
Ejecutar en: tu máquina local
Lanzá una instancia t3.medium con Ubuntu 22.04 LTS para el control plane, esperá a que esté corriendo y anotá su IP pública para conectarte por SSH.
CONTROL_PLANE_ID=$(aws ec2 run-instances \
--image-id ami-0030e4319cbf4dbf2 \
--instance-type t3.medium \
--security-group-ids $SG_ID \
--subnet-id $SUBNET_ID \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=k8s-control-plane}]' \
--query 'Instances[0].InstanceId' \
--output text)
echo "Waiting for instance to be ready..."
aws ec2 wait instance-running --instance-ids $CONTROL_PLANE_ID
CONTROL_PLANE_IP=$(aws ec2 describe-instances \
--instance-ids $CONTROL_PLANE_ID \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text)
echo "Control plane instance ready: $CONTROL_PLANE_IP"5. Entrar al control plane
Ejecutar en: tu máquina local
Conectate al control plane usando EC2 Instance Connect, que se encarga de la autenticación con tu clave SSH existente.
aws ec2-instance-connect ssh --instance-id $CONTROL_PLANE_ID --os-user ubuntuEl AWS CLI va a usar automáticamente tu clave SSH por defecto (~/.ssh/id_rsa.pub). Si preferís SSH estándar, enviá tu clave pública (válida por 60 segundos) y conectate directamente.
aws ec2-instance-connect send-ssh-public-key \
--instance-id $CONTROL_PLANE_ID \
--instance-os-user ubuntu \
--ssh-public-key file://~/.ssh/id_rsa.pub
ssh ubuntu@$CONTROL_PLANE_IP6. Preparar el software del control plane
Ejecutar en: el nodo del control plane (ya estás conectado por SSH a la instancia)
Preparemos el sistema operativo para Kubernetes habilitando las funciones de kernel necesarias, instalando containerd, e instalando las herramientas de Kubernetes.
6.1. Habilitar funciones esenciales del kernel
Kubernetes necesita módulos de kernel y configuraciones de red específicas para que el runtime de contenedores pueda manejar tráfico con bridging.
Creá un archivo de configuración para cargar los módulos necesarios al arrancar, y cargalos ahora:
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilterHabilitá el reenvío de IP y el networking con bridge. El comando sysctl aplica estos cambios de inmediato, sin necesidad de reiniciar.
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 --system6.2. Instalar el runtime de contenedores
Instalá containerd desde los repositorios oficiales de Ubuntu.
sudo apt update
sudo apt install -y containerd6.3. Configurar containerd para Kubernetes
Por defecto, containerd no usa el driver de cgroup de systemd, que Kubernetes recomienda para mayor estabilidad. Actualizá la configuración y reiniciá el servicio.
Creá el directorio y generá un archivo de configuración limpio:
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml > /dev/nullCambiá SystemdCgroup = false por true.
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.tomlAplicá la nueva configuración:
sudo systemctl restart containerd
sudo systemctl enable containerd6.4. Instalar el toolkit de Kubernetes
Agregá el repositorio de paquetes de Kubernetes e instalá los componentes. Los paquetes quedan retenidos para evitar actualizaciones no deseadas.
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.35/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.35/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
sudo systemctl enable --now kubelet7. Darle vida al control plane
Ejecutar en: el nodo del control plane
Inicializá el control plane con el CIDR de red de pods elegido.
sudo kubeadm init --pod-network-cidr=10.244.0.0/16Una vez que termine la inicialización, configurá el acceso de 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/configLa salida de la inicialización incluye un token de join para los nodos worker. Podés guardarlo, o generar uno nuevo más adelante.
Desplegá Flannel como plugin CNI para habilitar el networking pod-a-pod.
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.ymlHabilitá el autocompletado de kubectl para la sesión actual y persistilo en tu perfil de bash.
source <(kubectl completion bash)
echo "source <(kubectl completion bash)" >> ~/.bashrcDe forma opcional, creá un alias k con el mismo comportamiento de autocompletado.
alias k=kubectl
complete -o default -F __start_kubectl k8. Aprovisionar tu primer nodo worker
Ejecutar en: tu máquina local (abrí una terminal nueva, mantené activa la sesión SSH del control plane)
Aprovisioná una instancia para el nodo worker, esperá a que esté corriendo y anotá su IP pública.
WORKER_ID=$(aws ec2 run-instances \
--image-id ami-0030e4319cbf4dbf2 \
--instance-type t3.medium \
--security-group-ids $SG_ID \
--subnet-id $SUBNET_ID \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=k8s-worker-1}]' \
--query 'Instances[0].InstanceId' \
--output text)
echo "Waiting for worker instance to be ready..."
aws ec2 wait instance-running --instance-ids $WORKER_ID
WORKER_IP=$(aws ec2 describe-instances \
--instance-ids $WORKER_ID \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text)
echo "Worker instance ready: $WORKER_IP"9. Entrar al nodo worker
Ejecutar en: tu máquina local
Conectate al nodo worker usando EC2 Instance Connect.
aws ec2-instance-connect ssh --instance-id $WORKER_ID --os-user ubuntuComo alternativa, usá SSH estándar enviando tu clave pública (válida por 60 segundos) y conectándote directamente.
aws ec2-instance-connect send-ssh-public-key \
--instance-id $WORKER_ID \
--instance-os-user ubuntu \
--ssh-public-key file://~/.ssh/id_rsa.pub
ssh ubuntu@$WORKER_IP10. Preparar el software del nodo worker
Ejecutar en: el nodo worker (ya estás conectado por SSH a la instancia worker)
Repetí los pasos de configuración del sistema e instalación de la sección 6 en el nodo worker (secciones 6.1 a 6.4).
11. Sumar el worker a tu cluster
Ejecutar en: el nodo del control plane (primer comando), después el nodo worker (segundo comando)
Generá un comando de join nuevo en el control plane, y después ejecutalo en el nodo worker.
kubeadm token create --print-join-commandEsto registra el nodo worker en el cluster.
12. Ponele a prueba tu cluster
Ejecutar en: el nodo del control plane (comandos kubectl) y tu máquina local (comandos AWS)
Verificá el cluster desplegando un pod simple de nginx, revisando su estado, y opcionalmente exponiéndolo.
Desplegá un pod de nginx en tu cluster:
kubectl run nginx-test --image=nginx:latest --port=80Revisá el estado de tu pod:
kubectl get podsDeberías ver una salida similar a esta:
NAME READY STATUS RESTARTS AGE
nginx-test 1/1 Running 0 30sMirá los detalles del pod en ejecución:
kubectl describe pod nginx-testSi querés acceder a nginx desde adentro del cluster, creá un service:
kubectl expose pod nginx-test --type=NodePort --port=80Revisá el service y el puerto asignado:
kubectl get services nginx-testPara acceder a nginx desde tu máquina local, abrí el rango de NodePort en el grupo de seguridad y conseguí la IP pública de un nodo.
Volvé a la terminal de tu máquina local y actualizá tu grupo de seguridad para permitir tráfico en el rango de NodePort (30000-32767):
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID \
--protocol tcp \
--port 30000-32767 \
--cidr 0.0.0.0/0Conseguí la dirección IP pública de tu nodo worker o del control plane:
aws ec2 describe-instances \
--filters "Name=tag:Name,Values=k8s-*" \
--query 'Reservations[*].Instances[*].[InstanceId,Tags[?Key==`Name`].Value|[0],PublicIpAddress]' \
--output tableAnotá el PublicIpAddress de la salida, y después volvé al nodo del control plane para conseguir el NodePort asignado a tu service:
kubectl get service nginx-test -o jsonpath='{.spec.ports[0].nodePort}'Esto va a mostrar un número de puerto entre 30000 y 32767. Accedé a nginx abriendo en tu navegador:
http://PUBLIC_IP:NODEPORTPor ejemplo, si tu IP pública es 54.123.45.67 y el NodePort es 31234, visitá:
http://54.123.45.67:31234Deberías ver la página "Welcome to nginx!".
En el nodo del control plane, eliminá el pod y el service de nginx cuando termines de probar:
kubectl delete service nginx-test
kubectl delete pod nginx-testEn tu máquina local, si creaste la regla de NodePort en el grupo de seguridad, eliminala también:
aws ec2 revoke-security-group-ingress \
--group-id $SG_ID \
--protocol tcp \
--port 30000-32767 \
--cidr 0.0.0.0/013. Desarmar tu playground de Kubernetes
Ejecutar en: tu máquina local
Desarmá los recursos en orden inverso: terminá las instancias EC2, eliminá el grupo de seguridad, desconectá y eliminá el internet gateway, y por último eliminá la subred y la VPC.
aws ec2 terminate-instances \
--instance-ids $CONTROL_PLANE_ID $WORKER_ID
aws ec2 wait instance-terminated \
--instance-ids $CONTROL_PLANE_ID $WORKER_ID
aws ec2 delete-security-group \
--group-id $SG_ID
aws ec2 detach-internet-gateway \
--internet-gateway-id $IGW_ID \
--vpc-id $VPC_ID
aws ec2 delete-internet-gateway \
--internet-gateway-id $IGW_ID
aws ec2 delete-subnet \
--subnet-id $SUBNET_ID
aws ec2 delete-vpc \
--vpc-id $VPC_IDUna palabra sobre estar listo para producción
Antes de cerrar, tengamos una conversación honesta: este cluster es perfecto para aprender y hacer pruebas, pero no está listo para producción. Pensalo como tu terreno de entrenamiento—un espacio seguro para experimentar, romper cosas y aprender.
Reflexión final
¡Felicitaciones por construir tu propio cluster de Kubernetes! Ganaste experiencia práctica en orquestación de contenedores que muchos desarrolladores nunca llegan a tener. Usá esta configuración para experimentar, probar tus aplicaciones, y profundizar tu comprensión de cómo funciona Kubernetes en la realidad.
Cuando estés listo para producción, vas a apreciar todavía más los servicios administrados—pero también vas a entender qué está pasando detrás de escena. Y ese conocimiento no tiene precio.
¡Feliz clustering! Si tenés preguntas o querés compartir tu experiencia, escribime. Sigamos aprendiendo juntos.