Lokale Dateien beim Portieren einer Anwendung auf Kubernetes



Beim Erstellen eines CI / CD-Prozesses mit Kubernetes tritt manchmal das Problem der Inkompatibilität der Anforderungen der neuen Infrastruktur und der darauf übertragenen Anwendung auf. Insbesondere in der Phase der Anwendungsassemblierung ist es wichtig, ein Image zu erhalten, das in allen Projektumgebungen und Clustern verwendet wird. Dieses Prinzip liegt nach Ansicht von Google der korrekten Containerverwaltung zugrunde (unser Techdir hat wiederholt darüber gesprochen ).

Sie werden jedoch niemanden mit Situationen überraschen, in denen ein vorgefertigtes Framework im Site-Code verwendet wird, dessen Verwendung den weiteren Betrieb einschränkt. Und wenn es in einer „normalen Umgebung“ einfach ist, damit umzugehen, kann diese Art von Verhalten in Kubernetes ein Problem sein, insbesondere wenn Sie es zum ersten Mal antreffen. Obwohl ein genialer Verstand in der Lage ist, Infrastrukturlösungen anzubieten, die auf den ersten Blick offensichtlich und sogar recht gut erscheinen, ist es wichtig zu bedenken, dass die meisten Situationen architektonisch gelöst werden können und sollten.

Lassen Sie uns die gängigen Problemumgehungslösungen zum Speichern von Dateien analysieren, die zu unangenehmen Konsequenzen während des Betriebs des Clusters führen können, und auf einen korrekteren Pfad verweisen.

Statische Speicherung


Betrachten Sie zur Veranschaulichung eine Webanwendung, die einen statischen Generator verwendet, um eine Reihe von Bildern, Stilen und mehr zu erhalten. Das Yii PHP-Framework verfügt beispielsweise über einen integrierten Asset Manager, der eindeutige Verzeichnisnamen generiert. Dementsprechend ist die Ausgabe eine Reihe von sich absichtlich nicht überschneidenden Pfaden für die Standortstatik (dies wurde aus mehreren Gründen durchgeführt - zum Beispiel, um Duplikate zu vermeiden, wenn dieselbe Ressource mit vielen Komponenten verwendet wird). Wenn Sie also zum ersten Mal auf das Webressourcenmodul zugreifen, werden sofort statische Daten mit einem gemeinsamen Stammverzeichnis erstellt und angeordnet (in der Tat häufig Symlinks, aber dazu später mehr), das für diese Bereitstellung eindeutig ist:

  • webroot/assets/2072c2df/css/…
  • webroot/assets/2072c2df/images/…
  • webroot/assets/2072c2df/js/…

Womit ist das in Bezug auf einen Cluster behaftet?

Einfachstes Beispiel


Nehmen wir einen ziemlich häufigen Fall, in dem PHP mit Nginx konfrontiert ist, um Statiken zu verteilen und einfache Abfragen zu bearbeiten. Der einfachste Weg ist die Bereitstellung 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 lautet die Nginx-Konfiguration wie folgt:

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

Wenn Sie zum ersten Mal in einem Container mit PHP auf die Site zugreifen, werden Assets angezeigt. Bei zwei Containern im selben Pod weiß nginx jedoch nichts über diese statischen Dateien, die ihnen (je nach Konfiguration) übergeben werden sollten. Infolgedessen wird dem Client der Fehler 404 für alle Anforderungen an CSS- und JS-Dateien angezeigt. Die einfachste Lösung besteht darin, ein gemeinsames Verzeichnis für Container zu organisieren. Eine primitive Option ist das generische 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 von nginx korrekt angegeben. Aber ich möchte Sie daran erinnern, dass dies eine primitive Lösung ist, was bedeutet, dass sie alles andere als ideal ist und ihre eigenen Nuancen und Mängel aufweist, die nachstehend erörtert werden.

Erweiterter Speicher


Stellen Sie sich nun eine Situation vor, in der ein Benutzer eine Site besuchte, eine Seite mit den im Container verfügbaren Stilen lud und während er diese Seite las, den Container erneut bereitstellten. Das Asset-Verzeichnis ist leer geworden und erfordert eine Anforderung an PHP, um neue zu generieren. Auch danach sind Links zu alten Statiken veraltet, was zu Fehlern bei der Anzeige von Statiken führt.

Darüber hinaus haben wir höchstwahrscheinlich ein mehr oder weniger geladenes Projekt, was bedeutet, dass eine Kopie der Anwendung nicht ausreicht:

  • Skalieren Sie die Bereitstellung auf zwei Replikate.
  • Wenn Sie zum ersten Mal in einem Replikat auf die Site zugreifen, wurden Assets erstellt.
  • Irgendwann entschied sich ingress (um die Last auszugleichen), eine Anfrage für ein zweites Replikat zu senden, und diese Assets sind noch nicht vorhanden. Oder vielleicht sind sie nicht mehr da, weil wir RollingUpdate und derzeit eine Bereitstellung durchführen.

Im Allgemeinen ist das Ergebnis wieder ein Fehler.

Um die alten Assets nicht zu verlieren, können Sie emptyDir in hostPath ändern und die Statik physisch zum Clusterknoten hinzufügen. Dieser Ansatz ist schlecht, da wir mit unserer Anwendung tatsächlich an einen bestimmten Clusterknoten binden müssen, da das Verzeichnis beim Verschieben auf andere Knoten nicht die erforderlichen Dateien enthält. Oder es ist eine Hintergrundsynchronisation des Verzeichnisses zwischen Knoten erforderlich.

Was sind die Lösungen?

  1. Wenn Hardware und Ressourcen dies zulassen, können Sie mit cephfs ein gleichermaßen zugängliches Verzeichnis für die Anforderungen der Statik organisieren. In der offiziellen Dokumentation werden SSDs, mindestens dreifache Replikation und eine robuste „dicke“ Verbindung zwischen Clusterknoten empfohlen.
  2. Eine weniger anspruchsvolle Option wäre die Organisation eines NFS-Servers. Dann müssen Sie jedoch die mögliche Verlängerung der Antwortzeit auf die Verarbeitung von Anforderungen durch den Webserver berücksichtigen, und die Fehlertoleranz lässt zu wünschen übrig. Die Folgen des Ausfalls sind katastrophal: Der Verlust des Reittiers zerstört den Cluster unter dem Ansturm der LA-Ladung, die in den Himmel rast, zu Tode.

Unter anderem ist für alle Optionen zum Erstellen eines dauerhaften Speichers eine Hintergrundbereinigung veralteter Dateigruppen erforderlich, die sich über einen bestimmten Zeitraum angesammelt haben. Vor Containern mit PHP können Sie DaemonSet aus dem Caching von Nginx ablegen , in dem Kopien von Assets für eine begrenzte Zeit gespeichert werden. Dieses Verhalten kann einfach mithilfe von proxy_cache mit einer Speichertiefe in Tagen oder Gigabyte Festplattenspeicher konfiguriert werden.

Die Kombination dieser Methode mit den oben genannten verteilten Dateisystemen bietet ein großes Vorstellungsfeld, das nur das Budget und das technische Potenzial derjenigen einschränkt, die sie implementieren und unterstützen. Aus Erfahrung sagen wir, je einfacher das System ist, desto stabiler funktioniert es. Durch das Hinzufügen solcher Schichten wird es viel schwieriger, die Infrastruktur zu warten, und gleichzeitig nimmt der Zeitaufwand für Diagnose und Wiederherstellung im Falle von Fehlern zu.

Empfehlung


Wenn Ihnen die Implementierung der vorgeschlagenen Speicheroptionen ebenfalls ungerechtfertigt erscheint (kompliziert, teuer ...), sollten Sie die Situation von der anderen Seite betrachten. Wenn Sie sich mit der Architektur des Projekts befassen und das Problem im Code durch Verknüpfen mit einer statischen Datenstruktur im Bild beseitigen , erhalten Sie eine eindeutige Definition des Inhalts oder des Verfahrens zum „Aufwärmen“ und / oder Vorkompilieren von Assets in der Phase der Bildzusammenstellung. So erhalten wir für alle Umgebungen und Replikate der laufenden Anwendung ein absolut vorhersehbares Verhalten und denselben Satz von Dateien.

Wenn wir zu einem bestimmten Beispiel mit dem Yii-Framework zurückkehren und nicht auf dessen Struktur eingehen (was nicht der Zweck des Artikels ist), genügt es, auf zwei gängige Ansätze hinzuweisen:

  1. Ändern Sie den Prozess zum Zusammenstellen des Bildes so, dass Assets an einem vorhersehbaren Ort platziert werden. Bieten / implementieren Sie also Erweiterungen wie yii2-static-Assets .
  2. Definieren Sie bestimmte Hashes für Asset-Verzeichnisse, wie beispielsweise in dieser Präsentation beschrieben (beginnend mit Folie 35). Übrigens empfiehlt der Autor des Berichts letztendlich (und nicht ohne Grund!) Nach dem Zusammenstellen der Assets auf dem Build-Server, sie in ein zentrales Repository (wie S3) hochzuladen, vor das das CDN gestellt wird.

Herunterladbare Dateien


Ein weiterer Fall, der beim Übertragen einer Anwendung auf einen Kubernetes-Cluster mit Sicherheit ausgelöst wird, ist das Speichern von Benutzerdateien im Dateisystem. Zum Beispiel haben wir wieder eine PHP-Anwendung, die Dateien über das Upload-Formular akzeptiert, dabei etwas mit ihnen macht und sie zurückgibt.

Der Ort, an dem diese Dateien in Kubernetes Realitäten abgelegt werden sollen, sollte allen Anwendungsreplikaten gemeinsam sein. Abhängig von der Komplexität der Anwendung und der Notwendigkeit, die Persistenz dieser Dateien zu organisieren, kann ein solcher Ort die oben genannten Optionen für gemeinsam genutzte Geräte sein, aber wie wir sehen, haben sie ihre Nachteile.

Empfehlung


Eine Lösung besteht darin , einen S3-kompatiblen Speicher zu verwenden (selbst wenn es sich um eine selbst gehostete Kategorie wie minio handelt). Der Übergang zur Arbeit mit S3 erfordert Änderungen auf Codeebene , und wir haben bereits geschrieben, wie der Inhalt im Frontend zurückgegeben wird.

Benutzerdefinierte Sitzungen


Unabhängig davon ist die Organisation der Speicherung von Benutzersitzungen zu beachten. Oft sind dies auch Dateien auf der Festplatte, die im Kontext von Kubernetes zu ständigen Autorisierungsanforderungen des Benutzers führen, wenn seine Anforderung in einen anderen Container fällt.

Ein Teil des Problems wird gelöst, indem stickySessions on stickySessions (die Funktion wird von allen gängigen Ingress-Controllern unterstützt - weitere Informationen finden Sie in unserem Test ) unterstützt wird, um den Benutzer mit der Anwendung an einen bestimmten Pod 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: / 

Dies erspart Ihnen jedoch keine wiederholten Bereitstellungen.

Empfehlung


Ein korrekterer Weg wäre, die Anwendung auf das Speichern von Sitzungen in Memcached, Redis und ähnlichen Lösungen zu übertragen - im Allgemeinen sollten Sie die Dateioptionen vollständig aufgeben.

Fazit


Die im Text berücksichtigten Infrastrukturlösungen sind nur im Format temporärer „Krücken“ anwendbar (was auf Englisch als Problemumgehung schöner klingt). Sie können in den frühen Phasen der Anwendungsmigration zu Kubernetes relevant sein, sollten jedoch nicht "gerootet" werden.

Der allgemein empfohlene Weg besteht darin, sie zugunsten einer architektonischen Verfeinerung der Anwendung gemäß der bereits bekannten 12-Faktor-App zu entfernen . Dies - die Anwendung in eine zustandslose Form zu bringen - bedeutet jedoch zwangsläufig, dass Änderungen im Code erforderlich sind, und es ist wichtig, ein Gleichgewicht zwischen den Fähigkeiten / Anforderungen des Unternehmens und den Aussichten für die Implementierung und Aufrechterhaltung des gewählten Pfades zu finden.

PS


Lesen Sie auch in unserem Blog:

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


All Articles