Despliegue de clúster Kubernetes con Containerd y Harbor en entorno offline

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)
  1. 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
  1. 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

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

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

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

Etiquetas: Kubernetes containerd Harbor etcd calico

Publicado el 7-1 16:48