Kubernetes спроектирован так, чтобы быть надежным и устойчивым к сбоям, а также иметь возможность автоматически восстанавливаться. И он отлично справляется со всем этим! Однако рабочие узлы могут по разным причинам терять подключение к кластеру или выходить из строя. В этих случаях необходимо, чтобы Kubernetes быстро среагировал на инцидент.
Когда узел выходит из строя, pods сломанного узла все еще работают в течение некоторого времени. При этом они продолжают получать запросы, и эти запросы фейлятся. Скорее всего, совсем не то поведение, которое вы ожидали от Kubernetes, верно?
Чтобы разобраться, как Kubernetes реагирует на выход узла из строя, сначала рассмотрим взаимодействие между Kubelet и Controller Manager:
Kubelet периодически уведомляет kube-apiserver о своём статусе с интервалом, заданным в параметре
--node-status-update-frequency
. Значение по умолчанию 10 секунд.Controller manager проверяет статус Kubelet каждые
–-node-monitor-period
. Значение по умолчанию 5 секунд.Если от Kubelet получена информация в пределах
--node-monitor-grace-period
, Controller manager считает Kubelet исправным. Значение по умолчанию 40 секунд.
В случае отказа узла кластера происходит следующий алгоритм:
Kubelet отправляет свой статус kube-apiserver, используя -
node-status-update-frequency
= 10 сек.Узел выходит из строя.
Controller manager будет пытаться проверять статус узла, сообщаемый Kubelet, каждые
--node-monitor-period
= 5 сек.Controller manager увидит, что узел не отвечает, и даст ему тайм-аут
--node-monitor-grace-period
в 40 сек. Если за это время Controller manager не сочтет узел исправным, он установит статус NotReady.Kube Proxy удалит endpoints, указывающие на pods внутри этого узла из всех сервисов, поэтому pods сбойного узла больше не будут доступны.
В этом сценарии будет возможны ошибки при обращении в pods, работающим на этом узле, потому что модули будут продолжать получать трафик до тех пор, пока узел не будет считаться неработающим (NotReady) через 45 сек.
Есть множество параметров для настройки в Kubelet и Controller Manager.
Быстрое обновление и быстрая реакция
Чтобы увеличить скорость реакции Kubernetes на отказ узлов кластера, вы можете изменить эти параметры:
-–node-status-update-frequency
установить значение 1 сек (по умолчанию 10 сек)
--node-monitor-period
установить значение 1 сек (по умолчанию 5 сек )
--node-monitor-grace-period
установить значение 4 сек (по умолчанию 40 сек)
Протестируем изменения
Чтобы проверить изменения в тестовой среде, мы можем создать кластер Kubernetes с помощью Kind или любого другого инструмента. Мы создали конфигурационный файл для Kind Cluster с параметрами, указанными в предыдущем разделе, чтобы протестировать поведение кластера.
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
kubeadmConfigPatches:
- |
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
nodeStatusUpdateFrequency: 1s
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
controllerManager:
extraArgs:
node-monitor-period: 1s
node-monitor-grace-period: 4s
- role: worker
Затем мы устанавливаем deployment с двумя репликами Nginx, размещенными в control-plane и на worker. Также мы дополнительно создали на control-plane pod с Ubuntu, чтобы проверить доступность Nginx, когда worker станет недоступен.
#!/bin/bash
# create a K8S cluster with Kind
kind create cluster --config kind.yaml
# create a Ubuntu pod in control-plane Node
kubectl run ubuntu --wait=true --image ubuntu --overrides='{"spec": { "nodeName": "kind-control-plane"}}' sleep 30d
# untaint control-plane node in order to schedule pods on it
kubectl taint node kind-control-plane node-role.kubernetes.io/master-
# create Nginx deployment with 2 replicas, one on each node
kubectl create deploy ng --image nginx
sleep 30
kubectl scale deployment ng --replicas 2
# expose Nginx deployment so that is reachable on port 80
kubectl expose deploy ng --port 80 --type ClusterIP
# install curl in Ubuntu pod
kubectl exec ubuntu -- bash -c "apt update && apt install -y curl"
Чтобы проверить доступность Nginx, мы обратились к сервису с помощью curl из pod с Ubuntu, размещенного в control-plane, а также наблюдали за endpoints, принадлежащими сервису Nginx из терминала.
# test Nginx service access from Ubuntu pod
kubectl exec ubuntu -- bash -c 'while true ; do echo "$(date +"%T.%3N") - Status: $(curl -s -o /dev/null -w "%{http_code}" -m 0.2 -i ng)" ; done'
# show Nginx service endpoints
while true; do gdate +"%T.%3N"; kubectl get endpoints ng -o json | jq '.subsets' | jq '.[] | .addresses' | jq '.[] | .nodeName'; echo "------";done
Наконец, чтобы смоделировать сбой узла, мы остановили контейнер Kind, в котором запущен рабочий узел. Мы также добавили отметки времени, чтобы узнать когда узел был отключен и когда узел был обнаружен как NotReady.
#!/bin/bash
# kill Kind worker node
echo "Worker down at $(gdate +"%T.%3N")"
docker stop kind-worker > /dev/null
sleep 15
# show when the node was detected to be down
echo "Worker detected in down state by Control Plane at "
kubectl get event --field-selector reason=NodeNotReady --sort-by='.lastTimestamp' -oyaml | grep time | tail -n1
# start worker node again
docker start kind-worker > /dev/null
После запуска теста мы заметили, что узел отключился в 12:50:22, а Controller manager обнаружил, что он отключился в 12:50:26, что и следовало ожидать через 4 секунды.
Worker down at 12:50:22.285
Worker detected in down state by Control Plane at
time: "12:50:26Z"
Аналогичный результат при тестировании с терминала. Служба начала возвращать сообщения об ошибках в 12:50:23, потому что трафик был направлен на отказавший узел. А в 12:50:26.744 Kube Proxy удалил endpoint, указывающую на отказавший узел, и доступность службы была полностью восстановлена.
...
12:50:23.115 - Status: 200
12:50:23.141 - Status: 200
12:50:23.161 - Status: 200
12:50:23.190 - Status: 000
12:50:23.245 - Status: 200
12:50:23.269 - Status: 200
12:50:23.291 - Status: 000
12:50:23.503 - Status: 200
12:50:23.520 - Status: 000
12:50:23.738 - Status: 000
12:50:23.954 - Status: 000
12:50:24.166 - Status: 000
12:50:24.385 - Status: 200
12:50:24.407 - Status: 000
12:50:24.623 - Status: 000
12:50:24.839 - Status: 000
12:50:25.053 - Status: 000
12:50:25.276 - Status: 200
12:50:25.294 - Status: 000
12:50:25.509 - Status: 200
12:50:25.525 - Status: 200
12:50:25.541 - Status: 200
12:50:25.556 - Status: 200
12:50:25.575 - Status: 000
12:50:25.793 - Status: 200
12:50:25.809 - Status: 200
12:50:25.826 - Status: 200
12:50:25.847 - Status: 200
12:50:25.867 - Status: 200
12:50:25.890 - Status: 000
12:50:26.110 - Status: 000
12:50:26.325 - Status: 000
12:50:26.549 - Status: 000
12:50:26.604 - Status: 200
12:50:26.669 - Status: 000
12:50:27.108 - Status: 200
12:50:27.135 - Status: 200
12:50:27.162 - Status: 200
12:50:27.188 - Status: 200
...
...
------
12:50:26.523
"kind-control-plane"
"kind-worker"
------
12:50:26.618
"kind-control-plane"
"kind-worker"
------
12:50:26.744
"kind-control-plane"
------
12:50:26.878
"kind-control-plane"
------
...
Заключение
Мы убедились, что скорость реакции Kubernetes на инцидент значительно возросла. Возможны разные комбинации параметров для конкретных случаев, и у вас может возникнуть соблазн снизить значения, чтобы система Kubernetes реагировала быстрее, но примите во внимание, что этот сценарий создает накладные расходы на etcd, поскольку каждый узел будет постоянно пытаться обновлять свой статус через 1 секунду. Например, если в кластере 1000 узлов, будет происходить 60000 обновлений узлов в минуту, что может потребовать увеличения ресурсов контейнеров etcd или даже выделенных узлов для etcd.
Кроме того, если вы установите значения параметров слишком маленькими, возникнут некоторые риски. Например, временный сбой сети на короткий период может привести к ложному срабатыванию.