Cette étape est pour l’instant la plus complexe (pour moi). Je vais déployer la solution de « edge routing », Traefik, sur Kubernetes.
Traefik est à la fois un reverse proxy et un load balancer. Grâce à lui, les différentes requêtes à destination des applications hébergées sur le cluster kubernetes vont pouvoir être sécurisées et routées vers les bons services K8S.
Pour réaliser ces tâches, Traefik va s’appuyer sur les objets Kubernetes « ingress ».
Traefik est donc ce qu’on appelle un « Ingress controllers » . Il en existe de nombreux dans la galaxie K8S
Traefik à l’avantage d’être open source, moderne dans sa conception, et maintenu par une société française « Containous». L’application évolue très vite et a connu une cassure assez forte entre la version 1 et 2.
Au moment de la rédaction de cet article, je vais utiliser la version 2.2. Encore une fois, je me suis appuyé sur un tutorial existant réalisé cette fois par la société WeScale. J’ai pris pas mal de liberté et m’en suis peu à peu détaché.
J’ai décidé de m’organiser en exploitant 07 fichiers yaml.
Fichiers |
Yaml |
01-crd-traefik.yaml |
Création des objets spécifique à Traefik |
02-clr-traefik-autorization.yaml |
Création d’un rôle pour Traefik et des règles associées |
03-crb-traefik-binding.yaml |
Mappage du rôle à un compte de service pour Traefik |
04-sva-traefik-user.yaml |
Création du compte de service Traefik |
05-das-traefik-ingress-controller.yaml |
Déploiement des contrôleurs Traefik |
06-mid-consul-treafik-auth.yaml |
Déploiement du middleware pour l’authentification |
07-inr-treafik-dashboard.yaml |
Règle de routage pour les dashboards Traefik |
La première chose à faire est de déployer les objets types « CustomResourceDefinition ». En effet, depuis la v2 traefik utilise ses propres objets Kubernetes pour travailler. C’est en déclarant ces nouveaux objets dans le déploiement des applications que ces dernières vont pouvoir être reconnues par Traefik. Un routage dynamique et automatique va ainsi pouvoir être réalisé. Kubernetes devient ainsi un « provider » pour Traefik qui va construire sa configuration fonction des objets qui ont été rajoutés à l’API Kubernetes.
Le yaml qui permet de construire ces objets est fourni sur le site de traefik
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutes.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRoute
plural: ingressroutes
singular: ingressroute
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: middlewares.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: Middleware
plural: middlewares
singular: middleware
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressroutetcps.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteTCP
plural: ingressroutetcps
singular: ingressroutetcp
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: ingressrouteudps.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: IngressRouteUDP
plural: ingressrouteudps
singular: ingressrouteudp
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: tlsoptions.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TLSOption
plural: tlsoptions
singular: tlsoption
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: tlsstores.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TLSStore
plural: tlsstores
singular: tlsstore
scope: Namespaced
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: traefikservices.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TraefikService
plural: traefikservices
singular: traefikservice
scope: Namespaced
kubectl.exe apply -f .\01-crd-traefik.yaml
Le deuxième fichier yaml "02-clr-traefik-autorization" va créer un rôle dédié à traefik et associer des règles sur les objets qui viennent d’être déclarés.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: clr-traefik-ingress-controller
namespace: prd-lan-coolcorp
rules:
- apiGroups:
- ""
resources:
- services
- endpoints
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- traefik.containo.us
resources:
- middlewares
verbs:
- get
- list
- watch
- apiGroups:
- traefik.containo.us
resources:
- ingressroutes
verbs:
- get
- list
- watch
- apiGroups:
- traefik.containo.us
resources:
- ingressroutetcps
verbs:
- get
- list
- watch
- apiGroups:
- traefik.containo.us
resources:
- tlsoptions
verbs:
- get
- list
- watch
- apiGroups:
- traefik.containo.us
resources:
- ingressrouteudps
verbs:
- get
- list
- watch
- apiGroups:
- traefik.containo.us
resources:
- tlsstores
verbs:
- get
- list
- watch
- apiGroups:
- traefik.containo.us
resources:
- traefikservices
verbs:
- get
- list
- watch
kubectl.exe apply -f .\02-clr-traefik-autorization.yaml
Le troisième yaml va faire l’association entre ce rôle et un compte de service
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: crb-traefik-ingress-controller
namespace: prd-lan-coolcorp
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: clr-traefik-ingress-controller
subjects:
- kind: ServiceAccount
name: sva-traefik-ingress-controller
namespace: prd-lan-coolcorp
kubectl.exe apply -f .\03-crb-traefik-binding.yaml
Le yaml suivant va justement créer ce compte de service. C’est celui-ci qui va être utilisé par les contrôleurs Traefik pour communiquer avec le cluster K8S.
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sva-traefik-ingress-controller
namespace: prd-lan-coolcorp
J'arrive maintenant au yaml le plus important: celui qui va me permettre de déployer les contrôleurs traefik.
J’utilise pour cela des objets de type DaemonSet. Cela me permet d’opérer un déploiement dans lequel Kubernetes s’assure d’avoir un pod par node. Traefik étant le point d’entrée pour toutes les applications hébergées, son rôle est vital. Il faut donc s’assurer d’avoir au moins toujours une instance valide. Le fait d’utiliser un « DaemonSet » m’assure d’avoir une répartition des instances sur l’ensemble du cluster.
On pourrait considérer que certains nodes comme les masters n’ont pas à supporter ce rôle puisque que leurs missions est la maintenance et le pilotage du cluster, non de faire tourner des pods applicatifs. Je pense que cela est possible en utilisant des options de filtrages pour le daemonset afin d’exclure les nodes de type master. Dans mon cas, je ne peux pas me permettre le luxe de ne pas tirer partie de tous mes nœuds sur mon lab.
Bien entendu, cela va imposer d’utiliser en amont du cluster un loadbalancer en charge de distribuer les requêtes sur les différents nœuds du cluster. Je réutiliserais pour cela ma VM HAproxy déployée à l’étape 2.
apiVersion: apps/v1kind: DaemonSetmetadata:name: dae-traefik-ingress-controller-lan-prdnamespace: prd-lan-coolcorplabels:k8s-app: traefik-ingress-lb-prd-lankubernetes.io/cluster-service: "true"spec:selector:matchLabels:k8s-app: traefik-ingress-lb-prd-lantemplate:metadata:namespace: prd-lan-coolcorplabels:k8s-app: traefik-ingress-lb-prd-lanname: traefik-ingress-lb-prd-lanspec:hostNetwork: truednsPolicy: ClusterFirstWithHostNetserviceAccountName: sva-traefik-ingress-controllerterminationGracePeriodSeconds: 60tolerations:- key: node-role.kubernetes.io/mastereffect: NoSchedulecontainers:- image: traefik:v2.2.0name: traefik-ingress-lb-prd-lanimagePullPolicy: AlwaysvolumeMounts:- mountPath: "/etc/static_certs"name: traefik-certif-ro- mountPath: "/etc/traefik/"name: traefik-cfg-ro- mountPath: "/etc/dynamic_certs"name: traefik-certif-rwresources:requests:cpu: 100mmemory: 20Miargs:- --providers.kubernetescrd- --entrypoints.web.address=:80- --entrypoints.websecure.address=:443- --entrypoints.sftp.address=:2222- --api.dashboard=true- --log.level=INFO- --providers.file.directory=/etc/traefik/- --providers.consul.endpoints=svc-consul-for-traefik:8500volumes:- name: traefik-cfg-ronfs:path: /volume1/k8s-psv-nfs-rwm/traefik-ingress-controller-lan-prd/traefik-cfg-roserver: storage.coolcorp.privreadOnly: true- name: traefik-certif-ronfs:path: /volume1/k8s-psv-nfs-rwm/traefik-ingress-controller-lan-prd/traefik-certif-roserver: storage.coolcorp.privreadOnly: true- name: traefik-certif-rwnfs:path: /volume1/k8s-psv-nfs-rwm/traefik-ingress-controller-lan-prd/traefik-certif-rwserver: storage.coolcorp.priv
La partie « args » est très importante puisqu’elle définit les paramètres des instances traefik
Paramètres |
Détails |
providers.kubernetescrd
|
Précise à Traefik que les objets CustomResourceDefinition K8S doivent être utilisés comme source de configuration (provider) |
entrypoints.web.address=:80
|
Définis une entrée sur le port 80 |
entrypoints.websecure.address=:443
|
Définis une entrée sur le port 443 |
entrypoints.sftp.address=:2222
|
Définis une entrée sur le port 2222 |
api.dashboard=true
|
Active l’usage des dashboards |
log.level=INFO
|
Définis le niveau de log |
providers.file.directory=/etc/traefik/
|
Indique comme source de configuration les fichiers dans /etc/traefik |
providers.consul.endpoints=svc-consul-for-traefik:8500
|
Indique d’interroger consul comme source de configuration. Par défaut, traefik cherche une arborescence sous le mot clef « traefik » |
J'ai également décidé d'utiliser trois espaces de stockages persistents.
pvo-traefik-cfg-ro |
Emplacement où se trouvent les fichiers de configuration à plat utilisables par traefik. Je ne souhaite pas qu’il soit modifiable directement depuis le conteneur, je place donc l’emplacement en lecture seule, mais accessible à plusieurs pods en même temps. Concrètement c’est un partage NFS sur lequel je pourrais agir depuis l’extérieur du cluster pour apporter des modifications à la configuration de traefik si besoin |
pvo-traefik-certif-ro |
Emplacement où se trouvent des certificats utilisables pour sécuriser l’accès à certaines applications qui pourraient être hébergées par mon cluster Kubernetes. Il s’agit de certificats que je souhaite forcer, et non pas des certificats générés automatiquement par Traefik, c’est pourquoi l’emplacement est en lecture seul, mais accessible à plusieurs pods en même temps. Concrètement c’est un partage NFS sur lequel je pourrais agir depuis l’extérieur du cluster pour apporter ajouter ou retirer des certificats si besoin. |
pvo-traefik-certif-rw |
Emplacement accessible en lecture et écriture par de multiples nods. Il sert à l’hébergement de certificats qui pourraient être automatiquement générés par Traefik. En effet, ce dernier peut utiliser par exemple let’s encrypt pour traiter automatiquement la demande et la génération de certificats. Concrètement c’est également un partage NFS. |
Pour ces trois usages, je n’ai absolument pas besoin de performances et l’espace occupé est minime. Je vais donc aller au plus simple, ne pas utiliser de provisionnement automatique de l’espace et passer par le driver de stockage natif à chaque nœud K8S pour l’accès aux partages NFS (je n'utilise donc pas d'objets de type PersistentVolumeClaim)
kubectl.exe apply -f 05-das-traefik-ingress-controller.yaml
Arrivé là, je vérifie que les pods sont bien déployés:
kubectl.exe get pod --namespace=prd-lan-coolcorp -o wide
Avec l’option "-o wide" je peux connaitre le noeud d’exécution de mes pods. Pour les instances Traefik, j’en ai bien une par nœud, comme prévu.
Traefik permet d’appliquer des règles entre la requête qu’il intercepte et son routage vers le service associé au sein du cluster. Il s’agit d’objets « middelware ». J’en définis un dans le fichier « 08-mid-consul-treafik-auth.yaml » afin d’ajouter une couche d’authentification
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: mid-treafik-consul-auth-admin
namespace: prd-lan-coolcorp
spec:
basicAuth:
secret: sec-traefik-consul-auth-admin
---
apiVersion: v1
kind: Secret
metadata:
name: sec-traefik-consul-auth-admin
namespace: prd-lan-coolcorp
data:
users: |2
YnZpdmk1NzokYXByMSRGTzhGMDl2OCRDL3VzaFQ5eVdfdfdfdfdsf<dsfqfdqf
J’ai pas mal galéré pour comprendre la partie user/mot de passe qui se définit au niveau data :
En faite, il faut utiliser HTPasswd pour chiffrer une première fois le mot de passe. Si vous ne disposer pas de l’outil, des sites webs comme celui-ci le font pour vous
Vous obtenez une première combinaison du type user :pass_chiffer.
Il va falloir à nouveau chiffrer cette combinaison via par exemple ce genre de site
C’est le résultat final qu’il faudra indiquer au niveau du champ user : du fichier yaml
kubectl apply -f 06-mid-consul-treafik-auth.yaml
Il ne me reste plus qu’à utiliser mon dernier fichier yaml 09-inr-treafik-dashboard.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: traefik-dashboard
namespace: prd-lan-coolcorp
spec:
routes:
- match: Host(`traefik-lan.inf.prd.k8s.coolcorp.priv`)
kind: Rule
services:
- name: api@internal
kind: TraefikService
middlewares:
- name: mid-treafik-consul-auth-admin
Celui-ci défini la règle de routage pour que toute requêtes sur l’URL traefik-lan.inf.prd.k8s.coolcorp.priv soit redirigée vers le service de dashboard interne de traefik en forçant une authentification grâce au midedleware créer précédemment.
kubectl apply -f 07-inr-treafik-dashboard.yaml
Je vais en profiter également pour donner à un accès ma GUI consul auquel je ne pouvais accéder avant qu’à partir d’une commande d’encapsulation de la requête sur mon poste de travail et la commande kubectl port-forward (voir étape 5)
J’utilise le fichier yaml 03-svc-consul-ui.yaml suivant
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: consul-for-traefik-ui-http
namespace: prd-lan-coolcorp
spec:
entryPoints:
- web
routes:
- kind: Rule
match: Host(`consul-traefik-lan.inf.prd.k8s.coolcorp.priv`)
services:
- name: svc-consul-for-traefik
port: 8500
middlewares:
- name: mid-treafik-consul-auth-admin
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: consul-for-traefik-ui-https
namespace: prd-lan-coolcorp
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`consul-traefik-lan.inf.prd.k8s.coolcorp.priv`)
services:
- name: svc-consul-for-traefik
port: 8500
middlewares:
- name: mid-treafik-consul-auth-admin
tls:
certResolver: le
Je crée deux règles de routages, l’une pour des requêtes arrivant sur le port 80, l’autre sur le port 443. Si l’URL est consul-traefik-lan.inf.prd.k8s.coolcorp.priv alors traefik va rediriger la requête vers le service consul qui tourne sur le cluster kubernetes.
Je réutilise également mon objet « middelwares » pour fournir une authentification sur la page.
kubectl apply -f 03-svc-consul-ui.yaml
Avant de tester l’accès aux URLS, je vais d’abord configurer mon serveur HAproxy afin qu’il redirige les requêtes vers les nœuds K8S.
Je me connecte donc sur ma VM haproxy puis j’ajoute des paramètres au fichier /etc/haproxy/haproxy.cfg
J’ajoute mes frontend qui correspondent à mes entrypoint définis dans Traefik
frontend http_k8s
bind *:80
mode http
default_backend k8s_nodes_http
frontend https_k8s
bind *:443
option tcplog
mode tcp
default_backend k8s_nodes_https
frontend sftp_k8s
bind *:2222
option tcplog
mode tcp
default_backend k8s_nodes_sftp
Puis je les fais correspondre à mes backend pour renvoyer vers les nœuds K8S
backend k8s_nodes_http
balance roundrobin
option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
#option httpchk HEAD / HTTP/1.1\r\nHost:localhost
server k8smst001 192.168.10.70:80 check
server k8smst002 192.168.10.71:80 check
server k8smst003 192.168.10.72:80 check
server k8swok001 192.168.10.73:80 check
server k8swok002 192.168.10.74:80 check
backend k8s_nodes_https
mode tcp
balance source
hash-type consistent # optional
#cookie JSESSIONID prefix nocache
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
#option httpchk HEAD / HTTP/1.1\r\nHost:localhost
server k8smst001 192.168.10.70:443 check
server k8smst002 192.168.10.71:443 check
server k8smst003 192.168.10.72:443 check
server k8swok001 192.168.10.73:443 check
server k8swok002 192.168.10.74:443 check
backend k8s_nodes_sftp
mode tcp
balance source
hash-type consistent # optional
#cookie JSESSIONID prefix nocache
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
#option httpchk HEAD / HTTP/1.1\r\nHost:localhostk
server k8smst001 192.168.10.70:2222 check
server k8smst002 192.168.10.71:2222 check
server k8smst003 192.168.10.72:2222 check
server k8swok001 192.168.10.73:2222 check
server k8swok002 192.168.10.74:2222 check
Je termine par créer des alias DNS qui résout mes URLS vers l’IP du loadbalancer.
Si je fait un petit schéma qui résume la logique
Voilà ce que j’ai compris :
Vérifions que les URLs fonctionnent
- Accès aux dasboard traefik
- Accès à l’interface consul
Notez que dans les deux cas, j’ai eu une fenêtre d’authentification dans laquelle j’ai du renseigner mon login et mot de passe déclarer dans mon « Middelware ».
Il reste maintenant à parler de la configuration complémentaire de Traefik. Comme déjà vu, une première partie est poussée en arguments de l’exécution du conteneur.
J’ai indiqué également à Consul de traiter la configuration qu’il peut lire sous forme de fichier dans /etc/traefik/
En faite ce chemin pointe réellement vers un partage NFS via la définition des volumes que j’ai utilisé. Ce qui me permet d’aller éditer la configuration que je souhaite, directement depuis mon poste via ce partage.
En l’occurrence j’ai simplement créé un fichier tls.toml pour indiquer à Traefik d’utiliser des certificats par défaut pour les accès https
[tls]
[tls.stores]
[tls.stores.default]
[tls.stores.default.defaultCertificate]
certFile = "/etc/static_certs/prd/inf/prd_inf.cer"
keyFile = "/etc/static_certs/prd/inf/prd_inf.key"
[[tls.certificates]]
certFile = "/etc/static_certs/prd/inf/prd_inf.cer"
keyFile = "/etc/static_certs/prd/inf/prd_inf.key"
J’ai placé ces certificats dans un partage NFS qui correspond également à un des volumes que j’ai associé à mes pods traefik.
Pour la petite histoire, ces certificats ont été générés avec le wildcard * inf.prd.k8s.coolcorp.priv, ce qui me permet de fournir un accès https à tous mes services d’infrastructure de prod sans me prendre la tête.
J’ai également indiqué à Traefik de se « sourcer » dans consul.
Tout ne peut pas être positionné comme paramètre via cette solution, mais uniquement des éléments dynamiques comme par exemple l’ajout d’une nouvelle règle de routage.
Je réalise un test. Maintenant que je peux accéder facilement à la GUI de consul, je créer une arborescence traefik
Dans cette arborescence, je créer une sous arborescence de type http/routers/test_de_route
Dans cette sous-arborescence, je créer une clef "rule" avec comme valeur "Host(`mon-site-de-test.fr`)"
Si maintenant je retourne dans l’interface de Traefik, je vois bien apparaitre ma route dans la rubrique http. Elle est en erreur, car aucun de service de backend n’est associé, mais cela démontre que Traefik est bien en mesure de lire une configuration issue de consul.
update 11/05/2020
Traefik peut automatiquement rediriger un flux http vers https.
Pour cela, il faut créer un objet type «middleware ».
Je rédige donc un yaml « 10-mid-redirecthttps.yaml » avec le contenu suivant:
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: https-redirectscheme
namespace: prd-lan-coolcorp
spec:
redirectScheme:
scheme: https
permanent: true
kubecl apply -f 10-mid-redirecthttps.yaml
Je peux ensuite mettre à jour par exemple mon fichier 03-svc-consul-ui.yaml qui concerne l’accès à l’interface de consul en ajoutant la référence à ce nouveau middelware pour la partie http
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: consul-for-traefik-ui-http
namespace: prd-lan-coolcorp
spec:
entryPoints:
- web
routes:
- kind: Rule
match: Host(`consul-traefik-lan.inf.prd.k8s.coolcorp.priv`)
services:
- name: svc-consul-for-traefik
port: 8500
middlewares:
- name: https-redirectscheme
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: consul-for-traefik-ui-https
namespace: prd-lan-coolcorp
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`consul-traefik-lan.inf.prd.k8s.coolcorp.priv`)
services:
- name: svc-consul-for-traefik
port: 8500
middlewares:
- name: mid-treafik-consul-auth-admin
tls:
certResolver: le
kubecl apply -f 03-svc-consul-ui.yaml
J’ai désormais une redirection automatique de http://consul-traefik-lan.inf.prd.k8s.coolcorp.priv vers https://consul-traefik-lan.inf.prd.k8s.coolcorp.priv.