Lokalne datoteke prilikom migracije aplikacije na Kubernetes

Lokalne datoteke prilikom migracije aplikacije na Kubernetes

Prilikom izgradnje CI/CD procesa pomoću Kubernetesa ponekad se javlja problem nekompatibilnosti između zahtjeva nove infrastrukture i aplikacije koja se na nju prenosi. Konkretno, u fazi izrade aplikacije važno je dobiti jedan slika koja će se koristiti u Sve projektna okruženja i klastere. Ovo načelo je temelj ispravnog prema Googleu upravljanje kontejnerima (više puta o tome govorio i naš tehnički odjel).

Međutim, nećete vidjeti nikoga u situacijama kada kod web-mjesta koristi gotov okvir, čija upotreba nameće ograničenja za njegovu daljnju upotrebu. I dok je u "normalnom okruženju" s tim lako izaći na kraj, u Kubernetesu ovakvo ponašanje može postati problem, pogotovo kada se s njim susrećete prvi put. Dok inventivni um može smisliti infrastrukturna rješenja koja se na prvi pogled čine očigledna ili čak dobra... važno je zapamtiti da većina situacija može i treba biti arhitektonski riješena.

Pogledajmo popularna zaobilazna rješenja za pohranjivanje datoteka koja mogu dovesti do neugodnih posljedica pri radu s klasterom, a također naznačimo ispravniji put.

Statička pohrana

Za ilustraciju, razmotrite web aplikaciju koja koristi neku vrstu statičkog generatora za dobivanje skupa slika, stilova i drugih stvari. Na primjer, Yii PHP framework ima ugrađeni upravitelj imovine koji generira jedinstvena imena direktorija. Sukladno tome, izlaz je skup staza za statičnu stranicu koje se očito ne sijeku jedna s drugom (to je učinjeno iz nekoliko razloga - na primjer, da se uklone duplikati kada više komponenti koristi isti resurs). Dakle, izvan okvira, kada prvi put pristupite modulu web resursa, statičke datoteke (u stvari, često simboličke veze, ali o tome kasnije) formiraju se i postavljaju sa zajedničkim korijenskim direktorijem jedinstvenim za ovu implementaciju:

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

Što to znači u smislu klastera?

Najjednostavniji primjer

Uzmimo prilično čest slučaj, kada PHP-u prethodi nginx za distribuciju statičkih podataka i obradu jednostavnih zahtjeva. Najlakši način - razvoj sa dva spremnika:

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

U pojednostavljenom obliku, nginx konfiguracija se svodi na sljedeće:

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

Kada prvi put pristupite stranici, sredstva se pojavljuju u PHP spremniku. Ali u slučaju dva spremnika unutar jednog pod-a, nginx ne zna ništa o tim statičkim datotekama, koje bi im se (prema konfiguraciji) trebale dati. Kao rezultat toga, klijent će vidjeti pogrešku 404 za sve zahtjeve za CSS i JS datoteke. Najjednostavnije rješenje ovdje bilo bi organizirati zajednički direktorij za spremnike. Primitivna opcija - općenito 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

Sada nginx ispravno poslužuje statične datoteke generirane u spremniku. Ali podsjetit ću vas da je ovo primitivno rješenje, što znači da je daleko od idealnog i ima svoje nijanse i nedostatke, o kojima se govori u nastavku.

Naprednija pohrana

Zamislite sada situaciju u kojoj je korisnik posjetio stranicu, učitao stranicu sa stilovima dostupnim u spremniku i dok je čitao ovu stranicu, mi smo ponovno rasporedili spremnik. Katalog sredstava je postao prazan i potreban je zahtjev PHP-u za početak generiranja novih. Međutim, čak i nakon toga poveznice na stare statike bit će nevažne, što će dovesti do grešaka u prikazu statika.

Osim toga, najvjerojatnije imamo više ili manje opterećen projekt, što znači da jedna kopija aplikacije neće biti dovoljna:

  • Povećajmo to razvoj do dvije replike.
  • Kada se stranici prvi put pristupilo, sredstva su stvorena u jednoj replici.
  • U nekom je trenutku ingress odlučio (za potrebe balansiranja opterećenja) poslati zahtjev drugoj replici, a ta sredstva još nisu bila tamo. Ili ih možda više nema jer mi koristimo RollingUpdate i trenutno radimo implementaciju.

Općenito, rezultat su opet pogreške.

Kako biste izbjegli gubitak starih sredstava, možete promijeniti emptyDir na hostPath, fizički dodajući statiku čvoru klastera. Ovaj pristup je loš jer zapravo moramo vezati na određeni čvor klastera vašu aplikaciju, jer - u slučaju prelaska na druge čvorove - direktorij neće sadržavati potrebne datoteke. Ili je potrebna neka vrsta pozadinske sinkronizacije direktorija između čvorova.

Koja su rješenja?

  1. Ako hardver i resursi dopuštaju, možete koristiti cephfs organizirati jednako pristupačan imenik za statičke potrebe. Službena dokumentacija preporučuje SSD diskove, najmanje trostruku replikaciju i stabilnu "debelu" vezu između čvorova klastera.
  2. Manje zahtjevna opcija bila bi organizirati NFS poslužitelj. Međutim, tada morate uzeti u obzir moguće povećanje vremena odgovora za obradu zahtjeva od strane web poslužitelja, a tolerancija na pogreške ostavit će mnogo za poželjeti. Posljedice kvara su katastrofalne: gubitak nosača osuđuje klaster na smrt pod pritiskom LA tereta koji juri u nebo.

Između ostalog, bit će potrebne sve opcije za stvaranje trajne pohrane čišćenje pozadine zastarjeli skupovi datoteka nakupljeni tijekom određenog vremenskog razdoblja. Ispred spremnika s PHP-om možete staviti DaemonSet iz predmemoriranja nginxa, koji će pohranjivati ​​kopije sredstava na ograničeno vrijeme. Ovo se ponašanje lako konfigurira pomoću proxy_cache s dubinom pohrane u danima ili gigabajtima prostora na disku.

Kombinacija ove metode s gore spomenutim distribuiranim datotečnim sustavima pruža ogromno polje za maštu, ograničeno samo proračunom i tehničkim potencijalom onih koji će je implementirati i podržavati. Iz iskustva možemo reći da što je sustav jednostavniji, to stabilnije radi. Kada se takvi slojevi dodaju, postaje mnogo teže održavati infrastrukturu, a istovremeno se povećava vrijeme utrošeno na dijagnosticiranje i oporavak od eventualnih kvarova.

preporuka

Ako vam se i implementacija predloženih opcija skladištenja čini neopravdanom (kompliciranom, skupom ...), onda je vrijedno pogledati situaciju s druge strane. Naime, udubiti se u arhitekturu projekta i popravi problem u kodu, vezan uz neku statičnu podatkovnu strukturu na slici, nedvosmislena definicija sadržaja ili postupak za "zagrijavanje" i/ili predkompiliranje sredstava u fazi sklapanja slike. Na taj način dobivamo apsolutno predvidljivo ponašanje i isti skup datoteka za sva okruženja i replike pokrenute aplikacije.

Ako se vratimo na konkretan primjer s Yii frameworkom i ne ulazimo u njegovu strukturu (što nije svrha članka), dovoljno je istaknuti dva popularna pristupa:

  1. Promijenite proces izrade slike kako biste imovinu postavili na predvidljivu lokaciju. Ovo je predloženo/implementirano u ekstenzijama poput yii2-statička-imovina.
  2. Definirajte posebne hashove za imenike sredstava, kao što je objašnjeno u npr. ovu prezentaciju (počevši od slajda br. 35). Inače, autor izvješća naposljetku (i ne bez razloga!) savjetuje da nakon asembleranja aseta na build serveru, uploadate iste u centralnu pohranu (poput S3), ispred koje postavite CDN.

Preuzimanja

Još jedan slučaj koji će svakako doći u obzir prilikom migracije aplikacije na Kubernetes klaster je pohranjivanje korisničkih datoteka u datotečni sustav. Na primjer, opet imamo PHP aplikaciju koja prihvaća datoteke putem forme za upload, radi nešto s njima tijekom rada i šalje ih natrag.

U Kubernetesu bi mjesto na koje bi te datoteke trebale biti postavljene trebalo biti zajedničko za sve replike aplikacije. Ovisno o složenosti aplikacije i potrebi organiziranja postojanosti ovih datoteka, gore spomenute opcije dijeljenih uređaja mogu biti takvo mjesto, ali, kao što vidimo, one imaju svoje nedostatke.

preporuka

Jedno rješenje je koristeći S3-kompatibilnu pohranu (čak i ako je to neka vrsta kategorije koja se samostalno hostira kao što je minio). Prelazak na S3 zahtijevat će promjene na razini koda, a kako će se sadržaj isporučivati ​​na prednjoj strani, već znamo писали.

Korisničke sesije

Zasebno je vrijedno spomenuti organizaciju pohrane korisničkih sesija. Često su to i datoteke na disku, što će u kontekstu Kubernetesa dovesti do stalnih autorizacijskih zahtjeva korisnika ako njegov zahtjev završi u drugom spremniku.

Problem se dijelom rješava uključivanjem stickySessions na ulazu (značajka je podržana u svim popularnim ulaznim kontrolerima - za više detalja pogledajte naša recenzija)za vezanje korisnika na određenu pod s aplikacijom:

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

Ali to neće eliminirati probleme s ponovljenim implementacijama.

preporuka

Ispravniji način bio bi prijenos aplikacije na pohranjivanje sesija u memcached, Redis i slična rješenja - općenito, potpuno napustiti opcije datoteke.

Zaključak

Infrastrukturna rješenja o kojima se raspravlja u tekstu vrijedna su upotrebe samo u formatu privremenih “štaka” (što ljepše zvuči na engleskom kao zaobilazno rješenje). Oni mogu biti relevantni u prvim fazama migracije aplikacije na Kubernetes, ali ne bi se trebali ukorijeniti.

Općenito preporučeni put je riješiti ih se u korist arhitektonske izmjene aplikacije u skladu s onim što je već dobro poznato mnogima Aplikacija 12 faktora. Međutim, to - dovođenje aplikacije u oblik bez statusa - neizbježno znači da će biti potrebne promjene u kodu, a ovdje je važno pronaći ravnotežu između mogućnosti/zahtjeva poslovanja i izgleda za implementaciju i održavanje odabranog puta .

PS

Pročitajte i na našem blogu:

Izvor: www.habr.com

Dodajte komentar