resources/views/pages/frontend/docs/docker-registry-privado.md
Última modificación: 24 Oct, 2026

Docker Registry Privado: Golden Images On-Premise & Clúster K8s

En despliegues corporativos bajo redes privadas aisladas (Air-Gapped), entornos de soberanía de datos o infraestructuras donde se requiere eliminar la dependencia de cuotas de red externas (Docker Hub, Quay o GHCR), Mañuti implementa un Docker Registry v2 Privado.

Este servicio centraliza el ciclo de vida de las Golden Images del framework, gestiona la persistencia de imágenes base del plano de control de Kubernetes v1.31 (etcd, CoreDNS, Calico CNI) y optimiza el aprovisionamiento de nodos mediante el runtime containerd.

Tradicional

flowchart LR
    ClientT["🖥️<br/>CLIENT"]
    FELogicT["🧊<br/>FRONT-END<br/>LOGIC"]
    BELogicT["⚙️<br/>BACK-END<br/>LOGIC"]
    SecT["🔒<br/>SECURITY"]
    DbT[("🛢️<br/>DATABASE")]

    ClientT ==> FELogicT
    FELogicT --> BELogicT
    BELogicT --> SecT
    SecT --> DbT

Serverless

flowchart LR
    subgraph ClientSide ["CLIENT-SIDE"]
        ClientS["🖥️ 🧊<br/>FRONT-END<br/>LOGIC"]
    end

    subgraph CloudServices ["THIRD-PARTY SERVICES & BACKEND"]
        SecS["🔒<br/>SECURITY"]
        BELogicS["⚙️<br/>BACK-END<br/>LOGIC<br/>(FaaS / APIs)"]
        DbS[("🛢️<br/>DATABASE<br/>(BaaS)")]
    end

    NoteS["USING CLIENT-SIDE LOGIC AND<br/>THIRD-PARTY SERVICES"]

    ClientS ==> SecS
    ClientS ==> BELogicS
    ClientS ==> DbS
flowchart TD
    subgraph REG ["Host Registry Dedicado (192.168.1.187)"]
        DAEMON["daemon.json<br/>(insecure-registry)"]
        ENGINE["Docker Engine<br/>Container registry:2"]
        VOL["Volumen Persistente Físico<br/>/opt/docker-registry/data"]
        
        DAEMON ~~~ ENGINE
        ENGINE -->|":5000<br/>Storage Driver"| VOL
    end

    CTRL["k8s-control<br/>.182<br/>---<br/>containerd<br/>hosts.toml"]
    W1["k8s-worker1<br/>.183<br/>---<br/>containerd<br/>hosts.toml"]
    W2["k8s-worker2<br/>.184<br/>---<br/>containerd<br/>hosts.toml"]

    REG -->|"HTTP Pull / Push (Port 5000)"| CTRL
    REG -->|"HTTP Pull / Push (Port 5000)"| W1
    REG -->|"HTTP Pull / Push (Port 5000)"| W2

1. Topología del Servidor de Registro y Clúster de Nodos

Servidor / Host IP Privada Rol / Servicio Runtime / Motor
Node-Registry 192.168.1.187 Servidor de Registro Privado (Puerto 5000) Docker Engine 27+
k8s-control 192.168.1.182 Master / Control Plane Kubernetes containerd 1.7+ / CRI
k8s-worker1 192.168.1.183 Nodo de Cómputo / Worker Pods containerd 1.7+ / CRI
k8s-worker2 192.168.1.184 Nodo de Cómputo / Worker Pods containerd 1.7+ / CRI
k8s-worker3 192.168.1.185 Nodo de Cómputo / Worker Pods containerd 1.7+ / CRI
k8s-worker4 192.168.1.186 Nodo de Cómputo / Worker Pods containerd 1.7+ / CRI

2. Aprovisionamiento del Servidor de Registro (192.168.1.187)

2.1 Instalación del Motor Docker

Ejecutar en el host designado para el almacén de artefactos:

sudo apt update && sudo apt upgrade -y
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER

2.2 Despliegue del Contenedor con Persistencia Local

Se crea una partición persistente en /opt/docker-registry/data para garantizar que las capas de las imágenes no se destruyan si el contenedor o el servidor se reinician:

sudo mkdir -p /opt/docker-registry/data

sudo docker run -d \
  -p 5000:5000 \
  --name local-registry \
  --restart always \
  -v /opt/docker-registry/data:/var/lib/registry \
  registry:2

(En distribuciones basadas en Podman o si Docker Hub restringe el acceso, puede utilizarse la imagen espejo quay.io/libpod/registry:2).

2.3 Verificación de Disponibilidad Local

# Validar que el contenedor está activo
docker ps -f name=local-registry

# Consultar el catálogo v2 mediante la API HTTP (debe retornar un JSON vacío inicialmente)
curl http://localhost:5000/v2/_catalog
# Retorno esperado: {"repositories":[]}

3. Configuración de Registro Inseguro (HTTP) en Clientes Docker

Dado que el registro opera sobre HTTP plano dentro de una subred privada confiable, el demonio de Docker en los hosts donde se construyen imágenes debe autorizar la excepción:

3.1 Edición de /etc/docker/daemon.json

{
  "insecure-registries": ["192.168.1.187:5000"]
}
sudo systemctl restart docker

4. Integración Quirúrgica con Kubernetes y containerd

Por diseño de seguridad, containerd rechaza descargas desde registros que no implementen HTTPS con certificados firmados por una CA válida. Para conectar los nodos de Kubernetes (192.168.1.182 al .186) sin necesidad de certificados SSL autofirmados, se configura el subsistema CRI.

4.1 Corrección de Duplicidad en /etc/containerd/config.toml

Atención Técnica: containerd no permite rutas de certificados compuestas ni directivas heredadas de Docker que compitan con la interfaz CRI (io.containerd.grpc.v1.cri).

Ejecutar este bloque de depuración en todos los nodos del clúster K8s (Control Plane y Workers):

# 1. Respaldo preventivo
sudo cp /etc/containerd/config.toml /etc/containerd/config.toml.bak

# 2. Eliminar referencias legadas o duplicadas de docker/certs.d
sudo sed -i '/config_path = .*\/etc\/docker\/certs.d/d' /etc/containerd/config.toml

# 3. Establecer la ruta estándar de containerd en la sección CRI
sudo sed -i 's|config_path = ""|config_path = "/etc/containerd/certs.d"|g' /etc/containerd/config.toml

4.2 Definición del Archivo de Anfitrión (hosts.toml)

Crear la configuración de bypass TLS para el endpoint 192.168.1.187:5000 en cada nodo del clúster:

sudo mkdir -p /etc/containerd/certs.d/192.168.1.187:5000

cat <<EOF | sudo tee /etc/containerd/certs.d/192.168.1.187:5000/hosts.toml
server = "http://192.168.1.187:5000"

[host."http://192.168.1.187:5000"]
  capabilities = ["pull", "resolve", "push"]
  skip_verify = true
EOF

4.3 Reinicio y Verificación del Runtime

sudo systemctl restart containerd
sudo systemctl status containerd --no-pager

5. Script de Ingesta Masiva de Imágenes Críticas de Infraestructura

Para entornos donde el clúster carece de salida a internet, este script automatiza la descarga, re-etiquetado y publicación masiva de las dependencias base de Kubernetes y la red Calico CNI hacia el registro local:

#!/usr/bin/env bash
set -eo pipefail

REGISTRY="192.168.1.187:5000"

IMAGES=(
  # Calico CNI v3.29.1
  "quay.io/calico/apiserver:v3.29.1"
  "quay.io/calico/csi:v3.29.1"
  "quay.io/calico/kube-controllers:v3.29.1"
  "quay.io/calico/node-driver-registrar:v3.29.1"
  "quay.io/calico/node:v3.29.1"
  "quay.io/calico/typha:v3.29.1"
  "quay.io/tigera/operator:v1.36.2"
  
  # Kubernetes Core v1.31
  "registry.k8s.io/coredns/coredns:v1.11.3"
  "registry.k8s.io/etcd:3.5.24-0"
  "registry.k8s.io/kube-apiserver:v1.31.14"
  "registry.k8s.io/kube-controller-manager:v1.31.14"
  "registry.k8s.io/kube-proxy:v1.31.14"
  "registry.k8s.io/kube-scheduler:v1.31.14"
)

echo "=== Iniciando Ingesta hacia Docker Registry Local ==="

for IMG in "${IMAGES[@]}"; do
    NAME=$(echo "$IMG" | awk -F'/' '{print $NF}')
    
    if [[ "$IMG" == *"registry.k8s.io"* ]]; then
        LOCAL_TAG="$REGISTRY/k8s-core/$NAME"
    else
        LOCAL_TAG="$REGISTRY/calico/$NAME"
    fi

    echo "Descargando capa upstream: $IMG"
    docker pull "$IMG"
    
    echo "Etiquetando para almacén local: $LOCAL_TAG"
    docker tag "$IMG" "$LOCAL_TAG"
    
    echo "Publicando en el registro privado..."
    docker push "$LOCAL_TAG"
    echo "----------------------------------------------------"
done

echo "=== Almacén local sincronizado y listo para consumo ==="

6. Procedimiento de Rescate de Imágenes desde containerd

Si una imagen crítica ya se encuentra en la caché interna de containerd de un nodo de Kubernetes y se desea respaldar en el registro privado:

# 1. En el nodo K8s (k8s-control): Exportar imagen desde el namespace k8s.io
sudo ctr -n k8s.io images export calico-node-backup.tar quay.io/calico/node:v3.29.1

# 2. Transferir el archivo tar hacia el host del Registro
scp calico-node-backup.tar [email protected]:/tmp/

# 3. En el Servidor del Registro (192.168.1.187): Cargar en Docker Engine
docker load -i /tmp/calico-node-backup.tar

# 4. Re-etiquetar y subir al registro local
docker tag quay.io/calico/node:v3.29.1 192.168.1.187:5000/calico/node:v3.29.1
docker push 192.168.1.187:5000/calico/node:v3.29.1

7. Pruebas de Despliegue y Validación en Kubernetes

7.1 Construcción y Publicación de Imagen de Prueba

Desde el host de registro (192.168.1.187):

docker pull alpine:latest
docker tag alpine:latest 192.168.1.187:5000/manuti-test:v1
docker push 192.168.1.187:5000/manuti-test:v1

7.2 Lanzamiento de Pod de Validación en el Clúster

Desde el nodo maestro k8s-control (192.168.1.182):

kubectl run test-registry \
  --image=192.168.1.187:5000/manuti-test:v1 \
  --restart=Never \
  -- /bin/sh -c "echo 'Conexión a Docker Registry exitosa' && sleep 3600"

7.3 Verificación de Estado Operativo

kubectl get pods test-registry -o wide

Salida esperada:

NAME            READY   STATUS    RESTARTS   AGE   IP           NODE          NOMINATED NODE   READINESS GATES
test-registry   1/1     Running   0          12s   10.244.2.14  k8s-worker3   <none>           <none>

Si el pod alcanza el estado Running, se certifica que:

  1. El contenedor local-registry despacha capas de forma persistente.
  2. La configuración de hosts.toml en containerd ignora la ausencia de TLS sin rechazar la conexión.
  3. El clúster de Kubernetes es 100% autónomo para desplegar cargas de trabajo de Mañuti sin depender de repositorios públicos en la nube.
Edita este documento en >> docker-registry-privado.md
← Volver a la Landing Page