1 - Задание ресурсов памяти для контейнеров и Pod'ов

На этой странице рассказывается, как настраивать запрос памяти и её лимит для контейнеров. Контейнеру гарантируется столько памяти, сколько он запросит, но не больше установленных ограничений.

Подготовка к работе

Вам нужен Kubernetes кластер и инструмент командной строки kubectl должен быть настроен на связь с вашим кластером. Если у вас ещё нет кластера, вы можете создать, его используя Minikube, или вы можете использовать одну из песочниц Kubernetes:

Чтобы проверить версию, введите kubectl version.

Каждая нода вашего кластера должна располагать хотя бы 300 Мб памяти.

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

Если вы используете Minikube, выполните следующую команду, чтобы запустить сервер метрик:

minikube addons enable metrics-server

Чтобы проверить работу сервера меток или другого провайдера API ресурсов метрик (metrics.k8s.io), запустите команду:

kubectl get apiservices

Если API ресурсов метрики доступно, в выводе команды будет содержаться ссылка на metrics.k8s.io.

NAME      
v1beta1.metrics.k8s.io

Создание пространства имён

Создадим пространство имён, чтобы ресурсы, которыми будем пользоваться в данном упражнении, были изолированы от остального кластера:

kubectl create namespace mem-example

Установка запроса памяти и лимита памяти

Для установки запроса памяти контейнеру подключите поле resources:requests в манифест ресурсов контейнера. Для ограничений по памяти - добавьте resources:limits.

В этом упражнении создаётся Pod, содержащий один контейнер. Зададим контейнеру запрос памяти в 100 Мб и её ограничение в 200 Мб. Конфигурационный файл для Pod'а:

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo
  namespace: mem-example
spec:
  containers:
  - name: memory-demo-ctr
    image: polinux/stress
    resources:
      limits:
        memory: "200Mi"
      requests:
        memory: "100Mi"
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]

Раздел args конфигурационного файла содержит аргументы для контейнера в момент старта. Аргументы "--vm-bytes", "150M" указывают контейнеру попытаться занять 150 Мб памяти.

Создадим Pod:

kubectl apply -f https://k8s.io/examples/pods/resource/memory-request-limit.yaml --namespace=mem-example

Убедимся, что контейнер Pod'a запущен:

kubectl get pod memory-demo --namespace=mem-example

Посмотрим подробную информацию о Pod'е:

kubectl get pod memory-demo --output=yaml --namespace=mem-example

В выводе мы видим, что для контейнера в Pod'е зарезервировано 100 Мб памяти и выставлено 200 Мб ограничения.

...
resources:
  limits:
    memory: 200Mi
  requests:
    memory: 100Mi
...

Запустим kubectl top, чтобы получить метрики Pod'a:

kubectl top pod memory-demo --namespace=mem-example

Вывод команды показывает, что Pod использовал примерно 162900000 байт памяти - и это около 150 Мб. Данная величина больше установленного запроса в 100 Мб, но укладывается в имеющееся ограничение на 200 Мб.

NAME                        CPU(cores)   MEMORY(bytes)
memory-demo                 <something>  162856960

Удалим Pod:

kubectl delete pod memory-demo --namespace=mem-example

Превышение контейнером лимита памяти

Контейнер может превысить величину запроса памяти, если нода имеет достаточно ресурсов памяти. Но превышение заданного ограничения памяти не допускается. Если контейнер запрашивает больше памяти, чем ему разрешено использовать, то он становится кандидатом на удаление. Если превышение лимита памяти продолжится, контейнер удаляется. Если удалённый контейнер может быть перезапущен, то kubelet перезапускает его, как и в случае любой другой неполадки в работе.

В этом упражнении создадим Pod, который попытается занять больше памяти, чем для него ограничено. Ниже представлен конфигурационный файл для Pod'a с одним контейнером, имеющим 50 Мб на запрос памяти и 100 Мб лимита памяти:

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo-2
  namespace: mem-example
spec:
  containers:
  - name: memory-demo-2-ctr
    image: polinux/stress
    resources:
      requests:
        memory: "50Mi"
      limits:
        memory: "100Mi"
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]

В разделе args можно увидеть, что контейнер будет пытаться занять 250 Мб - и это значительно превышает лимит в 100 Мб.

Создадим Pod:

kubectl apply -f https://k8s.io/examples/pods/resource/memory-request-limit-2.yaml --namespace=mem-example

Посмотрим подробную информацию о Pod'е:

kubectl get pod memory-demo-2 --namespace=mem-example

В этот момент контейнер уже либо запущен, либо убит. Будем повторять предыдущую команду, пока контейнер не окажется убитым:

NAME            READY     STATUS      RESTARTS   AGE
memory-demo-2   0/1       OOMKilled   1          24s

Посмотрим ещё более подробный вид статуса контейнера:

kubectl get pod memory-demo-2 --output=yaml --namespace=mem-example

В выводе показано, что контейнер был убит по причине недостатка памяти (OOM):

lastState:
   terminated:
     containerID: docker://65183c1877aaec2e8427bc95609cc52677a454b56fcb24340dbd22917c23b10f
     exitCode: 137
     finishedAt: 2017-06-20T20:52:19Z
     reason: OOMKilled
     startedAt: null

В данном упражнении контейнер может быть перезапущен, поэтому kubelet стартует его. Выполните следующую команду несколько раз, чтобы увидеть, как контейнер раз за разом убивается и запускается снова:

kubectl get pod memory-demo-2 --namespace=mem-example

Вывод показывает, что контейнер убит, перезапущен, снова убит, перезапущен, и т.д.:

kubectl get pod memory-demo-2 --namespace=mem-example
NAME            READY     STATUS      RESTARTS   AGE
memory-demo-2   0/1       OOMKilled   1          37s

kubectl get pod memory-demo-2 --namespace=mem-example
NAME            READY     STATUS    RESTARTS   AGE
memory-demo-2   1/1       Running   2          40s

Посмотрим подробную информацию об истории Pod'a:

kubectl describe pod memory-demo-2 --namespace=mem-example

Вывод показывает, что контейнер постоянно запускается и падает:

... Normal  Created   Created container with id 66a3a20aa7980e61be4922780bf9d24d1a1d8b7395c09861225b0eba1b1f8511
... Warning BackOff   Back-off restarting failed container

Посмотрим детальную информацию о нодах на кластере:

kubectl describe nodes

В выводе содержится запись о том, что контейнер убивается по причине нехватки памяти:

Warning OOMKilling Memory cgroup out of memory: Kill process 4481 (stress) score 1994 or sacrifice child

Удалим Pod:

kubectl delete pod memory-demo-2 --namespace=mem-example

Установка слишком большого для нод запроса памяти

Запросы и ограничения памяти связаны с контейнерами, но полезно также рассматривать эти параметры и для Pod'а. Запросом памяти для Pod'a будет сумма всех запросов памяти контейнеров, имеющихся в Pod'е. Также и лимитом памяти будет сумма всех ограничений, установленных для контейнеров.

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

В данном упражнении мы создадим Pod, чей запрос памяти будет превышать ёмкость любой ноды в кластере. Ниже представлен конфигурационный файл для Pod'a с одним контейнером, имеющим запрос памяти в 1000 Гб (что наверняка превышает ёмкость любой имеющейся ноды):

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo-3
  namespace: mem-example
spec:
  containers:
  - name: memory-demo-3-ctr
    image: polinux/stress
    resources:
      limits:
        memory: "1000Gi"
      requests:
        memory: "1000Gi"
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]

Создадим Pod:

kubectl apply -f https://k8s.io/examples/pods/resource/memory-request-limit-3.yaml --namespace=mem-example

Проверим статус Pod'a:

kubectl get pod memory-demo-3 --namespace=mem-example

Вывод показывает, что Pod имеет статус PENDING. Это значит, что он не запланирован ни на одной ноде, и такой статус будет сохраняться всё время:

kubectl get pod memory-demo-3 --namespace=mem-example
NAME            READY     STATUS    RESTARTS   AGE
memory-demo-3   0/1       Pending   0          25s

Посмотрим подробную информацию о Pod'е, включающую события:

kubectl describe pod memory-demo-3 --namespace=mem-example

Вывод показывает невозможность запуска контейнера из-за нехватки памяти на нодах:

Events:
  ...  Reason            Message
       ------            -------
  ...  FailedScheduling  No nodes are available that match all of the following predicates:: Insufficient memory (3).

Удалим Pod:

kubectl delete pod memory-demo-3 --namespace=mem-example

Единицы измерения памяти

Ресурсы памяти измеряются в байтах. Их можно задавать просто целым числом либо целым числом с одним из следующих окончаний: E, P, T, G, M, K, Ei, Pi, Ti, Gi, Mi, Ki. Например, представленные здесь варианты задают приблизительно одну и ту же величину:

128974848, 129e6, 129M , 123Mi

Если лимит памяти не задан

Если вы не задали ограничение памяти для контейнера, возможны следующие варианты:

  • У контейнера отсутствует верхняя граница для памяти, которую он может использовать. Такой контейнер может занять всю память, доступную на ноде, где он запущен, что, в свою очередь, может вызвать OOM Killer. Также контейнеры без ограничений по ресурсам имеют более высокие шансы быть убитыми в случае вызова OOM Kill.

  • Контейнер запущен в пространстве имён, в котором настроена величина ограничений по умолчанию. Тогда контейнеру автоматически присваивается это стандартное значение лимита. Администраторы кластера могут использовать LimitRange для задания стандартной величины ограничений по памяти.

Мотивация для использования запросов и ограничений памяти

При помощи задания величины запросов и лимитов памяти для контейнеров, запущенных на вашем кластере, можно эффективно распоряжаться имеющимися на нодах ресурсами. Задание Pod'у небольшого запроса памяти даёт хорошие шансы для него быть запланированным. Ограничение памяти, превышающее величину запроса памяти, позволяет достичь 2 вещей:

  • Pod может иметь всплески активности, в течение которых ему может потребоваться дополнительная память.

  • Величина памяти, доступная Pod'у при повышении активности, ограничена некоторой разумной величиной.

Очистка

Удалим пространство имён. Эта операция удалит все Pod'ы, созданные в рамках данного упражнения:

kubectl delete namespace mem-example

Что дальше

Для разработчиков приложений

Для администраторов кластера

2 - Задание ресурсов CPU для контейнеров и Pod'ов

На этой странице показывается, как настроить запрос CPU и лимит CPU для контейнера. Контейнер не сможет использовать больше ресурсов CPU, чем для него ограничено. Если в системе есть свободное время CPU, контейнеру гарантируется выдача запрошенных им ресурсов CPU.

Подготовка к работе

Вам нужен Kubernetes кластер и инструмент командной строки kubectl должен быть настроен на связь с вашим кластером. Если у вас ещё нет кластера, вы можете создать, его используя Minikube, или вы можете использовать одну из песочниц Kubernetes:

Чтобы проверить версию, введите kubectl version.

На кластере должен быть хотя бы 1 доступный для работы CPU, чтобы запускать учебные примеры.

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

Если вы используете Minikube, выполните следующую команду, чтобы запустить сервер метрик:

minikube addons enable metrics-server

Проверим, работает ли сервер метрик (или другой провайдер API ресурсов метрик, metrics.k8s.io), выполните команду:

kubectl get apiservices

Если API ресурсов метрик доступно, в выводе будет присутствовать ссылка на metrics.k8s.io.

NAME
v1beta1.metrics.k8s.io

Создание пространства имён

Создадим Namespace, чтобы создаваемые в этом упражнении ресурсы были изолированы от остального кластера.

kubectl create namespace cpu-example

Установка запроса CPU и лимита CPU

Чтобы установить запрос CPU для контейнера, подключите поле resources:requests в манифест ресурсов контейнера. Для установки ограничения по CPU подключите resources:limits.

В этом упражнении мы создадим Pod, имеющий один контейнер. Зададим для контейнера запрос в 0.5 CPU и лимит в 1 CPU. Конфигурационный файл для такого Pod'а:

apiVersion: v1
kind: Pod
metadata:
  name: cpu-demo
  namespace: cpu-example
spec:
  containers:
  - name: cpu-demo-ctr
    image: vish/stress
    resources:
      limits:
        cpu: "1"
      requests:
        cpu: "0.5"
    args:
    - -cpus
    - "2"

Раздел args конфигурационного файла содержит аргументы для контейнера в момент старта. Аргумент -cpus "2" говорит контейнеру попытаться использовать 2 CPU.

Создадим Pod:

kubectl apply -f https://k8s.io/examples/pods/resource/cpu-request-limit.yaml --namespace=cpu-example

Удостоверимся, что Pod запущен:

kubectl get pod cpu-demo --namespace=cpu-example

Посмотрим детальную информацию о Pod'е:

kubectl get pod cpu-demo --output=yaml --namespace=cpu-example

В выводе видно, что Pod имеет один контейнер с запросом в 500 милли-CPU и с ограничением в 1 CPU.

resources:
  limits:
    cpu: "1"
  requests:
    cpu: 500m

Запустим kubectl top, чтобы получить метрики Pod'a:

kubectl top pod cpu-demo --namespace=cpu-example

В этом варианте вывода Pod'ом использовано 974 милли-CPU, что лишь чуть меньше заданного в конфигурации Pod'a ограничения в 1 CPU.

NAME                        CPU(cores)   MEMORY(bytes)
cpu-demo                    974m         <something>

Напомним, что установкой параметра -cpu "2" для контейнера было задано попытаться использовать 2 CPU, однако в конфигурации присутствует ограничение всего в 1 CPU. Использование контейнером CPU было отрегулировано, поскольку он попытался занять больше ресурсов, чем ему позволено.

Заметка: Другое возможное объяснение для выделения менее 1.0 CPU в отсутствии на ноде достаточного количества свободных CPU ресурсов. Напомним, что в начальных условиях для этого упражнения было наличие у кластера хотя бы 1 CPU, доступного для использования. Если контейнер запущен на ноде, имеющей в своём распоряжении всего 1 CPU, контейнер не сможет использовать более 1 CPU независимо от заданных для него ограничений.

Удалим Pod:

kubectl delete pod cpu-demo --namespace=cpu-example

Единицы измерения CPU

Ресурсы CPU измеряются в CPU единицах. Один CPU, в Kubernetes, соответствует:

  • 1 AWS vCPU
  • 1 GCP Core
  • 1 Azure vCore
  • 1 гипертрединговое ядро на физическом процессоре Intel с Гипертредингом

Дробные значения возможны. Контейнер, запрашивающий 0.5 CPU, получит вполовину меньше ресурсов, чем контейнер, запрашивающий 1 CPU. Можно использовать окончание m для обозначения милли. Например, 100m CPU, 100 milliCPU и 0.1 CPU обозначают одно и то же. Точность выше 1m не поддерживается.

CPU всегда запрашивается в абсолютных величинах, не в относительных; 0.1 будет одинаковой частью от CPU для одноядерного, двухъядерного или 48-ядерного процессора.

Запрос ресурсов CPU больше доступного на ноде

Запросы и лимиты CPU устанавливаются для контейнеров, но также полезно рассматривать и Pod имеющим эти характеристики. Запросом CPU для Pod'а является сумма запросов CPU всех его контейнеров. Аналогично и лимит CPU для Pod'а - сумма всех ограничений CPU у его контейнеров.

Планирование Pod'а основано на запросах. Pod попадает в расписание запуска на ноде лишь в случае достаточного количества доступных ресурсов CPU на ноде, чтобы удовлетворить запрос CPU Pod'а.

В этом упражнении мы создадим Pod с запросом CPU, превышающим мощности любой ноды в вашем кластере. Ниже представлен конфигурационный файл для Pod'а с одним контейнером. Контейнер запрашивает 100 CPU, что почти наверняка превышет имеющиеся мощности любой ноды в кластере.

apiVersion: v1
kind: Pod
metadata:
  name: cpu-demo-2
  namespace: cpu-example
spec:
  containers:
  - name: cpu-demo-ctr-2
    image: vish/stress
    resources:
      limits:
        cpu: "100"
      requests:
        cpu: "100"
    args:
    - -cpus
    - "2"

Создадим Pod:

kubectl apply -f https://k8s.io/examples/pods/resource/cpu-request-limit-2.yaml --namespace=cpu-example

Проверим статус Pod'а:

kubectl get pod cpu-demo-2 --namespace=cpu-example

Вывод показывает Pending статус у Pod'а. То есть Pod не запланирован к запуску ни на одной ноде и будет оставаться в статусе Pending постоянно:

NAME         READY     STATUS    RESTARTS   AGE
cpu-demo-2   0/1       Pending   0          7m

Посмотрим подробную информацию о Pod'е, включающую в себя события:

kubectl describe pod cpu-demo-2 --namespace=cpu-example

В выводе отражено, что контейнер не может быть запланирован из-за нехватки ресурсов CPU на нодах:

Events:
  Reason                        Message
  ------                        -------
  FailedScheduling      No nodes are available that match all of the following predicates:: Insufficient cpu (3).

Удалим Pod:

kubectl delete pod cpu-demo-2 --namespace=cpu-example

Если ограничения на CPU не заданы

Если ограничения на использование контейнером CPU не установлены, возможны следующие варианты:

  • У контейнера отсутствует верхняя граница количества CPU доступных ему ресурсов. В таком случае он может занять все ресурсы CPU, доступные на ноде, на которой он запущен.

  • Контейнер запущен в пространстве имён, в котором задана стандартная величина ограничения ресурсов CPU. Тогда контейнеру автоматически присваивается это ограничение. Администраторы кластера могут использовать LimitRange, чтобы задать стандартную величину ограничения ресурсов CPU.

Мотивация для использования запросов и лимитов CPU

Вы можете распоряжаться ресурсами CPU на нодах вашего кластера эффективнее, если для запущенных контейнеров установлены запросы и ограничения на использование ресурсов CPU. Задание небольшого запроса CPU даёт Pod'у хорошие шансы быть запланированным. Установка лимита на ресурсы CPU, большего, чем запрос, позволяет достичь 2 вещей:

  • При увеличении нагрузки Pod может задействовать дополнительные ресурсы CPU.
  • Количество ресурсов CPU, которые Pod может задействовать при повышении нагрузки, ограничено некоторой разумной величиной.

Очистка

Удалим созданное для этого упражнения пространство имён:

kubectl delete namespace cpu-example

Что дальше

Для разработчиков приложений

Для администраторов кластера

3 - Настройка Liveness, Readiness и Startup проб

На этой странице рассказывается, как настроить liveness, readiness и startup пробы для контейнеров.

Kubelet использует liveness пробу для проверки, когда перезапустить контейнер. Например, liveness проба должна поймать блокировку, когда приложение запущено, но не может ничего сделать. В этом случае перезапуск приложения может помочь сделать приложение более доступным, несмотря на баги.

Kubelet использует readiness пробы, чтобы узнать, готов ли контейнер принимать траффик. Pod считается готовым, когда все его контейнеры готовы.

Одно из применений такого сигнала - контроль, какие Pod будут использованы в качестве бек-енда для сервиса. Пока Pod не в статусе ready, он будет исключен из балансировщиков нагрузки сервиса.

Kubelet использует startup пробы, чтобы понять, когда приложение в контейнере было запущено. Если проба настроена, он блокирует liveness и readiness проверки, до того как проба становится успешной, и проверяет, что эта проба не мешает запуску приложения. Это может быть использовано для проверки работоспособности медленно стартующих контейнеров, чтобы избежать убийства kubelet'ом прежде, чем они будут запущены.

Подготовка к работе

Вам нужен Kubernetes кластер и инструмент командной строки kubectl должен быть настроен на связь с вашим кластером. Если у вас ещё нет кластера, вы можете создать, его используя Minikube, или вы можете использовать одну из песочниц Kubernetes:

Чтобы проверить версию, введите kubectl version.

Определение liveness команды

Многие приложения, работающие в течение длительного времени, ломаются и могут быть восстановлены только перезапуском. Kubernetes предоставляет liveness пробы, чтобы обнаруживать и исправлять такие ситуации.

В этом упражнении вы создадите Pod, который запускает контейнер, основанный на образе k8s.gcr.io/busybox. Конфигурационный файл для Pod'а:

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

В конфигурационном файле вы можете видеть, что Pod состоит из одного Container. Поле periodSeconds определяет, что kubelet должен производить liveness пробы каждые 5 секунд. Поле initialDelaySeconds говорит kubelet'у, что он должен ждать 5 секунд перед первой пробой. Для проведения пробы kubelet исполняет команду cat /tmp/healthy в целевом контейнере. Если команда успешна, она возвращает 0, и kubelet считает контейнер живым и здоровым. Если команда возвращает ненулевое значение, kubelet убивает и перезапускает контейнер.

Когда контейнер запускается, он исполняет команду

/bin/sh -c "touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600"

Для первых 30 секунд жизни контейнера существует файл /tmp/healthy. Поэтому в течение первых 30 секунд команда cat /tmp/healthy возвращает код успеха. После 30 секунд cat /tmp/healthy возвращает код ошибки.

Создание Pod:

kubectl apply -f https://k8s.io/examples/pods/probe/exec-liveness.yaml

В течение 30 секунд посмотрим события Pod:

kubectl describe pod liveness-exec

Вывод команды показывает, что ещё ни одна liveness проба не завалена:

FirstSeen    LastSeen    Count   From            SubobjectPath           Type        Reason      Message
--------- --------    -----   ----            -------------           --------    ------      -------
24s       24s     1   {default-scheduler }                    Normal      Scheduled   Successfully assigned liveness-exec to worker0
23s       23s     1   {kubelet worker0}   spec.containers{liveness}   Normal      Pulling     pulling image "k8s.gcr.io/busybox"
23s       23s     1   {kubelet worker0}   spec.containers{liveness}   Normal      Pulled      Successfully pulled image "k8s.gcr.io/busybox"
23s       23s     1   {kubelet worker0}   spec.containers{liveness}   Normal      Created     Created container with docker id 86849c15382e; Security:[seccomp=unconfined]
23s       23s     1   {kubelet worker0}   spec.containers{liveness}   Normal      Started     Started container with docker id 86849c15382e

После 35 секунд посмотрим события Pod снова:

kubectl describe pod liveness-exec

Внизу вывода появились сообщения, показывающие, что liveness проба завалена и containers был убит и пересоздан.

FirstSeen LastSeen    Count   From            SubobjectPath           Type        Reason      Message
--------- --------    -----   ----            -------------           --------    ------      -------
37s       37s     1   {default-scheduler }                    Normal      Scheduled   Successfully assigned liveness-exec to worker0
36s       36s     1   {kubelet worker0}   spec.containers{liveness}   Normal      Pulling     pulling image "k8s.gcr.io/busybox"
36s       36s     1   {kubelet worker0}   spec.containers{liveness}   Normal      Pulled      Successfully pulled image "k8s.gcr.io/busybox"
36s       36s     1   {kubelet worker0}   spec.containers{liveness}   Normal      Created     Created container with docker id 86849c15382e; Security:[seccomp=unconfined]
36s       36s     1   {kubelet worker0}   spec.containers{liveness}   Normal      Started     Started container with docker id 86849c15382e
2s        2s      1   {kubelet worker0}   spec.containers{liveness}   Warning     Unhealthy   Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory

Подождите ещё 30 секунд и убедитесь, что контейнер был перезапущен:

kubectl get pod liveness-exec

Вывод команды показывает, что RESTARTS увеличено на 1:

NAME            READY     STATUS    RESTARTS   AGE
liveness-exec   1/1       Running   1          1m

Определение liveness HTTP запроса

Другой вид liveness пробы использует запрос HTTP GET. Ниже представлен файл конфигурации для Pod, который запускает контейнер, основанный на образе k8s.gcr.io/liveness.

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-http
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/liveness
    args:
    - /server
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3

В конфигурационном файле вы можете видеть Pod с одним контейнером. Поле periodSeconds определяет, что kubelet должен производить liveness пробы каждые 3 секунды. Поле initialDelaySeconds сообщает kubelet'у, что он должен ждать 3 секунды перед проведением первой пробы. Для проведения пробы kubelet отправляет запрос HTTP GET на сервер, который запущен в контейнере и слушает порт 8080. Если обработчик пути /healthz на сервере возвращает код успеха, kubelet рассматривает контейнер как живой и здоровый. Если обработчик возвращает код ошибки, kubelet убивает и перезапускает контейнер.

Любой код, больший или равный 200 и меньший 400, означает успех. Любой другой код интерпретируется как ошибка.

Вы можете посмотреть исходные коды сервера в server.go.

В течение первых 10 секунд жизни контейнера обработчик /healthz возвращает статус 200. После обработчик возвращает статус 500.

http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
    duration := time.Now().Sub(started)
    if duration.Seconds() > 10 {
        w.WriteHeader(500)
        w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
    } else {
        w.WriteHeader(200)
        w.Write([]byte("ok"))
    }
})

Kubelet начинает выполнять health checks через 3 секунды после старта контейнера. Итак, первая пара проверок будет успешна. Но после 10 секунд health checks будут завалены и kubelet убьёт и перезапустит контейнер.

Чтобы попробовать HTTP liveness проверку, создайте Pod:

kubectl apply -f https://k8s.io/examples/pods/probe/http-liveness.yaml

Через 10 секунд посмотрите события Pod, чтобы проверить, что liveness probes завалилась и container перезапустился:

kubectl describe pod liveness-http

В релизах до v1.13 (включая v1.13), если переменная окружения http_proxy (или HTTP_PROXY) определена на node, где запущен Pod, HTTP liveness проба использует этот прокси. В версиях после v1.13, определение локальной HTTP прокси в переменной окружения не влияет на HTTP liveness пробу.

Определение TCP liveness пробы

Третий тип liveness проб использует TCP сокет. С этой конфигурацией kubelet будет пытаться открыть сокет к вашему контейнеру на определённый порт. Если он сможет установить соединение, контейнер будет считаться здоровым, если нет, будет считаться заваленным.

apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
  - name: goproxy
    image: k8s.gcr.io/goproxy:0.1
    ports:
    - containerPort: 8080
    readinessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20

Как вы можете видеть, конфигурация TCP проверок довольно похожа на HTTP проверки. Этот пример использует обе - readiness и liveness пробы. Kubelet будет отправлять первую readiness пробу через 5 секунд после старта контейнера. Он будет пытаться соединиться с goproxy контейнером на порт 8080. Если проба успешна, Pod будет помечен как ready. Kubelet будет продолжать запускать эту проверку каждые 10 секунд.

В дополнение к readiness пробе, конфигурация включает liveness пробу. Kubelet запустит первую liveness пробу через 15 секунд после старта контейнера. Аналогично readiness пробе, он будет пытаться соединиться с контейнером goproxy на порт 8080. Если liveness проба завалится, контейнер будет перезапущен.

Для испытаний TCP liveness проверки, создадим Pod:

kubectl apply -f https://k8s.io/examples/pods/probe/tcp-liveness-readiness.yaml

Через 15 секунд посмотрим события Pod'а для проверки liveness пробы:

kubectl describe pod goproxy

Использование именованных портов

Вы можете использовать именованный порт ContainerPort для HTTP или TCP liveness проверок:

ports:
- name: liveness-port
  containerPort: 8080
  hostPort: 8080

livenessProbe:
  httpGet:
    path: /healthz
    port: liveness-port

Защита медленно запускающихся контейнеров со startup пробами

Иногда приходится иметь дело со старыми приложениями, которым может требоваться дополнительное время для запуска на их первую инициализацию. В таких случаях бывает сложно настроить параметры liveness пробы без ущерба для скорости реакции на deadlock'и, для выявления которых как раз и нужна liveness проба. Хитрость заключается в том, чтобы настроить startup пробу с такой же командой, что и HTTP или TCP проверка, но failureThreshold * periodSeconds должно быть достаточным, чтобы покрыть наихудшее время старта.

Итак, предыдущий пример будет выглядеть так:

ports:
- name: liveness-port
  containerPort: 8080
  hostPort: 8080

livenessProbe:
  httpGet:
    path: /healthz
    port: liveness-port
  failureThreshold: 1
  periodSeconds: 10

startupProbe:
  httpGet:
    path: /healthz
    port: liveness-port
  failureThreshold: 30
  periodSeconds: 10

Благодаря startup пробе, приложению дано максимум 5 минут (30 * 10 = 300 сек.) для завершения его старта. Как только startup проба успешна 1 раз, liveness проба начинает контролировать дедлоки контейнеров. Если startup probe так и не заканчивается успехом, контейнер будет убит через 300 секунд и подвергнется restartPolicy pod'а.

Определение readiness проб

Иногда приложения временно не могут обслужить траффик. Например, приложение может требовать загрузки огромных данных или конфигурационных файлов во время старта, или зависит от внешних сервисов после старта. В таких случаях вы не хотите убивать приложение, но и отправлять ему клиентские запросы тоже не хотите. Kubernetes предоставляет readiness пробы для определения и нивелирования таких ситуаций. Pod с контейнерами сообщает, что они не готовы принимать траффик через Kubernetes Services.

Заметка: Readiness пробы запускаются на контейнере в течение всего его жизненного цикла.

Readiness пробы настраиваются аналогично liveness пробам. Единственная разница в использовании поля readinessProbe вместо livenessProbe.

readinessProbe:
  exec:
    command:
    - cat
    - /tmp/healthy
  initialDelaySeconds: 5
  periodSeconds: 5

Конфигурация HTTP и TCP readiness проб также идентичны liveness пробам.

Readiness и liveness пробы могут быть использованы одновременно на одном контейнере. Использование обеих проб может обеспечить отсутствие траффика в контейнер, пока он не готов для этого, и контейнер будет перезапущен, если сломается.

Конфигурация проб

Probes имеют несколько полей, которые вы можете использовать для более точного контроля поведения liveness и readiness проверок:

  • initialDelaySeconds: Количество секунд от старта контейнера до начала liveness или readiness проб. По умолчанию 0 секунд. Минимальное значение 0.
  • periodSeconds: Длительность времени (в секундах) между двумя последовательными проведениями проб. По умолчанию 10 секунд. Минимальное значение 1.
  • timeoutSeconds: Количество секунд ожидания пробы. По умолчанию 1 секунда. Минимальное значение 1.
  • successThreshold: Минимальное количество последовательных проверок, чтобы проба считалась успешной после неудачной. По умолчанию 1. Должна быть 1 для liveness. Минимальное значение 1.
  • failureThreshold: Когда Pod стартует и проба даёт ошибку, Kubernetes будет пытаться failureThreshold раз перед тем, как сдаться. Сдаться в этом случае для liveness пробы означает перезапуск контейнера. В случае readiness пробы Pod будет помечен Unready. По умолчанию 3. Минимальное значение 1.

HTTP пробы имеют дополнительные поля, которые могут быть установлены для httpGet:

  • host: Имя хоста для соединения, по умолчанию pod IP. Вы, возможно, захотите установить заголовок "Host" в httpHeaders вместо этого.
  • scheme: Схема для соединения к хосту (HTTP or HTTPS). По умолчанию HTTP.
  • path: Путь для доступа к HTTP серверу.
  • httpHeaders: Кастомные заголовки запроса. HTTP позволяет повторяющиеся заголовки.
  • port: Имя или номер порта для доступа к контейнеру. Номер должен быть в диапазоне от 1 до 65535.

Для HTTP проб kubelet отправляет HTTP запрос на настроенный путь и порт для проведения проверок. Kubelet отправляет пробу на IP адрес pod’а, если адрес не переопределён необязательным полем host в httpGet. Если поле scheme установлено в HTTPS, kubelet отправляет HTTPS запрос, пропуская проверку сертификата. В большинстве сценариев вам не нужно устанавливать поле host. Рассмотрим один сценарий, где бы он мог понадобиться. Допустим, контейнер слушает 127.0.0.1 и поле Pod'а hostNetwork задано в true. Поле host опции httpGet должно быть задано в 127.0.0.1. Если pod полагается на виртуальные хосты, что, скорее всего, более распространённая ситуация, лучше вместо поля host устанавливать заголовок Host в httpHeaders.

Для TCP проб kubelet устанавливает соединение с ноды, не внутри pod, что означает, что вы не можете использовать service name в параметре host, пока kubelet не может выполнить его резолв.

Что дальше

Вы также можете прочитать API references для: