Как стать автором
Обновить
Флант
DevOps-as-a-Service, Kubernetes, обслуживание 24×7

Kubernetes API и FlowControl: как контролировать количество и очередность запросов

Уровень сложностиСредний
Время на прочтение8 мин
Количество просмотров3.6K

В статье мы расскажем о реальном случае: как Kubernetes API в одном из кластеров «парализовало» множественными запросами. И поделимся, как избежать этой проблемы.

Как множественные запросы поломали Kubernetes API

Путешествие в длительное изучение работы Kubernetes API и приоритезации запросов к нему началось буднично. Однажды вечером мы получили звонок от инженера техподдержки. Оказалось, что у одного из клиентов ничего не работает, прод практически упал, и с этой аварией нужно что-то делать.

Подключившись к проблемному кластеру, мы обнаружили следующее: Kube API-серверы занимают всю память, падают, поднимаются, снова падают и так по кругу. Это приводит к тому, что API Kubernetes становится недоступен и полностью нефункционален.

Так как это был production-кластер, мы временно решили проблему ресурсами: добавили процессоров и памяти на узлы control-plane. С первого раза добавленного не хватило, но на второй работа API стабилизировалась.

Ищем проблему

Для начала мы оценили масштаб изменений. Изначально узлы control-plane были в конфигурации с 8 CPU и 16 Гбайт RAM, а после наших манипуляций размер узлов увеличился до 16 CPU и 64 Гбайт RAM.

Графики потребления памяти в момент возникновения проблем показали следующее:

Потребление памяти возрастало до 50 Гбайт. Некоторое время спустя мы выяснили, что в силу определенных условий поды cni-cilium запускали массовые LIST-запросы в API. А поскольку кластер большой и узлов в нем много (более 200), одновременные запросы увеличивали использование памяти.

Мы согласовали с клиентом окно для тестов, перезапустили агенты Cilium и увидели следующую картину:

  • Вырастает нагрузка на один из API-серверов.

  • Он начинает потреблять память.

  • Памяти не хватает.

  • API-сервер падает.

  • Запросы переключаются на другой сервер.

  • Повторяется все то же самое.

Мы решили, что будет разумно ограничить количество одновременных запросов к API со стороны cilium-agent. Даже если LIST-запросы выполнятся чуть медленнее, на работе Cilium это не скажется.

Решаем проблему

Мы подготовили манифесты FlowSchema и PriorityLevelConfiguration:

---
apiVersion: flowcontrol.apiserver.k8s.io/v1beta1
kind: FlowSchema
metadata:
 name: cilium-pods
spec:
 distinguisherMethod:
   type: ByUser
 matchingPrecedence: 1000
 priorityLevelConfiguration:
   name: cilium-pods
 rules:
   - resourceRules:
       - apiGroups:
           - 'cilium.io'
         clusterScope: true
         namespaces:
           - '*'
         resources:
           - '*'
         verbs:
           - 'list'
     subjects:
       - group:
           name: system:serviceaccounts:d8-cni-cilium
         kind: Group
---
apiVersion: flowcontrol.apiserver.k8s.io/v1beta1
kind: PriorityLevelConfiguration
metadata:
 name: cilium-pods
spec:
 type: Limited
 limited:
   assuredConcurrencyShares: 5
   limitResponse:
     queuing:
       handSize: 4
       queueLengthLimit: 50
       queues: 16
     type: Queue

… и задеплоили их в кластер.

Мы проверили, помогло ли наше решение: для этого перезапустили cilium-agent. Убедились, что на этот раз потребление памяти API-сервером значимо не изменилось. А значит, все работает так, как нужно, и мы можем вернуть параметры узлов к изначальным.

Ниже расшифровываем, что именно мы сделали и как это решение помогает устранить проблему с множественными запросами к Kubernetes API. Кстати, похожую ситуацию мы решили и описали для подов vector.

Как управлять запросами в Kubernetes API

Управление очередями запросов в Kubernetes API называется API Priority and Fairness (APF) и включено по умолчанию, начиная с Kubernetes 1.20. Сервер API тоже обладает функцией ограничения количества запросов, для этого предусмотрены два параметра: --max-requests-inflight (по умолчанию 400) и --max-mutating-requests-inflight (по умолчанию 200). Если APF включен, оба эти параметра суммируются — так определяется лимит параллельности API-сервера (total concurrency limit, максимальный лимит запросов).

При этом есть некоторые особенности. Долгоживущие запросы к API (например, просмотр логов или выполнение команд в поде) не попадают под APF, также как и WATCH запросы. Еще существует специальный предопределенный priority level — exempt. Попадающие в него запросы обрабатываются немедленно.

Без APF невозможно гарантировать, что запросы от агента Cilium не «задушат» пользовательские обращения к API. Также APF позволяет установить ограничения, чтобы важные запросы выполнились вне зависимости от нагруженности API-сервера.

APF настраивается при помощи двух ресурсов:

  • PriorityLevelConfiguration — определяет уровень приоритета запросов.

  • FlowSchema — определяет, для каких запросов применяется PriorityLevelConfiguration.

В каждом PiorityLevelConfiguration настраивается свой лимит параллельности. Общий лимит параллельности делится между всеми PriorityLevelConfiguration пропорционально их настройкам.

Давайте посчитаем этот лимит на примере, ориентируемся на параметр AssuredConcurrencyShares:

~# kubectl get prioritylevelconfigurations.flowcontrol.apiserver.k8s.io
NAME                 TYPE      ASSUREDCONCURRENCYSHARES   QUEUES   HANDSIZE   QUEUELENGTHLIMIT   AGE
catch-all            Limited   5                          <none>   <none>     <none>             193d
d8-serviceaccounts   Limited   5                          32       8          50                 53d
deckhouse-pod        Limited   10                         128      6          50                 90d
exempt               Exempt    <none>                     <none>   <none>     <none>             193d
global-default       Limited   20                         128      6          50                 193d
leader-election      Limited   10                         16       4          50                 193d
node-high            Limited   40                         64       6          50                 183d
system               Limited   30                         64       6          50                 193d
workload-high        Limited   40                         128      6          50                 193d
workload-low         Limited   100                        128      6          50                 193d

Рассчитаем, сколько одновременных запросов допустимо выполнять каждому уровню. 

  1. Суммируем все AssuredConcurrencyShares – 260. 

  2. Рассчитаем лимит запросов для priority level workload-low: (400+200)/260*100 = 230 запросов в секунду. 

Попробуем изменить одно из значений и посмотрим, что поменяется. Например, поднять AssuredConcurrencyShares для deckhouse-pod с 10 до 100. Лимит запросов изменится до величины (400+200)/350*100 = 171 запрос в секунду.

Увеличивая AssuredConcurrencyShares, мы увеличиваем лимит запросов для конкретного уровня, но уменьшаем для всех других.

Если количество запросов, попадающих в priority level, больше разрешенного лимита, запросы выстраиваются в очередь, которая обеспечивает им равноценный доступ. Параметры очереди можно настраивать. Также можно сразу отбрасывать запросы, выходящие за пределы лимита для этого priority level.

Посмотрим на пример ниже:

---
apiVersion: flowcontrol.apiserver.k8s.io/v1beta1
kind: PriorityLevelConfiguration
metadata:
 name: cilium-pods
spec:
 type: Limited
 limited:
   assuredConcurrencyShares: 5
   limitResponse:
     queuing:
       handSize: 4
       queueLengthLimit: 50
       queues: 16
     type: Queue

Здесь настроен priority level с AssuredConcurrencyShares = 5, при отсутствии других кастомных priority level это дает 12 запросов в секунду. Очередь запросов настроена на 200 запросов (handSize * queueLengthLimit), а для более равномерного распределения запросов от разных агентов созданы 16 внутренних очередей.

Особенности, которые стоит знать:

  • Увеличение параметра queue снижает количество столкновений между потоками, но увеличивает использование памяти. Значение 1 отключает логику справедливого распределения запросов, но все равно позволяет ставить запросы в очередь.

  • Увеличение queueLengthLimit позволяет выдерживать большие всплески трафика, не игнорируя ни одного запроса. Но запросы обрабатываются медленней и на них тратится больше памяти.

  • Меняя handSize, вы можете регулировать вероятность столкновений между потоками и общий параллелизм, доступный для одного потока при перегрузке.

Эти параметры подбираются экспериментально. С одной стороны, нам нужно добиться того, чтобы запросы в этом priority level работали не слишком медленно, с другой – чтобы при резком всплеске трафика API-сервер не перегружался.

Теперь перейдем к ресурсу FlowSchema. Он определяет, какие запросы попадут в соответствующий priority level. 

Основные параметры: 

  • matchingPrecedence определяет порядок применения этой flowSchema. Чем ниже число, тем выше приоритет. Таким образом можно писать перекрывающиеся flowSchemas от более частных случаев к более общим.

  • rules определяют правила выбора запросов, формат такой же, как используется в Kubernetes RBAC rules.

  • distinguisherMethod определяет, по какому параметру запросы будут делиться на потоки при передаче в priority level — по пользователю или по пространству имен. Если параметр не указать, все запросы будут в одном потоке.

Пример:

---
apiVersion: flowcontrol.apiserver.k8s.io/v1beta1
kind: FlowSchema
metadata:
 name: cilium-pods
spec:
 distinguisherMethod:
   type: ByUser
 matchingPrecedence: 1000
 priorityLevelConfiguration:
   name: cilium-pods
 rules:
   - resourceRules:
       - apiGroups:
           - 'cilium.io'
         clusterScope: true
         namespaces:
           - '*'
         resources:
           - '*'
         verbs:
           - 'list'
     subjects:
       - group:
           name: system:serviceaccounts:d8-cni-cilium
         kind: Group

В примере выше мы выбираем все запросы к apiGroup: cilium.io, включая запросы cluster-scope, из всех пространств имен ко всем ресурсам, тип запроса — list. Субъектом запроса выступают запросы от сервис-аккаунта d8-cni-cilium.

Как посмотреть, в какую FlowSchema и PriorityLevelConfiguration попал запрос?

API-сервер при ответе проставляет специальные заголовки: X-Kubernetes-PF-FlowSchema-UID и X-Kubernetes-PF-PriorityLevel-UID. По ним можно понять, куда попадает запрос.

Например, выполним запрос к API от сервис-аккаунта агента Cilium:

TOKEN=$(kubectl -n d8-cni-cilium get secrets agent-token-45s7n -o json | jq -r .data.token | base64 -d)

curl https://127.0.0.1:6445/apis/cilium.io/v2/ciliumclusterwidenetworkpolicies?limit=500  -X GET --header "Authorization: Bearer $TOKEN" -k -I
HTTP/2 200
audit-id: 4f647505-8581-4a99-8e4c-f3f4322f79fe
cache-control: no-cache, private
content-type: application/json
x-kubernetes-pf-flowschema-uid: 7f0afa35-07c3-4601-b92c-dfe7e74780f8
x-kubernetes-pf-prioritylevel-uid: df8f409a-ebe7-4d54-9f21-1f2a6bee2e81
content-length: 173
date: Sun, 26 Mar 2023 17:45:02 GMT

kubectl get flowschemas -o custom-columns="uid:{metadata.uid},name:{metadata.name}" | grep 7f0afa35-07c3-4601-b92c-dfe7e74780f8
7f0afa35-07c3-4601-b92c-dfe7e74780f8   d8-serviceaccounts

kubectl get prioritylevelconfiguration -o custom-columns="uid:{metadata.uid},name:{metadata.name}" | grep df8f409a-ebe7-4d54-9f21-1f2a6bee2e81
df8f409a-ebe7-4d54-9f21-1f2a6bee2e81   d8-serviceaccounts

В выводе видно, что запрос относится к flowSchema d8-serviceaccounts и priorityLevelConfiguration d8-serviceaccounts.

Какие метрики можно посмотреть ?

Полезные метрики:

  • Apiserver_flowcontrol_rejected_requests_total — общее количество отброшенных запросов.

  • Apiserver_current_inqueue_requests — текущее количество запросов в очереди.

  • Apiserver_flowcontrol_request_execution_seconds — длительность выполнения запросов.

Также полезно смотреть информацию из debug-эндпоинтов:

kubectl get --raw /debug/api_priority_and_fairness/dump_priority_levels
PriorityLevelName,  ActiveQueues, IsIdle, IsQuiescing, WaitingRequests, ExecutingRequests
system,             0,            true,   false,       0,               0
workload-high,      0,            true,   false,       0,               0
catch-all,          0,            true,   false,       0,               0
exempt,             <none>,       <none>, <none>,      <none>,          <none>
d8-serviceaccounts, 0,            true,   false,       0,               0
deckhouse-pod,      0,            true,   false,       0,               0
node-high,          0,            true,   false,       0,               0
global-default,     0,            true,   false,       0,               0
leader-election,    0,            true,   false,       0,               0
workload-low,       0,            true,   false,       0,               0


kubectl get --raw /debug/api_priority_and_fairness/dump_queues
PriorityLevelName,  Index,  PendingRequests, ExecutingRequests, SeatsInUse, NextDispatchR,    InitialSeatsSum, MaxSeatsSum, TotalWorkSum
exempt,             <none>, <none>,          <none>,            <none>,     <none>,           <none>,          <none>,      <none>
d8-serviceaccounts, 0,      0,               0,                 0,          71194.55330547ss, 0,               0,           0.00000000ss
d8-serviceaccounts, 1,      0,               0,                 0,          71195.15951496ss, 0,               0,           0.00000000ss
...
global-default,     125,    0,               0,                 0,          0.00000000ss,     0,               0,           0.00000000ss
global-default,     126,    0,               0,                 0,          0.00000000ss,     0,               0,           0.00000000ss
global-default,     127,    0,               0,                 0,          0.00000000ss,     0,               0,           0.00000000ss

Заключение

Мы решили наш кейс, настроив управление очередями запросов. Но это был далеко не единственный случай в нашей практике. После нескольких случаев, когда мы столкнулись с необходимостью ограничивать запросы к API, мы реализовали настройку APF для компонентов нашей Kubernetes-платформы Deckhouse. Это сокращает нам и нашим клиентам число проблем с перегрузкой API в больших и нагруженных кластерах. 

Поделитесь в комментариях, если тоже сталкивались с такими случаями в своей практике и знаете другие решения для них. 

P.S.

Читайте также в нашем блоге:

 

Теги:
Хабы:
Всего голосов 30: ↑30 и ↓0+30
Комментарии3

Публикации

Информация

Сайт
flant.ru
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия
Представитель
Тимур Тукаев