Cet article peut être lu comme une suite logique du précédent sur la mise en oeuvre d’une synchronisation uni-directionelle d’une boîte gmail vers un serveur IMAP local dovecot dans Kubernetes.
Après 2 mois d’utilisation, je peux dire que la synchronisation avec gmail fonctionne de manière très efficace. Pour me connecter au serveur IMAP dovecot (avec le client Thunderbird), j’estimais ne pas avoir besoin d’exposer le service lorsque je ne l’utilisais pas, et qu’une redirection de port ponctuelle me suffisait. En pratique, je vérifie de temps en temps cette boîte gmail à partir de mon serveur local (puisque la boîte gmail est vidée après chaque synchronisation), et devoir ouvrir la redirection de port est finalement déplaisante (même si ce n’est qu’à un clic de bouton dans l’outil Kube Forwarder).
La solution la plus simple aurait été d’exposer le service dovecot dans un service Kubernetes de type NodePort
(dont un exemple est inclus dans l’article référencé). Mais il est aussi possible d’utiliser un IngressRouteTCP
avec Traefik.
Pourquoi IngressRouteTCP plutôt que NodePort ?
J’avoue que pour l’instant, j’ai peu d’arguments sur l’intérêt d’un IngressRouteTCP
versus un NodePort
dans ma configuration actuelle, c’est-à-dire un cluster Kubernetes d’un seul noeud, sur lequel une seule instance dovecot tourne (donc pas besoin de load balancing).
J’aime cependant l’idée de savoir que dans mon cluster Kubernetes, tout ce qui vient de l’extérieur est routé par un seul outil (Traefik en l’occurence) et que tous mes services sont configurés comme locaux (de type ClusterIP
). Je trouve que ça simplifie les choix que je fais, en n’ayant « qu’une seule façon » de faire quelque chose, et réduire ma charge cognitive. C’est d’ailleurs, je pense, l’idée principale derrière Kubernetes Gateway API qui est encore un projet en cours de maturation (mais utilisable en mode expérimental).
Le fait d’utiliser Traefik permet aussi d’utiliser des middlewares. Pour TCP, ils sont réduits à deux pour le moment: InFlightConn (limiter le nombre de connexions concurrentes) et IPAllowList (limiter les adresses IP client).
Si plus tard je configure le protocol TLS sur dovecot, alors il se peut que le routeur Traefik (ou Gateway API si je passe à celle-ci) soit très intéressant car il supporte l’extension TLS SNI, qui permettrait de router le même port TCP exposé vers différents services Kubernetes en fonction du nom de domaine spécifié par le client qui se connecte. Sans SNI, on reste limité à un port exposé par service.
Mise en oeuvre
Bien que pas super pratique à configurer, le principe et la mise en oeuvre sont assez simple:
- Modifier le fichier de configuration de Traefik pour ajouter un
EntryPoint
. En pratique, il s’agit donc d’éditer un HelmChartConfig sur le serveur Kubernetes. Traefik va détecter ce changement et se redéployer automatiquement en ouvrant le ou les nouveaux ports (ports en écoute sur le serveur). - Déployer un nouveau manifeste de type
IngressRouteTCP
pour le lien entre l’EntryPoint
et notre service de typeClusterIP
. - Il faut aussi modifier la configuration par défaut de dovecot pour qu’il autorise les connexions distantes non sécurisées avec TLS (en dehors de localhost).
Pour rappel, voici le manifeste du service dovecot (serveur IMAP) que je souhaite exposer:
apiVersion: v1
kind: Service
metadata:
name: dovecot-service
labels:
app.kubernetes.io/name: dovecot
spec:
selector:
app.kubernetes.io/name: dovecot
type: ClusterIP
ports:
- name: imap
port: 143
protocol: TCP
targetPort: 31143
Il n’y a aucun changement à faire sur celui-ci.
Pour que dovecot accepte les connexions distantes sans TLS, il faut ajouter son réseau dans le paramètre login_trusted_networks
. Concrètement, il s’agit de mettre à jour le ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: dovecot-config
namespace: default
labels:
app.kubernetes.io/name: dovecot
data:
conf.d: |
# For unsecure remote connection
login_trusted_networks = 192.168.0.0/16
# https://doc.dovecot.org/main/core/config/service.html#service_vsz_limit
service_vsz_limit = unlimited
doveadm_api_key = $ENV:DOVEADM_API_KEY
doveadm_password = $ENV:DOVEADM_PASSWORD
Ci-dessus, j’ai ajouté login_trusted_networks = 192.168.0.0/16
qu’il faut adapter en fonction du réseau de chacun. J’ai mis « 192.168.0.0/16 » comme exemple (ça ne correspond pas non plus à mon réseau), qui correspond à la plage de 192.168.0.0 à 192.168.255.255.
On peut ensuite ajouter un IngressRouteTCP
:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: dovecot-ingress-tcp
spec:
entryPoints:
- imap
routes:
- match: HostSNI(`*`)
services:
- name: dovecot-service
port: 143
Les deux choses importantes: entryPoints
définit « imap » qui est le point d’entrée que l’on va devoir ajouter dans la configuration de Traefik pour que ça fonctionne. match
définit « HostSNI(`*`) » qui est la seule valeur possible quand on n’utilise pas TLS (avec TLS, on aurait eu l’avantage de pouvoir router vers ce service seulement en fonction du domaine indiqué par le client qui se connecte, et donc ça aurait permis d’utiliser le même port exposé pour différents services).
Le reste du manifeste est évident à comprendre. C’est un exemple très simple mais fonctionnel. La documentation de toutes les options est ici.
Avant de déployer ce manifeste (ou si on le déploie, avant que ça puisse fonctionner…): il faut ajouter l’entryPoint « imap » à la configuration de Traefik. Dans un cluster K3S, il s’agit d’un HelmChartConfig à cet emplacement dans le serveur (via une connexion SSH): /var/lib/rancher/k3s/server/manifests/traefik-config.yaml
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
# Structure traefik v2: https://github.com/traefik/traefik-helm-chart/blob/master/traefik/values.yaml
valuesContent: |-
ports:
imap:
# for dovecot
port: 143
exposedPort: 143
expose: true
L’exemple ci-dessus est partiel et ne contient que la partie intéressante: la section « ports » (qui correspond aux « entryPoints ») et la nouvelle section « imap » qui expose le port 143.
Un point d’attention à avoir: bien connaître la version de Trafik car la configuration peut différer en fonction des versions majeures. Dans mon cas, j’utilise encore la version 2.
Quand c’est fait, cela devrait fonctionner immédiatement. Il reste à reconfigurer le client (Thunderbird dans mon cas) pour ne plus se connecter en localhost (avec la redirection de port) mais avec le nom du serveur K3S. Aucun changement n’est nécessaire côté client si on remplace un service de type NodePort
.