Помните исключение кластера kubernetes: время ожидания подключения kubelet к apiserver истекло

Kubernetes Эксплуатация и обслуживание

В этой статье описывается причина и метод устранения проблемы с тайм-аутом соединения kubelet и apiserver.
Обзор предыдущей статьи:Одна статья, чтобы понять мультиарендность HBase

задний план

Kubernetes — это структура master-slave, а master node — это мозг кластера, когда master node выходит из строя, весь кластер «выходит из-под контроля». Наиболее важным компонентом в главном узле является компонент apiserver, который обрабатывает все запросы и сохраняет состояние в etcd. Как правило, мы развертываем несколько api-серверов для достижения высокой доступности. Официально рекомендуется развертывать LB перед несколькими аписерверами для балансировки нагрузки, при выходе из строя одного из аписерверов LB автоматически переключает трафик на другие инстансы. Хотя это просто, но также вводит дополнительные зависимости: в случае сбоя LB все api-сервера будут недоступны. Мы знаем, что после того, как время ожидания между kubelet и apiserver на узле node в kubernetes истечет, диспетчер-контроллер установит состояние узла в notReady, а затем вытеснит pod’ы на нем, чтобы эти pod’ы можно было пересобрать в другом месте. Поэтому при сбое LB все узлы в кластере перейдут в состояние notReady, что приведет к массовому выселению подов.

происходит сбой

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

E0415 17:03:11.351872   16624 kubelet_node_status.go:374] Error updating node status, will retry: error getting node "k8s-slave88": Get https://10.13.10.12:6443/api/v1/nodes/k8s-slave88?resourceVersion=0&timeout=5s: net/http: request canceled (Client.Timeout exceeded while awaiting headers)E0415 17:03:16.352108   16624 kubelet_node_status.go:374] Error updating node status, will retry: error getting node "k8s-slave88": Get https://10.13.10.12:6443/api/v1/nodes/k8s-slave88?timeout=5s: net/http: request canceled (Client.Timeout exceeded while awaiting headers)E0415 17:03:21.352335   16624 kubelet_node_status.go:374] Error updating node status, will retry: error getting node "k8s-slave88": Get https://10.13.10.12:6443/api/v1/nodes/k8s-slave88?timeout=5s: net/http: request canceled (Client.Timeout exceeded while awaiting headers)E0415 17:03:26.352548   16624 kubelet_node_status.go:374] Error updating node status, will retry: error getting node "k8s-slave88": Get https://10.13.10.12:6443/api/v1/nodes/k8s-slave88?timeout=5s: net/http: request canceled (Client.Timeout exceeded while awaiting headers)E0415 17:03:31.352790   16624 kubelet_node_status.go:374] Error updating node status, will retry: error getting node "k8s-slave88": Get https://10.13.10.12:6443/api/v1/nodes/k8s-slave88?timeout=5s: net/http: request canceled (Client.Timeout exceeded while awaiting headers)E0415 17:03:31.352810   16624 kubelet_node_status.go:366] Unable to update node status: update node status exceeds retry count

10.13.10.12, показанный в журнале, является адресом LB. Судя по этому логу, kubelet не удалось подключиться к аписерверу.Изначально было подозрение, что это сбой сети.После ручного телнетирования 10.13.10.12 6443 оказалось, что все в норме.Это довольно странно.Очевидно, сеть связь в норме.Почему kubelet не может подключиться к apiserver?

Я быстро использовал tcpdump для захвата и анализа пакетов и обнаружил, что kubelet постоянно отправляет пакеты на apiservre, но не получает ACK от узла.Вход в мастер для проверки службы apiserver также был нормальным. Позже мои коллеги обнаружили, что перезапуск kubelet — это нормально, чтобы решить проблему как можно быстрее, мы можем только перезапустить все kubelet, а затем медленно локализовать проблему.

проблема позиционирования

После восстановления кластера было обнаружено, что произошла ошибка, сообщающая о сбое LB.Я связался с соответствующими студентами и обнаружил, что время совпало.Было подозрение, что kubelet не может подключиться к apiserver из-за аномалии LB.

После связи выясняется, что: LB будет поддерживать некоторые структуры данных для каждого переадресованного им соединения.Когда новый сервер LB подключается к сети, он равномерно распределяет часть исходного трафика, но запись о соединении не может быть найдена в структуру данных, которую он поддерживает. Он будет считать запрос незаконным и сразу отбрасывать его. Подобных вещей действительно было много, таких случаев в использовании kubernetes много, и даже у LB, которым требуется публичное облако, будут такие проблемы. Например: kubernetes#41916, kubernetes#48638, kubernetes-incubator/kube-aws#598.

Приблизительно поняв причину, в то время как студенты push LB улучшились, kubelet также должен внести некоторые улучшения: когда время соединения kubelet с apiserver истекает, он должен сбросить соединение и повторить попытку. Был проведен простой тест с использованием правил iptables для отбрасывания трафика, отправляемого kubelet, для имитации сетевых аномалий.

Сначала убедитесь, что соединение между kubelet и apiserver нормальное Выполните netstat -antpl | grep 6443, чтобы убедиться, что соединение между kubelet и apiserver 10.132.106.115:6443 нормальное:

[root@c4-jm-i1-k8stest03 ~]# netstat -antpl  |grep kubelettcp        0      0 127.0.0.1:10248         0.0.0.0:*               LISTEN      23665/./kubelet     tcp        0      0 10.162.1.26:63876       10.132.106.115:6443     ESTABLISHED 23665/./kubelet     tcp6       0      0 :::4194                 :::*                    LISTEN      23665/./kubelet     tcp6       0      0 :::10250                :::*                    LISTEN      23665/./kubelet     tcp6       0      0 :::10255                :::*                    LISTEN      23665/./kubelet     tcp6       0      0 10.162.1.26:10250       10.132.1.30:61218       ESTABLISHED 23665/./kubelet  

выполнить в это время

iptables -I OUTPUT -p tcp --sport 63876 -j DROP

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

[root@c4-jm-i1-k8stest03 ~]# netstat -antpl  |grep kubelettcp        0      0 127.0.0.1:10248         0.0.0.0:*               LISTEN      23665/./kubelet     tcp        0    928 10.162.1.26:63876       10.132.106.115:6443     ESTABLISHED 23665/./kubelet  

Соединение зависло, и после перезапуска кублета все восстановилось.

Это явление точно такое же, как и сбой в тот момент: аномальное соединение приводит к тайм-ауту сердцебиения kubelet.После перезапуска kubelet будет создано новое соединение и нормальное сердцебиение будет восстановлено. Поскольку версия kubernetes, которую мы сейчас используем, v1.10.2, мы скачали код из ветки master и попытались скомпилировать.Также есть эта проблема.Я чувствую, что эта проблема существовала всегда.

трудно исправить

Далее, как решить эту проблему. Я нашел связанную проблему в Интернете.Первое, что я нашел, это проблема kubernetes/client-go#374.Описанная выше ситуация очень похожа на ту, с которой мы столкнулись.Некоторые люди говорят, что это связано с протоколом HTTP/2.0. (далее h2) Я посмотрел исходный код kubelet и обнаружил, что kubelet по умолчанию использует протокол h2, а конкретный код реализован в функции SetTransportDefaults.

Вы можете отключить h2, установив переменную среды DISABLE_HTTP2.После простой проверки, после явной установки переменной среды для отключения h2, соединение с использованием http1.1 не имеет этой проблемы.

Проверьте документацию и обнаружите, что разница между http1.1 и http2.0 заключается в следующем: в http1.1 по умолчанию используются сетевые соединения с мультиплексированием поддержки активности. соединение будет использоваться повторно. Если нет, создайте новое соединение. Когда соединение kubelet ненормальное, старое соединение занято, и оно ожидает ответа от однорангового узла, kubelet создаст новое в следующем цикле пульса, потому что нет доступного соединения. Пока новое соединение нормально обменивается данными, пакет пульса может быть отправлен нормально.

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

net.ipv4.tcp_retries2, net.ipv4.tcp_keepalive_time, net.ipv4.tcp_keepalive_probes, net.ipv4.tcp_keepalive_intvl

Быстрое восстановление путем корректировки аварийного отключения операционной системы.

h2 активно обнаруживает сбои подключения, отправляя кадр Ping, который представляет собой пакет с высоким приоритетом и небольшой полезной нагрузкой. Когда сеть в норме, он может быстро вернуться. По умолчанию кадр не будет отправлен, и его необходимо явно установить на Отправить. Кадр Ping реализован в некоторых коммуникационных средах, требующих высокой надежности, таких как gRPC.Он упоминается в gRPC на HTTP/2: разработка надежного, высокопроизводительного протокола:

The less clean version is where the endpoint dies or hangs without informing the client. In this case,TCP might undergo retry for as long as 10 minutes before the connection is considered failed.Of course, failing to recognize that the connection is dead for 10 minutes is unacceptable.

gRPC solves this problem using HTTP/2 semantics:when configured using KeepAlive,gRPC will periodically send HTTP/2 PING frames.These frames bypass flow control and are used to establish whether the connection is alive.

If a PING response does not return within a timely fashion,gRPC will consider the connection failed,close the connection,and begin reconnecting (as described above).

Видно, что у gRPC тоже есть такая проблема, для того, чтобы быстро выявить неисправное соединение и восстановить его, используется фрейм Ping. Однако в настоящее время соединение, установленное kubernetes, не реализует фрейм Ping, что делает невозможным своевременное обнаружение аномалии соединения и самолечение.

Проблема в сообществе давно открыта и кажется, что нет никаких признаков ее решения, поэтому вам нужно найти способ. Мы знаем, что http.Client сам выполняет только некоторую обработку протокола http.Основное взаимодействие передается транспорту для реализации, и транспорт решает, как вернуть соответствующий ответ в соответствии с запросом. В kubernetes client-go есть только одна функция для настроек Transporth2.

// SetTransportDefaults applies the defaults from http.DefaultTransport// for the Proxy, Dial, and TLSHandshakeTimeout fields if unsetfunc SetTransportDefaults(t *http.Transport) *http.Transport {  t = SetOldTransportDefaults(t)  // Allow clients to disable http2 if needed.  if s := os.Getenv("DISABLE_HTTP2"); len(s) > 0 {    klog.Infof("HTTP2 has been explicitly disabled")  } else {    if err := http2.ConfigureTransport(t); err != nil {      klog.Warningf("Transport failed http2 configuration: %v", err)    }  }  return t}

Просто вызовите http2.ConfigureTransport, чтобы настроить транспорт для поддержки h2. Этот код кажется слишком простым, и в нем отсутствует логика обработки, связанная с кадром Ping. Проверьте методы, связанные с Transport и Pingframe, в стандартной библиотеке golang.

К сожалению, текущий абстрактный Golang ClientConn для tcp-соединения уже поддерживает отправку кадров Ping, но соединение управляется пулом соединений clientConnPool, который является внутренней частной структурой, мы не можем напрямую оперировать, инкапсулировать пул соединений. выставить любой интерфейс для настройки всех соединений в пуле соединений для периодической отправки кадров Ping. Если мы хотим реализовать эту функцию, мы должны настроить транспорт и реализовать пул соединений.Кажется, реализовать стабильный и надежный транспорт непросто. Я могу только попросить сообщество golang посмотреть, есть ли решение. После отправки вопроса кто-то быстро ответил и отправил PR. После проверки реализация была относительно простой, поэтому на основе этого PR, кадр Ping clinet Был реализован -go.

повернись

Когда разработка будет завершена и будет готова к работе в сети, я хочу воспользоваться этим ремонтом, чтобы обновить версию kubernetes до версии 1.10.11.Как правило, выпуск исправления гарантированно совместим. Когда я тестировал v1.10.11, то с удивлением обнаружил, что даже без изменения кода эту проблему воспроизвести не удалось. На нем видно, что в v1.10.2 была проблема, в v1.10.11 она была восстановлена, а потом эта проблема была введена в master.Похоже, нам нужно внимательно прочитать эту часть кода, чтобы увидеть, что произошло.

Прочитав код, я обнаружил, что эта логика исправлена, смотрите ссылку ниже:

GitHub.com/Это так особенно/…

А в коде, портированном до версии 1.10.3, при ненормальном соединении будет вызываться closeAllConns, чтобы принудительно закрыть и перестроить все соединения.

Затем была введена регрессия, а для параметра closeAllConns было установлено значение nil, из-за чего соединение не могло нормально закрыться.

После понимания этой логики, модификация проста, просто установите правильное значение closeAllConns и отправьте pr официальному официальному лицу, которое принимает его и бэкпортирует на версию 1.14. Даже если это будет полностью исправлено, конечно, проблема может быть решена путем добавления кадра Ping к упомянутому выше h2, потому что это решение может быть более сложным, а время ремонта относительно большим.

Ссылка на ссылку

1, https: //github.com/kubernetes/kubernetes/issues/41916

2. https://github.com/kubernetes/kubernetes/issues/48638

3. https://github.com/kubernetes-incubator/kube-aws/issues/598

4. https://github.com/kubernetes/client-go/issues/374

5. https://github.com/kubernetes/apimachinery/blob/b874eabb9a4eb99cef27db5c8d06f16542580cec/pkg/util/net/http.go#L109-L120

6. https://www.cncf.io/blog/2018/08/31/grpc-on-http-2-engineering-a-robust-high-performance-protocol/

7. https://github.com/kubernetes/kubernetes/pull/63492

8. https://github.com/kubernetes/kubernetes/pull/71174

9. https://github.com/golang/go/issues/31643

10. https://github.com/kubernetes/kubernetes/pull/78016


Данная статья была впервые опубликована в паблике "Облачные технологии Xiaomi", просьба указывать источник для перепечатки.Нажмите, чтобы просмотреть исходный текст.