Lokálne súbory pri presune aplikácie do Kubernetes

Lokálne súbory pri presune aplikácie do Kubernetes

Pri budovaní procesu CI/CD s využitím Kubernetes sa občas objaví problém nekompatibility požiadaviek novej infraštruktúry a aplikácie, ktorá sa do nej prenáša. Konkrétne, v priebehu zostavovania aplikácie je dôležité získať jednu obraz, ktorý sa bude používať v všetkých prostriediach a klastra projektu. Tento princíp leží v základe správneho podľa Google riadenia kontajnerov (o tom už viackrát písal aj náš techdirektor). povedal a náš technický riaditeľ).

Avšak nikto nezaznamená situácie, keď v kóde webovej stránky používa hotový rámec, ktorého využitie kladie obmedzenia na jeho ďalšie využívanie. A ak v „bežnom prostredí“ je s tým ľahké sa vysporiadať, v Kubernetes môže takéto správanie spôsobiť problém, najmä keď sa s tým stretávate po prvýkrát. Aj keď vynaliezavý mozog dokáže navrhnúť infraštruktúrne riešenia, ktoré sa na prvý pohľad javia ako očividné a dokonca aj celkom dobré... je dôležité si uvedomiť, že väčšina situácií by mala a môže byť riešená architektonicky.

Prejdeme si populárne workaround riešenia pre ukladanie súborov, ktoré môžu viesť k nepríjemným následkom pri prevádzke klastra, a poukážeme na správnejšiu cestu.

Ukladanie statiky

Na ilustráciu si vezmime webovú aplikáciu, ktorá využíva akýsi generátor statiky pre získanie sady obrázkov, štýlov a iného. Napríklad v PHP rámci Yii existuje vstavaný manažér assetov, ktorý generuje jedinečné názvy adresárov. Preto sa na výstupe získava súbor vopred neprekryvných ciest pre statiku webu (prinieslo to niekoľko benefitov — napríklad, na vylúčenie duplikátov pri využívaní rovnakého zdroja mnohými komponentmi). Tak, z krabice, pri prvom prístupe k modulu webového zdroja dochádza k formovaniu a rozkladaniu statiky (v skutočnosti — často symlinkov, ale o tom sa neskôr zmienime) s jedinečným pre dané nasadenie spoločným koreňovým adresárom:

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

Čím to hrozí v kontexte klastra?

Najjednoduchší príklad

Vezmime si dosť rozšírený prípad, keď je pred PHP nginx pre distribúciu statiky a spracovanie jednoduchých požiadaviek. Najjednoduchší spôsob — Nasadenie s dvoma kontajnermi:

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

V zjednodušenej forme sa konfigurácia nginx zhrňuje do nasledovného:

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

Pri prvom prístupe na stránku sa v kontajnéri s PHP objavia assety. Avšak v prípade dvoch kontajnerov v rámci jedného podu — nginx nič nevie o týchto statických súboroch, ktoré (podľa konfigurácie) by sa mali dodávať práve jemu. V dôsledku toho klient uvidí chybu 404 pri všetkých požiadavkách na CSS a JS súbory. Najjednoduchším riešením je usporiadať spoločný adresár pre kontajnery. Základná verzia — spoločný 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 sa generované súbory statického obsahu správne poskytujú nginxom. Ale pripomínam, že toto je primitívne riešenie, čo znamená, že je ďaleko od ideálu a má svoje nuansy a nedostatky, o ktorých budem hovoriť nižšie.

Pokročilejšie úložisko

Teraz si predstavme situáciu, keď používateľ navštívil webovú stránku, načítal stránku so štýlmi, ktoré sú v kontajneri, a zatiaľ čo ju čítal, znovu sme nasadili kontajner. V adresári assetov sa stalo prázdno a je potrebný dotaz na PHP, aby spustil generovanie nových. Avšak aj po tom budú odkazy na starú statiku neaktuálne, čo povedie k chybám zobrazenia statiky.

Okrem toho máme pravdepodobne viac-menej preťažený projekt, a teda — jedna kópia aplikácie nebude stačiť:

  • Naskalujeme Nasadenie na dve repliky.
  • Pri prvom prístupe na webovú stránku sa v jednej repliky vytvorili assety.
  • V nejakom momente ingress sa rozhodol (na účel vyváženia záťaže) poslať dotaz na druhú repliky, kde tie assety ešte nie sú. A možno tam už nie sú, pretože používame RollingUpdate a momentálne vykonávame nasadenie.

Celkovo, výsledok — opäť chyby.

Aby sme nestratili staré assety, môžeme zmeniť emptyDir na hostPath, ukladaním statiky fyzicky na uzol klastra. Tento prístup je zlý tým, že sa v podstate musíme priviazať k konkrétnemu uzlu klastra svojou aplikáciou, pretože — v prípade presunu na iné uzly — adresár nebude obsahovať potrebné súbory. Alebo je potrebná nejaká pozadová synchronizácia adresára medzi uzlami.

Aké sú možnosti riešení?

  1. Ak hardvér a zdroje umožňujú, môžeme využiť cephfs na organizovanie rovnocennej adresárovej štruktúry pre potreby statiky. Oficiálna dokumentácia odporúča SSD disky, aspoň trojnásobnú replikáciu a stabilné „hrubé“ pripojenie medzi uzlami klastra.
  2. Menej náročnou možnosťou bude organizovanie NFS servera. Avšak v tom prípade je potrebné zohľadniť možné zvýšenie doby odozvy pri spracovaní dotazov webovým serverom, a tiež odolnosť voči výpadkom ostane žiadať lepšie. Dôsledky výpadku sú katastrofálne: strata mountu odsudzuje klaster na zánik pod náporom záťaže LA, ktorá sa ženú do neba.

Okrem všetkého, pre všetky možnosti vytvorenia trvalého ukladania bude potrebná pozadová údržba zastaralých súborových súborov, ktoré sa nahromadili za určitý čas. Pred kontajnermi s PHP môžeme umiestniť DaemonSet z cache-ujúcich nginx, ktoré budú uchovávať kópie assetov obmedzený čas. Toto správanie sa ľahko nastavuje pomocou proxy_cache s hĺbkami ukladania v dňoch alebo gigabajtoch diskového priestoru.

Kombinácia tohto prístupu s vyššie uvedenými distribuovanými súborovými systémami poskytuje obrovské možnosti, limitom je len rozpočet a technický potenciál tých, ktorí ich budú realizovať a udržiavať. Na základe skúseností môžeme povedať, že čím jednoduchší je systém, tým stabilnejšie funguje. Pridanie takýchto vrstiev robí údržbu infraštruktúry oveľa zložitejšou a súčasne zvyšuje čas potrebný na diagnostiku a obnovenie v prípade akýchkoľvek porúch.

Odporúčanie

Ak vám realizácia navrhovaných úložných variantov tiež príde neopodstatnená (zložitá, drahá…), stojí za to pozrieť sa na situáciu z iného uhla. A to tak, že sa prekopete do architektúry projektu a vyriešite problém v kóde, pripevniac sa k nejakej statickej štruktúre dát v obraze, jednoznačné definovanie obsahu alebo postupu „ohrevu“ a/alebo prekompilácie assetov počas fázy zostavenia obrazu. Takto dosiahneme úplne predvídateľné správanie a rovnaký súbor súborov pre všetky prostredia a repliky spusteného aplikácie.

Ak sa vrátime k konkrétnemu príkladu s frameworkom Yii a nebudeme sa zaoberať jeho konštrukciou (čo nie je cieľom článku), stačí poukázať na dva populárne prístupy:

  1. Zmeniť proces zostavovania obrazu tak, aby súbory assetov boli umiestnené na predvídateľnom mieste. Takto navrhujú/implementujú v rozšíreniach ako yii2-static-assets.
  2. Definovať konkrétne hash pre adresáre assetov, ako to opisuje napríklad v tejto prezentácie (od slidu č. 35). Mimochodom, autor správy nakoniec (a nie bezdôvodne!) odporúča po zostavení assetov na build-serveri nahrať ich do centrálneho úložiska (ako S3), pred ktorým umiestniť CDN.

Nahrávané súbory

Ďalší prípad, ktorý rozhodne uspeje pri presune aplikácie do klastrov Kubernetes, je uchovávanie používateľských súborov v súborovom systéme. Napríklad máme opäť aplikáciu na PHP, ktorá prijíma súbory prostredníctvom formulára na nahrávanie, niečo s nimi robí počas prevádzky a následne ich vracia späť.

Miesto, kde by tieto súbory mali byť umiestnené, v realitách Kubernetes by malo byť spoločné pre všetky repliky aplikácie. V závislosti od zložitosti aplikácie a potreby organizácie trvalosti týchto súborov, týmto miestom môžu byť vyššie uvedené možnosti zdieľaných zariadení, ale, ako vidíme, majú svoje nevýhody.

Odporúčanie

Jednou z možností riešenia je použitie úložiska kompatibilného s S3 (nech už ide o akúsi variáciu kategórie self-hosted, ako minio). Prechod na prácu so S3 si vyžaduje zmeny na úrovni kódu, a ako bude prebiehať odovzdávanie obsahu na frontende, už písali.

Používateľské relácie

Samostatne stojí za to poznamenať organizáciu ukladania používateľských relácií. Často sú to tiež súbory na disku, čo v kontexte Kubernetes povedie k neustálym požiadavkám na autorizáciu používateľa, ak jeho požiadavka padne do iného kontajnera.

Čiastočne sa problém rieši zapnutím stickySessions na ingress (funkcia je podporovaná vo všetkých obľúbených kontroléroch ingress — podrobnejšie pozri v našom prehľade), aby sa používateľ pripojil k určitému podu s aplikáciou:

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

Ale to nevyrieši problémy pri opakovaných nasadeniach.

Odporúčanie

Správnejším spôsobom by bolo prejsť aplikáciu na ukladanie relácií v memcached, Redis a podobných riešeniach — vo všeobecnosti úplne sa vzdať súborových variantov.

Záver

Zvažované v texte infraštrukturálne riešenia sú hodné aplikovania iba v formáte dočasných „barličiek“ (čo znie krajšie v angličtine ako workaround). Môžu byť aktuálne v prvých fázach migrácie aplikácie do Kubernetes, ale nemali by „zakoreniť“.

Odporúčaná cesta spočíva v tom, aby ste sa ich zbavili v prospech architektonickej úpravy aplikácie v súlade s už dobre známym 12-Factor App. Avšak to — uvedenie aplikácie do stateless podoby — nevyhnutne znamená, že budú potrebné zmeny v kóde, a tu je dôležité nájsť rovnováhu medzi možnosťami/požiadavkami podnikania a perspektívami realizácie a údržby vybratej cesty.

P.S.

Prečítajte si tiež na našom blogu:

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster