Кожна WorkerNode в Kubernetes може мати обмежену кількість подів, і цей ліміт визначається трьома параметрами:
- CPU: загальна кількість
requests.cpu
не може бути більше, ніж є CPU на Node - Memory: загальна кількість
requests.memory
не може бути більше, ніж є Memory на Node - IP: загальна кількість подів не може бути більшою, ніж є IP-адрес у ноди
І якщо перші два ліміти такі собі “soft” – бо ми можемо просто не задавати requests
взагалі – то ліміт по кількості IP-адрес на ноді це вже “hard” ліміт, бо кожному поду, який запускається на ноді, потрібно видати власну адресу з пула Secondary IP його ноди.
А проблема полягає в тому, що ці адреси дуже часто використовуються ще до того, як на ноді закінчаться CPU або Memory – і в такому випадку ми опиняємось в ситуації, коли наша нода underutilized, тобто – ми могли б ще запустити на ній поди, але не можемо, бо для них нема вільних IP.
Наприклад, одна з наших нод t3.medium
виглядає так:
В неї є вільні CPU, не вся пам’ять requested, але Pods Allocation вже 100% – бо до ноди t3.medium
може бути додано 17 Secondary IP для подів, і вони всі вже зайняті.
Зміст
Максимум Secondary IP на AWS EC2
Кількість же додаткових (secondary) IP на ЕС2 залежить від кількості ENI (Elastic network Interface) та кількості IP на кожен інтерфейс, і ці параметри залежать від типу EC2.
Наприклад, t3.medium
може мати 3 інтерфейси, і на кожному може бути до 6 IP (див. IP addresses per network interface per instance type):
Тобто всього 18 адрес, але мінус по 1 Private IP роботу ENI самого інстансу – і для подів на такій ноді буде доступно 17 адрес.
Amazon VPC Prefix Assignment Mode
Щоб вирішити проблему з кількістью Secondary IP на EC2 можна використати VPC Prefix Assignment Mode – коли на інтерфейс підключається не окремий IP, а цілий блок /28
, див. Assign prefixes to Amazon EC2 network interfaces.
Наприклад, ми можемо створити новий ENI і йому присвоїти CIDR (Classless Inter-Domain Routing) префікс:
$ aws --profile work ec2 create-network-interface --subnet-id subnet-01de26778bea10395 --ipv4-prefix-count 1
Перевіряємо цей ENI:
Єдиний момент, який варто мати на увазі це те, що VPC Prefix Assignment Mode доступний тільки для інстансів на AWS Nitro System – останнє покоління гіпервізорів AWS, на якому працюють інстанси T3, M5, C5, R5 і т.д. – див. Instances built on the Nitro System.
What is: CIDR /28
Кожна IPv4-адреса складається з 32 бітів, поділених на 4 октети (групи по 8 біт). Ці біти можуть бути представлені у двійковій системі (0 або 1) або у десятковій формі (значення між 0 та 255 для кожного октету). Ми будемо оперувати саме 0 та 1.
Маска підмережі /28
вказує на те, що перші 28 бітів IP-адреси зарезервовані для ідентифікації мережі – тоді 4 біти (32 всього мінус 28 зарезервованих) залишаються для визначення індивідуальних хостів в мережі:
Знаючи, що у нас є 4 вільні біти, а кожен біт може мати значення 0 або 1, ми можемо порахувати загальну кількість комбінацій: 2 в ступені 4, або 2×2×2×2=16 – тобто в мережі /28
може бути загалом 16 адрес, включаючи як адресу мережі (перший IP), так і broadcast адресу (останній IP), отже саме для хостів буде доступно 14 адрес.
Тож замість того, щоб на ENI підключати один Secondary IP – ми підключаємо відразу 16.
При цьому варто враховувати скільки ваша VPC Subnet зможе мати таких блоків, бо це буде визначати кільікість WorkerNodes, які ви зможете запустити.
Тут вже простіше використати утіліти накшалт ipcalc
.
Наприклад, в мене Private Subnets мають префікс /20
, і якщо всю цю мережу розбити на блоки по /28
, то будемо мати 256 підмереж і 3584 адрес:
$ ipcalc 10.0.16.0/20 /28 ... Subnets: 256 Hosts: 3584
Або можна використати калькулятор онлайн – ipcalc.
VPC Prefix та AWS EKS VPC CNI
Добре – ми побачили, як ми можемо виділити блок адрес на інтерфейс, який підключений до EC2.
Що далі? Як з цього пулу адрес видається окрема адреса поду, який ми запускаємо в Kubernetes?
Цим займається VPC Container Networking Interface (CNI) Plugin, який складається з двох основних компонентів:
- L-IPAM daemon (IPAMD): відповідає за створення та підключення ENI до EC2-інстансів, призначення блоків адрес до цих інтерфейсів та “прогрів” IP-префіксів для пришвидшення запуску подів (поговоримо далі)
- CNI plugin: відповідає за налаштування мережевих інтерфейсів на ноді – як ethernet, та і віртуальних, і комунікує з IPAMD через RPC (Remote Procedure Call)
Як саме це реалізовано чудово описано в пості Amazon VPC CNI plugin increases pods per node limits > How it works:
Тож процес виглядає таким чином:
- Kubernetes Pod при запуску виконує запит до IPAMD на виділення IP
- IPAMD перевіряє доступні адреси, якщо вільні адреси є – виділяє один для поду
- якщо вільних адрес в підключених до ЕС2 префіксах нема – то IPAMD робить запит на підключення до ENI нового префіксу
- якщо до існуючого ENI вже не можна додавати нові префікси – робиться запит на підключення нового ENI
- якщо нода вже має максимальну кількість ENI – то запит поду на новий IP фейлиться
- якщо новий префікс додано (на існуючий або новий ENI) – то з нього вибирається IP для поду
- якщо до існуючого ENI вже не можна додавати нові префікси – робиться запит на підключення нового ENI
WARM_PREFIX_TARGET
, WARM_IP_TARGET
та MINIMUM_IP_TARGET
Див. WARM_PREFIX_TARGET, WARM_IP_TARGET and MINIMUM_IP_TARGET.
Для конфігурації виділення префіксів нодам та IP подам VPC CNI має три додаткові опції – WARM_PREFIX_TARGET
, WARM_IP_TARGET
та MINIMUM_IP_TARGET
:
WARM_PREFIX_TARGET
: скільки підключених/28
префіксів тримати “в запасі”, тобто вони будуть підключені до ENI, але адреси з них ще не використовуютьсяWARM_IP_TARGET
: скільки мінімально IP адрес підключати при створенні нодиMINIMUM_IP_TARGET
: скільки мінімально IP адрес тримати “в запасі”
При використанні VPC Prefix Assignment Mode ви не можете задати всі три параметри в нуль – як мінімум або WARM_PREFIX_TARGET
або WARM_IP_TARGET
мають бути задані хоча б в 1.
Якщо заданий WARM_IP_TARGET
та/або MINIMUM_IP_TARGET
– вони будуть мати перевагу над WARM_PREFIX_TARGET
, тобто значення з WARM_PREFIX_TARGET
буде ігноруватись.
Subnet CIDR reservations
Документація – Subnet CIDR reservations.
При використанні Prefix IP, адреси в префіксу мають бути суміжні, тобто в одному префіксу не можуть бути адреси “10.0.31.162” (блок 10.0.31.160/28
) та “10.0.31.178” (блок 10.0.31.176/28
).
Якщо сабнет активно використовується, і в ньому немає безперервного блоку адрес для виділння цілого префіксу, то ви отримаєте помилку:
failed to allocate a private IP/Prefix address: InsufficientCidrBlocks: The specified subnet does not have enough free cidr blocks to satisfy the request
Щоб запобігти цьому, можна використати функцію резервації блоків – VPC Subnet CIDR reservations для створення єдиного блоку, з якого потім будуть “нарізатись” блоки по /28
. Такий блок не буде використовуватись для виділення Private IP для EC2, натомість VPC CNI буде створювати префікси саме з цієї “резервації”.
При цьому ви можете створити таку резервацію навіть якщо окремі IP в цьому блоку вже використовуються на EC2 – як тільки такі адреси звільняться, вони більше не будуть виділятись окремим інстансам EC2, а будуть зберігатись для формування префіксів /28
.
Отже, якщо в мене є VPC Subnet з блоком /20
– я можу розбити її на два CIDR Reservation блоки по /21
, і в кожному /21
блоці мати:
$ ipcalc 10.0.24.0/21 /28 ... Subnets: 128 Hosts: 1792
128 блоків /28
по 14 IP для хостів – разом 1792 IP для подів.
Активація VPC CNI Prefix Assignment Mode в AWS EKS
Документація – Increase the amount of available IP addresses for your Amazon EC2 nodes.
Все, що треба зробити – це змінити значення змінної ENABLE_PREFIX_DELEGATION
в aws-node
DaemonSet:
$ kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
При використанні Terraform модулю terraform-aws-modules/eks/aws
це можна зробити через configuration_values
для vpc-cni
AddOn:
... module "eks" { ... cluster_addons = { coredns = { most_recent = true } kube-proxy = { most_recent = true } vpc-cni = { most_recent = true before_compute = true configuration_values = jsonencode({ env = { ENABLE_PREFIX_DELEGATION = "true" WARM_PREFIX_TARGET = "1" } }) } } ...
Див. examples.
Перевіряємо:
$ kubectl describe daemonset aws-node -n kube-system ... Environment: ... VPC_ID: vpc-0fbaffe234c0d81ea WARM_ENI_TARGET: 1 WARM_PREFIX_TARGET: 1 ...
При використанні AWS Managed NodeGroups новий ліміт буде заданий автоматично.
В цілому, максимальна кількість подів буде залежати від типу інстансу і кількості vCPU на ньому – 110 подів на кожні 10 ядер (див. Kubernetes scalability thresholds). Але є ще ї ліміти, які задані самим AWS.
Наприклад для t3.nano
з 2 vCPU це буде 34 поди – перевіримо скриптом max-pod-calculator.sh:
$ ./max-pods-calculator.sh --instance-type t3.nano --cni-version 1.9.0 --cni-prefix-delegation-enabled 34
На c5.4xlarge
з 16 vCPU – 110 подів:
$ ./max-pods-calculator.sh --instance-type c5.4xlarge --cni-version 1.9.0 --cni-prefix-delegation-enabled 110
А на c5.24xlarge
з 96 ядрами – 250 подів, бо це вже обмеження від AWS:
$ ./max-pods-calculator.sh --instance-type c5.24xlarge --cni-version 1.9.0 --cni-prefix-delegation-enabled 250
Налаштування Karpenter
Для того, щоб задати максимальну кількість подів на WorkerNodes, які створює Karpenter – використовуємо опцію maxPods
для NodePool:
... - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand"] kubelet: maxPods: 110 ...
Тестую на тестовому кластері, де зараз є тільки одна нода для CiritcalAddons, тобто звичайні поди на ній не запускаються:
$ kk get node NAME STATUS ROLES AGE VERSION ip-10-0-61-176.ec2.internal Ready <none> 2d v1.28.5-eks-5e0fdde
Для перевірки створимо Deployment з 3 подами:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:latest ports: - containerPort: 80
Деплоїмо, і Karpenter створює один NodeClaim з t3.small
:
$ kk get nodeclaim NAME TYPE ZONE NODE READY AGE default-w7g9l t3.small us-east-1a False 3s
Пара хвилин – і поди на ньому запустились:
Тепер скейлимо Deployment до, наприклад, 50 подів:
$ kk scale deployment nginx-deployment --replicas=50 deployment.apps/nginx-deployment scaled
І все ще маємо один NodeClaim з тим же t3.small
, але тепер на ньому запущено 50 подів:
Звісно, при такому підході треба завжди задавати Pod requests, щоб кожен под мав доступ до CPU та Memory – саме requests
для нас тепер будуть лімітами на кількість подів на нодах.