Registry Docker avec TLS
Registry privé auto-signé déployé sur Kubernetes avec Traefik IngressRoute et authentification basic auth
Importer manuellement chaque image sur chaque nœud du cluster est rapidement impraticable. La solution : un registry privé hébergé dans le cluster, accessible en HTTPS avec un certificat auto-signé et protégé par mot de passe.
Installation de Docker
Docker est installé sur cube01 pour construire et pousser des images multi-architecture depuis le réseau local.
sudo apt-get install ca-certificates curl gnupg lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian $(lsb_release -cs) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
Configuration du daemon (/etc/docker/daemon.json) :
{
"exec-opts": ["native.cgroupdriver=systemd"],
"experimental": true,
"log-driver": "json-file",
"storage-driver": "overlay2",
"log-opts": {
"max-size": "100m"
}
}
sudo systemctl enable docker
sudo systemctl start docker
# Support multi-architecture (arm64 + amd64)
sudo apt-get install binfmt-support qemu-user-static
docker buildx create --use --platform=linux/arm64,linux/amd64 --name multi-platform-builder
docker buildx inspect --bootstrap
# Ajouter l'utilisateur courant au groupe docker
sudo groupadd docker
sudo usermod -aG docker $USER
Mise en place du registry HTTP (étape initiale)
Dans un premier temps, le registry est déployé sans TLS pour valider le fonctionnement de base. Les manifestes Kubernetes créent un namespace dédié, un PVC, un Deployment, un Service et un Ingress.
Sur chaque nœud, ajouter le registry à /etc/hosts :
<IP-cube04> registry docker-registry.local
Puis créer /etc/rancher/k3s/registries.yaml pour que containerd connaisse le mirror :
mirrors:
docker-registry:
endpoint:
- "http://docker-registry.local:80"
Test de validation :
curl http://docker-registry.local:80/v2/_catalog
# Retourne la liste des dépôts
Passage en TLS avec CA locale
.cluster n’est pas un domaine public, Let’s Encrypt ne peut pas émettre de certificat. On crée donc une CA interne et on signe soi-même le certificat du registry.
Génération de la CA et du certificat
mkdir registry-ca && cd registry-ca
# Clé et certificat CA (valable 10 ans)
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes \
-key ca.key -sha256 -days 3650 \
-out ca.crt -subj "/CN=Cluster Registry CA"
# Clé et CSR pour registry.cluster
openssl genrsa -out registry.key 4096
openssl req -new -key registry.key -out registry.csr -subj "/CN=registry.cluster"
# Extension SAN
echo "subjectAltName = DNS:registry.cluster" > registry.ext
# Signature du certificat
openssl x509 -req \
-in registry.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out registry.crt -days 3650 -sha256 -extfile registry.ext
Déploiement de la CA sur toutes les machines
Sur le poste de travail et sur chaque nœud Raspberry Pi :
sudo cp ca.crt /usr/local/share/ca-certificates/cluster-ca.crt
sudo update-ca-certificates
# Sur les nœuds seulement :
sudo systemctl restart containerd
Ressources Kubernetes
Namespace et secret TLS
kubectl create namespace registry
kubectl create secret tls registry-tls \
--cert=registry.crt --key=registry.key -n registry
Authentification basic auth
sudo apt install apache2-utils
htpasswd -Bc htpasswd admin
kubectl create secret generic registry-auth \
--from-file=htpasswd -n registry
PVC (stockage Longhorn)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: registry-pvc
namespace: registry
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 20Gi
storageClassName: longhorn
Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: registry
namespace: registry
spec:
replicas: 1
selector:
matchLabels:
app: registry
template:
metadata:
labels:
app: registry
spec:
containers:
- name: registry
image: registry:2
ports:
- containerPort: 5000
env:
- name: REGISTRY_HTTP_ADDR
value: "0.0.0.0:5000"
- name: REGISTRY_AUTH
value: "htpasswd"
- name: REGISTRY_AUTH_HTPASSWD_REALM
value: "Registry Realm"
- name: REGISTRY_AUTH_HTPASSWD_PATH
value: "/auth/htpasswd"
volumeMounts:
- name: storage
mountPath: /var/lib/registry
- name: auth
mountPath: /auth
volumes:
- name: storage
persistentVolumeClaim:
claimName: registry-pvc
- name: auth
secret:
secretName: registry-auth
Service
apiVersion: v1
kind: Service
metadata:
name: registry
namespace: registry
spec:
selector:
app: registry
ports:
- port: 5000
targetPort: 5000
IngressRoute Traefik (HTTPS)
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: registry
namespace: registry
spec:
entryPoints:
- websecure
routes:
- match: Host(`registry.cluster`)
kind: Rule
services:
- name: registry
port: 5000
tls:
secretName: registry-tls
Configuration de k3s sur tous les nœuds
Éditer /etc/rancher/k3s/registries.yaml sur chaque nœud :
mirrors:
registry.cluster:
endpoint:
- "https://registry.cluster"
configs:
registry.cluster:
auth:
username: admin
password: <password>
tls:
ca_file: /usr/local/share/ca-certificates/cluster-ca.crt
# Sur le control plane
sudo systemctl restart k3s
# Sur les workers
sudo systemctl restart k3s-agent
Tests
# Connexion
docker login registry.cluster
# Tag et push
docker tag nginx registry.cluster/nginx:test
docker push registry.cluster/nginx:test
# Vérification via l'API
curl -u admin:<password> https://registry.cluster/v2/_catalog
La réponse {"errors":[{"code":"UNAUTHORIZED"...}]} sans authentification confirme que TLS fonctionne et que le registry est correctement protégé.
Dans les manifestes Kubernetes, les images se référencent directement :
image: registry.cluster/myapp:latest
k3s résout le nom et tire l’image depuis le registry privé avec les credentials configurés.