Saltar al contenido

Construir un cluster de Kubernetes en AWS con kubeadm

Publicado 7 min de lectura
Diagrama de arquitectura de Kubernetes en AWS

¿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 configure para autenticarte
  • 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-regions

Elegí 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_ID

3. 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_ID

4. 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 ubuntu

El 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_IP

6. 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_netfilter

Habilitá 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 --system

6.2. Instalar el runtime de contenedores

Instalá containerd desde los repositorios oficiales de Ubuntu.

sudo apt update
sudo apt install -y containerd

6.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/null

Cambiá SystemdCgroup = false por true.

sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

Aplicá la nueva configuración:

sudo systemctl restart containerd
sudo systemctl enable containerd

6.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 kubelet

7. 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/16

Una 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/config

La 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.yml

Habilitá 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)" >> ~/.bashrc

De forma opcional, creá un alias k con el mismo comportamiento de autocompletado.

alias k=kubectl
complete -o default -F __start_kubectl k

8. 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 ubuntu

Como 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_IP

10. 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-command

Esto 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=80

Revisá el estado de tu pod:

kubectl get pods

Deberías ver una salida similar a esta:

NAME         READY   STATUS    RESTARTS   AGE
nginx-test   1/1     Running   0          30s

Mirá los detalles del pod en ejecución:

kubectl describe pod nginx-test

Si querés acceder a nginx desde adentro del cluster, creá un service:

kubectl expose pod nginx-test --type=NodePort --port=80

Revisá el service y el puerto asignado:

kubectl get services nginx-test

Para 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/0

Conseguí 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 table

Anotá 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:NODEPORT

Por ejemplo, si tu IP pública es 54.123.45.67 y el NodePort es 31234, visitá:

http://54.123.45.67:31234

Deberí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-test

En 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/0

13. 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_ID

Una 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.