Fișiere locale la migrarea unei aplicații la Kubernetes

Fișiere locale la migrarea unei aplicații la Kubernetes

La construirea unui proces CI/CD folosind Kubernetes, uneori apare problema incompatibilității între cerințele noii infrastructuri și aplicația care este transferată către aceasta. În special, în etapa de construire a aplicației, este important să obțineți o imagine care va fi folosită în toate medii de proiect și clustere. Acest principiu stă la baza corectului conform Google managementul containerelor (de mai multe ori despre asta vorbit și departamentul nostru tehnic).

Cu toate acestea, nu veți vedea pe nimeni în situațiile în care codul site-ului folosește un cadru gata făcut, a cărui utilizare impune restricții privind utilizarea ulterioară. Și în timp ce într-un „mediu normal” acest lucru este ușor de gestionat, în Kubernetes acest comportament poate deveni o problemă, mai ales când îl întâlnești pentru prima dată. În timp ce o minte inventiva poate veni cu soluții de infrastructură care par evidente sau chiar bune la prima vedere... este important să ne amintim că majoritatea situațiilor pot și ar trebui fi rezolvat arhitectural.

Vom analiza soluții de soluționare populare pentru stocarea fișierelor care pot duce la consecințe neplăcute la operarea unui cluster și, de asemenea, vom indica o cale mai corectă.

Stocare statică

Pentru a ilustra, luați în considerare o aplicație web care utilizează un fel de generator static pentru a obține un set de imagini, stiluri și alte lucruri. De exemplu, cadrul Yii PHP are un manager de active încorporat care generează nume de directoare unice. În consecință, rezultatul este un set de căi pentru site-ul static care, evident, nu se intersectează între ele (acest lucru a fost făcut din mai multe motive - de exemplu, pentru a elimina duplicatele atunci când mai multe componente folosesc aceeași resursă). Deci, din cutie, prima dată când accesați un modul de resurse web, fișierele statice (de fapt, adesea legături simbolice, dar mai multe despre asta mai târziu) sunt formate și așezate cu un director rădăcină comun unic pentru această implementare:

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

Ce înseamnă acest lucru în termeni de cluster?

Cel mai simplu exemplu

Să luăm un caz destul de comun, când PHP este precedat de nginx pentru a distribui date statice și a procesa cereri simple. Cel mai simplu mod - Implementare cu doua recipiente:

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

Într-o formă simplificată, configurația nginx se rezumă la următoarele:

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

Când accesați pentru prima dată site-ul, activele apar în containerul PHP. Dar în cazul a două containere într-un singur pod, nginx nu știe nimic despre aceste fișiere statice, care (conform configurației) ar trebui să le fie date. Ca rezultat, clientul va vedea o eroare 404 pentru toate solicitările către fișierele CSS și JS. Cea mai simplă soluție aici ar fi organizarea unui director comun pentru containere. Opțiune primitivă - generală 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

Acum fișierele statice generate în container sunt servite corect de nginx. Dar permiteți-mi să vă reamintesc că aceasta este o soluție primitivă, ceea ce înseamnă că este departe de a fi ideală și are propriile sale nuanțe și deficiențe, care sunt discutate mai jos.

Stocare mai avansată

Acum imaginați-vă o situație în care un utilizator a vizitat site-ul, a încărcat o pagină cu stilurile disponibile în container și, în timp ce citea această pagină, am reinstalat containerul. Catalogul de active a devenit gol și este necesară o solicitare către PHP pentru a începe generarea altora noi. Cu toate acestea, chiar și după aceasta, legăturile către statistici vechi vor fi irelevante, ceea ce va duce la erori în afișarea statisticilor.

În plus, cel mai probabil avem un proiect mai mult sau mai puțin încărcat, ceea ce înseamnă că o copie a aplicației nu va fi suficientă:

  • Să o extindem Implementare până la două replici.
  • Când site-ul a fost accesat prima dată, activele au fost create într-o singură replică.
  • La un moment dat, ingress a decis (în scopuri de echilibrare a încărcăturii) să trimită o solicitare către a doua replică, iar aceste active nu erau încă acolo. Sau poate nu mai sunt acolo pentru că noi folosim RollingUpdate iar momentan facem implementare.

În general, rezultatul este din nou greșeli.

Pentru a evita pierderea vechilor active, puteți schimba emptyDir pe hostPath, adăugând static fizic la un nod de cluster. Această abordare este proastă pentru că de fapt trebuie se leagă la un anumit nod de cluster aplicația dumneavoastră, deoarece – în cazul trecerii la alte noduri – directorul nu va conține fișierele necesare. Sau este necesară un fel de sincronizare a directorului de fundal între noduri.

Care sunt solutiile?

  1. Dacă hardware-ul și resursele permit, puteți utiliza cephfs pentru a organiza un director la fel de accesibil pentru nevoile statice. Documentație oficială recomandă unități SSD, replicare de cel puțin trei ori și o conexiune „groasă” stabilă între nodurile clusterului.
  2. O opțiune mai puțin solicitantă ar fi organizarea unui server NFS. Cu toate acestea, atunci trebuie să țineți cont de posibila creștere a timpului de răspuns pentru procesarea cererilor de către serverul web, iar toleranța la erori va lăsa mult de dorit. Consecințele eșecului sunt catastrofale: pierderea monturii condamnă clusterul la moarte sub presiunea încărcăturii LA care se repezi spre cer.

Printre altele, toate opțiunile pentru crearea stocării persistente vor necesita curățarea fundalului seturi învechite de fișiere acumulate într-o anumită perioadă de timp. În fața containerelor cu PHP poți pune DaemonSet din cache nginx, care va stoca copii ale activelor pentru o perioadă limitată de timp. Acest comportament este ușor de configurat folosind proxy_cache cu adâncime de stocare în zile sau gigaocteți de spațiu pe disc.

Combinarea acestei metode cu sistemele de fișiere distribuite menționate mai sus oferă un câmp imens de imaginație, limitat doar de bugetul și potențialul tehnic al celor care o vor implementa și susține. Din experiență, putem spune că, cu cât sistemul este mai simplu, cu atât funcționează mai stabil. Când se adaugă astfel de straturi, devine mult mai dificilă întreținerea infrastructurii și, în același timp, timpul petrecut cu diagnosticarea și recuperarea de la orice defecțiuni crește.

recomandare

Dacă și implementarea opțiunilor de stocare propuse vi se pare nejustificată (complicată, costisitoare...), atunci merită să priviți situația din cealaltă parte. Și anume, să sapă în arhitectura proiectului și remediați problema din cod, legat de o anumită structură de date statice din imagine, o definiție neechivocă a conținutului sau a procedurii de „încălzire” și/sau precompilare a activelor în etapa de asamblare a imaginii. În acest fel obținem un comportament absolut previzibil și același set de fișiere pentru toate mediile și replicile aplicației care rulează.

Dacă revenim la exemplul specific cu cadrul Yii și nu ne adâncim în structura acestuia (care nu este scopul articolului), este suficient să subliniem două abordări populare:

  1. Schimbați procesul de creare a imaginii pentru a plasa activele într-o locație previzibilă. Acest lucru este sugerat/implementat în extensii precum yii2-static-active.
  2. Definiți hash-uri specifice pentru directoarele de active, așa cum se discută în, de ex. această prezentare (începând cu slide-ul nr. 35). Apropo, autorul raportului în cele din urmă (și nu fără motiv!) vă sfătuiește ca după asamblarea activelor pe serverul de compilare, să le încărcați într-un stocare central (cum ar fi S3), în fața căruia să plasați un CDN.

Descărcări

Un alt caz care va intra cu siguranță în joc la migrarea unei aplicații către un cluster Kubernetes este stocarea fișierelor utilizatorului în sistemul de fișiere. De exemplu, avem din nou o aplicație PHP care acceptă fișiere printr-un formular de încărcare, face ceva cu ele în timpul funcționării și le trimite înapoi.

În Kubernetes, locația în care ar trebui să fie plasate aceste fișiere ar trebui să fie comună pentru toate replicile aplicației. În funcție de complexitatea aplicației și de necesitatea de a organiza persistența acestor fișiere, opțiunile de dispozitiv partajate menționate mai sus pot fi un astfel de loc, dar, după cum vedem, au dezavantajele lor.

recomandare

O soluție este folosind stocarea compatibilă cu S3 (chiar dacă este un fel de categorie auto-găzduită, cum ar fi minio). Trecerea la S3 va necesita modificări la nivel de cod, și modul în care conținutul va fi livrat pe front end, avem deja писали.

Sesiuni de utilizator

Separat, este de remarcat organizarea stocării sesiunilor utilizatorilor. De multe ori acestea sunt și fișiere de pe disc, care în contextul Kubernetes vor duce la solicitări constante de autorizare din partea utilizatorului dacă solicitarea acestuia ajunge într-un alt container.

Problema este parțial rezolvată prin pornire stickySessions la intrare (funcția este acceptată de toate controlerele de intrare populare - pentru mai multe detalii, consultați recenzia noastră)pentru a lega utilizatorul la un anumit pod cu aplicația:

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

Dar acest lucru nu va elimina problemele cu implementările repetate.

recomandare

O modalitate mai corectă ar fi să transferați aplicația la stocarea sesiunilor în soluții memcached, Redis și similare - în general, abandonați complet opțiunile de fișier.

Concluzie

Soluțiile de infrastructură discutate în text sunt demne de a fi folosite doar în formatul unor „cârje” temporare (care sună mai frumos în engleză ca soluție). Ele pot fi relevante în primele etape ale migrării unei aplicații către Kubernetes, dar nu ar trebui să se înrădăcineze.

Calea generală recomandată este de a scăpa de ele în favoarea modificării arhitecturale a aplicației în conformitate cu ceea ce este deja bine cunoscut de mulți Aplicația cu 12 factori. Totuși, aceasta - aducerea aplicației într-o formă apatridă - înseamnă inevitabil că vor fi necesare modificări ale codului, iar aici este important să se găsească un echilibru între capabilitățile/cerințele afacerii și perspectivele de implementare și menținere a căii alese. .

PS

Citește și pe blogul nostru:

Sursa: www.habr.com

Adauga un comentariu