Vous disposez maintenant d’un cluster Kubernetes, vous créer vos applications disposées dans votre propres conteneurs. Par défaut, K8s va “se servir” sur le docker HUB. Si vous ne souhaitez pas stocker vos images chez “Docker”, la solution reste de créer votre propre “registry Docker”.
D’autres raisons peuvent vous amener à créer votre propres conteneurs, notamment, en rapport au niveau de sécurité des images que vous pouvez trouver sur le “DockerHUB” :
- 51% of 4 million Docker images have critical vulnerabilities
- Who’s at the Helm?. Or, how to deploy 25+ CVEs to prod in… | by Dan Lorenc | Jan, 2021
Le but de cette procédure sera donc de créer votre propre “registry Docker” et d’y conneter votre cluster Kubernetes.
Source de ce document : Deploy Your Private Docker Registry as a Pod in Kubernetes
Opération à réaliser sur tous les noeuds
Sur Debian 10, des paquets sont manquant pour Docker.
Réalisez cette opération sur tous les noeuds
Commencez par le master :
apt -y install gnupg2 pass
Opération à réaliser sur le serveur Master (Debian10)
Contexte : j’utilise un serveur Gluster accessible du cluster Kubernetes (voir son installation ici).
Le nom du volume Gluster exporté est : “docker-registry”. Toutes les opérations réalisées ci-dessous seront réalisées dans le namespace Kubernetes : “docker-registry”
J’ai suivi scrupuleusement la documentation de Varun Kumar G, en adaptant les opérations par “Manifests” et l’ajout de shell d’automatisation.
Création du namespace “docker-registry”
Créer le fichier “namespace.yaml” :
apiVersion: "v1"
kind: "Namespace"
metadata:
name: "docker-registry"
labels:
name: "docker-registry"
Exécution : kubectl apply -f namespace.yaml
Création des fichiers secrets et authenfication
J’ai créé un shell à partir des sources de Varun Kumar G. Les “secrets” sont utilisés par Kubernetes, l’authentification sert dans le processus “docker pull” des workers. Les workers pourront ainsi, aller chercher les images dans votre registry privée en utilisant ces paramètres d’authenfication (login/password).
Créer le fichier gensecretauthmanifest.sh contenant :
#!/bin/bash
echo "Entrez le login d'authentification d'accès au registry privé Docker"
read LOGIN
echo "Entrez le mot de passe d'authentification d'accès au registry privé Docker"
read PASSWORD
mkdir -p ./registry/certs ./registry/auth
echo "Generating tls..."
openssl req -x509 -newkey rsa:4096 -days 365 -nodes -sha256 -keyout registry/certs/tls.key -out registry/certs/tls.crt -subj "/CN=docker-registry"
SECRETMANIFEST="secrets-auth.yaml"
echo "Generating tls Manifest"
cat << EOF > "$SECRETMANIFEST"
apiVersion: v1
kind: Secret
metadata:
name: certs-secret-docker-registry
namespace: "docker-registry"
type: kubernetes.io/tls
data:
tls.crt: $(cat registry/certs/tls.crt | base64 --wrap=0)
tls.key: $(cat registry/certs/tls.key | base64 --wrap=0)
EOF
echo "Generation auth file"
AUTHFILE="registry/auth/htpasswd"
docker run --rm --entrypoint htpasswd registry:2.6.2 -Bbn "$LOGIN" "$PASSWORD" > "$AUTHFILE"
# Check
echo "CHECK..."
if [ ! -f "$AUTHFILE" ];then
echo "Le login/password n'a pas été créé par le container htpasswd"
exit 1
fi
echo " [*] $AUTHFILE content : "
cat "$AUTHFILE"
echo "Displaying architecture created"
ls registry/*
echo "Generating auth Manifest"
echo "---" >> "$SECRETMANIFEST"
cat << EOF >> "$SECRETMANIFEST"
apiVersion: v1
kind: Secret
metadata:
name: auth-secret-docker-registry
namespace: "docker-registry"
type: Opaque
data:
username: $(echo "$LOGIN" | base64 --wrap=0)
password: $(echo "$PASSWORD" | base64 --wrap=0)
EOF
echo "---" >> "$SECRETMANIFEST"
cat << EOF >> "$SECRETMANIFEST"
apiVersion: v1
kind: Secret
metadata:
name: auth-secret-docker-registry
namespace: "docker-registry"
type: Opaque
data:
htpasswd: $(cat registry/auth/htpasswd | base64 --wrap=0)
EOF
echo "Execute now : "
echo ""
echo " kubectl: apply -f $SECRETMANIFEST"
echo ""
Ce shell généréra :
- le fichier secrets-auth.yaml contenant les commandes pour créer les secrets.
- un répertoire registry, contenant le certificat qui devra être disposés sur tous les workers.
A cette étape, pensez à conserver le login et mot de passe qui seront utilisés ci-après…
# Exécution du shell
bash ./gensecretauthmanifest.sh
#Ajout des secrets dans le cluster K8s
kubectl apply -f secrets-auth.yaml
Création du Persistent Volume et Claim concernant le stockage des images
Sur le serveur Gluster, j’ai créé et démarré le volume : “docker-registry”, et je travaille dans le namespace : “docker-registry”
Attention : Adapter la valeur de l’adresse IP du serveur Gluster ([server IP addr]) volumes.yaml :
apiVersion: "v1"
kind: "Service"
metadata:
name: "glusterfs-cluster"
namespace: "docker-registry"
spec:
ports:
- port: 1
---
apiVersion: v1
kind: "Endpoints"
metadata:
name: "glusterfs-cluster"
namespace: "docker-registry"
subsets:
- addresses:
- ip: [server IP addr]
ports:
- port: 1
---
apiVersion: "v1"
kind: "PersistentVolume"
metadata:
name: "gluster-pv-docker-registry"
namespace: "docker-registry"
spec:
capacity:
storage: "1Gi"
accessModes:
- "ReadWriteOnce"
storageClassName: "docker-registry"
persistentVolumeReclaimPolicy: "Recycle"
glusterfs:
endpoints: "glusterfs-cluster"
path: "docker-registry"
readOnly: false
---
apiVersion: "v1"
kind: "PersistentVolumeClaim"
metadata:
name: "glusterfs-claim-docker-registry"
namespace: "docker-registry"
spec:
accessModes:
- "ReadWriteOnce"
storageClassName: "docker-registry"
resources:
requests:
storage: "1Gi"
Pour créer le volume, exécuter la commande
kubectl apply -f volumes.yaml
Création du deployment “Registry” et de son service
Toujours dans le namespace “docker-registry”, je crée le “deployment” et le service de l’application :
deploy.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: "docker-registry-deploy"
namespace: "docker-registry"
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: registry
replicas: 1
template:
metadata:
labels:
app: registry
spec:
containers:
- name: registry
image: registry:2.6.2
volumeMounts:
- name: repo-vol
mountPath: "/var/lib/registry"
- name: certs-vol
mountPath: "/certs"
readOnly: true
- name: auth-vol
mountPath: "/auth"
readOnly: true
env:
- name: REGISTRY_AUTH
value: "htpasswd"
- name: REGISTRY_AUTH_HTPASSWD_REALM
value: "Registry Realm"
- name: REGISTRY_AUTH_HTPASSWD_PATH
value: "/auth/htpasswd"
- name: REGISTRY_HTTP_TLS_CERTIFICATE
value: "/certs/tls.crt"
- name: REGISTRY_HTTP_TLS_KEY
value: "/certs/tls.key"
- name: REGISTRY_STORAGE_DELETE_ENABLED
value: "true"
volumes:
- name: repo-vol
persistentVolumeClaim:
claimName: "glusterfs-claim-docker-registry"
- name: certs-vol
secret:
secretName: certs-secret-docker-registry
- name: auth-vol
secret:
secretName: auth-secret-docker-registry
---
apiVersion: v1
kind: Service
metadata:
name: docker-registry
namespace: "docker-registry"
spec:
selector:
app: registry
ports:
- port: 5000
targetPort: 5000
Pour déployer l’application, exécuter la commande
kubectl apply -f deploy.yaml
Vérification du démarage du container
Utilisez la commande : kubectl describe pod
et la touche tab (completion automatique, ceci nécessite de l’avoir implémenté Voir vers la fin de cette procédure)
kubectl describe pod -n docker-registry [TAB]
Répéter la commande jusqu’à obtenir “Started container registry” dans ce type de résultat (le déploiement peut prendre quelques minutes) :
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 36s default-scheduler Successfully assigned docker-registry/docker-registry-deploy-6467fb7ffc-8q762 to MY_SERVER
Normal Pulling 33s kubelet Pulling image "registry:2.6.2"
Normal Pulled 26s kubelet Successfully pulled image "registry:2.6.2" in 6.542667447s
Normal Created 10s kubelet Created container registry
Normal Started 9s kubelet Started container registry
Post-installation (à réaliser sur tous les noeuds)
Résolution IP du service de registry
Tous les noeuds du cluster doivent avoir accès à la registry privée. Cette registry est créée dans le cluster, donc accessible uniquement par le réseau du cluster.
Qui récupère les images auprès de la registry ? Le processus Docker du noeud choisi par le plan de control. Docker devra résoudre lui-même l’adresse de l’image, fournie dans le manifest de déploiement. Par conséquent tous les noeuds devront résoudre ce FQDN.
Notre registry écoute sur le port 5000 et sur une adresse définie lors de la création du service. Pour connaitre l’adresse IP du service créé pour la registry privée :
kubectl get svc -n docker-registry
Exemple:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
docker-registry ClusterIP 10.106.2.93 <none> 5000/TCP 16h
Ce service sera accessible par l’adresse/port : 10.106.2.87:5000
Pour permettre une résolution de cette Ip par le nom “docker-registry”, j’ajoute le résultat de la commande suivante dans le fichier “/etc/hosts” de chacun des noeuds du cluster :
kubectl get svc -A | grep -E "docker-registry.*5000" | awk '{print $4" docker-registry"}'
Exemple :
10.106.2.93 docker-registry
Ajouter cette valeur au fichier /etc/hosts de tous les noeuds
Certificat
Cette opération va consister à copier le certificat “tls.crt”, créé lors de la génération des secrets, dans la liste des certificats de confiance de docker. L’opération est à réaliser sur chaque noeud du cluster.
Master
Le plus facile, puisque c’est lui qui dispose du répertoire “registry” contenant le certificat de la registry privée.
# créer le répertoire contenant le nom hôte et son port, définis ci-avant
mkdir -p /etc/docker/certs.d/docker-registry:5000
# copie du fichier tls.crt
cp registry/certs/tls.crt /etc/docker/certs.d/docker-registry:5000/.
Sur TOUS les workers
Cette opération est à réaliser à partir du Master et sur tous les autres noeuds :
ssh root@[noeud] 'mkdir -p /etc/docker/certs.d/docker-registry:5000'
# copier le fichier registry/certs/tls.crt du master sur les noeuds worker
scp registry/certs/tls.crt root@[Noeud]:/etc/docker/certs.d/docker-registry:5000/.
PS : Pour éviter la demande du mot de passe à chaque commande, installez la clé ssh public du Master, sur tous les workers du cluster.
Test de fonctionnement
La résolution, ainsi que le certificat sont mis en place sur tous les noeuds
Donc pour s’y connecter à partir de n’importe quel noeud, nous utiliserons la commande :
docker login docker-registry:5000 -u [LOGIN] -p [PASSWORD]
LOGIN et PASSWORD ont été définis dans la première partie de cette procédure.
Le nom sera résolu par l’hôte (fichier /etc/hosts)
vous devre obtenir :
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
Creation du secret d’authentifications Kubernetes
Nous avons donc vérifié que Docker peut accèder à la registry privée sur tous les noeuds. Les applications seront déployées dans le cluster Kubernetes et c’est kubernetes qui pilotera Docker. Et Kubernetes devra fournir à Docker le compte et mot de passe d’accès à cette registry privée.
Kubernetes utilisera dans ce cas un mécanisme basé sur les “secrets” (détails).
Les paramétres d’authentification (login/mot de passe d’accès à la registry Docker privée) doivent être créés dans le même namespace que le “pod” ou le “deployment”.
Stockons ce secret
Ce secret sera stocké dans le namespace “default”
Sur le master, connectez-vous à la registry privée :
docker login
LOGIN et PASSWORD ont été définis dans la première partie de cette procédure.
Cette action va créer la propriété “auth” dans le fichier ~/.docker/config.json et donc permettre à docker de se connecter automatiquement à la registry privée.
Utilisons ce secret pour créer le secret “regcred” dans kubernetes :
kubectl create secret generic regcred --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson
Tout est maintenant en place pour permettre un déploiement à partir de fichiers manifest.
Déploiement d’un manifest incluant une image de la registry privée
Créer votre manifest, et ajouter ce composant en indiquant bien le namespace de votre application :
---
apiVersion: v1
data:
**secret**
kind: Secret
metadata:
name: auth-secret-docker-registry
namespace: [namespace utilisé]
type: kubernetes.io/dockerconfigjson
---
où **secret** sera la valeur contenu dans le secret regcred et obtenu avec la commande :
kubectl get secret regcred -o jsonpath='{.data}' | sed -E 's/\{|\}|"/ /g' | xargs
exemple obtenu : .dockerconfigjson : xcfvdfsd….==
Remplacer **secret** par la valeur obtenue, et au niveau du “Deployment”, ajouter la propriété “imagePullSecrets” :
apiVersion: apps/v1
kind: Deployment
metadata:
[...]
spec:
[...]
template:
metadata:
labels:
[...]
spec:
imagePullSecrets:
- name: auth-secret-docker-registry
containers:
- name: ...
image: docker-registry:5000/[image]
Durant la phase de déploiement, l’image sera récupérée en fournissant le secret “auth-secret-docker-registry”
Suppression de la registry privée
# suppression du namespace
kubectl delete namespace docker-registry
# suppression du "Persistent Volume"
kubectl delete pv gluster-pv-docker-registry
Gérer le contenu de la registry
A partir d’un serveur d’un master du cluster
curl -k -H 'Accept: application/json' -u [login:password] https://docker-registry:5000/v2/
# retourne {}
# si problème avec l'authentification retourne :
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":null}]}
Suppression une version d’image
Pour pouvoir supprimer une image, il faut que le container dispose de la variable d’environnement : REGISTRY_STORAGE_DELETE_ENABLED="true"
- Liste du catalogue :
curl -k -H 'Accept: application/json' -u [login:password] https://docker-registry:5000/v2/_catalog
# Retourne un contenu json présentant les images disponibles
{"repositories":["apache2-php7","backuppc","nextcloudcustom","trudesk"]}
- Lister les références (tag) d’une image Par exemple, le résultat de la commande précédente l’image “trudesk”, listons les tag de cette image
curl -k -H 'Accept: application/json' -u [login:password] https://docker-registry:5000/v2/trudesk/tags/list
# Retourne un contenu json présentant les images disponibles
{"name":"trudesk","tags":["1.0.10"]}
L’image trudesk:1.0.10 existe dans le dépot.
- Suppression de l’image:tag
Créer le fichier registry-delete-image.sh
## Set values
registry="docker-registry:5000"
user="docker login"
password="docker password"
IMAGENAME="$1"
tag="$2"
if [ "$1" == "" ] || [ "$2" == "" ];then
echo "Please provide imagename and tag"
exit 1
fi
echo "imagename:$IMAGENAME"
echo "tag:$tag"
response=$(curl -u $user:$password -sSL -I -H "Accept: application/vnd.docker.distribution.manifest.v2+json" https://${registry}/v2/${IMAGENAME}/manifests/$tag|grep "docker-content-digest:"|cut -d ":" -f 2,3|xargs)
if [ "$reponse" != "" ];then
echo "digest:$response"
curl -v -u $user:$password -X DELETE https://${registry}/v2/${IMAGENAME}/manifests/${response}
echo "$1:$2 has been deleted"
else
echo "Nothing to delete"
fi
Pour utiliser ce shell :
chmod +x registry-delete-image.sh
# Exemple pour supprimer l'image : trudesk:1.0.10
registry-delete-image.sh "trudesk" "1.0.10"