Místní soubory při migraci aplikace do Kubernetes

Místní soubory při migraci aplikace do Kubernetes

Při budování procesu CI/CD pomocí Kubernetes někdy nastává problém s nekompatibilitou mezi požadavky nové infrastruktury a aplikací, která se do ní přenáší. Zejména ve fázi vytváření aplikace je důležité získat jeden obrázek, který bude použit vše projektová prostředí a klastry. Tento princip je základem správného podle Google správa kontejnerů (o tom více než jednou promluvil a naše technické oddělení).

Nikoho však neuvidíte v situacích, kdy kód webu používá hotový framework, jehož použití omezuje jeho další použití. A zatímco v „normálním prostředí“ je to snadné, v Kubernetes se toto chování může stát problémem, zvláště když se s ním setkáte poprvé. Zatímco vynalézavá mysl může přijít s řešeními infrastruktury, která se na první pohled zdají samozřejmá nebo dokonce dobrá... je důležité si uvědomit, že většina situací může a měla by řešit architektonicky.

Podívejme se na oblíbená řešení pro řešení ukládání souborů, která mohou vést k nepříjemným následkům při provozu clusteru, a také poukažme na správnější cestu.

Statické úložiště

Pro ilustraci si představme webovou aplikaci, která používá nějaký druh statického generátoru k získání sady obrázků, stylů a dalších věcí. Rámec Yii PHP má například vestavěný správce aktiv, který generuje jedinečné názvy adresářů. V souladu s tím je výstupem sada cest pro statický web, které se zjevně vzájemně neprotínají (to bylo provedeno z několika důvodů - například pro odstranění duplicit, když více komponent používá stejný zdroj). Takže hned po prvním přístupu k modulu webových zdrojů se vytvoří statické soubory (ve skutečnosti často symbolické odkazy, ale o tom později) a rozloží se se společným kořenovým adresářem jedinečným pro toto nasazení:

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

Co to znamená z hlediska klastru?

Nejjednodušší příklad

Vezměme si docela běžný případ, kdy PHP předchází nginx pro distribuci statických dat a zpracování jednoduchých požadavků. Nejjednodušší způsob - Rozvinutí se dvěma kontejnery:

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

Ve zjednodušené podobě se konfigurace nginx scvrkává na následující:

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

Při prvním přístupu na web se aktiva objeví v kontejneru PHP. Ale v případě dvou kontejnerů v rámci jednoho podu nginx neví nic o těchto statických souborech, které by jim (podle konfigurace) měly být předány. Výsledkem je, že klient uvidí u všech požadavků na soubory CSS a JS chybu 404. Nejjednodušším řešením by zde bylo uspořádat společný adresář pro kontejnery. Primitivní možnost - obecná 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

Nyní jsou statické soubory generované v kontejneru obsluhovány nginx správně. Dovolte mi však připomenout, že se jedná o primitivní řešení, což znamená, že je daleko od ideálu a má své vlastní nuance a nedostatky, které jsou popsány níže.

Pokročilejší úložiště

Nyní si představte situaci, kdy uživatel navštívil web, načetl stránku se styly dostupnými v kontejneru, a zatímco četl tuto stránku, kontejner jsme znovu nasadili. Katalog aktiv se vyprázdnil a pro zahájení generování nových je vyžadován požadavek na PHP. I po tomto však budou odkazy na starou statiku irelevantní, což povede k chybám při zobrazování statiky.

Navíc máme s největší pravděpodobností více či méně nabitý projekt, což znamená, že jedna kopie aplikace nebude stačit:

  • Pojďme to zvětšit Rozvinutí až dvě repliky.
  • Při prvním přístupu k webu byly prostředky vytvořeny v jedné replice.
  • V určitém okamžiku se ingress rozhodl (pro účely vyrovnávání zátěže) poslat požadavek na druhou repliku a tato aktiva tam ještě nebyla. Nebo možná už tam nejsou, protože je používáme RollingUpdate a v tuto chvíli provádíme nasazení.

Obecně jsou výsledkem opět chyby.

Chcete-li se vyhnout ztrátě starých aktiv, můžete je změnit emptyDir na hostPath, přidání statické elektřiny do uzlu clusteru. Tento přístup je špatný, protože vlastně musíme vázat se na konkrétní uzel clusteru vaší aplikaci, protože - v případě přesunu do jiných uzlů - adresář nebude obsahovat potřebné soubory. Nebo je vyžadována nějaká synchronizace adresářů na pozadí mezi uzly.

Jaká jsou řešení?

  1. Pokud to hardware a prostředky dovolí, můžete použít cephfs uspořádat stejně dostupný adresář pro statické potřeby. Oficiální dokumentace doporučuje SSD disky, alespoň trojnásobnou replikaci a stabilní „tlusté“ spojení mezi uzly clusteru.
  2. Méně náročnou možností by bylo uspořádání serveru NFS. Poté však musíte vzít v úvahu možné prodloužení doby odezvy na zpracování požadavků webovým serverem a odolnost proti chybám bude velmi žádoucí. Důsledky neúspěchu jsou katastrofální: ztráta mounta odsoudí cluster k smrti pod tlakem nákladu LA řítícího se do nebe.

Kromě jiného budou vyžadovat všechny možnosti pro vytvoření trvalého úložiště čištění pozadí zastaralé sady souborů nashromážděné za určité časové období. Před kontejnery s PHP můžete umístit DaemonSet z mezipaměti nginx, která bude ukládat kopie aktiv po omezenou dobu. Toto chování lze snadno konfigurovat pomocí proxy_cache s hloubkou úložiště ve dnech nebo gigabajtech místa na disku.

Kombinace této metody s výše zmíněnými distribuovanými souborovými systémy poskytuje obrovské pole pro představivost, omezené pouze rozpočtem a technickým potenciálem těch, kteří ji budou implementovat a podporovat. Ze zkušenosti můžeme říci, že čím je systém jednodušší, tím stabilněji funguje. Když jsou takové vrstvy přidány, je mnohem obtížnější udržovat infrastrukturu a zároveň se zvyšuje čas strávený diagnostikou a zotavením se z případných selhání.

Doporučení

Pokud se vám i implementace navrhovaných možností úložiště zdá neopodstatněná (složitá, drahá...), pak se vyplatí podívat se na situaci z druhé strany. Totiž kopat do architektury projektu a opravte problém v kódu, vázané na nějakou statickou datovou strukturu v obraze, jednoznačnou definici obsahu nebo postupu pro „zahřívání“ a/nebo předkompilaci aktiv ve fázi sestavování obrazu. Získáme tak naprosto předvídatelné chování a stejnou sadu souborů pro všechna prostředí a repliky běžící aplikace.

Pokud se vrátíme ke konkrétnímu příkladu s frameworkem Yii a nebudeme se vrtat v jeho struktuře (což není účelem článku), stačí poukázat na dva oblíbené přístupy:

  1. Změňte proces vytváření obrazu tak, aby byly prostředky umístěny na předvídatelné místo. Toto je navrženo/implementováno v rozšířeních jako yii2-static-assets.
  2. Definujte konkrétní hashe pro adresáře aktiv, jak je popsáno např. tuto prezentaci (počínaje snímkem č. 35). Mimochodem, autor zprávy nakonec (a ne bezdůvodně!) doporučuje, abyste po sestavení aktiv na sestavení serveru je nahráli na centrální úložiště (jako S3), před které umístěte CDN.

Stahování

Dalším případem, který určitě přijde v úvahu při migraci aplikace do clusteru Kubernetes, je ukládání uživatelských souborů do souborového systému. Máme například opět PHP aplikaci, která přijímá soubory přes uploadový formulář, za provozu s nimi něco dělá a posílá je zpět.

V Kubernetes by umístění, kam by měly být tyto soubory umístěny, mělo být společné pro všechny repliky aplikace. V závislosti na složitosti aplikace a potřebě organizovat perzistenci těchto souborů mohou být takovým místem výše uvedené možnosti sdíleného zařízení, ale jak vidíme, mají své nevýhody.

Doporučení

Jedno řešení je pomocí úložiště kompatibilního s S3 (i když je to nějaká kategorie s vlastním hostitelem, jako je minio). Přechod na S3 bude vyžadovat změny na úrovni kódu, a jak bude obsah doručován na frontendu, už máme писали.

Uživatelské relace

Samostatně stojí za zmínku organizace ukládání uživatelských relací. Často se jedná i o soubory na disku, což v kontextu Kubernetes povede k neustálým autorizačním požadavkům uživatele, pokud jeho požadavek skončí v jiném kontejneru.

Problém je částečně vyřešen zapnutím stickySessions na vstupu (funkce je podporována ve všech populárních kontrolérech vstupu - více podrobností viz naše recenze)pro navázání uživatele na konkrétní modul s aplikací:

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

To ale neodstraní problémy s opakovaným nasazením.

Doporučení

Správnějším způsobem by bylo přenést aplikaci na ukládání relací v memcached, Redis a podobných řešeních - obecně úplně opustit možnosti souboru.

Závěr

Infrastrukturní řešení probíraná v textu jsou hodná použití pouze ve formátu dočasných „berliček“ (což zní v angličtině krásněji jako řešení). Mohou být relevantní v prvních fázích migrace aplikace na Kubernetes, ale neměly by se zakořenit.

Obecnou doporučenou cestou je zbavit se jich ve prospěch architektonické úpravy aplikace v souladu s tím, co je již mnohým dobře známé 12faktorová aplikace. Toto – uvedení aplikace do bezstavové podoby – však nevyhnutelně znamená, že budou vyžadovány změny v kódu, a zde je důležité najít rovnováhu mezi schopnostmi/požadavky podniku a vyhlídkami na implementaci a udržení zvolené cesty. .

PS

Přečtěte si také na našem blogu:

Zdroj: www.habr.com

Přidat komentář