Introduction
Avec l’arrêt de la possibilité d’héberger Exchange en interne à une organisation, de plus en plus d’entreprises cherchent à migrer sur des solutions de messageries alternatives.
L’infrastructure d’une messagerie peut être élaborée selon deux approches :
- Un serveur monolithique utilisant beaucoup de ressources
- Plusieurs petits serveurs distribués
Cet article détaille, selon la seconde approche, le déploiement d’une infrastructure de messagerie basée sur Cyrus IMAP avec Murder dans un environnement Kubernetes. Il s’adresse à un public déjà connaisseur des concepts de la messagerie et l’hébergement au sein d’une solution comme Kubernetes.
Nous intégrerons OpenLDAP, Postfix, Rspamd, LL::NG, Roundcube, et Z-Push, tout en utilisant Nginx comme reverse proxy. Nous aborderons également l’importance du rate limiting pour sécuriser et optimiser les services.
Cet article décrit une solution de messagerie minimale incluant les fonctionnalités suivantes :
- Courriel
- Antispam / Antivirus
- Filtres Sieve
- Gestion des rates limits
- Calendrier
- Tâche
- Contact
- Partage des pièces jointes (WebDAV)
- Monitoring par Prometheus / Grafana
- Centralisation des logs via ElasticSearch / Kibana
- ActivSync
- Authentification via un service de SSO
Architecture du Système
Partons d’une base solide en utilisant des outils robustes :
-
Cyrus IMAP avec Murder : Murder permet de répartir la charge entre plusieurs serveurs backend Cyrus IMAP. Configuration pour supporter IMAP, LMTP, WebDAV, CalDAV, et CardDAV. Dans notre cas, chaque backend gère 2000 utilisateurs avec réplication pour la haute disponibilité.
-
OpenLDAP : utilisé pour l’authentification centralisée des utilisateurs. Intégration avec Cyrus IMAP pour la gestion des comptes utilisateurs.
-
Postfix : serveur SMTP pour le relais des emails entrants et sortants. Configuration pour utiliser Cyrus IMAP comme backend pour la livraison des emails.
-
Rspamd : solution de filtrage anti-spam intégrée avec Postfix.
-
LemonLDAP::NG : Gestion des identités et des accès pour une authentification sécurisée. Intégration avec Roundcube pour l’authentification unique (SSO).
-
Roundcube : Interface Webmail permettant aux utilisateurs d’accéder à leurs emails via un navigateur web.
-
Z-Push : Permet la synchronisation des emails, contacts et calendriers avec les clients mobiles via ActiveSync.
-
Nginx : Utilisé comme reverse proxy pour diriger le trafic vers les différents services. Assure la terminaison SSL/TLS pour sécuriser les communications.
Ce qui permet de définir comme infrastructure :
[ Nginx Ingress ]
│
[ LemonLDAP::NG (SSO) ]
│
[ Roundcube / Z-Push ] - [Redis]
│
[ Cyrus Murder Frontend ] - [ Cyrus IMAP Backend 1 (2000 users) ]
│ [ Cyrus IMAP Backend 2 (2000 users) ]
│ [ ... ]
[ Mupdate Master ]
│
[ Postfix (SMTP) ] - [ Rspamd (Antispam) ]
│
[ Prometheus / Grafana (Surveillance) ]
│
[ ELK Stack (Centralisation des logs) ]
│
[ Velero (Sauvegardes) ]
Cet article se focalise sur les composants centraux de la solution. De la littérature concernant l’intégration des autres éléments est facilement trouvable.
Déploiement dans Kubernetes
Déploiement de Cyrus Murder Frontend
Le Frontend est déployé en tant que Deployment avec un Service pour exposer les ports IMAP (143) et IMAPS (993).
apiVersion: apps/v1
kind: Deployment
metadata:
name: cyrus-murder-frontend
namespace: mail
spec:
replicas: 2
selector:
matchLabels:
app: cyrus-murder-frontend
template:
metadata:
labels:
app: cyrus-murder-frontend
spec:
containers:
- name: cyrus-murder-frontend
image: cyrusimap/cyrus-imapd:latest
args: ["-f"]
ports:
- containerPort: 143
- containerPort: 993
volumeMounts:
- name: cyrus-config
mountPath: /etc/imapd.conf
subPath: imapd.conf
volumes:
- name: cyrus-config
configMap:
name: cyrus-config
Déploiement des Backends avec Auto-Scaling
Chaque backend est déployé en tant que StatefulSet avec un PersistentVolume pour stocker les données des utilisateurs. Un script surveille le nombre de comptes et déclenche le déploiement d’un nouveau backend lorsque le seuil de 2000 comptes est atteint.
StatefulSet pour un Backend
Le StatefulSet doit être configuré pour permettre la découverte des backends par Cyrus Murder. Chaque backend doit avoir un nom de service unique.
Service pour les Backends
apiVersion: v1
kind: Service
metadata:
name: cyrus-backend
namespace: mail
spec:
clusterIP: None
selector:
app: cyrus-backend
ports:
- port: 143
name: imap
- port: 993
name: imaps
StatefulSet pour les Backends
Le StatefulSet est configuré pour créer des pods avec des noms uniques (cyrus-backend-0, cyrus-backend-1, etc.).
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cyrus-backend
namespace: mail
spec:
serviceName: cyrus-backend
replicas: 1
selector:
matchLabels:
app: cyrus-backend
template:
metadata:
labels:
app: cyrus-backend
spec:
containers:
- name: cyrus-backend
image: cyrusimap/cyrus-imapd:latest
args: ["-f"]
env:
- name: BACKEND_USERS_LIMIT
value: "2000"
ports:
- containerPort: 143
- containerPort: 993
volumeMounts:
- name: cyrus-config
mountPath: /etc/imapd.conf
subPath: imapd.conf
- name: cyrus-data
mountPath: /var/lib/cyrus
volumes:
- name: cyrus-config
configMap:
name: cyrus-config
volumeClaimTemplates:
- metadata:
name: cyrus-data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 50Gi
Script d’Auto-Scaling
Nous allons prendre un cas simple avec la réalisation d’un script bash mis dans un cron pour gérer la scalabilité des backends. Il est envisageable de réaliser un opérateur directement au sein de Kubernetes pour rendre cela plus dynamique.
Le script surveille le nombre de comptes utilisateurs et augmente le nombre de replicas du StatefulSet si le seuil de 2000 comptes par backend est atteint. Il met également à jour la configuration de Cyrus Murder pour inclure les nouveaux backends.
!/bin/bash
Variables
NAMESPACE="mail"
STATEFULSET_NAME="cyrus-backend"
MURDER_FRONTEND="cyrus-murder-frontend"
BACKEND_LIMIT=2000
Récupérer le nombre actuel de replicas
CURRENT_REPLICAS=$(kubectl get statefulset -n $NAMESPACE $STATEFULSET_NAME -o jsonpath='{.spec.replicas}')
Récupérer le nombre total de comptes utilisateurs
CURRENT_USERS=$(cyradm --user cyrus --auth PLAIN --server $MURDER_FRONTEND --cmd "sam user.count")
Calculer le nombre de replicas nécessaires
REQUIRED_REPLICAS=$(( (CURRENT_USERS / BACKEND_LIMIT) + 1 ))
Si le nombre de replicas nécessaires est supérieur au nombre actuel, mettre à jour le StatefulSet
if [ "$REQUIRED_REPLICAS" -gt "$CURRENT_REPLICAS" ]; then
echo "Scaling StatefulSet to $REQUIRED_REPLICAS replicas..."
kubectl scale statefulset -n $NAMESPACE $STATEFULSET_NAME --replicas=$REQUIRED_REPLICAS
# Attendre que les nouveaux pods soient prêts
echo "Waiting for new backends to be ready..."
kubectl wait --for=condition=Ready pod -n $NAMESPACE -l app=cyrus-backend --timeout=300s
# Mettre à jour la configuration de Cyrus Murder
echo "Updating Cyrus Murder configuration..."
BACKEND_LIST=""
for i in $(seq 0 $((REQUIRED_REPLICAS - 1))); do
BACKEND_LIST+="backend$i.${STATEFULSET_NAME}.${NAMESPACE}.svc.cluster.local:143 "
done
# Appliquer la nouvelle configuration
kubectl create configmap -n $NAMESPACE cyrus-config --from-literal=imapd.conf="
murder: yes
mupdate_server: mupdate-master.mail.svc.cluster.local
backend_servers: $BACKEND_LIST
" --dry-run=client -o yaml | kubectl apply -f -
# Redémarrer le frontend pour appliquer la nouvelle configuration
echo "Restarting Cyrus Murder frontend..."
kubectl rollout restart deployment -n $NAMESPACE $MURDER_FRONTEND
else
echo "No scaling required. Current replicas: $CURRENT_REPLICAS, Required replicas: $REQUIRED_REPLICAS"
fi
Explication du script
Scaling du StatefulSet
Le script utilise kubectl scale
pour augmenter le nombre de replicas du StatefulSet existant cyrus-backend
au lieu de créer un nouveau StatefulSet.
Il calcule le nombre de replicas nécessaires en fonction du nombre total de comptes utilisateurs et du seuil de 2000 comptes par backend.
Mise à Jour de la Configuration de Cyrus Murder
Le script génère dynamiquement une liste de backends backend_servers
en fonction du nombre de replicas.
Il met à jour le ConfigMap cyrus-config
avec la nouvelle configuration. Il redémarre le Cyrus Murder Frontend pour appliquer la nouvelle configuration.
Attente des Nouveaux Pods
Le script utilise kubectl wait
pour s’assurer que les nouveaux pods sont prêts avant de mettre à jour la configuration.
Configuration de Mupdate Master
Le Mupdate Master synchronise les métadonnées entre les backends. Il est déployé en tant que Deployment.
apiVersion: apps/v1
kind: Deployment
metadata:
name: mupdate-master
namespace: mail
spec:
replicas: 1
selector:
matchLabels:
app: mupdate-master
template:
metadata:
labels:
app: mupdate-master
spec:
containers:
- name: mupdate-master
image: cyrusimap/cyrus-imapd:latest
args: ["-f"]
ports:
- containerPort: 3905
volumeMounts:
- name: cyrus-config
mountPath: /etc/imapd.conf
subPath: imapd.conf
volumes:
- name: cyrus-config
configMap:
name: cyrus-config
Rate Limiting avec Nginx
Rate Limiting pour Cyrus IMAP
Nginx est configuré pour limiter le nombre de requêtes IMAP par seconde (20 requêtes/s par défaut).
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-imap-config
namespace: mail
data:
nginx.conf: |
limit_req_zone $binary_remote_addr zone=imap_limit:10m rate=20r/s;
server {
listen 143;
listen 993 ssl;
location / {
limit_req zone=imap_limit burst=5;
proxy_pass http://cyrus-murder-frontend;
}
}
Rate Limiting pour Postfix
Nginx est également configuré pour limiter le nombre de connexions SMTP (10 connexions/s par défaut).
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-smtp-config
namespace: mail
data:
nginx.conf: |
limit_conn_zone $binary_remote_addr zone=smtp_limit:10m;
server {
listen 25;
location / {
limit_conn smtp_limit 10;
proxy_pass http://postfix;
}
}
Conclusion
L’objectif de cet article est de démontrer la faisabilité d’un tel montage dans lequel nous faisons cohabiter deux typologies d’outils que l’on a peu l’occasion de voir ensemble. En revanche, il est à noter qu’une telle approche demandera beaucoup plus de temps de maintenance qu’une solution entièrement intégrée comme BlueMind par exemple.
Le déploiement de Cyrus IMAP avec Murder dans un cluster Kubernetes, intégré avec OpenLDAP, Postfix, Rspamd, Roundcube, LL::NG, et Z-Push, offre une solution de messagerie robuste et évolutive. L’utilisation de Nginx comme reverse proxy permet de gérer efficacement le trafic entrant et de sécuriser les communications. L’implémentation du rate limiting est cruciale pour protéger les services contre les abus d’utilisation et garantir une disponibilité optimale. Cette architecture modulaire et évolutive est idéale pour gérer au mieux une scalabilité horizontale avec un nombre cohérent de services.