Tout le minimum vital a été déployé lors des parties précédentes pour l’exécution de KubeVirt. La cible a été définie avec la configuration du serveur hôte, puis les composants principaux ont été installés.
Avant d’aller plus loin, il convient de mettre en œuvre quelques briques complémentaires, non directement liées à KubeVirt mais tout de même bien pratique pour la suite et souvent indispensable pour un cluster Kubernetes.
N’oublions pas que ce n’est pas parce qu’on exécute des VMs qu’on ne veut pas se donner la possibilité d’exécuter des applications conteneurisées plus classique.
Le but est d’avoir un usage de Kubernetes aussi bien pour des machines virtuelles que pour des conteneurs.
Dans ce cadre, il devient donc nécessaire d’ajouter au cluster K8S un ingress controler qu’on va rendre compatible avec la Gateway API, en l’occurrence Traefik et une solution de gestion automatisée des certificats, à savoir Cert-Manager.
L’un comme l’autre permettra d’accéder aux GUI des différents outils qu’on aura à déployer par la suite.
Voici un schéma d’illustration qui résume les composants que nous allons mettre en oeuvre
Cliquez sur l'image pour l'agrandir.
J’ai déjà eu l’occasion de présenter Traefik. N’hésitez pas à parcourir mon article dédié au sujet et intégré à mon CookBook K8S.
Traefik est un ingress controler, c’est-à-dire qu’il permet d’exposer ses services K8S à l’extérieur du cluster offrant des fonctionnalités de reverse proxy, comme l’ajout automatique de certificats, la réécriture d’URL, la protection par mot de passe….
Avec l’arrivée de la Gateway API que j’ai présenté dans cet article, Traefik est devenu une GatewayClass et permet donc d’exploiter cette nouvelle manière de faire pour la gestion de ses publications externes.
On démarre par la mise en œuvre d’un namespace dédié :
inf-traefik-lan
kubectl create ns inf-traefik-lan
Cliquez sur l'image pour l'agrandir.
Cela va me permettre d’y exécuter toutes les dépendances nécessaires et de distinguer mon instance de Traefik dédiée à mes applications publiées en interne d’une future instance de Traefik de DMZ. Si je devais avoir l’idée de dédier des nœuds de mon cluster à l’hébergement d’applications en DMZ, je pourrais le faire. C’est cet usage que je décris dans mon cookbook K8S, mais ici, vu que je ne dispose que d’un seul node, je me contenterais de mon instance lan de traefik.
Puis on crée un certificat générique qui va servir à la fois de certificat pour l’accès au dashboard de Traefik et de certificat par défaut pour la Gateway.
Chacun a sa propre technique. Je ne vais pas entrer dans les détails à ce stade.
Me concernant, je vais choisir le nom de publication traefikrubikub-gtw-lan.coolcorp.priv
Je génère la clef privée et le csr (cetificat submit requets) avec openssl
openssl req -new -nodes -sha256 -keyout traefikrubikub-gtw-lan.coolcorp.priv.key -out traefikrubikub-gtw-lan.coolcorp.priv.csr -newkey rsa:4096 -subj "/C=FR/ST=Ile-de-France/L=Paris/O=COOLCORP/OU=Infrastructure/CN=traefikrubikub-gtw-lan.coolcorp.priv" -reqexts SAN -config <(printf "[req]\ndistinguished_name = req_distinguished_name\n[req_distinguished_name]\n[SAN]\nsubjectAltName=DNS:traefikrubikub-gtw-lan.coolcorp.priv")
Puis je soumets mon CSR à ma PKI interne (un CA Windows Serveur intégré à mon domaine AD).
certreq -attrib "CertificateTemplate:TPL-SRV-WEB-DEFAULT" -submit .\traefikrubikub-gtw-lan.coolcorp.priv.csr
Pour récupérer mon certificat.
L’obtention de ce certificat est propre à chacun et à sa solution de certificat interne. Il suffit de pouvoir générer un certificat type serveur WEB, tout ce qu’il y a de plus classique en ayant un attribut CN et un SubjectAltName identique.
On va stoker ce certificat et la clef privée associée dans un secret Kubernetes au sein du namespace dédié à Traefik
kubectl create secret tls sec-gateway-default-cert --cert=traefikrubikub-gtw-lan.coolcorp.priv.cer --key=traefikrubikub-gtw-lan.coolcorp.priv.key -n inf-traefik-lan
Cliquez sur l'image pour l'agrandir.
Traefik a besoin d’interagir avec le cluster pour interroger l’API.
On va donc exploiter un compte de service pour cela. Plutôt que de laisser l’installation créer un compte, on va le générer en amont via le fichier svc-traefik-lan.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: "svc-prd-traefik"
namespace: "inf-traefik-lan"
Il suffit de l'appliquer.
kubectl apply -f svc-traefik-lan.yaml
Cliquez sur l'image pour l'agrandir.
Je ne rentre pas plus dans le détail, n’hésitez pas à parcourir mon article dédié dédié au besoin.
Comme pour Cilium déployé dans l’article précédent, on va utiliser helm pour déployer Traefik.
On déclare les repos
helm repo add traefik https://helm.traefik.io/traefik
helm repo update
Cliquez sur l'image pour l'agrandir.
On labélise mon node pour qu’il soit sélectionné dans l’exécution des pods de traefik, en lien avec la configuration que je vais associer à Traefik.
kubectl label node prdk8sctp001 network=lan
kubectl label node prdk8sctp001 traefik-lan=yes
Cliquez sur l'image pour l'agrandir.
Cette configuration, justement, est dans le fichier traefik-config-lan.yaml
image:
tag: "3.6.4"
pullPolicy: IfNotPresent
deployment:
enabled: true
kind: DaemonSet
labels:
network: lan
podLabels:
network: lan
gatewayClass:
enabled: true
name: traefik-lan
labels:
network: lan
gateway:
enabled: false
providers:
kubernetesGateway:
enabled: true
labelSelector: "network=lan"
kubernetesIngress:
labelSelector: "network=lan"
allowExternalNameServices: true
kubernetesCRD:
enabled: true
allowCrossNamespace: false
allowExternalNameServices: true
allowEmptyServices: false
namespaces:
- inf-traefik-lan
- longhorn-system
- inf-kubevirtmanager-lan
serviceAccount:
name: "svc-prd-traefik"
logs:
general:
level: ERROR
hostNetwork: true
ports:
web:
port: 80
expose:
default: true
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
port: 443
expose:
default: true
proxyProtocol:
trustedIPs:
- "192.168.10.160"
forwardedHeaders:
trustedIPs:
- "192.168.10.160"
tls:
enabled: true
securityContext:
capabilities:
drop: [ALL]
add: [NET_BIND_SERVICE]
readOnlyRootFilesystem: true
runAsGroup: 0
runAsNonRoot: false
runAsUser: 0
updateStrategy:
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
type: RollingUpdate
additionalArguments:
- "--providers.kubernetesingress.ingressclass=traefik-lan"
- "--serversTransport.insecureSkipVerify=true"
Globalement on indique qu’on va déployer Traefik en tant que DaemonSet, ce qui permettra d’avoir une mise à l’échelle automatique du nombre de pod associé sur tout nouveau node qui rejoindrait le cluster et qui répondrait au critère d’éligibilité.
Ensuite, on indique qu’on va utiliser la Gateway API en activant Traefik comme gatewayClass, qu’on va nommer traefik-lan.
Néanmoins, on continu d’activer les providers plus classiques qui sont :
À noter que les CRD s’activent par namespace, ce qui implique qu’il faudra peut-être, par la suite, redéployer Traefik si un nouveau namespace nécessite l’usage d’objets personnalisés de Traefik. Dans mon cas j’ai anticipé pour la suite en indiquant les namespaces qu’on exploitera plus tard.
Ainsi, on peut couvrir tous les besoins.
On précise l’usage du service account qu’on a déployé précédemment.
Il ne reste plus qu’à exécuter la commande helm suivante:
helm install --namespace=inf-traefik-lan traefik-lan traefik/traefik -f traefik-config-lan.yaml --set nodeSelector.traefik-lan=yes --set tolerations[0].key=node-role.kubernetes.io/control-plane --set tolerations[0].operator=Exists --set tolerations[0].effect=NoSchedule
On notera les complements à la commande en charge de supporter l’exécution d’un pod traefik sur un control plane et l’usage du selector traefik-lan à yes pour correspondre à la labélisation du node réalisé précédement.
Cliquez sur l'image pour l'agrandir.
À ce stade on est en mesure de vérifier que traefik c’est bien déployé.
kubectl get pod -n inf-traefik-lan
Cliquez sur l'image pour l'agrandir.
On souhaite exploiter l’API Gateway avec Traefik, de manière a être en conformité avec les nouveaux usage qui pousse à la Gateway API en lieu et place des Ingress.
N’hésitez pas à parcourir mon article dédié sur le sujet pour mieux comprendre ce point.
À noter que, dans cet article, je parle de l’installation en amont de la Gateway API car elle n’est pas incluse par défaut dans Kubernetes. Mais ici, elle est implicitement installée avec son activation dans la configuration de Traefik.
C’est pourquoi on peut directement exploiter le fichier gateway-lan.yaml pour créer l’objet gateway.
Voici son contenu:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: traefik-gateway-lan
namespace: inf-traefik-lan
labels:
network: lan
spec:
gatewayClassName: traefik-lan
listeners:
- name: web
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
network: lan
- name: websecure
port: 443
protocol: HTTPS
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
network: lan
tls:
mode: Terminate
certificateRefs:
# Domaine pour les certificat dynamique
- name: sec-tls-wildcard-coolcorp-priv
namespace: inf-cert-lan
# Certificat par défaut
- name: sec-gateway-default-cert
On l’héberge dans le même namespace que Traefik.
À noter qu’on évoque l’usage de certificat type wildcard référencer sous le secret sec-tls-wildcard-coolcorp-priv. Pour l’instant ne vous en souciez pas, ça viendra un peu plus tard. Pour l’instant on a notre certificat par défaut.
Il est crucial de noter que, dans l’architecture utilisant la Gateway API, l’objet Gateway sert à lier un certificat à une URL, ce qui permet de la rendre accessible depuis l’extérieur en s’appuyant sur la classe Gateway, en l’occurrence Traefik.
On déploie la gateway:
kubectl apply -f gateway-lan.yaml
Cliquez sur l'image pour l'agrandir.
À ce stade, si on vérifie son statut, il est possible qu’elle ne soit pas opérationnelle en raison du secret manquant, mais cela viendra par la suite
kubectl describe gateway -n inf-traefik-lan
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
Il ne reste plus qu’à déployer le dashboard de Traefik pour offrir une GUI à l’outil et suivre ce qui se passe.
Bien que la GUI de Traefik n’autorise que de la lecture, on va tout de même la protéger avec un login et un mot de passe d’accès.
Pour ça on s’appuie d’abord sur l’outil htpasswd disponible dans le package httpd-tools (sudo dnf install httpd-tools) pour générer ce qu’il faut au bon format
htpasswd -Bbn bvivi57 'pass' > traefik-dashboard-lan-password
Cliquez sur l'image pour l'agrandir.
On exploite le fichier contenant les credential pour l’inclure dans un secret Kubernetes
kubectl create secret generic sec-traefik-dashboard-lan-password --from-file=traefik-dashboard-lan-password --namespace inf-traefik-lan
Puis on applique le contenu du fichier traefik-lan-dashboard.yaml.
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: traefik-lan-dashboard
namespace: "inf-traefik-lan"
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`traefikrubikub-gtw-lan.coolcorp.priv`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))
services:
- name: api@internal
kind: TraefikService
middlewares:
- name: traefik-dashboard-lan-auth # Referencing the BasicAuth middleware
namespace: inf-traefik-lan
tls:
secretName: sec-gateway-default-cert
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: traefik-dashboard-lan-auth
namespace: inf-traefik-lan
spec:
basicAuth:
secret: sec-traefik-dashboard-lan-password
avec la commande kubectl apply -f traefik-lan-dashboard.yaml
Cliquez sur l'image pour l'agrandir.
Dans mon exemple, cela me permet d’exposer la GUI sur l’URL https://traefikrubikub-gtw-lan.coolcorp.priv/dashboard avec comme certificat celui crée précédemment et partager par la gateway.
L’URL est protégée par le login et le mot de passe qu’on a choisi.
Il ne reste plus qu’à vérifier que l’URL est accessible. (Il est important de noter que j’ai créé un alias DNS sur mon serveur DNS pour rediriger l’IP par défaut de mon nœud et que celui-ci dispose bien entendu des ports 443 et 80 ouverts (configurés lors du déploiement du nœud dans le premier article via le rôle Ansible)).
Dans ce cas de figure, l’URL est desservie par un objet custom de Traefik et non par la Gateway, par contre, on a bien dans la GUI la confirmation que la Gateway API est prise en compte par Traefik.
Cliquez sur l'image pour l'agrandir.
On l’utilisera plus tard pour d’autres besoins.
Cert-Manager est un composant additionnel déployable dans Kubernetes qui va permettre d’automatiser tout le cycle de vie des certificats associés aux applications déployées sur le cluster.
Je détaille son fonctionnement et son usage ici. N’hésitez pas à y jeter un œil.
Dans notre tutoriel sur kubevirt, il va servir à traiter tous les certificats dont nous pourrions avoir besoin pour l’outillage tiers associé à KubeVirt, et même à couvrir tout besoin futur de publication d’applications.
Contrairement à l’article où j’associe Cert-Manager à acme2certifier, un proxy ACME permettant d’interagir avec une PKI Windows, je vais choisir ici une autre stratégie, plus simple et efficace.
Je vais simplement demander à ma PKI root hébergée sur mon serveur Windows (Services de certificats Active Directory (AD CS) ) de générer un certificat d’autorité intermédiaire.
Je vais associer un objet ClusterIssuer, soit un objet propre à Cert-Manager en charge de délivrer des certificats en interne du cluster, le certificat d’autorité intermédiaire.
De cette manière, ce ClusterIssuer va être une autorité de confiance validée par ma CA ROOT. Elle pourra ainsi délivrer un certificat wildcard sur mon domaine, renouvelable automatiquement tout les trois mois.
Cette stratégie va me permettre d’être totalement libéré de la contrainte des certificats. Le point critique sera le secret Kubernetes qui va stocker la clef privée et le certificat de l’autorité intermédiaire. Ce secret ne devra absolument pas être interrogeable par un tiers, n’oubliez pas que, sous K8S, un secret n’est rien d’autre qu’une valeur en base 64 dont il est possible de retrouver le contenu très simplement. D’où l’importance de bien gérer vos stratégies d’accès à vos clusters K8S. N’hésitez pas à parcourir mon articles sur le sujet.
Comme pour traefik on va passer par Helm.
Mais avant, on crée un namespace dédié
kubectl create ns inf-cert-lan
Cliquez sur l'image pour l'agrandir.
On peut y déployer directement Cert-Manager en ayant au préalable pris soin de déclarer le repo.
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager --namespace inf-cert-lan --version v1.19.2 --set installCRDs=true --set nodeSelector.network=lan
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
À noter qu’ici on n’exploite pas de fichier de configuration. On se contente de demander une version précise de Cert-Manager (ici la 1.19.2), d’installer les Custom Ressource de Cert-Manager, et de retenir le node avec le bon label (le meme qu’on a utilisé pour traefik)
À ce stade, on peut vérifier que tout est en ordre au niveau de Cert-Manager.
Kubectl get pod -n inf-cert-lan
Comme cela a été expliqué précédemment, on va créer un certificat propre à une CA intermédiaire.
On commence par créer la clef et le csr
openssl req -new -newkey rsa:4096 -nodes -keyout subordinate-ca-rubikube.key -out subordinate-ca-rubikube.csr -config ca-csr.conf
Puis on créer le certificat. Dans l’univers Microsoft, il y a un template pour cela, SubCA
certreq -attrib "CertificateTemplate:SubCA" -submit .\subordinate-ca-rubikube.csr
Cliquez sur l'image pour l'agrandir.
Avec la clef et le certificat (qui sont tout deux des données très sensibles, je le répète), on peut créer le secret qui va contenir l’ensemble
kubectl create secret tls sec-subordinate-ca-rubikube --cert=subordinate-ca-rubikube.cer --key=subordinate-ca-rubikube.key --namespace=inf-cert-lan
Cliquez sur l'image pour l'agrandir.
Tout est prêt pour l’issuer dont la description est contenue dans le fichier 01-clusterissuer-lan.yml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: clusterissuer-lan
spec:
ca:
secretName: sec-subordinate-ca-rubikube
On y référence le secret qu’on vient de créer et on décide bien qu’il s’agit d’un ClusterIssuer, soit un issuer compatible avec n’importe quel namespace.
N’hésitez pas à consulter mon article sur Cert-Manager issu du mon cookbook K8S si vous souhaitez en savoir plus.
kubectl apply -f 01-clusterissuer-lan.yml
Cliquez sur l'image pour l'agrandir.
On peut vérifier que l’issuer est opérationnelle.
kubectl get clusterissuer
Maintenant que l’issuer est up, on peut le solliciter pour créer notre wildcard via le fichier 02-certificat-wildcard.yml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: clusterissuer-lan
spec:
ca:
secretName: sec-subordinate-ca-rubikube
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: cert-wildcard-coolcorp
namespace: inf-cert-lan
spec:
secretName: sec-tls-wildcard-coolcorp-priv
issuerRef:
name: clusterissuer-lan
kind: ClusterIssuer
commonName: "*.coolcorp.priv"
dnsNames:
- "coolcorp.priv"
- "*.coolcorp.priv"
# Optionnel : Renouvellement auto 30 jours avant expiration
renewBefore: 720h
Au sein de de ce fichier, je sollicite mon ClusterIssuer fraichement crée pour qu’il me génère un wildcard pour mon domaine interne
kubectl apply -f 02-certificat-wildcard.yml
Cliquez sur l'image pour l'agrandir.
Je me retrouve bien avec ce certificat dans mon namespace dédié
kubectl get cert -n inf-cert-lan
Cliquez sur l'image pour l'agrandir.
Ce namespace justement, n’est pas le namespace de la gateway. Hors si vous regardez plus haut dans la configuration de cette derniere, je faisais déjà référence à ce certificat via son secret associé dans ce namespace spécifique.
Pour autoriser la passerelle à utiliser un certificat hébergé en dehors de son namespace par défaut, on doit ajouter une délégation via l’objet ReferenceGrant.
Celui-ci est décrit dans mon fichier 03-referentgrant-wildcart.yml
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-traefik-read-coolcorp
namespace: inf-cert-lan
spec:
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: inf-traefik-lan
to:
- group: ""
kind: Secret
name: sec-tls-wildcard-coolcorp-priv[
Cela autorise la gateway à exploiter le nouveau certificat wildcard que je viens de créer grâce à mon ClusterIssuer, lui-même autorisé comme CA intermédiaire.
kubectl apply -f 03-referentgrant-wildcart.yml
Cliquez sur l'image pour l'agrandir.
On peut vérifier que cela fonctionne, car si on interroge à nouveau la gateway, désormais celle-ci est à l’état true.
kubectl get gateway -n inf-traefik-lan
Cliquez sur l'image pour l'agrandir.
Cette partie du tutoriel s’éloigne un peu de kubevirt et touche un usage plus global de Kubernetes. Mais il me semblait important de l’inclure, tant il facilitera la suite des opérations en n’ayant plus à se préoccuper des problématiques liées à la publication des GUI, comme celle de Longhorn dont on va avoir besoin par la suite.
N’hésitez pas à vous inspirez de cette configuration pour vos clusters K8S, même si kubevirt ne fait pas partie de vos besoins. La combinaison Traefik, Gateway API, Cert-Manager est exploitable pour bien des usages.
Pour la suite, c'est par là