melmus.github.io

DevOps Customer

View on GitHub

Request and Limits Kubernetes

Данная статья предназначена для понимания принципа и приёмов использования лимитов в Kubernetes

Теория

Лимиты в Kubernetes делятся на 2 вида request и limits. При этом каждый вид можно ограничить по CPU(ядра процессора), RAM(байты оперативной памяти). Отдельно стоит ограничение Storage(байты жесткого диска) - его мы рассмотрим отдельно..

Requests

Requests - задание количество ресурсов, сколько нужно гарантировать контейнеру для работы приложения.

Полностью управляется Kubernetes
Работает до запуска контейнера
Задаётся вручную и не может быть превышено заданным значением в Namespace'е или автоматически расчитанным в кластере
В Docker-сервисе никак не участвует.
Не следит за фактическим потребление ресурсов контейнером.

Kubernetes ВСЕГДА имеет некие параметры Request CPU и Request RAM. Их можно задать явно для определённого Namespace’а, но по умолчанию они ровны сумме всех CPU и RAM Worker’ов кластера. Kubernetes не сможет развернуть новые контейнеры, если для них не хватает квоты, однако можно (и нужно) превышать данные установленные лимиты. Лучше указывать здесь маленькие лимиты, чтобы не столкнуться с ситуацией, что запущенные контейнеры не потребляют много CPU и RAM, а новые уже отказываются запусткаться,

Limits

Limits - задание максимального объёма ресурсов, который контейнер может потреблять в штатной работе. Контейнер не сможет превысить заданные тут значения ресурсов: потребление CPU и RAM не подниматься выше заданных параметров.

Полностью управляется Docker-сервисом (Kubernetes просто передаёт эти параметры Docker-сервису при запуске).
Фактическое потребление контейнером ресурсов не может быть превышено заданным тут параметрам

Docker умеет ограничивать ресурсы контейнера, при этом стараясь поддерживать жизнь в контейнере. То есть, если контейнер достигнет лимита по CPU, все запущенные процессы начнут делить немногие ядра для своих вычислений, а если достигнет лимита по RAM - запущенные и новые процессы не смогут больше выделить себе ОЗУ (ООМ не будет, но стабильность работы приложения будет нарушено).

Значение Limits может (и должно) быть большим. Возможно даже превышение общего объёма ресурсов во всём кластере, но под свой страх и риск (если ресурсы RAM на кластере кончаться раньше, чем сработает данный лимит - кластер будет медленно умирать от OOM).

Практика

Имеется кластер Kubernetes 1.12.7 и контейнер со следующими лимитами: Request Limits CPU 100m 512Mi RAM 2 2048Mi

Yaml:

---
apiVersion: apps/v1
kind: Deployment # Вид сущности Kubernetes (краткое описание ниже, там же ссылка на полное)
metadata:
  name: sshd  # Название микросервиса - именно по нему будет происходить обращение к данному микросервису от других микросервисов
  namespace: sfo-hightest  # Название Namespace - обязательно определяйте его в каждом yaml-файле
spec:
  replicas: 1 # Количество экземпляров контейнеров в микросервисе (описание ниже)
  selector:
    matchLabels:
      name: sshd # Ярлык - привязка одних видов сущностей Kubernetes к другим. Чтобы не запутаться - пишите его одноимённо с metadata.name  
  template:
    metadata:
      labels:
        name: sshd # Дополнительные к metadata.name имена микросервиса. В нашем примере не используется, поэтому просто укажем такое же название.
    spec:
      containers: # Данные о запускаемом конетейнере
      - name: sshd # Название контейнера в запущенном состоянии - чтобы не путаться, оставляем одноимённо с metadata.name  
        image: rastasheep/ubuntu-sshd:18.04 # Название и тег контейнера - его мы нашли на hub.docker.com
        ports:
        - containerPort: 22 # Рабочий порт контейнера
        resources: # Лимиты памяти и CPU - всегда знайте свои лимиты на Namespace и распределяйте их на микросервисы в этих пределах (см. ниже)
          requests:
            memory: "512Mi"
            cpu: "100m"
          limits:
            memory: "2048Mi"
            cpu: "2"
      hostAliases:
      - ip: "127.0.0.1"
        hostnames:
          - "sshd" # Пропишите возможность обращаться контейнерам сами на себя (localhost)

---
apiVersion: v1
kind: Service
metadata:
  name: sshd # Название сервиса
  labels:
    name: sshd # Ярлык - привязка в Deployment "sshd"
  namespace: sfo-hightest
spec:
  type: NodePort
  ports:
  - port: 22 # Указываем рабочие порты контейнера
    targetPort: 22
  selector:
    name: sshd # Ярлык - привязка в Deployment "sshd"

Состояние простоя

В состоянии простоя мы можем увидеть следующие данные:

Статистика относительная Docker

docker stats
CONTAINER ID        NAME                                                                                        CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
bd9af9612d23        k8s_sshd_sshd-5c64f7646f-l44jj_sfo-hightest_a416b450-61f9-11e9-a907-0050562f04b1_0          0.00%               3.371MiB / 2GiB       0.16%               0B / 0B             0B / 0B             1

*Статистика относительно Namespace’а

kubectl describe namespaces sfo-hightest 
Resource Quotas
 Name:            sfoteam-quota
 Resource         Used   Hard
 --------         ---    ---
 limits.cpu       2      16
 limits.memory    2Gi    64Gi
 requests.cpu     100m   2
 requests.memory  512Mi  4Gi

Статистка относительно Ноды в kubernetes

kubectl describe nodes dev-wrk02
  Namespace                  Name                     CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ---------                  ----                     ------------  ----------  ---------------  -------------
  sfo-hightest               sshd-5c64f7646f-l44jj    100m (1%)     2 (25%)     512Mi (0%)       2Gi (3%)

Статистика относительно Ноды в ОС

ps auxf
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root      93195  0.1  0.0 1247552 53868 ?       Ssl  апр08  15:41 /usr/bin/containerd
root       7317  0.0  0.0 451444 11760 ?        Sl   19:48   0:00  \_ containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/bd9af9612d233c6910983a7
root       7334  0.0  0.0  72296  6540 ?        Ss   19:48   0:00      \_ /usr/sbin/sshd -D

Как видите, контейнер забрал себе 0.1 CPU и 512Mi RAM request-лимита, но имеет запас лимита в 2 CPU и 2Gi RAM hard-лимита (limits).

При этом видно, что request ограничивает сам Kubernetes, а Limits - сам docker-сервис.

Тестируем Limits

Нагрузка по CPU

Попробуем нагрузить все 2 ядра - запустим 4 ресурсоёмких процесса.

ssh -p 30332 root@k8s.testdomain.com
root@k8s.testdomain.com's password: 
root@sshd-5c64f7646f-l44jj:~# yes > /dev/null & 
[1] 129
root@sshd-5c64f7646f-l44jj:~# yes > /dev/null & 
[2] 130
root@sshd-5c64f7646f-l44jj:~# yes > /dev/null & 
[3] 131
root@sshd-5c64f7646f-l44jj:~# yes > /dev/null & 
[4] 132
root@sshd-5c64f7646f-l44jj:~# 

Смотрим статистику

Статистика относительная Docker

docker stats
CONTAINER ID        NAME                                                                                        CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
bd9af9612d23        k8s_sshd_sshd-5c64f7646f-l44jj_sfo-hightest_a416b450-61f9-11e9-a907-0050562f04b1_0          197.68%             8.305MiB / 2GiB       0.41%               0B / 0B 

*Статистика относительно Namespace’а

kubectl describe namespaces sfo-hightest 
Resource Quotas
 Name:            sfoteam-quota
 Resource         Used   Hard
 --------         ---    ---
 limits.cpu       2      16
 limits.memory    2Gi    64Gi
 requests.cpu     100m   2
 requests.memory  512Mi  4Gi

Статистка относительно Ноды в kubernetes

kubectl describe nodes dev-wrk02
  Namespace                  Name                     CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ---------                  ----                     ------------  ----------  ---------------  -------------
  sfo-hightest               sshd-5c64f7646f-l44jj    100m (1%)     2 (25%)     512Mi (0%)       2Gi (3%)

Статистика относительно Ноды в ОС

ps auxf
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root       7317  0.0  0.0 451444 11792 ?        Sl   19:48   0:00  \_ containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/bd9af9612d233c6910983a7
root       7334  0.0  0.0  72296  6540 ?        Ss   19:48   0:00      \_ /usr/sbin/sshd -D
root      12938  0.0  0.0  74656  6684 ?        Ss   20:10   0:00          \_ sshd: root@pts/0
root      12942  0.0  0.0  18508  3536 pts/0    Ss+  20:10   0:00              \_ -bash
root      14217 53.7  0.0   4532   744 pts/0    R    20:16   0:36                  \_ yes
root      14226 53.7  0.0   4532   828 pts/0    R    20:16   0:34                  \_ yes
root      14234 49.9  0.0   4532   772 pts/0    R    20:16   0:30                  \_ yes
root      14242 50.1  0.0   4532   768 pts/0    R    20:16   0:30                  \_ yes

Зафиксировали, отпускаем нагрузку

root@sshd-5c64f7646f-l44jj:~# kill 132 131 130 129

Нагрузка по RAM

Загружает всё RAM (приложение сразу же сообщает о недостатке ОЗУ)

root@sshd-5c64f7646f-l44jj:~# :(){ :|:& };:
[1] 243
root@sshd-5c64f7646f-l44jj:~# -bash: fork: Cannot allocate memory
-bash: fork: Cannot allocate memory
-bash: fork: Cannot allocate memory
-bash: fork: Cannot allocate memory
-bash: fork: Cannot allocate memory

Смотрим статистику

Статистика относительная Docker

docker stats
CONTAINER ID        NAME                                                                                        CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
bd9af9612d23        k8s_sshd_sshd-5c64f7646f-l44jj_sfo-hightest_a416b450-61f9-11e9-a907-0050562f04b1_0          201.47%             1.999GiB / 2GiB       99.94%              0B / 0B             0B / 2MB            12840

*Статистика относительно Namespace’а

kubectl describe namespaces sfo-hightest 
Resource Quotas
 Name:            sfoteam-quota
 Resource         Used   Hard
 --------         ---    ---
 limits.cpu       2      16
 limits.memory    2Gi    64Gi
 requests.cpu     100m   2
 requests.memory  512Mi  4Gi

Статистка относительно Ноды в kubernetes

kubectl describe nodes dev-wrk02
  Namespace                  Name                     CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ---------                  ----                     ------------  ----------  ---------------  -------------
  sfo-hightest               sshd-5c64f7646f-l44jj    100m (1%)     2 (25%)     512Mi (0%)       2Gi (3%)

Статистика относительно Ноды в ОС

ps auxf
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root       7317  0.0  0.0 451444 11760 ?        Sl   19:48   0:00  \_ containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/bd9af9612d233c6910983a7
root       7334  0.0  0.0  72296  6540 ?        Rs   19:48   0:00      \_ /usr/sbin/sshd -D
root      12938  0.0  0.0  74656  6684 ?        Ss   20:10   0:00          \_ sshd: root@pts/0
root      12942  0.0  0.0      0     0 pts/0    Rs+  20:10   0:00          |   \_ [bash]
root      15643  0.0  0.0  74656  6744 ?        Ss   20:22   0:00          \_ sshd: root@pts/1
root      15677  0.0  0.0  18508  3464 ?        Ss+  20:22   0:00          |   \_ -bash
root      20390  0.0  0.0  18628  2480 pts/0    R    20:28   0:00          \_ -bash
root      26583  0.0  0.0  18628   512 pts/0    R    20:28   0:00          |   \_ -bash
root      23760  0.0  0.0  18628  1980 pts/0    D    20:28   0:00          \_ -bash
root      24163  0.0  0.0  18628   500 pts/0    R    20:28   0:00          \_ -bash
root      22330  0.0  0.0  18628  1920 pts/0    R    20:28   0:00          \_ -bash
root      22554  0.0  0.0  18628  1788 pts/0    R    20:28   0:00          \_ -bash
root      31242  0.0  0.0  18628   508 pts/0    R    20:28   0:00          |   \_ -bash
root      22592  0.0  0.0  18628  2112 pts/0    R    20:28   0:00          \_ -bash
root      22614  0.0  0.0  18628  1980 pts/0    R    20:28   0:00          \_ -bash
root      22684  0.0  0.0  18628  1792 pts/0    R    20:28   0:00          \_ -bash
root      29300  0.0  0.0  18628   512 pts/0    D    20:28   0:00          |   \_ -bash
root      22703  0.0  0.0  18628  1788 pts/0    R    20:28   0:00          \_ -bash
root      29520  0.0  0.0  18628   508 pts/0    R    20:28   0:00          |   \_ -bash
root      22880  0.0  0.0  18628  1920 pts/0    R    20:28   0:00          \_ -bash
root      25617  0.0  0.0  18628   500 pts/0    R    20:28   0:00          \_ -bash
root      23025  0.0  0.0  18628  2280 pts/0    R    20:28   0:00          \_ -bash
root      30050  0.0  0.0  18628   512 pts/0    R    20:28   0:00          |   \_ -bash
root      30054  0.0  0.0  18628   512 pts/0    R    20:28   0:00          |   \_ -bash
root      23033  0.0  0.0  18628  1788 pts/0    R    20:28   0:00          \_ -bash
root      31935  0.0  0.0  18628   508 pts/0    R    20:28   0:00          |   \_ -bash

Подведение итогов

Как видите, сам Kubernetes просто передаёт все лимиты в Docker и он уже самостоятельно следит, чтобы они не были привышены.

Таким образом, превышение CPU выше установленного невозможно и приложения будут довольствоваться только имеющимся лимитами CPU

В свою очередь, превышение RAM не позволит больше выделять памяти новым или существующим процессам, однако приложения, которые успели отхватить себе кусок ОЗУ работают штатно. Тестируем Requests

Requests никак не повлияют на уже запущенный контейнер, однако могут очень сильно повлиять на момент запуска контейнера (т.н. планирование).

Приведём пример, у нас есть один Namespace, ограниченный по CPU и RAM.

kubectl describe namespaces sfo-hightest 
Resource Quotas
 Name:            sfoteam-quota
 Resource         Used   Hard
 --------         ---    ---
 limits.cpu       2      16
 limits.memory    2Gi    64Gi
 requests.cpu     100m   2
 requests.memory  512Mi  4Gi

То есть мы имеет request лимит в 2 CPU и 4Gi RAM

Если вы не используйте лимиты в Namespace’ах, то за Request-лимит будет браться сумма всех CPU и RAM(включая SWAP) кластера. То есть, если вы имеете 3 Worker’а по 4 CPU и 16Gi RAM - Ваш request-лимит будет задан на 12 CPU и 48Gi RAM, даже если Вы сами об этом не знаете.

Попробуем увеличить request-потребление - запустим 3 контейнера с большими request-лимитами

vim sshd.yml 
---
...
  replicas: 3
...
        resources: 
          requests:
            memory: "1000Mi"
            cpu: "500m"
          limits:
            memory: "12048Mi"
            cpu: "2"
...

kubectl apply -f sshd.yml 

Смотрим

kubectl describe namespaces sfo-hightest 
Resource Quotas
 Name:            sfoteam-quota
 Resource         Used     Hard
 --------         ---      ---
 limits.cpu       6        16
 limits.memory    36144Mi  64Gi
 requests.cpu     1500m    2
 requests.memory  3000Mi   4Gi

kubectl -n sfo-hightest get pods
NAME                    READY   STATUS    RESTARTS   AGE
sshd-778fb9948d-krz2d   1/1     Running   0          3m50s
sshd-778fb9948d-m7sxq   1/1     Running   0          3m50s
sshd-778fb9948d-vblnb   1/1     Running   0          3m50s

3 контейнера запущены. Используемые request-лимиты не привысили заданных лимитов кластере, поэтому всё успешно запустилось. Тут каждый контейнер запросил у Kubernetes по 0.5 CPU и 1Gi RAM

Запустим 4 контейнера

kubectl describe namespaces sfo-hightest 
Resource Quotas
 Name:            sfoteam-quota
 Resource         Used     Hard
 --------         ---      ---
 limits.cpu       8        16
 limits.memory    48192Mi  64Gi
 requests.cpu     2        2
 requests.memory  4000Mi   4Gi

kubectl -n sfo-hightest get pods
NAME                    READY   STATUS    RESTARTS   AGE
sshd-778fb9948d-krz2d   1/1     Running   0          5m56s
sshd-778fb9948d-m7sxq   1/1     Running   0          5m56s
sshd-778fb9948d-vblnb   1/1     Running   0          5m56s
sshd-778fb9948d-zbnvz   1/1     Running   0          51s

Request достигли лимитов, но не привысили их, а значит 4-ый под доразвернулся.

Пробуем 5 контейнеров

kubectl describe namespaces sfo-hightest 
Resource Quotas
 Name:            sfoteam-quota
 Resource         Used     Hard
 --------         ---      ---
 limits.cpu       8        16
 limits.memory    48192Mi  64Gi
 requests.cpu     2        2
 requests.memory  4000Mi   4Gi

kubectl -n sfo-hightest get pods
NAME                    READY   STATUS    RESTARTS   AGE
sshd-778fb9948d-krz2d   1/1     Running   0          7m23s
sshd-778fb9948d-m7sxq   1/1     Running   0          7m23s
sshd-778fb9948d-vblnb   1/1     Running   0          7m23s
sshd-778fb9948d-zbnvz   1/1     Running   0          2m18s

Ничего не изменилось. Случилось это потому-что для нового контейнера не нашлось свободных ресурсов и Kubernetes ему просто нигде не разворачивает. При этом по факту limits-ресурсов (и тем более самого кластера) ещё достаточно.

При этом нигде явно не сообщается, что “у Вас превышение кворы и поэтому мы Вам 5-ый под не развернём” - просто разворачивает столько контейнеров, сколько получится и всё.