В этой статье описывается причина и метод устранения проблемы с тайм-аутом соединения 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: разработка надежного, высокопроизводительного протокола:
Видно, что у 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.Похоже, нам нужно внимательно прочитать эту часть кода, чтобы увидеть, что произошло.
Прочитав код, я обнаружил, что эта логика исправлена, смотрите ссылку ниже:
А в коде, портированном до версии 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", просьба указывать источник для перепечатки.Нажмите, чтобы просмотреть исходный текст.