Configuring Kind with a Private Registry Mirror
This document describes configuring Kind to use a private registry mirror when direct access to public registries is restricted.
Problem Description
Kind clusters may encounter 403 Forbidden errors when pulling images from registry.k8s.io due to network restrictions. Initial attempts using HTTP proxy configuration proved ineffective - containerd requires registry mirror configuration instead.
Network Architecture
Kind nodes run as Docker containers in a bridge network with the following characteristics:
127.0.0.1inside a Kind node refers to the container’s loopback interface172.18.0.1(typical value) represents the Docker bridge gateway IP accessible from nodes- Registry mirrors must be reachable via the bridge network interface
Configuration Requirements
The configuration requires three components:
1. hosts.toml Configuration
Create a hosts.toml file without a server directive to enforce exclusive mirror usage:
# /etc/containerd/certs.d/registry.k8s.io/hosts.toml
# NO server line - this forces exclusive use of the mirror
[host."https://your-registry.example.com:8093"]
capabilities = ["pull", "resolve"]
skip_verify = true # If using self-signed certificates
2. Containerd Authentication Configuration
Configure authentication via Kind’s containerdConfigPatches:
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d"
[plugins."io.containerd.grpc.v1.cri".registry.configs."your-registry.example.com:8093".auth]
username = "your-username"
password = "your-password"
[plugins."io.containerd.grpc.v1.cri".registry.configs."your-registry.example.com:8093".tls]
insecure_skip_verify = true
3. Configuration Mount Points
Kind configuration with required mount paths:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraMounts:
- hostPath: ./registry-config/hosts.toml
containerPath: /etc/containerd/certs.d/registry.k8s.io/hosts.toml
- role: worker
extraMounts:
- hostPath: ./registry-config/hosts.toml
containerPath: /etc/containerd/certs.d/registry.k8s.io/hosts.toml
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d"
[plugins."io.containerd.grpc.v1.cri".registry.configs."your-registry.example.com:8093".auth]
username = "${REGISTRY_USERNAME}"
password = "${REGISTRY_PASSWORD}"
[plugins."io.containerd.grpc.v1.cri".registry.configs."your-registry.example.com:8093".tls]
insecure_skip_verify = true
Implementation Script
Setup script for automated configuration:
#!/bin/bash
# Configuration
CLUSTER_NAME="my-cluster"
REGISTRY_URL="your-registry.example.com:8093"
REGISTRY_USERNAME="your-username"
REGISTRY_PASSWORD="your-password"
# Create directory structure
mkdir -p ./generated/registry-config
# Generate hosts.toml (no server line!)
cat > ./generated/registry-config/hosts.toml <<EOF
[host."https://${REGISTRY_URL}"]
capabilities = ["pull", "resolve"]
skip_verify = true
EOF
# Generate Kind configuration
cat > ./generated/kind-config.yaml <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraMounts:
- hostPath: $(pwd)/generated/registry-config/hosts.toml
containerPath: /etc/containerd/certs.d/registry.k8s.io/hosts.toml
- role: worker
extraMounts:
- hostPath: $(pwd)/generated/registry-config/hosts.toml
containerPath: /etc/containerd/certs.d/registry.k8s.io/hosts.toml
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d"
[plugins."io.containerd.grpc.v1.cri".registry.configs."${REGISTRY_URL}".auth]
username = "${REGISTRY_USERNAME}"
password = "${REGISTRY_PASSWORD}"
[plugins."io.containerd.grpc.v1.cri".registry.configs."${REGISTRY_URL}".tls]
insecure_skip_verify = true
EOF
# Create the cluster
kind create cluster --name ${CLUSTER_NAME} --config ./generated/kind-config.yaml
# Wait for cluster to be ready
kubectl wait --for=condition=Ready nodes --all --timeout=60s
# Test with metrics-server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# Verify the deployment
kubectl get pods -n kube-system | grep metrics-server
Verification
Verification methods:
- Examine pod events:
kubectl describe pod -n kube-system metrics-server-<pod-id>
Pull timing observations:
- Initial pull: ~9 seconds (remote fetch via mirror)
- Subsequent pull: ~270ms (mirror cache hit)
- Inspect containerd state:
docker exec kind-control-plane crictl images | grep metrics-server - Query registry mirror:
Registry API endpoint for cached tags:
curl -u username:password \
"https://your-registry.example.com:8093/v2/metrics-server/metrics-server/tags/list"
Technical Notes
HTTP Proxy vs Registry Mirror
HTTP proxy configuration (HTTP_PROXY/HTTPS_PROXY environment variables) applies to general HTTP traffic. Containerd implements a separate registry configuration mechanism for image pulls.
hosts.toml Behavior
Omitting the server directive in hosts.toml changes containerd’s behavior. With the server directive present, containerd attempts upstream registry access first, falling back to mirrors on failure. Without it, containerd uses only the configured mirror.
With server directive (fallback behavior):
server = "https://registry.k8s.io"
[host."https://your-registry.example.com:8093"]
capabilities = ["pull", "resolve"]
Without server directive (exclusive mirror):
[host."https://your-registry.example.com:8093"]
capabilities = ["pull", "resolve"]
skip_verify = true
Configuration Components
Required elements:
- Registry authentication via
containerdConfigPatches - TLS verification skip for self-signed certificates (configured in hosts.toml and containerd config)
- Correct mount paths for configuration files
Performance Observations
Registry mirror usage results:
- Reduced bandwidth consumption through caching
- Cache hits approximately 30-40x faster than remote pulls
- Elimination of external registry dependencies
- Centralized image availability management
Troubleshooting
Common issues and diagnostics:
- Verify hosts.toml configuration: Confirm absence of server directive
- Authentication validation: Verify credentials
- Network connectivity from Kind node:
docker exec -it kind-control-plane bash curl -k https://your-registry.example.com:8093/v2/ - Containerd configuration inspection:
docker exec kind-control-plane cat /etc/containerd/config.toml - Containerd service logs:
docker exec kind-control-plane journalctl -u containerd
Summary
Configuring Kind with a private registry mirror requires containerd registry configuration rather than HTTP proxy settings. The configuration consists of: hosts.toml without a server directive, authentication through containerdConfigPatches, and appropriate file mounts. This approach addresses network-restricted environments where direct registry access is unavailable.
Ilgar's tech musings