Местни файлове при преместване на приложението в Kubernetes

Местни файлове при преместване на приложението в Kubernetes

При изграждане на CI/CD процес с Kubernetes понякога възниква проблем с несъответствието на изискванията на новата инфраструктура и преносимото приложение. В частност, в етапа на изграждане на приложението е важно да се получи един образ, който ще се използва в на всичките средите и клъстерите на проекта. Този принцип е основа на правилното управление на контейнери (неведнъж е било споменавано от Google и нашия технически директор). казваше Въпреки това, никой не вижда ситуации, в които в кода на сайта се използва готов фреймуърк, употребата на който налага ограничения за по-нататъшната му експлоатация. И ако в "обикновена среда" с това лесно се справяме, в Kubernetes такова поведение може да се превърне в проблем, особено когато се сблъсквате с него за първи път. Макар че изобретателният ум може да предложи инфраструктурни решения, които изглеждат очевидни и дори добри на пръв поглед... важно е да се помни, че повечето ситуации могат и трябва

да се решават архитектурно. Нека разгледаме популярни работни алтернативи за съхранение на файлове, които могат да доведат до неприятни последствия при експлоатация на клъстера, а също така ще посочим по-правилния път..

Съхранение на статични файлове

За илюстрация, нека разгледаме уеб приложение, което използва генератор на статични файлове за получаване на набор от снимки, стилове и други. Например, в PHP фреймуърка Yii има вграден мениджър на активи, който генерира уникални имена на директории. Съответно, на изхода получаваме набор от пътища за статичните файлове на сайта, които се припокриват. (Сделано е това по няколко причини — например, за изключване на дубликати при използването на един и същ ресурс от множество компоненти). Така, от фабриката, при първото искане към модула на уеб ресурса се извършва формиране и разполагане на статиката (в действителност — често символни линкове, но за това след малко) с уникален за това внедряване главен коренов каталог:

webroot/assets/2072c2df/css/…

  • webroot/assets/2072c2df/images/…
  • webroot/assets/2072c2df/js/…
  • Какъв е рискът за клъстера?

Най-простият пример

Нека вземем доста разпространен случай, когато пред PHP стои nginx за раздаване на статични файлове и обработка на прости заявки. Най-простият начин е

с два контейнери: Deployment с два контейнера:

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

В опростена форма конфигурацията на nginx изглежда така:

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

При първото посещение на сайта в контейнера с PHP се появяват активи. Но в случая с два контейнера в рамките на един pod — nginx не знае за тези статични файлове, които (според конфигурацията) трябва да се предоставят именно от него. В резултат на това, при всички заявки за CSS и JS файлове клиентът ще види грешка 404. Най-простото решение тук е да се организира обща директория за контейнерите. Примитивен вариант — обща 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

Сега генерираните в контейнера статични файлове се предоставят коректно от nginx. Но напомням, че това е примитивно решение, а следователно — то е далеч от идеала и има свои нюанси и недостатъци, за които ще говорим по-долу.

По-напреднало хранилище

Нека сега си представим ситуация, в която потребителят е влязъл в сайта, заредил е страницата с наличните в контейнера стилове, а докато е чел тази страница, ние повторно деплойнахме контейнера. В каталога с активи стана празно и е необходимо запитване към PHP, за да стартираме генерирането на нови. Въпреки това, дори след това линковете към старата статика ще бъдат неактуални, което ще доведе до грешки в представянето на статика.

Освен това, вероятно имаме по-натоварен проект, а значи — едно копие на приложението няма да е достатъчно:

  • Масштабиране Deployment до две реплики.
  • При първото обръщение към сайта в една реплика се създадоха активи.
  • В един момент ingress реши (в името на балансирането на натоварването) да изпрати запитване към втората реплика, и там тези активи все още ги няма. Или пък може вече да ги няма, защото използваме RollingUpdate и в момента правим деплой.

В общи линии, резултатът — отново грешки.

За да не губим старите активи, можем да променим emptyDir на hostPath, като съхраняваме статиката физически на възел на клъстера. Този подход е лош, защото буквално трябва да се привържем към конкретен възел на клъстера с приложението си, понеже — в случай на преместване на други възли — директорията няма да съдържа необходимите файлове. Или пък е необходима някаква фоново синхронизиране на директорията между възлите.

Какви са възможните решения?

  1. Ако хардуерът и ресурсите позволяват, можем да използваме cephfs за организиране на достъпна директория за нуждите на статиката. Официална документация препоръчва SSD дискове, поне трикратна репликация и устойчиво "дебело" свързване между възлите на клъстера.
  2. По-малко взискателният вариант е организирането на NFS сървър. Въпреки това, тогава трябва да се има предвид възможното увеличаване на времето за отговор при обработка на запитвания от уеб сървъра, а и наличието на отказы остава желаното. Последствията от отказа са катастрофални: загубата на mount обрека клъстера на гибел под натиска на натоварване LA, устремило се към небето.

Освен всичко друго, за всички варианти за създаване на постоянно хранилище ще е необходима фонова очистка на остарели комплекти файлове, натрупани за определен период от време. Пред контейнерите с PHP можем да поставим DaemonSet от кеширащи nginx, които ще съхраняват копия на активите за ограничено време. Този поведение лесно се конфигурира с помощта на proxy_cache с дълбочина на съхранение в дни или гигабайти дисково пространство.

Комбинирането на този метод с посочените по-горе разпределени файлови системи дава огромно поле за фантазии, единственото ограничение е бюджетът и техническия потенциал на тези, които ще го реализират и поддържат. От опит можем да кажем, че колкото по-проста е системата, толкова по-стабилно работи. При добавяне на подобни слоеве поддържането на инфраструктурата става много по-сложно и с това се увеличава времето, необходимо за диагностика и възстановяване при всякакви сривове.

Препоръка

Ако реализирането на предлаганите варианти за хранилища също ви изглежда неоправдано (сложно, скъпо…), тогава си струва да се погледне на ситуацията от друга страна. А именно — да се задълбочи в архитектурата на проекта и да се изкорени проблема в кода, свързвайки се с някаква статична структура на данни в образа, еднозначно определяне на съдържанието или процедурата за "прогряване" и/или прекомпилация на активите на етапа на изграждане на образа. Така получаваме абсолютно предсказуемо поведение и единен набор файлове за всички среди и реплики на стартираното приложение.

Ако се върнем към конкретния пример с фреймуорка Yii и не навлизаме в устройството му (което не е целта на статията), достатъчно е да посочим два популярни подхода:

  1. Да променим процеса на изграждане на образа, за да поставим активите на предсказуемо място. Това предлагат/реализират в разширения като yii2-static-assets.
  2. Да се определят конкретни хешове за директориите на активите, както се разказва, например, в този семинар (начинайки от слайда №35). Между другото, авторът на доклада накрая (и не без основание!) съветва след изграждането на активите на build-сервера да ги заредите в централно хранилище (като S3), преди което да поставите CDN.

Зарежданите файлове

Друг случай, който задължително ще сработи при прехвърляне на приложението в клъстър Kubernetes, — съхранение на потребителски файлове в файловата система. Например, отново имаме приложение на PHP, което приема файлове чрез форма за качване, прави нещо с тях по време на работа и ги връща обратно.

Мястото, където тези файлове трябва да се помещават, в реалностите на Kubernetes, трябва да бъде общо за всички реплики на приложението. В зависимост от сложността на приложението и необходимостта от организиране на персистентността на тези файлове, такова място могат да бъдат споменатите по-горе варианти на shared-устройства, но, както виждаме, те имат свои минуси.

Препоръка

Един от вариантите за решение е използването на хранилище, съвместимо с S3 (нека дори да бъде някаква разновидност на категориите self-hosted, като minio). Преходът към работа с S3 изисква промени на ниво код, а как ще се осъществява предоставянето на съдържание на фронтенда, вече пишеха.

Потребителски сесии

Отделно си струва да се отбележи организирането на съхранението на потребителските сесии. Често това са също файлове на диска, което в контекста на Kubernetes ще доведе до постоянни искания за авторизация от потребителя, ако искането му попадне в друг контейнер.

Отчасти проблемът се решава с включването на stickySessions на ingress (функцията се поддържа във всички популярни контролери ingress — повече информация в нашето ревю), за да се привърже потребителят към конкретен pod с приложението:

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

Но това не ще освободи от проблемите при повторни деплои.

Препоръка

По-правилният начин ще бъде да се прехвърли приложението на съхранение на сесии в memcached, Redis и подобни решения — по същество, напълно да се откажем от файловите варианти.

Заключение

Обсъжданите в текста инфраструктурни решения заслужават приложение само в формат на временни "костенурки" (което по-красиво звучи на английски като workaround). Те могат да бъдат актуални в ранните етапи на миграцията на приложението в Kubernetes, но не бива да "пуснат корени".

Общият препоръчителен път е да се отървем от тях в полза на архитектурната доработка на приложението в съответствие с вече добре известния на много 12-Factor App. Въпреки това — приведението на приложението в статeless вид — неизбежно означава, че ще са необходими промени в кода, и тук е важно да се намери баланс между възможностите/изискванията на бизнеса и перспективите за реализация и поддръжка на избрания път.

P.S.

Прочетете също в нашия блог:

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster