Lokalne datoteke prilikom migracije aplikacije na Kubernetes

Lokalne datoteke prilikom migracije aplikacije na Kubernetes

Prilikom izrade CI/CD procesa koristeći Kubernetes, 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 один slika koja će se koristiti u всех projektna okruženja i klasteri. Ovaj princip je u osnovi ispravnog prema Google-u upravljanje kontejnerima (više puta o ovome govorio i naš tehnički odjel).

Međutim, nećete vidjeti nikoga u situacijama kada kod web-mjesta koristi gotovi 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 ovo ponašanje može postati problem, posebno kada se s njim susrećete prvi put. Iako inventivni um može smisliti infrastrukturna rješenja koja na prvi pogled izgledaju očigledna, pa čak i dobra... važno je zapamtiti da većina situacija može i treba biti riješen arhitektonski.

Pogledajmo popularna rješenja za zaobilaženje za pohranjivanje datoteka koje mogu dovesti do neugodnih posljedica pri radu klastera, a također ukazati na 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 okvir ima ugrađeni upravitelj sredstava koji generiše jedinstvena imena direktorija. Shodno tome, izlaz je skup putanja za statičnu lokaciju koje se očigledno ne seku jedna s drugom (ovo je učinjeno iz nekoliko razloga - na primjer, da bi se eliminisali duplikati kada više komponenti koristi isti resurs). Dakle, izvan kutije, prvi put kada pristupite modulu web resursa, statičke datoteke (u stvari, često simbolične veze, ali o tome kasnije) se formiraju i postavljaju sa zajedničkim korijenskim direktorijumom jedinstvenim za ovu implementaciju:

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

Šta to znači u smislu klastera?

Najjednostavniji primjer

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

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 kontejneru. Ali u slučaju dva kontejnera unutar jednog pod-a, nginx ne zna ništa o ovim statičkim datotekama, koje bi im (prema konfiguraciji) trebalo dati. Kao rezultat toga, klijent će vidjeti grešku 404 za sve zahtjeve prema CSS i JS datotekama. Najjednostavnije rješenje ovdje bi bilo organiziranje zajedničkog direktorija za kontejnere. 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 opslužuje statičke datoteke generirane u kontejneru. Ali da vas podsjetim da je ovo primitivno rješenje, što znači da je daleko od idealnog i da ima svoje nijanse i nedostatke, o kojima ćemo govoriti u nastavku.

Naprednija pohrana

Sada zamislite situaciju u kojoj je korisnik posjetio stranicu, učitao stranicu sa stilovima dostupnim u kontejneru i dok je čitao ovu stranicu, mi smo ponovo postavili kontejner. Katalog sredstava je postao prazan i potreban je zahtjev PHP-u da započne generiranje novih. Međutim, čak i nakon toga, linkovi na staru statiku neće biti relevantni, što će dovesti do grešaka u prikazivanju statike.

Osim toga, najvjerovatnije imamo manje-više opterećen projekat, što znači da jedna kopija aplikacije neće biti dovoljna:

  • Hajde da ga povećamo razvoj do dvije replike.
  • Kada se sajtu prvi put pristupilo, sredstva su kreirana u jednoj replici.
  • U nekom trenutku, ingress je odlučio (za potrebe balansiranja opterećenja) poslati zahtjev drugoj replici, a ova sredstva još nisu bila tamo. Ili ih možda više nema jer ih koristimo RollingUpdate i trenutno vršimo raspoređivanje.

Generalno, rezultat su opet greške.

Kako biste izbjegli gubitak stare imovine, možete promijeniti emptyDir na hostPath, fizičko dodavanje statike čvoru klastera. Ovaj pristup je loš jer zapravo moramo vezati se za 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 sinhronizacije direktorija između čvorova.

Koja su rješenja?

  1. Ako hardver i resursi dozvoljavaju, možete koristiti cephfs organizirati jednako dostupan direktorij 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 organiziranje NFS servera. Međutim, tada morate uzeti u obzir moguće povećanje vremena odgovora za obradu zahtjeva od strane web servera, a tolerancija grešaka će ostaviti mnogo željenog. Posljedice neuspjeha su katastrofalne: gubitak brda osuđuje klaster na smrt pod naletom tereta LA koji juri u nebo.

Između ostalog, bit će potrebne sve opcije za stvaranje trajne pohrane čišćenje pozadine zastarjeli skupovi datoteka akumuliranih u određenom vremenskom periodu. Ispred kontejnera sa PHP-om možete staviti DaemonSet od keširanja nginxa, koji će pohraniti kopije imovine ograničeno vrijeme. Ovo ponašanje se lako može konfigurirati korištenjem proxy_cache sa dubinom skladištenja u danima ili gigabajtima prostora na disku.

Kombinacija ove metode sa gore navedenim distribuiranim sistemima datoteka pruža ogromno polje za maštu, ograničeno samo budžetom i tehničkim potencijalom onih koji će ga implementirati i podržavati. Iz iskustva možemo reći da što je sistem jednostavniji, to stabilnije funkcioniše. Kada se dodaju takvi slojevi, postaje mnogo teže održavati infrastrukturu, a istovremeno se povećava vrijeme utrošeno na dijagnosticiranje i oporavak od kvarova.

Preporuka

Ako vam se i implementacija predloženih opcija skladištenja čini neopravdanom (kompliciranom, skupom...), onda vrijedi pogledati situaciju s druge strane. Naime, kopati u arhitekturu projekta i riješite problem u kodu, vezano za neku statičku strukturu podataka u slici, nedvosmislenu definiciju sadržaja ili procedure za “zagrevanje” i/ili prethodno kompajliranje sredstava u fazi sklapanja slike. Na ovaj način dobijamo apsolutno predvidljivo ponašanje i isti skup datoteka za sva okruženja i replike pokrenute aplikacije.

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

  1. Promijenite proces izgradnje slike kako biste sredstva smjestili na predvidljivu lokaciju. Ovo se predlaže/implementira u ekstenzijama poput yii2-static-assets.
  2. Definirajte specifične hashove za direktorije sredstava, kao što je objašnjeno u npr. ovu prezentaciju (počevši od slajda br. 35). Inače, autor izvještaja na kraju (i to ne bez razloga!) savjetuje da nakon sastavljanja sredstava na build serveru otpremite ih u centralno skladište (poput S3), ispred koje postavite CDN.

Preuzimanja

Još jedan slučaj koji će definitivno doći u igru ​​prilikom migracije aplikacije u Kubernetes klaster je pohranjivanje korisničkih datoteka u sistem datoteka. Na primjer, opet imamo PHP aplikaciju koja prihvata datoteke putem obrasca za upload, radi nešto s njima tokom rada i šalje ih nazad.

U Kubernetes-u, lokacija na kojoj se ti fajlovi trebaju postaviti treba da bude zajednička za sve replike aplikacije. Ovisno o složenosti aplikacije i potrebi da se organizira postojanost ovih datoteka, gore navedene opcije zajedničkog uređaja mogu biti takvo mjesto, ali, kao što vidimo, imaju svoje nedostatke.

Preporuka

Jedno rešenje je koristeći S3-kompatibilnu pohranu (čak i ako se radi o nekoj vrsti kategorije koja sama hostuje kao što je minio). Prelazak na S3 će zahtijevati promjene na nivou koda, a kako će sadržaj biti isporučen na front end-u, već smo znali napisao je.

Korisničke sesije

Odvojeno, vrijedi napomenuti organizaciju skladištenja korisničkih sesija. Često su to i fajlovi na disku, što će u kontekstu Kubernetesa dovesti do stalnih zahtjeva za autorizacijom od korisnika ako njegov zahtjev završi u drugom kontejneru.

Problem se djelimično rješava uključivanjem stickySessions na ulazu (funkcija je podržana u svim popularnim ulaznim kontrolerima - za više detalja pogledajte naša recenzija)da povežete korisnika s određenim podom 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 bi bio da prenesete aplikaciju na pohranjivanje sesija u memcached, Redis i slična rješenja - općenito, potpuno napustiti opcije datoteka.

zaključak

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

Općenito preporučeni put je da ih se riješite u korist arhitektonske modifikacije aplikacije u skladu s onim što je mnogima već dobro poznato 12-faktorska aplikacija. Međutim, ovo - dovođenje aplikacije u oblik bez državljanstva - neminovno znači da će biti potrebne promjene u kodu, a ovdje je važno pronaći balans 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