10 типични грешки при използването на Kubernetes

Прим. прев.: авторите на тази статия са инженери от малка чешка компания pipetail. Те успяха да съберат впечатляващ списък от [понякога банални, но все пак] толкова актуални проблеми и заблуди, свързани с експлоатацията на Kubernetes клъстери.

10 типични грешки при използването на Kubernetes

През годините на използване на Kubernetes, имахме възможността да работим с много клъстери (както управлявани, така и неуправлявани — на GCP, AWS и Azure). С времето забелязахме, че някои грешки се повтарят постоянно. В това няма нищо срамно: ние самите сме допуснали повечето от тях!

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

1. Ресурси: заявки и лимити

Този пункт определено заслужава най-голямо внимание и първо място в списъка.

CPU заявката обикновено или изобщо не е зададена, или има много низка стойност (за да може да се разположат колкото се може повече pod’ове на всеки възел). В резултат на това възлите се оказват претоварени. По време на високо натоварване процесорните мощности на възела са напълно заети и конкретната работна натовареност получава само това, което е "заявила" чрез тротлинг на CPU. Това води до повишени закъснения в приложението, таймаути и други неприятни последствия. (Повече за това можете да прочетете в друга наша скорошна превод: «CPU лимити и агресивен тротлинг в Kubernetes» — забележка на преводача.)

BestEffort (крайно не препоръчително):

resources: {}

екстремално ниска CPU заявка (крайно не препоръчително):

   resources:
      Requests:
        cpu: "1m"

От друга страна, наличието на CPU лимит може да доведе до неоснователно пропускане на тактове от pod’овете, дори и ако процесорът на възела не е напълно натоварен. Отново, това може да доведе до увеличени закъснения. Трайни спорове съществуват около параметъра CPU CFS quota в ядрото на Linux и тротлинга на CPU в зависимост от зададените лимити, както и спирането на CFS квотата… Уви, CPU лимитите могат да причинят повече проблеми, отколкото да решат. Повече информация можете да намерите на линка по-долу.

Прекомерно разпределение (overcommiting) на паметта може да доведе до по-големи проблеми. Достигането на лимита на CPU води до пропускане на тактове, докато достигането на лимита на паметта води до "убиване" на pod’а. Някой някога наблюдавал ли е OOMkill? Да, речь идет именно о нем.

Искате ли да минимализирате вероятността от това събитие? Не разпределяйте прекомерни обеми памет и използвайте Guaranteed QoS (Quality of Service), като зададете memory request равен на лимита (както в примера по-долу). Повече за това четете в презентацията на Хенинг Якобс (водещ инженер в Zalando).

Burstable (по-висока вероятност да получите OOMkilled):

   resources:
      requests:
        memory: "128Mi"
        cpu: "500m"
      limits:
        memory: "256Mi"
        cpu: 2

Guaranteed:

   resources:
      requests:
        memory: "128Mi"
        cpu: 2
      limits:
        memory: "128Mi"
        cpu: 2

Какво потенциално може да помогне при настройка на ресурсите?

С помощта на metrics-server може да покаже текущото потребление на CPU и използването на памет от pod-овете (и контейнерите в тях). Вероятно вече го използвате. Просто изпълнете следните команди:

kubectl top pods
kubectl top pods --containers
kubectl top nodes

Въпреки това те показват само текущото използване. С тях можете да получите приблизителна представа за масштабите, но в крайна сметка ще е необходимо историческо изменение на метриките във времето (за да отговорите на въпроси като: „Какво беше пиковото натоварване на CPU?“, „Какво беше натоварването вчера сутринта?“ — и т.н.). За това можете да използвате Prometheus, DataDog и други инструменти. Те просто получават метрики от metrics-server и ги съхраняват, а потребителят може да ги извиква и да изгражда съответни графики.

VerticalPodAutoscaler позволява да автоматизира този процес. Той проследява историята на използването на CPU и памет и настройва нови request-и и limit-и на базата на тази информация.

Ефективното използване на изчислителни мощности е сложна задача. Все едно е постоянно да играете тетрис. Ако плащате твърде много за изчислителни мощности при ниско средно потребление (казано с други думи, ~10 %), препоръчваме да обърнете внимание на продукти, основани на AWS Fargate или Virtual Kubelet. Те са изградени на основата на модел на таксуване serverless/pay-per-usage, което в такива условия може да се окаже по-евтино.

2. Liveness и readiness probes

По подразбиране проверките за състояние liveness и readiness в Kubernetes не са включени. И понякога забравят да ги включат…

Но как можем иначе да инициираме рестартиране на услугата в случай на необратима грешка? И как балансьорът на натоварването ще разбере, че даден pod е готов да приема трафик? Или че е способен да обработи повече трафик?

Често тези проби се объркват помежду си:

  • Жизненост — проверка на «живучест», която рестартира pod при неуспешно завършване;
  • Готовност — проверка на готовността, която при неуспех изключва pod от услугата Kubernetes (това може да се провери с помощта на kubectl get endpoints) и трафикът към него не постъпва, докато следващата проверка не приключи успешно.

И двете тези проверки СЕ ИЗПЪЛНЯВАТ ПО ВРЕМЕТО НА ЦЕЛИЯ ЖИЗНЕН ЦИКЛ НА POD’А. Това е много важно.

Съществува широко разпространено заблуждение, че readiness-пробите се стартират само в началото, за да може балансировачът да разбере, че pod е готов (Готов) и може да започне да обработва трафика. Въпреки това, това е само един от вариантите за тяхното приложение.

Друг вариант е да разберете, че трафикът към pod е прекалено голям и претоварва него (или pod извършва ресурсоемки изчисления). В този случай, проверката за готовност помага да се намали натоварването на pod и да го „охлади“. Успешното приключване на проверката за готовност в бъдеще позволява отново да се увеличи натоварването на pod. В този случай (при неуспех на проверката за готовност) провалът на проверката за живост би бил много контрапродуктивен. Защо да рестартирате pod, който е здрав и работи изцяло?

Затова в някои случаи пълното отсъствие на проверки е по-добро, отколкото включването им с неправилно настроени параметри. Както бе споменато по-горе, ако проверка за живост копира проверката за готовност, то вие сте в сериозни затруднения. Възможен вариант е да настроите само тест за готовност, а опасната проверка за живост оставете настрана.

И двата типа проверки не трябва да завършват неуспешно при срив на общи зависимости, в противен случай това води до каскаден (лавинообразен) отказ на всички pod’ове. С други думи, не вредите на себе си.

3. LoadBalancer за всеки HTTP-сервис

Скоро ще имате HTTP-сервиси в клъстера, които искате да изведете навън.

Ако отворите услуга като type: LoadBalancer, неговият контролер (в зависимост от доставчика на услуги) ще предостави и съгласува външен LoadBalancer (не задължително работещ на L7, по-скоро на L4), и това може да повлияе на разходите (външен статичен адрес IPv4, изчислителна мощ, таксување на секунда) поради необходимостта от създаване на голям брой подобни ресурси.

В този случай е много по-логично да се използва един външен балансировач на натоварването, отваряйки услугите като type: NodePort. Или, което е още по-добре, да разположите нещо като nginx-ingress-controller (или traefik), който ще бъде единственият NodePort endpoint, свързан с външния балансировчик на натоварването и ще маршрутизира трафика в кластера с помощта на вход-ресурси на Kubernetes.

Други вътрешнокластерни (микро)услуги, които взаимодействат помежду си, могат да «общуват» чрез услуги от тип ClusterIP и вграден механизъм за откриване на услуги чрез DNS. Просто не използвайте техните публични DNS/IP, тъй като това може да повлияе на забавянето и да доведе до увеличаване на разходите за облачни услуги.

4. Автоматично мащабиране на кластера, без да се вземат предвид неговите особености

При добавяне на възли в кластера и тяхното отстраняване не трябва да се разчита на някои основни метрики, като например използването на CPU на тези възли. Планирането на pod-а трябва да се извършва с оглед на множество ограничения, като affinity на pod-овете/възлите, taints и tolerations, заявки за ресурси, QoS и т.н. Използването на външен autoscaler, който не взема предвид тези нюанси, може да доведе до проблеми.

Представете си, че някой pod трябва да бъде планиран, но всички налични ресурси на CPU са заявени/разпределени и pod се засяда в състояние Pending. Външният autoscaler вижда средното текущо натоварване на CPU (а не заявеното) и не инициира разширяване (scale-out) — не добавя още един възел. В резултат на това даденият pod няма да бъде планиран.

Същевременно обратното мащабиране (scale-in) — премахване на възел от кластера — винаги е по-трудно за реализиране. Представете си, че имате stateful pod (с прикрепено постоянно хранилище). Persistent-томовете обикновено принадлежат към определена зона на достъпност и не се репликират в региона. Така, ако външният autoscaler премахне възела с този pod, планиращият не може да планира този pod на друг възел, тъй като това може да се направи само в зоната на достъпност, където се намира постоянното хранилище. Pod ще остане в състояние Pending.

В Kubernetes-сообществото значителна популярност има cluster-autoscaler.Той работи в кластера, поддържа API от основните доставчици на облачни услуги, взема предвид всички ограничения и може да се мащабира в посочените случаи. Той също така може да извършва scale-in, запазвайки всички зададени ограничения, икономейки по този начин средства (които в противен случай биха били похарчени за ненужни мощности).

5. Пренебрегване на възможностите IAM/RBAC

Бъдете внимателни при използването на IAM потребители с постоянни секрети за машини и приложения. Организирайте временно достъп, използвайки роли и служебни акаунти (служебни акаунти).

Често се срещаме с това, че ключовете за достъп (и секретите) са открито 'вградени' в конфигурацията на приложението, както и с пренебрегване на ротацията на секретите, въпреки наличния достъп до Cloud IAM. Използвайте IAM роли и служебни акаунти вместо потребители там, където е приложимо.

10 типични грешки при използването на Kubernetes

Забравете kube2iam и преминете направо към IAM роли за служебни акаунти (както е описано в едноименната бележка Štěpán Vraný):

apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role
  name: my-serviceaccount
  namespace: default

Една анотация. Не е толкова сложно, нали?

Освен това, не предоставяйте на служебните акаунти и профилите на инстанции права админ и cluster-admin, освен ако не са им необходими. Това е малко по-сложно за реализиране, особено в RBAC K8s, но определено си заслужава усилията.

6. Не разчитайте на автоматично anti-affinity за pod-ове

Представете си, че имате три реплики на определено разгръщане на възел. Възелът пада и заедно с него всички реплики. Неприятна ситуация, нали? Но защо всички реплики бяха на един и същ възел? Нали Kubernetes трябва да осигури висока наличност (HA)?!

За съжаление, Kubernetes планировчикът по свое усмотрение не спазва правилата за разделно съществуване (anti-affinity) за pod-ове. Те трябва да бъдат изрично зададени:

// опущено для краткости
      labels:
        app: zk
// опущено для краткости
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: "app"
                    operator: In
                    values:
                    - zk
              topologyKey: "kubernetes.io/hostname"

Ето и всичко. Сега pod-овете ще бъдат планирани на различни възли (това условие се провери само по време на планирането, а не по време на тяхната работа — откъдето и requiredDuringSchedulingIgnoredDuringExecution).

Тук говорим за podAntiAffinity на различни възли: topologyKey: "kubernetes.io/hostname", — а не за различни зони на достъпност. За да реализирате пълна HA, ще трябва да се задълбочите в тази тема.

7. Игнорирането на PodDisruptionBudget-ове

Представете си, че имате производствена заявка в Kubernetes клъстера. Периодично възлите и самият клъстер трябва да бъдат актуализирани (или изключвани). PodDisruptionBudget (PDB) е нещо като гаранционно споразумение за обслужване между администраторите на клъстера и потребителите.

PDB позволява да се избегнат прекъсвания в работата на услугите, причинени от недостатъчно количество възли:

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: zookeeper

В този пример вие, като потребител на клъстера, заявявате на администраторите: „Хей, имам услуга zookeeper и независимо какво правите, бих искал поне 2 реплики от тази услуга да са винаги налични“.

Повече информация можете да намерите тук тук.

8. Няколко потребителя или среди в общия клъстер

Пространства на имената в Kubernetes (namespaces) не осигуряват силна изолация.

Разпространено заблуждение е, че ако разположите не-prod натоварване в едно пространство на имената, а prod натоварване в друго, те няма да влияят един на друг… Въпреки това, определено ниво на изолация може да бъде постигнато чрез заявки/ограничения на ресурсите, установяване на квоти и задаване на priorityClass-ове. Някаква „физическа“ изолация в data plane осигуряват affinities, tolerations, taints (или nodeselectors), но подобно разделение е доста сложно за реализиране.

На тези, които трябва да съвместяват и двата типа работни натоварвания в един клъстер, ще се наложи да се справят с трудностите. Ако обаче няма такава нужда и можете да си позволите да създадете още един клъстер (например в публично облако), по-добре е да направите именно това. Това ще позволи да се постигне много по-високо ниво на изолация.

9. externalTrafficPolicy: Cluster

Много често наблюдаваме, че целият трафик към клъстера преминава през услуга тип NodePort, за която по подразбиране е установена политиката externalTrafficPolicy: Cluster. Това означава, че NodePort е отворен на всеки възел в клъстера и можете да използвате който и да е от тях, за да взаимодействате с нужната услуга (набор от pod-ове).

10 типични грешки при използването на Kubernetes

В същото време, реалните pod-ове, свързани с гореспоменатата NodePort услуга, обикновено съществуват само на някои подмножество от тези възли. С други думи, ако се свържа с възел, на който няма нужния pod, той ще пренасочи трафика към друг възел, добавяйки транзитен участък (hop) и увеличавайки забавянето (ако възлите са в различни зони на достъпност/дата-центрове, забавянето може да бъде доста високо; да не говорим за увеличените разходи за egress трафик).

От друга страна, ако за някоя услуга в Kubernetes е зададена политиката externalTrafficPolicy: Local, то NodePort се отваря само на тези възли, където наистина са пуснати нужните pod-ове. При използване на външен балансировчик на натоварването, който проверява състоянията (healthchecking) на endpoint-ите (както прави AWS ELB), той ще изпраща трафика само към нужните възли., което ще се отрази положително на забавянето, изчислителните нужди, сметките за egress (а и здравият смисъл подсказва същото).

Има голяма вероятност да използвате нещо като traefik или nginx-ingress-controller като крайна NodePort точка (или LoadBalancer, който също използва NodePort) за маршрутизиране на HTTP ingress трафик, а активирането на тази опция може значително да намали забавянето при подобни заявки.

В този публикация можете да научите по-подробно за externalTrafficPolicy, нейните предимства и недостатъци.

10. Не се връзвайте към клъстери и не злоупотребявайте с контролния план

Преди беше обичайно сървърите да се назовават с собствени имена: Антон, HAL9000 и Колос… Днес на тяхно място са случайно генерирани идентификатори. Въпреки това, навикът остана и сега собствените имена отиват към клъстерите.

Типична история (основаваща се на истински събития): започна с доказателство на концепцията, затова клъстерът носеше гордото име тест… Изминаха години и той все още се използва в продукция, и всички се страхуват да го докоснат.

Няма нищо смешно в това, че клъстерите се превръщат в домашни любимци, затова препоръчваме периодично да ги премахвате, непрекъснато упражнявайки се в възстановяване след повреди (в това ще помогне chaos engineering — бел. ред.). Освен това, не е лошо да се занимае и с контролния слой (контролен план). Страхът да се докоснете до него — не е много добър знак. Etcd мъртъв? Хора, наистина сте в беда!

От друга страна, не бива да се увличате в манипулации с него. С времето контролният слой може да стане бавен. Вероятно това е свързано с голямото количество създавани обекти без тяхната ротация (обичайна ситуация при използване на Helm с настройки по подразбиране, поради което не се обновява състоянието му в configmap-овете/секретите — в резултат, в контролния слой се натрупват хиляди обекти) или с постоянно редактиране на обектите kube-api (за автоматично мащабиране, за CI/CD, за мониторинг, логове на събития, контролери и т.н.).

Освен това, препоръчваме да проверите споразуменията SLA/SLO с доставчика на управляван Kubernetes и да обърнете внимание на гаранциите. Доставчикът може да гарантира наличността на контролния слой (или неговите субкомпоненти), но не p99 забавянето на заявките, които изпращате към него. С други думи, можете да въведете kubectl get nodes, а отговорът да се получи чак след 10 минути, и това няма да бъде нарушение на условията на споразумението за обслужване.

11. Бонус: използване на таг latest

А това вече е класика. В последно време не срещаме толкова често подобна техника, тъй като много, поучени от горчивия опит, са спрели да използват тага :latest и започнаха да закрепват (pin) версиите. Ура!

ECR поддържа непроменяемост на таговете на образите; препоръчваме да се запознаете с тази забележителна особеност.

Резюме

Не очаквайте всичко да заработи с магическо движение на ръката: Kubernetes не е панацея. Лошото приложение остава такова дори в Kubernetes (и, възможно, става още по-лошо). Небрежността води до излишна сложност, бавна и натоварена работа на управляващия слой. Освен това рискувате да останете без стратегия за възстановяване след авария. Не разчитайте, че Kubernetes "от кутията" ще поеме осигуряването на изолация и висока наличност. Отделете известно време, за да направите приложението си наистина cloud native.

С неуспешния опит на различни екипи можете да се запознаете в този сборник истории от Henning Jacobs.

Желаещите да допълнят списъка с грешки, представен в тази статия, могат да се свържат с нас в Twitter (@MarekBartik, @MstrsObserver).

P.S. от преводача

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

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

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