Даниэле Поленчич - технический консультант learnk8s.io и сертифицированный партнер по обучению Kubernetes и Linux Foundation. 5 ноября 2018 года он представит Масштабирование микросервисов с помощью очередей сообщений, Spring Boot и Kubernetes на µCon London 2018. Его следующий курс на Skills Matter - Kubernetes: Master Application Deployment and Scaling 13-14 декабря 2018 года.

Когда вы развертываете приложение в Kubernetes, ваш код запускается на одном или нескольких рабочих узлах. Узел может быть физической машиной или виртуальной машиной, такой как AWS EC2 или Google Compute Engine, и наличие нескольких из них означает, что вы можете эффективно запускать и масштабировать свое приложение между экземплярами. Если у вас есть кластер, состоящий из трех узлов, и вы решили масштабировать свое приложение, чтобы иметь четыре реплики, Kubernetes равномерно распределит реплики по узлам следующим образом:

Описанная выше архитектура особенно хорошо работает в случае сбоев. Если первый узел будет недоступен, два других по-прежнему смогут обслуживать приложение. Между тем у Kubernetes есть достаточно времени, чтобы перенести четвертую реплику на другой узел.

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

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

В Kubernetes есть двоичный файл kube-proxy, который запускается на каждом узле и отвечает за маршрутизацию трафика к определенному модулю. Вы можете думать о kube-proxy как о секретарше. Прокси-сервер перехватывает весь трафик, направленный на узел, и направляет его в правый модуль.

Но как kube-proxy узнать, где находятся все капсулы?

Это не так.

Главный узел знает все и отвечает за создание списка со всеми правилами маршрутизации. kube-proxy отвечает за проверку и соблюдение правил в списке. В приведенном выше простом сценарии список выглядит так:

  • Экземпляр приложения 1 доступен на узле 1
  • Экземпляр приложения 2 доступен на узле 2

Неважно, с какого узла идет трафик; kube-proxy знает, куда должен быть направлен трафик, просматривая список.

Но что происходит, когда kube-proxy вылетает?

А что, если список правил утерян?

Что произойдет, если нет правила для перенаправления трафика?

У Манабу Сакаи были те же вопросы. Поэтому он решил выяснить.

Предположим, у вас есть двухузловой кластер на GCP:

$ kubectl get nodes
NAME        STATUS  ROLES   AGE VERSION
node1       Ready   <none>  17h v1.8.8-gke.0
node2       Ready   <none>  18h v1.8.8-gke.0

И вы развернули приложение Manabu с:

$ kubectl create -f https://raw.githubusercontent.com/manabusakai/k8s-hello-world/master/kubernetes/deployment.yml
$ kubectl create -f https://raw.githubusercontent.com/manabusakai/k8s-hello-world/master/kubernetes/service.yml

Приложение простое. Он отображает имя хоста текущего модуля на веб-странице:

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

$ kubectl scale --replicas 10 deployment/k8s-hello-world

Десять реплик равномерно распределены по двум узлам:

$ kubectl get pods
NAME                              READY STATUS  NODE
k8s-hello-world-55f48f8c94-7shq5  1/1   Running node1
k8s-hello-world-55f48f8c94-9w5tj  1/1   Running node1
k8s-hello-world-55f48f8c94-cdc64  1/1   Running node2
k8s-hello-world-55f48f8c94-lkdvj  1/1   Running node2
k8s-hello-world-55f48f8c94-npkn6  1/1   Running node1
k8s-hello-world-55f48f8c94-ppsqk  1/1   Running node2
k8s-hello-world-55f48f8c94-sc9pf  1/1   Running node1
k8s-hello-world-55f48f8c94-tjg4n  1/1   Running node2
k8s-hello-world-55f48f8c94-vrkr9  1/1   Running node1
k8s-hello-world-55f48f8c94-xzvlc  1/1   Running node2

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

$ kubectl get services
NAME              TYPE      CLUSTER-IP      EXTERNAL-IP PORT(S)         AGE
k8s-hello-world   NodePort  100.69.211.31   <none>      8080:30000/TCP  3h
kubernetes        ClusterIP 100.64.0.1      <none>      443/TCP         18h

Служба предоставляется внешнему миру через NodePort на порту 30000. Другими словами, каждый узел имеет порт 30000, открытый для общедоступного Интернета, и может принимать входящий трафик.

Но как трафик направляется от порта 30000 к моему модулю?

kube-proxy отвечает за настройку правил для маршрутизации входящего трафика от порта 30000 к одному из десяти модулей.

Вы должны попытаться запросить узел на порту 30000:

Обратите внимание, что вы можете получить IP-адрес узла с помощью kubectl get nodes -o wide

Приложение отвечает Hello World!, а имя хоста контейнера запущено. В предыдущей команде вас должен приветствовать Hello world! via <hostname>.

Если вы продолжите запрашивать один и тот же URL-адрес, вы можете заметить, как иногда вы получаете один и тот же ответ, а иногда он меняется. kube-proxy действует как балансировщик нагрузки, просматривает список маршрутизации и распределяет трафик между десятью модулями.

Что еще интереснее, не имеет значения, какой узел вы запрашиваете. Ответ может прийти из любого модуля, даже если он не размещен на том же узле, который вы запросили.

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

Балансировщик нагрузки направит входящий трафик из Интернета на один из двух узлов.

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

  1. Трафик, исходящий из Интернета, направляется на основной балансировщик нагрузки.
  2. Балансировщик нагрузки перенаправляет трафик на один из двух узлов на порт 30000.
  3. Правила, установленные kube-proxy, направляют трафик от узла к поду.
  4. трафик достигает капсулы

Фух! Это было давно!

👍 Чтобы получать новости и статьи из Skills Matter, подпишитесь на нашу рассылку здесь.

Пришло время сломать вещи

Теперь, когда вы знаете, как все взаимосвязано, давайте вернемся к исходному вопросу.

Что делать, если вы нарушите правила маршрутизации?

Будет ли кластер работать?

Пакеты по-прежнему обслуживают запросы?

Давайте удалим правила маршрутизации.

В отдельной оболочке вы должны следить за приложением по времени и потерянным запросам.

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

$ while sleep 1; do date +%X; curl -sS http://<your load balancer ip>/ | grep ^Hello; done

В этом случае у вас есть время в первом столбце и ответ от модуля в другом:

10:14:41 Hello world! via k8s-hello-world-55f48f8c94-vrkr9
10:14:43 Hello world! via k8s-hello-world-55f48f8c94-tjg4n

Первый вызов модуля k8s-hello-world-55f48f8c94-vrkr9 был сделан в 10:14 и 41 секунда.

Второй вызов был сделан в модуль k8s-hello-world-55f48f8c94-tjg4n в 10:14 и 43 секунды.

Удалим правила маршрутизации с узла.

kube-proxy может работать в трех режимах: пользовательское пространство, iptables и ipvs. По умолчанию, начиная с Kubernetes 1.2, это iptables.

В режиме iptables kube-proxy записывает список правил маршрутизации на узел, используя правила iptables.

Таким образом, вы можете войти в систему на одном из серверов узлов и удалить правила iptables с помощью iptables -F.

Обратите внимание, что iptables -F может мешать вашему SSH-соединению.

Если все пойдет по плану, вы должны испытать нечто подобное:

10:14:41 Hello world! via k8s-hello-world-55f48f8c94-xzvlc
10:14:43 Hello world! via k8s-hello-world-55f48f8c94-tjg4n
# this is when `iptables -F` was issued
10:15:10 Hello world! via k8s-hello-world-55f48f8c94-vrkr9
10:15:11 Hello world! via k8s-hello-world-55f48f8c94-vrkr9

Как вы заметили, с момента сброса правил iptables и следующего ответа прошло около 27 секунд, с 10:14:43 до 10:15:10.

Что произошло за эти 27 секунд?

Почему все возвращается в нормальное состояние через 27 секунд?

Возможно, это просто совпадение. Давайте снова очистим правила:

11:29:55 Hello world! via k8s-hello-world-55f48f8c94-xzvlc
11:29:56 Hello world! via k8s-hello-world-55f48f8c94-tjg4n
# this is when `iptables -F` was issued
11:30:25 Hello world! via k8s-hello-world-55f48f8c94-npkn6
11:30:27 Hello world! via k8s-hello-world-55f48f8c94-vrkr9

Был разрыв в 29 секунд с 11:29:56 до 11:30:25, но кластер вернулся в нормальное состояние.

Почему на ответ уходит около 30 секунд?

Получает ли узел трафик, несмотря на отсутствие таблицы маршрутизации?

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

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

$ while sleep 1; printf %"s\n" $(curl -sS http://<ip of the node>:30000); done

И давайте откажемся от правил iptables. Журнал предыдущей команды:

Hello world! via k8s-hello-world-55f48f8c94-xzvlc
Hello world! via k8s-hello-world-55f48f8c94-tjg4n
# this is when `iptables -F` was issued
curl: (28) Connection timed out after 10003 milliseconds
curl: (28) Connection timed out after 10004 milliseconds
Hello world! via k8s-hello-world-55f48f8c94-npkn6
Hello world! via k8s-hello-world-55f48f8c94-vrkr9

Неудивительно, что время ожидания подключения к узлу истекает после того, как вы отбрасываете правила iptables. Что еще интереснее, curl выжидает десять секунд, прежде чем сдаться.

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

Это объясняет 30-секундную задержку. Но он не говорит, почему узел готов принять соединение, если вы ждете достаточно долго.

Так почему трафик восстанавливается через 30 секунд?

Кто возвращает правила iptables?

Прежде чем отбросить правила iptables, вы можете проверить их с помощью:

Вскоре после того, как вы отбросите правила, вы должны продолжить выполнение iptables -F и заметить, что правила вернутся через несколько секунд!

Это ты, kube-proxy?

Да, это так.

Копаясь в официальной документации для kube-proxy, можно обнаружить два интересных флага:

  • --iptables-sync-period - Максимальный интервал обновления правил iptables (например, «5сек», «1м», «2ч22м»). Должно быть больше 0. (по умолчанию 30 секунд)
  • --iptables-min-sync-period - минимальный интервал обновления правил iptables при изменении конечных точек и сервисов (например, «5 с», «1 мин», «2 ч 22 мин»). (по умолчанию 10 с)

kube-proxy обновляет правила iptables каждые 10–30 секунд. Если мы отбросим правила iptables, kube-proxy потребуется до 30 секунд, чтобы реализовать и восстановить их.

Это объясняет, почему на возврат узла потребовалось 30 секунд!

Он также объясняет, как таблицы маршрутизации распространяются от главного узла к рабочему узлу. kube-proxy отвечает за их регулярную синхронизацию. Другими словами, каждый раз, когда модуль добавляется или удаляется, главный узел пересчитывает список маршрутизации. Регулярно kube-proxy синхронизирует правила с текущим узлом.

Давайте вспомним, как Kubernetes и kube-proxy могут оправиться от несанкционированного вмешательства в правила iptables на узле:

  1. Правила iptables удаляются с узла
  2. Запрос перенаправляется на балансировщик нагрузки и направляется на узел.
  3. Узел не принимает входящие запросы, поэтому балансировщик нагрузки ожидает
  4. Через 30 секунд kube-proxy восстанавливает iptables
  5. Узел снова может обслуживать трафик. Правила iptables перенаправляют запрос от балансировщика нагрузки к поду.
  6. Модуль отвечает на балансировщик нагрузки с задержкой в ​​30 секунд.

Ожидание в течение 30 секунд может быть неприемлемым для вашего приложения. Возможно, вас заинтересует настройка интервала обновления по умолчанию для kube-proxy.

Так где же настройки и как их изменить?

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

Если вы проверите процесс кублета в узле, вы увидите, что кубелет работает с --pod-manifest-path=/etc/kubernetes/manifests.

Выполнение простого ls раскрывает правду:

$ ls -l /etc/kubernetes/manifests
total 4 -rw-r--r-- 1 root root 1398 Feb 24 08:08 kube-proxy.manifest

И быстрый cat из kube-proxy.manifest раскрывает содержание:

apiVersion: v1
kind: Pod
metadata:
  name: kube-proxy
spec:
  hostNetwork: true
  containers:
  - name: kube-proxy
    image: gcr.io/google_containers/kube-proxy:v1.8.7-gke.1
    command:
    - /bin/sh
    - -c
    ->
    echo -998 > /proc/$$$/oom_score_adj &&
    exec kube-proxy
    --master=https://35.190.207.197
    --kubeconfig=/var/lib/kube-proxy/kubeconfig
    --cluster-cidr=10.4.0.0/14
    --resource-container=""
    --v=2
    --feature-gates=ExperimentalCriticalPodAnnotation=true
    --iptables-sync-period=30s
    1>>/var/log/kube-proxy.log 2>&1

Обратите внимание, что содержание было усечено и не отображается полностью.

Тайна разгадана!

Вы можете увидеть, как --iptables-sync-period=30s используется для обновления правил iptables каждые 30 секунд. Вы можете пойти дальше и изменить эту команду, чтобы настроить минимальное и максимальное время для обновления правил iptables для этого узла.

Уроки выучены

Удаление правил iptables похоже на отключение узла. Трафик по-прежнему направляется к узлу, но узел не может пересылать его дальше. Kubernetes может восстановиться после подобного сбоя, отслеживая состояние правил маршрутизации и обновляя их при необходимости.

Большое спасибо сообщению в блоге Manabu Sakai, которое стало огромным источником вдохновения, и Valentin Ouvrard за исследование проблемы с распространением iptables.

Вот и все, ребята!

Если вам понравилась эта статья, возможно, вам будет интересно почитать:

Станьте экспертом в развертывании и масштабировании приложений в Kubernetes

Нарушение кластера - один из основных модулей наших учебных курсов.

В конце концов, нужно быть готовым к худшему.

Никогда не знаешь!

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

Узнайте, как:

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

Стать экспертом в Kubernetes

Первоначально опубликовано на learnk8s.io