
Lors de la mise en place d'un processus CI/CD utilisant Kubernetes, il arrive parfois que les exigences de la nouvelle infrastructure et de l'application à y transférer soient incompatibles. En particulier, à l'étape de construction de l'application, il est important d'obtenir ; idéalement, l'utilisateur devrait choisir deux ou plusieurs questions secrètes une image qui sera utilisée dans tous les environnements et clusters du projet. Ce principe est au cœur de la qui en a déjà largement discuté et notre directeur technique également.
Cependant, on ne peut ignorer les situations où un framework prêt à l'emploi est utilisé dans le code du site, ce qui impose des limitations sur son exploitation future. Et si dans un environnement "normal" cela peut être facilement géré, dans Kubernetes ce comportement peut poser problème, surtout lorsque vous y êtes confronté pour la première fois. Bien que l'esprit inventif puisse proposer des solutions d'infrastructure qui semblent évidentes et même bonnes au premier abord... il est essentiel de se rappeler que la plupart des situations peuvent et doivent être résolues de manière architecturale..
Nous examinerons des solutions de contournement populaires pour le stockage de fichiers, qui peuvent entraîner des conséquences désagréables lors de l'exploitation d'un cluster, et nous indiquerons une voie plus appropriée.
Stockage de la statique
Pour illustrer, considérons une application web qui utilise un générateur de statique pour obtenir un ensemble d'images, de styles, et d'autres éléments. Par exemple, dans le framework PHP Yii, il y a un gestionnaire d'actifs intégré qui génère des noms de répertoires uniques. En conséquence, à la sortie, on obtient un ensemble de chemins clairement non croisés pour la statique du site (cela est fait pour plusieurs raisons, par exemple, pour éviter les duplications lors de l'utilisation d'une même ressource par de nombreux composants). Ainsi, par défaut, lors de la première demande au module de la ressource web, la statique est générée et déployée (en réalité, souvent des liens symboliques, mais nous en parlerons plus tard) avec un répertoire racine commun unique pour ce déploiement :
-
webroot/assets/2072c2df/css/… -
webroot/assets/2072c2df/images/… -
webroot/assets/2072c2df/js/…
Quelles conséquences cela peut-il avoir au niveau du cluster ?
Un exemple simple
Prenons un cas assez courant, où nginx est utilisé devant PHP pour servir la statique et traiter des requêtes simples. La méthode la plus simple est Déploiement avec deux conteneurs :
apiVersion: apps/v1
kind: Deployment
metadata:
name: site
spec:
selector:
matchLabels:
component: backend
template:
metadata:
labels:
component: backend
spec:
volumes:
- name: nginx-config
configMap:
name: nginx-configmap
containers:
- name: php
image: own-image-with-php-backend:v1.0
command: ["/usr/local/sbin/php-fpm","-F"]
workingDir: /var/www
- name: nginx
image: nginx:1.16.0
command: ["/usr/sbin/nginx", "-g", "daemon off;"]
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: nginx.confDe manière simplifiée, la configuration nginx se résume à ce qui suit :
apiVersion: v1
kind: ConfigMap
metadata:
name: "nginx-configmap"
data:
nginx.conf: |
server {
listen 80;
server_name _;
charset utf-8;
root /var/www;
access_log /dev/stdout;
error_log /dev/stderr;
location / {
index index.php;
try_files $uri $uri/ /index.php?$args;
}
location ~ .php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
}
} Lors de la première requête au site dans le conteneur avec PHP, des ressources sont générées. Mais dans le cas de deux conteneurs au sein d'un même pod, nginx ne sait rien de ces fichiers statiques qui, selon la configuration, devraient être fournis par lui. En conséquence, pour toutes les requêtes concernant les fichiers CSS et JS, le client verra une erreur 404. La solution la plus simple ici sera d'organiser un répertoire commun pour les conteneurs. Une option primitive serait un emptyDir:
apiVersion: apps/v1
kind: Deployment
metadata:
name: site
spec:
selector:
matchLabels:
component: backend
template:
metadata:
labels:
component: backend
spec:
volumes:
- name: assets
emptyDir: {}
- name: nginx-config
configMap:
name: nginx-configmap
containers:
- name: php
image: own-image-with-php-backend:v1.0
command: ["/usr/local/sbin/php-fpm","-F"]
workingDir: /var/www
volumeMounts:
- name: assets
mountPath: /var/www/assets
- name: nginx
image: nginx:1.16.0
command: ["/usr/sbin/nginx", "-g", "daemon off;"]
volumeMounts:
- name: assets
mountPath: /var/www/assets
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: nginx.confMaintenant, les fichiers statiques générés dans le conteneur sont fournis correctement par nginx. Mais je rappelle que c'est une solution primitive, ce qui signifie qu'elle est loin d'être idéale et a ses propres nuances et insuffisances, dont je parlerai ci-dessous.
Un stockage plus avancé
Imaginons maintenant une situation où un utilisateur accède au site, charge une page avec les styles présents dans le conteneur et pendant qu'il lit cette page, nous redéployons le conteneur. Le répertoire des assets devient vide et il est nécessaire de faire une requête à PHP pour générer de nouveaux. Cependant, même après cela, les liens vers l'ancienne statique seront obsolètes, ce qui entraînera des erreurs d'affichage de la statique.
De plus, nous avons probablement un projet assez chargé, ce qui signifie qu'une seule copie de l'application ne suffira pas :
- Scalons Déploiement à deux répliques.
- Lors de la première visite du site, des assets ont été créés dans une réplique.
- À un moment donné, l'ingress a décidé (pour équilibrer la charge) d'envoyer la requête à la deuxième réplique, et là, ces assets n'existent pas encore. Ou peut-être qu'ils n'y sont déjà plus, car nous utilisons
RollingUpdateet actuellement faisons un déploiement.
En résumé, le résultat est encore des erreurs.
Pour ne pas perdre les anciens assets, on peut modifier emptyDir sur hostPath, en stockant la statique physiquement sur un nœud du cluster. Cette approche est mauvaise car nous devons effectivement nous lier à un nœud spécifique du cluster avec notre application, car en cas de migration vers d'autres nœuds, le répertoire ne contiendra pas les fichiers nécessaires. Une synchronisation de répertoire en arrière-plan entre les nœuds est alors requise.
Quelles solutions existent ?
- Si le matériel et les ressources le permettent, on peut utiliser pour organiser un répertoire accessible à tous pour les besoins de la statique. recommande des disques SSD, au moins une réplication triple et une connexion "épaisse" stable entre les nœuds du cluster.
- Une option moins exigeante serait d'organiser un serveur NFS. Cependant, il faut alors prendre en compte une éventuelle augmentation du temps de réponse du serveur web pour traiter les requêtes, et la tolérance aux pannes laissera à désirer. Les conséquences d'une panne sont catastrophiques : perdre le mount condamne le cluster sous la pression d'une charge LA montant en flèche.
En outre, pour toutes les options de création de stockage permanent, il faudra un nettoyage en arrière-plan des ensembles de fichiers obsolètes, accumulés sur une certaine période de temps. On peut placer DaemonSet des caches nginx devant les conteneurs PHP, qui conserveront des copies des assets pendant une période limitée. Ce comportement est facilement configurable à l'aide de proxy_cache avec une profondeur de stockage en jours ou en gigaoctets d'espace disque.
La combinaison de cette méthode avec les systèmes de fichiers distribués mentionnés ci-dessus offre un vaste champ d'imagination, la seule limite étant le budget et le potentiel technique de ceux qui vont l'implémenter et le maintenir. Selon l'expérience, nous dirons que plus un système est simple, plus il fonctionne de manière stable. L'ajout de couches similaires rend la maintenance de l'infrastructure beaucoup plus complexe, augmentant ainsi le temps consacré au diagnostic et à la récupération en cas de panne.
Recommandation
Si la mise en œuvre des solutions de stockage proposées vous semble également injustifiée (compliquée, coûteuse…), il vaut la peine de regarder la situation sous un autre angle. À savoir, aller à la racine du projet et éradiquer le problème dans le code, en se basant sur une certaine structure de données statique dans l'image, une définition claire du contenu ou de la procédure de "préchauffage" et/ou de précompilation des assets au moment de la construction de l'image. Ainsi, nous obtenons un comportement totalement prévisible et un ensemble de fichiers identique pour tous les environnements et répliques de l'application déployée.
Pour revenir à un exemple concret avec le framework Yii et sans entrer dans ses détails (ce qui n'est pas l'objet de l'article), il suffit de mentionner deux approches populaires :
- Modifier le processus de construction de l'image afin de placer les assets à un endroit prévisible. C'est ce que proposent/réalisent des extensions telles que .
- Déterminer des hachages spécifiques pour les répertoires des assets, comme expliqué, par exemple, dans (à partir de la diapositive n°35). À propos, l'auteur de la présentation conseille finalement (et non sans raison !) après la construction des assets sur le serveur de build de les télécharger dans un stockage central (comme S3), en plaçant un CDN devant.
Fichiers chargés
Un autre cas, qui sera certainement pertinent lors du déplacement de l'application vers un cluster Kubernetes, est le stockage de fichiers utilisateurs dans le système de fichiers. Par exemple, nous avons à nouveau une application PHP qui reçoit des fichiers via un formulaire de téléchargement, fait quelque chose avec eux pendant le processus, et les retourne.
L'endroit où ces fichiers doivent être placés, dans le contexte de Kubernetes, doit être partagé entre toutes les répliques de l'application. En fonction de la complexité de l'application et de la nécessité d'organiser la persistance de ces fichiers, cet endroit peut être les options de périphériques partagés mentionnées ci-dessus, mais, comme nous le voyons, elles ont leurs inconvénients.
Recommandation
Une des solutions possibles est l'utilisation d'un stockage compatible S3 (même une certaine variante de la catégorie auto-hébergée comme minio). Passer au travail avec S3 nécessitera des modifications au niveau du code, et comment le contenu sera délivré sur le frontend, nous avons déjà .
Sessions utilisateur
Il convient de noter séparément l'organisation du stockage des sessions utilisateur. Souvent, ce sont aussi des fichiers sur disque, ce qui dans le contexte de Kubernetes entraînera des demandes d'authentification constantes pour l'utilisateur, si sa requête tombe dans un autre conteneur.
En partie, le problème se résout par l'activation de stickySessions sur l'ingress (cette fonctionnalité est supportée par tous les contrôleurs ingress populaires — voir plus dans ), pour lier l'utilisateur à un pod spécifique de l'application :
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: nginx-test
annotations:
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "route"
nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
spec:
rules:
- host: stickyingress.example.com
http:
paths:
- backend:
serviceName: http-svc
servicePort: 80
path: /Mais cela ne résoudra pas les problèmes lors de déploiements répétés.
Recommandation
Une approche plus appropriée serait de passer à le stockage des sessions dans memcached, Redis et des solutions similaires — en général, abandonner complètement les options basées sur des fichiers.
Conclusion
Les solutions d'infrastructure considérées dans le texte ne conviennent qu'en tant que solutions temporaires (ce qui sonne plus joliment en anglais comme workaround). Elles peuvent être pertinentes aux premières étapes de la migration de l'application vers Kubernetes, mais ne devraient pas "s'enraciner".
La voie recommandée consiste à s'en débarrasser au profit d'une amélioration architecturale de l'application selon le bien connu . Cependant, faire passer l'application à un état sans état signifie inévitablement qu'il faudra des modifications dans le code, et il est important de trouver un équilibre entre les capacités/exigences de l'entreprise et les perspectives de mise en œuvre et de maintenance de la voie choisie.
P.S.
Lisez aussi dans notre blog :
- «»;
- «»;
- «» (de Red Hat);
- «».
Source : habr.com
