Introducción y Arquitectura
A partir de la versión 1.24, Kubernetes eliminó el soporte directo para el motor de Docker. Aunque es posible adaptar la configuración para seguir utilizándolo, invocar a Containerd dircetamente desde el kubelet reduce la sobrecarga de rendimiento y simplifica la cadena de ejecución. A diferencia de otras alternativas como Podman, que requieren compilación o múltiples dependencias de paquetes, Containerd distribuye binarios estáticos que facilitan enormemente su instalación en redes aisladas (offline).
A continuación, se detalla el despliegue de un clúster de tres nodos con alta disponibilidad para el plano de control, utilizando Nginx como balanceador de carga TCP y un registro privado Harbor para gestionar las imágenes.
Topología del Entorno
| Dirección IP | Sistema Operativo | Hostname | Rol y Componentes |
|---|---|---|---|
| 10.10.5.11 | Debian 11 amd64 | k8s-node-1 | Nginx, etcd, Master, Worker |
| 10.10.5.12 | Debian 11 amd64 | k8s-node-2 | etcd, Master, Worker |
| 10.10.5.13 | Debian 11 amd64 | k8s-node-3 | etcd, Master, Worker |
| 10.10.5.20 | Debian 11 amd64 | registry.local | Harbor (Registro de imágenes) |
- Preparación del Sistema Operativo
Estos pasos deben ejecutarse en todos los nodos del clúster de Kubernetes (10.10.5.11, .12, .13).
Configuración de Hostname y Resolución
# Asignar nombres únicos a cada nodo
hostnamectl set-hostname k8s-node-1 # Ejecutar en el nodo 1
hostnamectl set-hostname k8s-node-2 # Ejecutar en el nodo 2
hostnamectl set-hostname k8s-node-3 # Ejecutar en el nodo 3
# Actualizar el archivo de hosts
cat <<EOF >> /etc/hosts
10.10.5.11 k8s-node-1
10.10.5.12 k8s-node-2
10.10.5.13 k8s-node-3
EOF
Sincronización Horaria y Deshabilitación de Swap
# Instalar y configurar Chrony (usando un servidor NTP interno)
apt install -y chrony
echo 'server 10.10.5.2 iburst' > /etc/chrony/sources.d/internal-ntp.sources
systemctl enable --now chrony
# Desactivar la memoria de intercambio permanentemente
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
Módulos del Kernel y Parámetros Sysctl
# Cargar módulos necesarios para Containerd y redes
cat <<EOF > /etc/modules-load.d/k8s-network.conf
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
# Configurar parámetros de red y namespaces
cat <<EOF > /etc/sysctl.d/99-k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
vm.swappiness = 0
EOF
sysctl --system
Habilitación de IPVS
apt install -y ipset ipvsadm
# Script para cargar módulos de balanceo
mkdir -p /opt/scripts
cat <<EOF > /opt/scripts/load_ipvs.sh
#!/bin/bash
modprobe ip_vs
modprobe ip_vs_rr
modprobe ip_vs_wrr
modprobe ip_vs_sh
modprobe nf_conntrack
EOF
chmod +x /opt/scripts/load_ipvs.sh
# Crear servicio systemd para ejecución en arranque
cat <<EOF > /etc/systemd/system/ipvs-loader.service
[Unit]
Description=Load IPVS kernel modules
After=network.target
[Service]
Type=oneshot
ExecStart=/opt/scripts/load_ipvs.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now ipvs-loader.service
- Despliegue del Registro Privado Harbor
El registro se instala en un servidor independiente (10.10.5.20) fuera del clúster. Se requiere tener Docker y Docker Compose instalados previamente en este nodo.
Generación de Certificados TLS
mkdir -p /opt/harbor/certs && cd /opt/harbor/certs
# Generar clave privada y solicitud de certificado
openssl genrsa -out harbor_tls.key 2048
openssl req -new -key harbor_tls.key -out harbor_tls.csr -subj "/CN=registry.local"
# Firmar el certificado con validez de 10 años
openssl x509 -req -days 3650 -in harbor_tls.csr -signkey harbor_tls.key -out harbor_tls.crt
Configuración e Instalación
Editar el archivo harbor.yml con los siguientes ajustes:
hostname: 10.10.5.20
http:
port: 80
https:
port: 443
certificate: /opt/harbor/certs/harbor_tls.crt
private_key: /opt/harbor/certs/harbor_tls.key
harbor_admin_password: R3gistryS3cure!
data_volume: /opt/harbor/data
location: /opt/harbor/logs
Ejecutar el script de instalación: ./install.sh
- Instalación del Runtime Containerd
Descargar el paquete binario estático desde el repositorio oficial y extraerlo en la raíz del sistema.
tar xvf cri-containerd-cni-1.7.2-linux-amd64.tar.gz -C /
# Generar y ajustar la configuración
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
Modificar /etc/containerd/config.toml para integrar el registor privado y systemd:
root = "/var/lib/containerd"
state = "/run/containerd"
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.local/base/pause:3.9"
[plugins."io.containerd.grpc.v1.cri".containerd]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
[plugins."io.containerd.grpc.v1.cri".registry]
[plugins."io.containerd.grpc.v1.cri".registry.configs]
[plugins."io.containerd.grpc.v1.cri".registry.configs."10.10.5.20"]
[plugins."io.containerd.grpc.v1.cri".registry.configs."10.10.5.20".tls]
insecure_skip_verify = true
[plugins."io.containerd.grpc.v1.cri".registry.configs."10.10.5.20".auth]
username = "admin"
password = "R3gistryS3cure!"
systemctl daemon-reload
systemctl enable --now containerd
- Infraestructura de Certificados (CA)
Se generará una Autoridad Certificadora (CA) autofirmada para asegurar las comunicaciones internas del clúster.
mkdir -p /etc/kubernetes/pki
# Generar CA privada y pública
openssl genrsa -out /etc/kubernetes/pki/cluster-ca.key 2048
openssl req -x509 -new -nodes -key /etc/kubernetes/pki/cluster-ca.key -subj "/CN=kubernetes-ca" -days 3650 -out /etc/kubernetes/pki/cluster-ca.crt
- Despliegue del Clúster etcd
Extraer los binarios de etcd en /usr/local/bin. Se requeire configurar certificados específicos para los nodos.
Configuración de Certificados etcd
cat <<EOF > etcd_ext.cnf
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
IP.1 = 10.10.5.11
IP.2 = 10.10.5.12
IP.3 = 10.10.5.13
EOF
# Generar certificados de servidor y cliente
openssl genrsa -out etcd_server.key 2048
openssl req -new -key etcd_server.key -config etcd_ext.cnf -subj "/CN=etcd-server" -out etcd_server.csr
openssl x509 -req -in etcd_server.csr -CA /etc/kubernetes/pki/cluster-ca.crt -CAkey /etc/kubernetes/pki/cluster-ca.key -CAcreateserial -days 3650 -extensions v3_req -extfile etcd_ext.cnf -out etcd_server.crt
openssl genrsa -out etcd_client.key 2048
openssl req -new -key etcd_client.key -config etcd_ext.cnf -subj "/CN=etcd-client" -out etcd_client.csr
openssl x509 -req -in etcd_client.csr -CA /etc/kubernetes/pki/cluster-ca.crt -CAkey /etc/kubernetes/pki/cluster-ca.key -CAcreateserial -days 3650 -extensions v3_req -extfile etcd_ext.cnf -out etcd_client.crt
Configuración del Servicio etcd
Crear el archivo de entorno /etc/etcd/etcd.env (ajustar ETCD_NAME y las IPs según el nodo):
ETCD_NAME=etcd-node-1
ETCD_DATA_DIR=/var/lib/etcd/data
ETCD_CERT_FILE=/etc/etcd/pki/etcd_server.crt
ETCD_KEY_FILE=/etc/etcd/pki/etcd_server.key
ETCD_TRUSTED_CA_FILE=/etc/kubernetes/pki/cluster-ca.crt
ETCD_CLIENT_CERT_AUTH=true
ETCD_LISTEN_CLIENT_URLS=https://10.10.5.11:2379
ETCD_ADVERTISE_CLIENT_URLS=https://10.10.5.11:2379
ETCD_PEER_CERT_FILE=/etc/etcd/pki/etcd_server.crt
ETCD_PEER_KEY_FILE=/etc/etcd/pki/etcd_server.key
ETCD_PEER_TRUSTED_CA_FILE=/etc/kubernetes/pki/cluster-ca.crt
ETCD_LISTEN_PEER_URLS=https://10.10.5.11:2380
ETCD_INITIAL_ADVERTISE_PEER_URLS=https://10.10.5.11:2380
ETCD_INITIAL_CLUSTER_TOKEN=etcd-ha-cluster
ETCD_INITIAL_CLUSTER="etcd-node-1=https://10.10.5.11:2380,etcd-node-2=https://10.10.5.12:2380,etcd-node-3=https://10.10.5.13:2380"
ETCD_INITIAL_CLUSTER_STATE=new
Crear la unidad systemd /etc/systemd/system/etcd.service:
[Unit]
Description=etcd key-value store
After=network.target
[Service]
EnvironmentFile=/etc/etcd/etcd.env
ExecStart=/usr/local/bin/etcd
Restart=on-failure
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now etcd
# Verificar salud del clúster
etcdctl --cacert=/etc/kubernetes/pki/cluster-ca.crt --cert=/etc/etcd/pki/etcd_client.crt --key=/etc/etcd/pki/etcd_client.key --endpoints=https://10.10.5.11:2379,https://10.10.5.12:2379,https://10.10.5.13:2379 endpoint health
- Componentes del Plano de Control y Nodos
Descargar los binarios de Kubernetes y moverlos a /usr/local/bin.
6.1 kube-apiserver
# Configurar extensiones SSL para el API Server
cat <<EOF > apiserver_ext.cnf
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc
DNS.4 = kubernetes.default.svc.cluster.local
DNS.5 = k8s-node-1
DNS.6 = k8s-node-2
DNS.7 = k8s-node-3
IP.1 = 10.96.0.1
IP.2 = 10.10.5.11
IP.3 = 10.10.5.12
IP.4 = 10.10.5.13
EOF
openssl genrsa -out apiserver.key 2048
openssl req -new -key apiserver.key -config apiserver_ext.cnf -subj "/CN=kube-apiserver" -out apiserver.csr
openssl x509 -req -in apiserver.csr -CA /etc/kubernetes/pki/cluster-ca.crt -CAkey /etc/kubernetes/pki/cluster-ca.key -CAcreateserial -days 3650 -extensions v3_req -extfile apiserver_ext.cnf -out apiserver.crt
# Generar claves para ServiceAccount
openssl genrsa -out sa.key 2048
openssl rsa -in sa.key -pubout -out sa.pub
Configurar el servicio /etc/kubernetes/manifests/apiserver.env:
APISERVER_OPTS="--secure-port=6443 \
--tls-cert-file=/etc/kubernetes/pki/apiserver.crt \
--tls-private-key-file=/etc/kubernetes/pki/apiserver.key \
--client-ca-file=/etc/kubernetes/pki/cluster-ca.crt \
--service-account-issuer=https://kubernetes.default.svc.cluster.local \
--service-account-key-file=/etc/kubernetes/pki/sa.pub \
--service-account-signing-key-file=/etc/kubernetes/pki/sa.key \
--etcd-servers=https://10.10.5.11:2379,https://10.10.5.12:2379,https://10.10.5.13:2379 \
--etcd-cafile=/etc/kubernetes/pki/cluster-ca.crt \
--etcd-certfile=/etc/etcd/pki/etcd_client.crt \
--etcd-keyfile=/etc/etcd/pki/etcd_client.key \
--service-cluster-ip-range=10.96.0.0/16 \
--service-node-port-range=30000-32767 \
--allow-privileged=true \
--v=2"
Crear y iniciar el servicio systemd kube-apiserver.service apuntando a EnvironmentFile=/etc/kubernetes/manifests/apiserver.env y ExecStart=/usr/local/bin/kube-apiserver $APISERVER_OPTS.
Generar el kubeconfig del administrador:
openssl genrsa -out admin.key 2048
openssl req -new -key admin.key -subj "/CN=kubernetes-admin/O=system:masters" -out admin.csr
openssl x509 -req -in admin.csr -CA /etc/kubernetes/pki/cluster-ca.crt -CAkey /etc/kubernetes/pki/cluster-ca.key -CAcreateserial -out admin.crt -days 3650
# El archivo admin.conf debe apuntar al VIP de Nginx (10.10.5.11:8443)
6.2 kube-controller-manager y kube-scheduler
Configurar los archivos de entorno respectivos (cm.env y sched.env) incluyendo el --kubeconfig generado, --leader-elect=true y los rangos de IP de servicio. Crear sus respectivas unidades systemd e iniciarlas.
6.3 Balanceador de Carga Nginx
Configurar Nginx en el nodo 1 para realizar proxy TCP hacia los puertos 6443 de los tres masters.
stream {
upstream k8s_apiservers {
server 10.10.5.11:6443;
server 10.10.5.12:6443;
server 10.10.5.13:6443;
}
server {
listen 8443;
proxy_pass k8s_apiservers;
}
}
6.4 kubelet y kube-proxy
Configurar kubelet.env y kubelet.yaml (estableciendo cgroupDriver: systemd y clusterDNS: ["10.96.0.10"]). Asegurar que --container-runtime-endpoint apunte a unix:///run/containerd/containerd.sock.
Para kube-proxy, configurar proxy.env con --proxy-mode=ipvs. Iniciar ambos servicios mediante systemd.
- Redes y DNS del Clúster
Instalación de Calico CNI
Descargar el manifiesto de Calico y modificar las referencias de imágenes para apuntar al registro interno:
image: registry.local/net/calico/cni:v3.26.1
image: registry.local/net/calico/node:v3.26.1
image: registry.local/net/calico/kube-controllers:v3.26.1
Aplicar el manifiesto: kubectl apply -f calico.yaml
Despliegue de CoreDNS
Crear un manifiesto personalizado para CoreDNS, configurando el ConfigMap para reenviar consultas externas al DNS interno (10.10.5.2) y estableciendo el clusterIP del servicio en 10.96.0.10.
- Validación Funcional
Para comprobar la resolución de nombres y la conectividad de la red, se despliegan recursos de prueba.
# Despliegue de un servidor web
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: nginx
image: registry.local/base/nginx:1.25.1
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: svc-frontend
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 80
EOF
# Despliegue de un pod de depuración
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: debug-tools
spec:
containers:
- name: ubuntu
image: registry.local/base/ubuntu:22.04-dns
command: ["sleep", "infinity"]
EOF
Acceder al pod de depuración y ejecutar las pruebas:
kubectl exec -it debug-tools -- bash
# Dentro del pod
nslookup svc-frontend
curl http://svc-frontend
Solución de Problemas Comunes
Error al aplicar parámetros sysctl
Síntoma: Al ejecutar sysctl --system se reportan errores indicando que no existen los archivos /proc/sys/net/bridge/bridge-nf-call-iptables.
Causa: El módulo del kernel responsable de filtrar tráfico de puentes no está cargado.
Solución: Cargar el módulo manualmente y asegurar su persistencia:
modprobe br_netfilter
echo "br_netfilter" >> /etc/modules