Pliki lokalne podczas migracji aplikacji do Kubernetes

Pliki lokalne podczas migracji aplikacji do Kubernetes

Budując proces CI/CD z wykorzystaniem Kubernetesa czasami pojawia się problem niezgodności wymagań nowej infrastruktury z przenoszoną do niej aplikacją. W szczególności na etapie budowy aplikacji ważne jest uzyskanie jeden obraz, który będzie używany wszystko środowiska projektowe i klastry. Zasada ta leży u podstaw prawidłowego według Google’a zarządzanie kontenerami (nie raz o tym powiedział i nasz dział techniczny).

Nie zobaczysz jednak nikogo w sytuacjach, gdy kod strony korzysta z gotowego frameworka, którego użycie nakłada ograniczenia na jej dalsze wykorzystanie. I o ile w „normalnym środowisku” łatwo sobie z tym poradzić, o tyle w Kubernetesie takie zachowanie może stać się problemem, zwłaszcza gdy spotykasz się z nim po raz pierwszy. Choć pomysłowy umysł może wymyślić rozwiązania infrastrukturalne, które na pierwszy rzut oka wydają się oczywiste lub nawet dobre... należy pamiętać, że w większości sytuacji można i należy rozwiązać architektonicznie.

Przyjrzyjmy się popularnym obejściom przechowywania plików, które mogą prowadzić do nieprzyjemnych konsekwencji podczas obsługi klastra, a także wskażmy bardziej poprawną ścieżkę.

Magazyn statyczny

Aby to zilustrować, rozważmy aplikację internetową, która korzysta z pewnego rodzaju generatora statycznego w celu uzyskania zestawu obrazów, stylów i innych rzeczy. Na przykład framework Yii PHP ma wbudowany menedżer zasobów, który generuje unikalne nazwy katalogów. W związku z tym wynikiem jest zestaw ścieżek witryny statycznej, które oczywiście nie przecinają się ze sobą (zrobiono to z kilku powodów - na przykład, aby wyeliminować duplikaty, gdy wiele komponentów korzysta z tego samego zasobu). Tak więc, gdy po raz pierwszy uzyskujesz dostęp do modułu zasobów sieciowych, tworzone są pliki statyczne (w rzeczywistości często dowiązania symboliczne, ale o tym później) i układane we wspólnym katalogu głównym, unikalnym dla tego wdrożenia:

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

Co to oznacza w kontekście klastra?

Najprostszy przykład

Weźmy dość częsty przypadek, gdy PHP jest poprzedzone przez nginx w celu dystrybucji danych statycznych i przetwarzania prostych żądań. Najprostszy sposób - Rozlokowanie z dwoma pojemnikami:

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

W uproszczonej formie konfiguracja nginx sprowadza się do następujących elementów:

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

Kiedy po raz pierwszy uzyskasz dostęp do witryny, zasoby pojawią się w kontenerze PHP. Jednak w przypadku dwóch kontenerów w jednym podzespole, nginx nic nie wie o tych plikach statycznych, które (zgodnie z konfiguracją) należy im przekazać. W rezultacie przy wszystkich żądaniach do plików CSS i JS klient zobaczy błąd 404. Najprostszym rozwiązaniem byłoby zorganizowanie wspólnego katalogu dla kontenerów. Opcja prymitywna - ogólna 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

Teraz pliki statyczne wygenerowane w kontenerze są poprawnie obsługiwane przez nginx. Przypomnę jednak, że jest to rozwiązanie prymitywne, czyli dalekie od ideału i posiadające swoje niuanse i mankamenty, o których poniżej.

Bardziej zaawansowana pamięć masowa

Teraz wyobraźmy sobie sytuację, w której użytkownik odwiedził witrynę, załadował stronę ze stylami dostępnymi w kontenerze i podczas czytania tej strony ponownie uruchomiliśmy kontener. Katalog zasobów stał się pusty i wymagane jest żądanie do PHP, aby rozpocząć generowanie nowych. Jednak nawet po tym linki do starych statystyk będą nieistotne, co będzie prowadzić do błędów w wyświetlaniu statystyk.

Poza tym najprawdopodobniej mamy mniej lub bardziej obciążony projekt, co oznacza, że ​​jedna kopia aplikacji nie wystarczy:

  • Zwiększmy skalę Rozlokowanie do dwóch replik.
  • Podczas pierwszego dostępu do witryny zasoby zostały utworzone w jednej replice.
  • W pewnym momencie funkcja ingresu zdecydowała się (w celu równoważenia obciążenia) wysłać żądanie do drugiej repliki, a tych zasobów jeszcze tam nie było. A może już ich nie ma, bo używamy RollingUpdate i w tej chwili przeprowadzamy wdrażanie.

Ogólnie rzecz biorąc, rezultatem są znowu błędy.

Aby uniknąć utraty starych zasobów, możesz je zmienić emptyDir na hostPath, dodając statycznie fizycznie do węzła klastra. To podejście jest złe, bo tak naprawdę musimy powiązać z określonym węzłem klastra Twojej aplikacji, ponieważ – w przypadku przeniesienia do innych węzłów – w katalogu nie będą znajdować się potrzebne pliki. Lub wymagany jest jakiś rodzaj synchronizacji katalogów w tle między węzłami.

Jakie są rozwiązania?

  1. Jeśli sprzęt i zasoby na to pozwalają, możesz użyć ceffs zorganizować równie dostępny katalog dla potrzeb statycznych. Oficjalna dokumentacja zaleca dyski SSD, co najmniej trzykrotną replikację i stabilne „grube” połączenie pomiędzy węzłami klastra.
  2. Mniej wymagającą opcją byłoby zorganizowanie serwera NFS. Trzeba jednak wtedy liczyć się z możliwym wydłużeniem czasu odpowiedzi na przetwarzanie żądań przez serwer WWW, a tolerancja na błędy będzie pozostawiała wiele do życzenia. Konsekwencje awarii są katastrofalne: utrata wierzchowca skazuje gromadę na śmierć pod naporem ładunku LA pędzącego w niebo.

Między innymi będą wymagane wszystkie opcje tworzenia trwałej pamięci czyszczenie tła nieaktualne zestawy plików zgromadzone przez pewien okres czasu. Przed kontenerami z PHP możesz postawić Zestaw demonów z buforowania Nginx, który będzie przechowywać kopie zasobów przez ograniczony czas. To zachowanie można łatwo skonfigurować za pomocą proxy_cache z głębokością przechowywania w dniach lub gigabajtach miejsca na dysku.

Połączenie tej metody z wymienionymi wyżej rozproszonymi systemami plików daje ogromne pole wyobraźni, ograniczone jedynie budżetem i potencjałem technicznym tych, którzy będą ją wdrażać i wspierać. Z doświadczenia możemy powiedzieć, że im prostszy system, tym stabilniej działa. Po dodaniu takich warstw znacznie trudniej jest utrzymać infrastrukturę, a jednocześnie wydłuża się czas poświęcony na diagnozowanie i usuwanie awarii.

Zalecenie

Jeśli wdrożenie proponowanych opcji przechowywania również wydaje Ci się nieuzasadnione (skomplikowane, drogie...), to warto spojrzeć na sytuację od drugiej strony. Mianowicie zagłębić się w architekturę projektu i napraw problem w kodzie, powiązany z jakąś statyczną strukturą danych w obrazie, jednoznaczne określenie zawartości lub procedury „rozgrzewania” i/lub prekompilowania zasobów na etapie montażu obrazu. W ten sposób uzyskujemy całkowicie przewidywalne zachowanie i ten sam zestaw plików dla wszystkich środowisk i replik działającej aplikacji.

Jeśli wrócimy do konkretnego przykładu z frameworkiem Yii i nie zagłębimy się w jego strukturę (co nie jest celem artykułu), wystarczy wskazać dwa popularne podejścia:

  1. Zmień proces tworzenia obrazu, aby umieścić zasoby w przewidywalnej lokalizacji. Jest to sugerowane/implementowane w rozszerzeniach takich jak yii2-static-assets.
  2. Zdefiniuj konkretne skróty dla katalogów zasobów, jak omówiono np. w: tę prezentację (począwszy od slajdu nr 35). Swoją drogą autor raportu ostatecznie (i nie bez powodu!) radzi, aby po zmontowaniu zasobów na serwerze kompilacji wgrać je do centralnego magazynu (np. S3), przed którym umieść CDN.

Pliki do pobrania

Kolejnym przypadkiem, który na pewno wejdzie w grę podczas migracji aplikacji do klastra Kubernetes, jest przechowywanie plików użytkownika w systemie plików. Na przykład znowu mamy aplikację PHP, która akceptuje pliki poprzez formularz przesyłania, robi z nimi coś podczas operacji i odsyła je z powrotem.

W Kubernetesie lokalizacja, w której należy umieścić te pliki, powinna być wspólna dla wszystkich replik aplikacji. W zależności od złożoności aplikacji i konieczności zorganizowania trwałości tych plików, takim miejscem mogą być wspomniane wyżej opcje urządzenia współdzielonego, jednak jak widzimy mają one swoje wady.

Zalecenie

Jednym z rozwiązań jest przy użyciu pamięci zgodnej z S3 (nawet jeśli jest to kategoria hostowana samodzielnie, taka jak minio). Przejście na S3 będzie wymagało zmian na poziomie kodui sposobu, w jaki treść będzie dostarczana na interfejsie, już to ustaliliśmy napisał.

Sesje użytkowników

Osobno warto zwrócić uwagę na organizację przechowywania sesji użytkowników. Często są to także pliki na dysku, co w kontekście Kubernetesa będzie skutkować ciągłymi żądaniami autoryzacyjnymi od użytkownika, jeśli jego żądanie trafi do innego kontenera.

Problem częściowo rozwiązuje się poprzez włączenie stickySessions na wejściu (funkcja jest obsługiwana we wszystkich popularnych kontrolerach wejściowych - więcej szczegółów znajdziesz w nasza recenzja)aby powiązać użytkownika z konkretnym podem za pomocą aplikacji:

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

Nie wyeliminuje to jednak problemów związanych z powtarzającymi się wdrożeniami.

Zalecenie

Bardziej poprawnym sposobem byłoby przeniesienie aplikacji do przechowywanie sesji w memcached, Redis i podobnych rozwiązaniach - ogólnie rzecz biorąc, całkowicie porzuć opcje plików.

wniosek

Omówione w tekście rozwiązania infrastrukturalne warto stosować jedynie w formie tymczasowych „kul” (co brzmi piękniej w języku angielskim jako obejście). Mogą mieć znaczenie w pierwszych etapach migracji aplikacji do Kubernetesa, ale nie powinny się zakorzenić.

Ogólnie zalecaną ścieżką jest pozbycie się ich na rzecz modyfikacji architektonicznej aplikacji zgodnie z tym, co jest już dobrze znane wielu Aplikacja 12-czynnikowa. Jednak to – doprowadzenie wniosku do postaci bezpaństwowej – nieuchronnie oznacza, że ​​konieczne będą zmiany w kodzie i tutaj istotne jest znalezienie równowagi pomiędzy możliwościami/wymaganiami biznesu a perspektywami wdrożenia i utrzymania wybranej ścieżki .

PS

Przeczytaj także na naszym blogu:

Źródło: www.habr.com

Dodaj komentarz