
При изграждане на CI/CD процес с Kubernetes понякога възниква проблем с несъответствието на изискванията на новата инфраструктура и преносимото приложение. В частност, в етапа на изграждане на приложението е важно да се получи един образ, който ще се използва в на всичките средите и клъстерите на проекта. Този принцип е основа на правилното и нашия технически директор). Въпреки това, никой не вижда ситуации, в които в кода на сайта се използва готов фреймуърк, употребата на който налага ограничения за по-нататъшната му експлоатация. И ако в "обикновена среда" с това лесно се справяме, в 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, като съхраняваме статиката физически на възел на клъстера. Този подход е лош, защото буквално трябва да се привържем към конкретен възел на клъстера с приложението си, понеже — в случай на преместване на други възли — директорията няма да съдържа необходимите файлове. Или пък е необходима някаква фоново синхронизиране на директорията между възлите.
Какви са възможните решения?
- Ако хардуерът и ресурсите позволяват, можем да използваме за организиране на достъпна директория за нуждите на статиката. препоръчва SSD дискове, поне трикратна репликация и устойчиво "дебело" свързване между възлите на клъстера.
- По-малко взискателният вариант е организирането на NFS сървър. Въпреки това, тогава трябва да се има предвид възможното увеличаване на времето за отговор при обработка на запитвания от уеб сървъра, а и наличието на отказы остава желаното. Последствията от отказа са катастрофални: загубата на mount обрека клъстера на гибел под натиска на натоварване LA, устремило се към небето.
Освен всичко друго, за всички варианти за създаване на постоянно хранилище ще е необходима фонова очистка на остарели комплекти файлове, натрупани за определен период от време. Пред контейнерите с PHP можем да поставим DaemonSet от кеширащи nginx, които ще съхраняват копия на активите за ограничено време. Този поведение лесно се конфигурира с помощта на proxy_cache с дълбочина на съхранение в дни или гигабайти дисково пространство.
Комбинирането на този метод с посочените по-горе разпределени файлови системи дава огромно поле за фантазии, единственото ограничение е бюджетът и техническия потенциал на тези, които ще го реализират и поддържат. От опит можем да кажем, че колкото по-проста е системата, толкова по-стабилно работи. При добавяне на подобни слоеве поддържането на инфраструктурата става много по-сложно и с това се увеличава времето, необходимо за диагностика и възстановяване при всякакви сривове.
Препоръка
Ако реализирането на предлаганите варианти за хранилища също ви изглежда неоправдано (сложно, скъпо…), тогава си струва да се погледне на ситуацията от друга страна. А именно — да се задълбочи в архитектурата на проекта и да се изкорени проблема в кода, свързвайки се с някаква статична структура на данни в образа, еднозначно определяне на съдържанието или процедурата за "прогряване" и/или прекомпилация на активите на етапа на изграждане на образа. Така получаваме абсолютно предсказуемо поведение и единен набор файлове за всички среди и реплики на стартираното приложение.
Ако се върнем към конкретния пример с фреймуорка Yii и не навлизаме в устройството му (което не е целта на статията), достатъчно е да посочим два популярни подхода:
- Да променим процеса на изграждане на образа, за да поставим активите на предсказуемо място. Това предлагат/реализират в разширения като .
- Да се определят конкретни хешове за директориите на активите, както се разказва, например, в (начинайки от слайда №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, но не бива да "пуснат корени".
Общият препоръчителен път е да се отървем от тях в полза на архитектурната доработка на приложението в съответствие с вече добре известния на много . Въпреки това — приведението на приложението в статeless вид — неизбежно означава, че ще са необходими промени в кода, и тук е важно да се намери баланс между възможностите/изискванията на бизнеса и перспективите за реализация и поддръжка на избрания път.
P.S.
Прочетете също в нашия блог:
- «»;
- «»;
- «» (от Red Hat);
- «».
Източник: habr.com
