Fichiers locaux lors du portage d'une application vers Kubernetes



Lors de la construction d'un processus CI / CD à l'aide de Kubernetes, il y a parfois un problème d'incompatibilité des exigences de la nouvelle infrastructure et de l'application qui y est transférée. En particulier, au stade de l'assemblage de l'application, il est important d'obtenir une image qui sera utilisée dans tous les environnements et clusters de projet. Ce principe sous-tend la gestion correcte des conteneurs de l'avis de Google (notre techdir en a parlé à plusieurs reprises).

Cependant, vous ne surprendrez personne dans des situations où un framework prêt à l'emploi est utilisé dans le code du site, dont l'utilisation impose des restrictions sur son fonctionnement ultérieur. Et s'il est facile à gérer dans un «environnement normal», dans Kubernetes, ce type de comportement peut être un problème, surtout lorsque vous le rencontrez pour la première fois. Bien qu'un esprit ingénieux soit capable de proposer des solutions d'infrastructure qui semblent évidentes et même assez bonnes à première vue ... il est important de se rappeler que la plupart des situations peuvent et doivent être résolues sur le plan architectural .

Analysons les solutions de contournement populaires pour le stockage de fichiers, qui peuvent entraîner des conséquences désagréables pendant le fonctionnement du cluster, et pointons également vers un chemin plus correct.

Stockage statique


Pour illustrer, considérons une application Web qui utilise un générateur statique pour obtenir un ensemble d'images, de styles, etc. Par exemple, le framework PHP Yii possède un gestionnaire d'actifs intégré qui génère des noms de répertoire uniques. Par conséquent, la sortie est un ensemble de chemins délibérément sans intersection pour la statique du site (cela a été fait pour plusieurs raisons - par exemple, pour éliminer les doublons lors de l'utilisation de la même ressource avec de nombreux composants). Ainsi, dès la première utilisation, lorsque vous accédez au module de ressources Web, des statiques sont formées et présentées (en fait, souvent des liens symboliques, mais plus à ce sujet 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/…

Qu'est-ce que cela représente en termes de cluster?

Exemple le plus simple


Prenons un cas assez courant lorsque PHP fait face à nginx pour distribuer des statistiques et gérer des requêtes simples. Le moyen le plus simple est le 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.conf 

Dans une forme simplifiée, la configuration de 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; } } 

Lorsque vous accédez pour la première fois au site dans un conteneur avec PHP, les ressources apparaissent. Mais dans le cas de deux conteneurs dans le même pod, nginx ne sait rien de ces fichiers statiques qui (selon la configuration) doivent leur être donnés. Par conséquent, le client verra l'erreur 404 pour toutes les demandes de fichiers CSS et JS. La solution la plus simple ici est d'organiser un répertoire commun pour les conteneurs. Une option primitive est le emptyDir générique:

 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.conf 

Maintenant, les fichiers statiques générés dans le conteneur sont correctement fournis par nginx. Mais permettez-moi de vous rappeler qu'il s'agit d'une solution primitive, ce qui signifie qu'elle est loin d'être idéale et a ses propres nuances et lacunes, qui sont discutées ci-dessous.

Stockage plus avancé


Imaginons maintenant une situation où un utilisateur a visité un site, chargé une page avec les styles disponibles dans le conteneur et pendant qu'il lisait cette page, nous avons redéployé le conteneur. Le répertoire des actifs est devenu vide et nécessite une demande à PHP pour commencer à en générer de nouveaux. Cependant, même après cela, les liens vers les anciennes statiques seront obsolètes, ce qui entraînera des erreurs d'affichage des statiques.

De plus, nous avons très probablement un projet plus ou moins chargé, ce qui signifie qu'une copie de l'application ne suffira pas:

  • Faites évoluer le déploiement vers deux répliques.
  • Lorsque vous accédez au site pour la première fois dans une réplique, des actifs ont été créés.
  • À un moment donné, Ingress a décidé (afin d'équilibrer la charge) d'envoyer une demande de deuxième réplique, et ces actifs ne sont pas encore là. Ou peut-être qu'ils ne sont plus là, car nous utilisons RollingUpdate et faisons actuellement un déploiement.

En général, le résultat est à nouveau des erreurs.

Afin de ne pas perdre les anciens actifs, vous pouvez remplacer emptyDir par emptyDir , en ajoutant physiquement la statique au nœud de cluster. Cette approche est mauvaise car nous devons réellement nous lier à un nœud de cluster spécifique avec notre application, car - en cas de déplacement vers d'autres nœuds - le répertoire ne contiendra pas les fichiers nécessaires. Ou, une synchronisation d'arrière-plan du répertoire entre les nœuds est requise.

Quelles sont les solutions?

  1. Si le matériel et les ressources le permettent, vous pouvez utiliser cephfs pour organiser un répertoire également accessible pour les besoins de la statique. La documentation officielle recommande des disques SSD, au moins une réplication triple et une connexion «épaisse» robuste entre les nœuds du cluster.
  2. Une option moins exigeante serait d'organiser un serveur NFS. Cependant, vous devez alors considérer l'augmentation possible du temps de réponse aux demandes de traitement par le serveur Web, et la tolérance aux pannes laissera beaucoup à désirer. Les conséquences de la panne sont catastrophiques: la perte de monture détruit le cluster à mort sous l'assaut de la charge de LA qui se précipite dans le ciel.

Entre autres choses, pour toutes les options de création de stockage persistant, un nettoyage en arrière - plan des ensembles de fichiers obsolètes accumulés sur une certaine période sera nécessaire. Avant les conteneurs avec PHP, vous pouvez mettre DaemonSet à partir de la mise en cache de nginx, qui stockera des copies des actifs pendant une durée limitée. Ce comportement peut être facilement configuré à 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 immense champ d'imagination, une limitation uniquement dans le budget et le potentiel technique de ceux qui la mettront en œuvre et la soutiendront. Par expérience, nous disons que plus le système est simple, plus il fonctionne de manière stable. Avec l'ajout de telles couches, il devient beaucoup plus difficile de maintenir l'infrastructure et, en même temps, le temps consacré aux diagnostics et à la récupération en cas de défaillance augmente.

Recommandation


Si la mise en œuvre des options de stockage proposées vous semble également injustifiée (compliquée, coûteuse ...), alors vous devriez regarder la situation de l'autre côté. À savoir, creuser dans l'architecture du projet et éradiquer le problème dans le code en reliant à une structure de données statique dans l'image, fournit une définition sans ambiguïté du contenu ou de la procédure de «réchauffement» et / ou de précompilation des actifs au stade de l'assemblage de l'image. Nous obtenons donc un comportement absolument prévisible et le même ensemble de fichiers pour tous les environnements et répliques de l'application en cours d'exécution.

Si nous revenons à un exemple spécifique avec le framework Yii et ne nous penchons pas sur sa structure (ce qui n'est pas le but de l'article), il suffit de souligner deux approches populaires:

  1. Modifiez le processus d'assemblage de l'image afin que les actifs soient placés dans un endroit prévisible. Donc, offrez / implémentez des extensions comme yii2-static-assets .
  2. Définissez des hachages spécifiques pour les répertoires d'actifs, comme décrit, par exemple, dans cette présentation (en commençant par la diapositive 35). Soit dit en passant, l'auteur du rapport en fin de compte (et non sans raison!) Conseille après avoir assemblé les actifs sur le serveur de build de les télécharger vers un référentiel central (comme S3), devant lequel mettre le CDN.

Fichiers téléchargeables


Un autre cas qui se déclenchera sûrement lors du transfert d'une application vers un cluster Kubernetes est le stockage des fichiers utilisateur dans le système de fichiers. Par exemple, nous avons à nouveau une application PHP qui accepte les fichiers via le formulaire de téléchargement, fait quelque chose avec eux dans le processus et les rend.

L'endroit où ces fichiers doivent être placés dans les réalités Kubernetes doit être commun à toutes les répliques d'applications. Selon la complexité de l'application et la nécessité d'organiser la persistance de ces fichiers, un tel endroit peut être les options pour les appareils partagés mentionnés ci-dessus, mais, comme nous le voyons, ils ont leurs inconvénients.

Recommandation


Une solution consiste à utiliser un stockage compatible S3 (même si une sorte de catégorie auto-hébergée comme minio). La transition pour travailler avec S3 nécessitera des modifications au niveau du code , et nous avons déjà écrit comment le contenu sera renvoyé sur le frontend.

Sessions personnalisées


Séparément, il convient de noter l'organisation du stockage des sessions utilisateur. Il s'agit souvent de fichiers sur disque, ce qui, dans le contexte de Kubernetes, entraînera des demandes d'autorisation constantes de l'utilisateur si sa demande tombe dans un autre conteneur.

Une partie du problème est résolue en incluant des stickySessions sur l'entrée (la fonctionnalité est prise en charge dans tous les contrôleurs d'entrée populaires - voir notre revue pour plus de détails) afin de lier l'utilisateur à un module spécifique avec 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 vous évitera pas de déploiements répétés.

Recommandation


Une façon plus correcte serait de transférer l'application vers des sessions de stockage dans memcached, Redis et des solutions similaires - en général, abandonner complètement les options de fichier.

Conclusion


Les solutions d'infrastructure envisagées dans le texte ne méritent d'être appliquées que sous la forme de «béquilles» temporaires (ce qui semble plus beau en anglais comme solution de contournement). Ils peuvent être pertinents aux premiers stades de la migration des applications vers Kubernetes, mais ils ne doivent pas être "enracinés".

La manière généralement recommandée est de les supprimer en faveur d'un raffinement architectural de l'application conformément à l'application 12 facteurs déjà connue. Cependant, cela - amener l'application sous une forme sans état - signifie inévitablement que des changements dans le code seront nécessaires, 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 maintien du chemin choisi.

PS


Lisez aussi dans notre blog:

Source: https://habr.com/ru/post/fr471582/


All Articles