Lokale Dateien bei der Migration einer Anwendung zu Kubernetes

Lokale Dateien bei der Migration einer Anwendung zu Kubernetes

Beim Aufbau eines CI/CD-Prozesses mit Kubernetes entsteht manchmal das Problem der Inkompatibilität zwischen den Anforderungen der neuen Infrastruktur und der darauf übertragenen Anwendung. Insbesondere in der Phase der Anwendungserstellung ist es wichtig, Folgendes zu erreichen ein Bild, das verwendet werden soll alle Projektumgebungen und Cluster. Dieses Prinzip liegt dem Richtigen zugrunde laut Google Containerverwaltung (mehr als einmal darüber говорил und unsere technische Abteilung).

Sie werden jedoch niemanden sehen, wenn der Site-Code ein vorgefertigtes Framework verwendet, dessen Verwendung Einschränkungen für die weitere Verwendung mit sich bringt. Und während dies in einer „normalen Umgebung“ leicht zu bewältigen ist, kann dieses Verhalten in Kubernetes zum Problem werden, insbesondere wenn es zum ersten Mal auftritt. Während ein erfinderischer Geist Infrastrukturlösungen entwickeln kann, die auf den ersten Blick offensichtlich oder sogar gut erscheinen, ist es wichtig, sich daran zu erinnern, dass dies in den meisten Situationen möglich ist und sein sollte architektonisch gelöst werden.

Schauen wir uns beliebte Workaround-Lösungen zum Speichern von Dateien an, die beim Betrieb eines Clusters zu unangenehmen Folgen führen können, und zeigen auch einen korrekteren Pfad auf.

Statischer Speicher

Stellen Sie sich zur Veranschaulichung eine Webanwendung vor, die eine Art statischen Generator verwendet, um eine Reihe von Bildern, Stilen und anderen Dingen zu erhalten. Das Yii-PHP-Framework verfügt beispielsweise über einen integrierten Asset-Manager, der eindeutige Verzeichnisnamen generiert. Dementsprechend handelt es sich bei der Ausgabe um eine Reihe von Pfaden für die statische Site, die sich offensichtlich nicht überschneiden (dies geschah aus mehreren Gründen – beispielsweise um Duplikate zu vermeiden, wenn mehrere Komponenten dieselbe Ressource verwenden). Wenn Sie also zum ersten Mal auf ein Webressourcenmodul zugreifen, werden statische Dateien (oftmals Symlinks, aber dazu später mehr) erstellt und mit einem gemeinsamen Stammverzeichnis angelegt, das für diese Bereitstellung einzigartig ist:

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

Was bedeutet das im Hinblick auf einen Cluster?

Das einfachste Beispiel

Nehmen wir einen ziemlich häufigen Fall, bei dem PHP vor Nginx steht, um statische Daten zu verteilen und einfache Anfragen zu verarbeiten. Der einfachste Weg - Einsatz mit zwei Behältern:

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äuft die Nginx-Konfiguration auf Folgendes hinaus:

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 auf die Site zugreifen, werden Assets im PHP-Container angezeigt. Bei zwei Containern innerhalb eines Pods weiß nginx jedoch nichts über diese statischen Dateien, die ihnen (je nach Konfiguration) übergeben werden sollten. Infolgedessen sieht der Client bei allen Anfragen an CSS- und JS-Dateien einen 404-Fehler. Die einfachste Lösung wäre hier, ein gemeinsames Verzeichnis für Container zu organisieren. Primitive Option - allgemein 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 im Container generierte statische Dateien von Nginx korrekt bereitgestellt. Ich möchte Sie jedoch 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 im Folgenden erörtert werden.

Erweiterter Speicher

Stellen Sie sich nun eine Situation vor, in der ein Benutzer die Website besucht, eine Seite mit den im Container verfügbaren Stilen geladen hat und wir den Container erneut bereitgestellt haben, während er diese Seite gelesen hat. Der Assets-Katalog ist leer und es ist eine Anfrage an PHP erforderlich, um mit der Generierung neuer Assets zu beginnen. Allerdings sind auch danach Links zu alten Statistiken nicht mehr relevant, was zu Fehlern bei der Anzeige der Statistiken führen wird.

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

  • Lass es uns vergrößern Einsatz bis zu zwei Replikate.
  • Beim ersten Zugriff auf die Site wurden Assets in einer Replik erstellt.
  • Irgendwann beschloss Ingress (aus Gründen des Lastausgleichs), eine Anfrage an das zweite Replikat zu senden, und diese Assets waren noch nicht vorhanden. Oder vielleicht sind sie nicht mehr da, weil wir sie verwenden RollingUpdate und im Moment führen wir die Bereitstellung durch.

Generell kommt es immer wieder zu Fehlern.

Um den Verlust alter Vermögenswerte zu vermeiden, können Sie wechseln emptyDir auf hostPath, wodurch statische Aufladung physisch zu einem Clusterknoten hinzugefügt wird. Dieser Ansatz ist schlecht, weil wir es tatsächlich müssen An einen bestimmten Clusterknoten binden Ihrer Anwendung, da das Verzeichnis bei einem Umzug auf andere Knoten nicht die erforderlichen Dateien enthält. Oder es ist eine Art Hintergrundsynchronisierung des Verzeichnisses zwischen Knoten erforderlich.

Was sind die Lösungen?

  1. Wenn Hardware und Ressourcen es zulassen, können Sie verwenden cephfs ein gleichermaßen zugängliches Verzeichnis für statische Bedürfnisse zu organisieren. Offizielle Dokumentation empfiehlt SSD-Laufwerke, mindestens dreifache Replikation und eine stabile „dicke“ Verbindung zwischen Clusterknoten.
  2. Eine weniger anspruchsvolle Option wäre die Organisation eines NFS-Servers. Dann müssen Sie jedoch die mögliche Verlängerung der Antwortzeit für die Verarbeitung von Anfragen durch den Webserver berücksichtigen, und die Fehlertoleranz lässt zu wünschen übrig. Die Folgen eines Scheiterns sind katastrophal: Der Verlust der Halterung führt dazu, dass der Sternhaufen unter dem Druck der in den Himmel rasenden LA-Last stirbt.

Unter anderem werden alle Optionen zum Erstellen von persistentem Speicher benötigt Hintergrundreinigung veraltete Dateisätze, die sich über einen bestimmten Zeitraum angesammelt haben. Vor Container mit PHP können Sie setzen Dämonenset aus dem Caching von Nginx, das Kopien von Assets für eine begrenzte Zeit speichert. Dieses Verhalten lässt sich mithilfe von einfach konfigurieren proxy_cache mit Speichertiefe in Tagen oder Gigabyte Speicherplatz.

Die Kombination dieser Methode mit den oben genannten verteilten Dateisystemen bietet einen riesigen Spielraum für Fantasie, der nur durch das Budget und das technische Potenzial derjenigen begrenzt ist, die sie implementieren und unterstützen. Aus Erfahrung können wir sagen: Je einfacher das System, desto stabiler funktioniert es. Wenn solche Schichten hinzugefügt werden, wird es deutlich schwieriger, die Infrastruktur aufrechtzuerhalten, und gleichzeitig erhöht sich der Zeitaufwand für die Diagnose und Wiederherstellung nach etwaigen Ausfällen.

Empfehlung

Wenn Ihnen die Umsetzung der vorgeschlagenen Speichermöglichkeiten auch ungerechtfertigt erscheint (kompliziert, teuer...), dann lohnt es sich, die Situation von der anderen Seite zu betrachten. Nämlich, sich mit der Projektarchitektur auseinanderzusetzen und Beheben Sie das Problem im Code, gebunden an eine statische Datenstruktur im Bild, eine eindeutige Definition des Inhalts oder ein Verfahren zum „Aufwärmen“ und/oder Vorkompilieren von Assets in der Bildassemblierungsphase. Auf diese Weise erhalten wir ein absolut vorhersehbares Verhalten und den gleichen Dateisatz für alle Umgebungen und Replikate der laufenden Anwendung.

Wenn wir auf das konkrete Beispiel mit dem Yii-Framework zurückkommen und uns nicht näher mit seiner Struktur befassen (was nicht der Zweck des Artikels ist), genügt es, auf zwei beliebte Ansätze hinzuweisen:

  1. Ändern Sie den Image-Erstellungsprozess, um Assets an einem vorhersehbaren Ort zu platzieren. Dies wird in Erweiterungen wie vorgeschlagen/implementiert yii2-statische-Assets.
  2. Definieren Sie spezifische Hashes für Asset-Verzeichnisse, wie z. B. besprochen. diese Präsentation (ab Folie Nr. 35). Übrigens rät der Autor des Berichts letztendlich (und das nicht ohne Grund!) dazu, Assets nach dem Zusammenstellen auf dem Build-Server in einen zentralen Speicher (z. B. S3) hochzuladen und vor diesem ein CDN zu platzieren.

Downloads

Ein weiterer Fall, der bei der Migration einer Anwendung auf einen Kubernetes-Cluster definitiv ins Spiel kommt, ist die Speicherung von Benutzerdateien im Dateisystem. Wir haben zum Beispiel wieder eine PHP-Anwendung, die über ein Upload-Formular Dateien entgegennimmt, im laufenden Betrieb etwas damit macht und sie zurücksendet.

In Kubernetes sollte der Speicherort, an dem diese Dateien abgelegt werden sollen, für alle Replikate der Anwendung gleich sein. Abhängig von der Komplexität der Anwendung und der Notwendigkeit, die Persistenz dieser Dateien zu organisieren, können die oben genannten Optionen für gemeinsam genutzte Geräte ein solcher Ort sein, aber wie wir sehen, haben sie ihre Nachteile.

Empfehlung

Eine Lösung ist Verwendung von S3-kompatiblem Speicher (auch wenn es sich um eine Art selbstgehostete Kategorie wie Minio handelt). Der Wechsel zu S3 erfordert Änderungen auf Codeebene, und wie Inhalte im Frontend bereitgestellt werden, haben wir bereits schrieb.

Benutzersitzungen

Unabhängig davon ist die Organisation der Speicherung von Benutzersitzungen erwähnenswert. Häufig handelt es sich dabei auch um Dateien auf der Festplatte, was im Kontext von Kubernetes zu ständigen Autorisierungsanfragen des Benutzers führt, wenn seine Anfrage in einem anderen Container landet.

Das Problem wird teilweise durch das Einschalten gelöst stickySessions beim Eindringen (Die Funktion wird in allen gängigen Ingress-Controllern unterstützt – weitere Einzelheiten finden Sie unter unsere Bewertung)So binden Sie den Benutzer mit der Anwendung an einen bestimmten Pod:

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 wird jedoch die Probleme bei wiederholten Bereitstellungen nicht beseitigen.

Empfehlung

Ein korrekterer Weg wäre, den Antrag an zu übertragen Speichern von Sitzungen in Memcached, Redis und ähnlichen Lösungen - Verzichten Sie im Allgemeinen vollständig auf Dateioptionen.

Abschluss

Die im Text besprochenen Infrastrukturlösungen sind nur im Format temporärer „Krücken“ (was auf Englisch als Workaround schöner klingt) sinnvoll. Sie können in den ersten Phasen der Migration einer Anwendung zu Kubernetes relevant sein, sollten sich aber nicht durchsetzen.

Der allgemein empfohlene Weg besteht darin, sie loszuwerden und stattdessen eine architektonische Änderung der Anwendung in Übereinstimmung mit dem vorzunehmen, was vielen bereits bekannt ist 12-Faktor-App. Allerdings bedeutet dies – die Anwendung in eine zustandslose Form zu bringen – zwangsläufig, dass Änderungen im Code erforderlich sind, und hier ist es wichtig, ein Gleichgewicht zwischen den Fähigkeiten/Anforderungen des Unternehmens und den Aussichten für die Implementierung und Aufrechterhaltung des gewählten Wegs zu finden .

PS

Lesen Sie auch auf unserem Blog:

Source: habr.com

Kommentar hinzufügen