Lokale Dateien beim Umzug der Anwendung nach Kubernetes

Lokale Dateien beim Umzug der Anwendung nach Kubernetes

Bei der Einrichtung eines CI/CD-Prozesses mit Kubernetes gibt es manchmal Probleme mit der KompatibilitĂ€t zwischen den Anforderungen der neuen Infrastruktur und der darauf portierten Anwendung. Insbesondere ist es wichtig, in der Phase der Anwendungsentwicklung ein eins Image zu erhalten, das in alle den Umgebungen und Clustern des Projekts verwendet wird. Dieses Prinzip liegt der korrekten Containerverwaltung zugrunde, laut Google. gesagt Und unser Technologiedirektor hat oft darĂŒber gesprochen.

Es gibt jedoch Situationen, in denen ein fertiger Framework im Code der Website verwendet wird, dessen Nutzung EinschrĂ€nkungen fĂŒr die weitere Nutzung mit sich bringt. WĂ€hrend dies in einer 'normalen Umgebung' leicht zu bewĂ€ltigen ist, kann ein solches Verhalten in Kubernetes problematisch werden, insbesondere wenn man damit zum ersten Mal konfrontiert wird. Obwohl ein einfallsreicher Geist in der Lage ist, infrastrukturelle Lösungen vorzuschlagen, die auf den ersten Blick offensichtlich und sogar gut erscheinen, ist es wichtig, sich daran zu erinnern, dass die meisten Situationen architektonisch gelöst werden mĂŒssen..

Wir werden populĂ€re Workaround-Lösungen fĂŒr die Speicherung von Dateien untersuchen, die zu unangenehmen Folgen bei der Nutzung des Clusters fĂŒhren können, und auf den korrekteren Weg hinweisen.

Statische Speicherung

Zur Veranschaulichung betrachten wir eine Webanwendung, die einen statischen Generator verwendet, um eine Sammlung von Bildern, Styles und anderem zu erhalten. Zum Beispiel gibt es im PHP-Framework Yii einen integrierten Asset-Manager, der einzigartige Verzeichnisnamen generiert. So ergibt sich ein Satz von eindeutig nicht ĂŒberschneidenden Pfaden fĂŒr die Website-Statische (dies geschieht aus mehreren GrĂŒnden — beispielsweise zur Vermeidung von Duplikaten bei der Nutzung des gleichen Ressourcen durch viele Komponenten). Daher wird beim ersten Zugriff auf das Modul der Web-Ressource die Statische generiert und abgelegt (in der Regel handelt es sich hĂ€ufig um Symlinks, aber darauf kommen wir spĂ€ter zurĂŒck), wobei ein einzigartiger Stammverzeichnis fĂŒr dieses Deployment verwendet wird:

  • webroot/assets/2072c2df/css/

  • webroot/assets/2072c2df/images/

  • webroot/assets/2072c2df/js/


Welche Risiken birgt dies im Hinblick auf den Cluster?

Ein einfaches Beispiel

Nehmen wir einen ziemlich hĂ€ufigen Fall an, bei dem nginx vor PHP steht, um statische Dateien auszuliefern und einfache Anfragen zu bearbeiten. Die einfachste Methode ist— Deployment mit zwei Containern:

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

In vereinfachter Form lÀsst sich die nginx-Konfiguration wie folgt zusammenfassen:

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;
        }
    }

Beim ersten Zugriff auf die Website im PHP-Container erscheinen Assets. Wenn jedoch zwei Container innerhalb eines Pods verwendet werden, ist nginx sich dieser statischen Dateien nicht bewusst, die - gemĂ€ĂŸ der Konfiguration - an ihn ausgegeben werden sollten. Infolgedessen wird der Kunde bei allen Anfragen an CSS- und JS-Dateien einen 404-Fehler sehen. Die einfachste Lösung besteht darin, ein gemeinsames Verzeichnis fĂŒr die Container einzurichten. Eine primitive Variante wĂ€re ein gemeinsamer. 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.conf

Jetzt werden die im Container generierten statischen Dateien korrekt von nginx bereitgestellt. Ich möchte jedoch daran erinnern, dass dies eine primitive Lösung ist, und somit ist sie weit entfernt von der idealen Umsetzung und hat ihre eigenen Nuancen und Verbesserungspotenziale, die ich weiter unten erlÀutern werde.

Fortschrittlicherer Speicher

Stellen wir uns nun vor, der Benutzer hat die Website betreten, die Seite mit den im Container vorhandenen Stilen geladen, und wĂ€hrend er diese Seite liest, haben wir den Container erneut bereitgestellt. Im Asset-Verzeichnis ist es leer geworden, und es ist ein PHP-Request erforderlich, um die Generierung neuer Assets zu starten. Selbst danach werden die Links zur alten Statische nicht mehr aktuell sein, was zu Anzeige-Fehlern fĂŒhrt.

DarĂŒber hinaus haben wir wahrscheinlich ein mehr oder weniger stark belastetes Projekt, was bedeutet, dass eine einzige Kopie der Anwendung nicht ausreicht:

  • Wir skalieren Deployment auf zwei Replikate.
  • Bei der ersten Anfrage an die Website wurden in einer Replik Assets erstellt.
  • Irgendwann hat der Ingress (zu Lastenverteilungszwecken) beschlossen, die Anfrage an die zweite Replik zu senden, und dort sind diese Assets noch nicht vorhanden. Vielleicht sind sie dort bereits nicht mehr vorhanden, weil wir gerade RollingUpdate und im Moment ein Deployment durchfĂŒhren.

Zusammenfassend ergibt sich — erneut Fehler.

Um alte Assets nicht zu verlieren, kann man Ă€ndern emptyDir findet man hostPath, indem man die Statische physisch an einen Knoten des Clusters speichert. Dieser Ansatz hat den Nachteil, dass wir faktisch an einen bestimmten Knoten des Clusters gebunden sein mĂŒssen. mit Ihrer Anwendung, denn im Falle eines Wechsels zu anderen Knoten wird das Verzeichnis möglicherweise nicht die erforderlichen Dateien enthalten. Alternativ ist eine Art von Hintergrundsynchronisierung des Verzeichnisses zwischen den Knoten erforderlich.

Welche LösungsansÀtze gibt es?

  1. Wenn die Hardware und die Ressourcen es zulassen, kann man cephfs verwenden, um ein gleichberechtigtes Verzeichnis fĂŒr statische Inhalte zu organisieren. Offizielle Dokumentation empfiehlt SSD-Laufwerke, mindestens dreifache Replikation und eine stabile "dicke" Verbindung zwischen den Knoten des Clusters.
  2. Eine weniger anspruchsvolle Option wĂ€re die Einrichtung eines NFS-Servers. Dabei sollte jedoch das mögliche erhöhte Antwortzeit bei der Bearbeitung von Anfragen durch den Webserver berĂŒcksichtigt werden, und die Ausfallsicherheit lĂ€sst zu wĂŒnschen ĂŒbrig. Die Folgen eines Ausfalls können catastrophal sein: der Verlust des Mounts fĂŒhrt dazu, dass der Cluster durch die aufkommende LA-Last, die in die Höhe strebt, zum Scheitern verurteilt ist.

Neben allem, um alle Optionen fĂŒr die Erstellung eines dauerhaften Speichers zu berĂŒcksichtigen, wird eine Hintergrundbereinigung veralteter DateisĂ€tze erforderlich sein, die ĂŒber einen bestimmten Zeitraum angesammelt wurden. Vor den PHP-Containern kann man DaemonSet von caching-nginx, die Kopien von Assets fĂŒr eine begrenzte Zeit speichern. Dieses Verhalten lĂ€sst sich leicht mit proxy_cache in Tagen oder Gigabyte Speicherplatz anpassen.

Die Kombination dieser Methode mit den oben genannten verteilten Dateisystemen bietet enorme Möglichkeiten, wobei das einzige Limit das Budget und die technischen Möglichkeiten derjenigen sind, die dies umsetzen und pflegen werden. Aus Erfahrung können wir sagen, dass je einfacher das System ist, desto stabiler funktioniert es. Mit der HinzufĂŒgung solcher Schichten wird die Infrastruktur viel schwieriger zu warten, und gleichzeitig erhöht sich die Zeit, die fĂŒr Diagnosen und Wiederherstellungen im Falle von AusfĂ€llen benötigt wird.

Empfehlung

Wenn die Umsetzung der vorgeschlagenen Speicheroptionen auch fĂŒr Sie nicht gerechtfertigt erscheint (zu kompliziert, zu teuer ...), sollten Sie die Situation aus einer anderen Perspektive betrachten. NĂ€mlich — die Architektur des Projekts zu durchleuchten und das Problem im Code zu beheben., indem man sich an eine statische Datenstruktur im Image bindet, wird der Inhalt oder das Verfahren zur „AufwĂ€rmung“ und/oder Vor-Compilierung von Assets wĂ€hrend des Build-Prozesses eindeutig definiert. So erhalten wir absolut vorhersehbares Verhalten und einen einheitlichen Satz von Dateien fĂŒr alle Umgebungen und Replikate der gestarteten Anwendung.

Wenn wir zu einem konkreten Beispiel mit dem Yii-Framework zurĂŒckkehren und nicht auf seine Struktur eingehen (was nicht das Ziel des Artikels ist), reicht es aus, auf zwei gĂ€ngige AnsĂ€tze hinzuweisen:

  1. Den Build-Prozess so Àndern, dass Assets an einem vorhersehbaren Ort abgelegt werden. So wird in Erweiterungen wie yii2-static-assets.
  2. konkrete Hashes fĂŒr Asset-Verzeichnisse zu definieren, wie zum Beispiel in dieser PrĂ€sentation (ab Folie №35). Übrigens empfiehlt der Autor des Vortrags letztendlich (und nicht ohne Grund!), die Assets nach dem Build-Prozess auf dem Build-Server in ein zentrales Repository (wie S3) hochzuladen und davor ein CDN zu setzen.

Hochgeladene Dateien

Ein weiterer Fall, der beim Transfer einer Anwendung in einen Kubernetes-Cluster unbedingt funktionieren wird, ist die Speicherung von Benutzerdaten im Dateisystem. Zum Beispiel haben wir wieder eine PHP-Anwendung, die Dateien ĂŒber ein Hochladeformular entgegennimmt, wĂ€hrend der Verarbeitung etwas damit macht und sie zurĂŒckgibt.

Der Ort, an dem diese Dateien abgelegt werden sollen, muss in der Kubernetes-RealitĂ€t fĂŒr alle Replikate der Anwendung gemeinsam sein. Je nach KomplexitĂ€t der Anwendung und der Notwendigkeit zur Organisation der Persistenz dieser Dateien können dies die oben genannten Optionen fĂŒr gemeinsame GerĂ€te sein, aber wie wir sehen, haben sie ihre eigenen Nachteile.

Empfehlung

Eine der Lösungsmöglichkeiten ist die Verwendung eines S3-kompatiblen Speichers (sei es auch nur eine Art selbst gehosteten Typs wie minio). Der Übergang zur Arbeit mit S3 erfordert Änderungen auf der Code-Ebene, und wie die Bereitstellung von Inhalten im Frontend erfolgen wird, haben wir bereits schrieben.

Benutzersitzungen

Es ist wichtig, die Organisation der Speicherung von Benutzersitzungen gesondert zu erwĂ€hnen. Oft sind auch das Dateien auf der Festplatte, was in Bezug auf Kubernetes zu stĂ€ndigen Authentifizierungsanfragen an den Benutzer fĂŒhrt, wenn seine Anfrage in einen anderen Container gelangt.

Ein Teil des Problems wird durch die Aktivierung von stickySessions in der Ingress (dieses Feature wird von allen gĂ€ngigen Ingress-Controllern unterstĂŒtzt — weitere Informationen finden Sie in unserer Bewertung zu sehen.), um den Benutzer an einen bestimmten Pod mit der Anwendung zu binden:

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: /

Aber das wird nicht alle Probleme bei wiederholten Deployments lösen.

Empfehlung

Eine bessere Lösung wĂ€re es, die Anwendung auf Sessionspeicherung in memcached, Redis und Ă€hnlichen Lösungen zu migrieren — im Grunde genommen, vollstĂ€ndig auf dateibasierte Optionen zu verzichten.

Fazit

Die hier besprochenen infrastrukturellen Lösungen sollten nur als vorĂŒbergehende „Notlösungen“ (was im Englischen eleganter als "workaround" klingt) betrachtet werden. Sie können in den frĂŒhen Phasen der Migration der Anwendung nach Kubernetes relevant sein, sollten jedoch keine dauerhafte Lösung werden.

Der allgemein empfohlene Weg besteht darin, diese durch die architektonische Weiterentwicklung der Anwendung zu ersetzen, gemĂ€ĂŸ dem, was bereits vielen bekannt ist. 12-Factor AppDie Anpassung der Anwendung an einen stateless Zustand erfordert jedoch zwangslĂ€ufig Änderungen am Code. Hier ist es wichtig, ein Gleichgewicht zwischen den geschĂ€ftlichen Anforderungen und den Möglichkeiten der Umsetzung und Wartung des gewĂ€hlten Ansatzes zu finden.

P.S.

Lesen Sie auch in unserem Blog:

Quelle: habr.com

Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server đŸ”„ Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster