Как стать автором
Обновить

Отладка высоконагруженных Golang-приложений или как мы искали проблему в Kubernetes, которой не было

Время на прочтение4 мин
Количество просмотров8.4K
Всего голосов 36: ↑33 и ↓3+30
Комментарии18

Комментарии 18

Подпись под КДПВ: «Учись, сынок, а то всю жизнь ключи подавать будешь».
Спасибо за пример благополучного разрешения проблемы за счёт настойчивости и глубокого погружения.
структура, которая не очищалась при закрытии UDP-сессии.


Не уловил, что за структура, и как ее надо правильно очищать. Можете пояснить?
Вот эта структура:

github.com/miekg/dns/blob/v1.0.14/server.go#L229
Мапа map[net.Conn]struct{} не очищалась никогда.

Проблема где-то здесь:
github.com/miekg/dns/blob/v1.0.14/server.go#L260

На это место у меня ругался staticcheck.
резюме статьи:
Данная статья, возможно, поможет новичку в мире Golang и Kubernetes понять некоторые способы отладки своего и чужого софта


Я долго не мог понять, как вообще собрать свою версию nodelocaldns
решение —
С проблемой мне очень помог справиться Павел Селиванов
А где кровавые подробности и пути решения, которые так необходимы новичкам? Или автор предлагает всем обращаться к Павлу?

решение всех проблем —
официальный Docker-образ nodelocaldns 1.15.8


Может есть смысл сделать видео с комментариями.
начало — есть проблема см. график, скачали source прогнали анализаторы увидели узкие места, сняли дамп, подтвердили опасения, сбилдили новую сборку и вот она в докере. Проверили и счастье нам всем.

Для новичков самое оно будет отойти от заданий «hello word» и посмотреть на жизнь
А где кровавые подробности и пути решения, которые так необходимы новичкам? Или автор предлагает всем обращаться к Павлу?


Паша помог мне с командой:
cd ~/go/src/k8s.io/dns && \
GO111MODULE=off GOOS=linux go install ./... ./vendor/...

Дальше сам :)

Может есть смысл сделать видео с комментариями.


Спасибо за конструктив. Была идея сделать доклад на эту тему, но сами знаете что остановило :(
Доклад будет не то, для новичков, нельзя отмотать и посмотреть что на экране было и если что то не понял в начале, то дальше слушать бессмысленно, нельзя параллельно у себя на ПК делать.
А Стрим (видео) легче делается, записал экран а потом сиди звук пиши сколько сочтешь нужным в любой одежде из любого места. И изоляция не помеха.

В общем похвастаться — лучше докад
улучшить мир для новичнов — видео

и не болеть :) :)
> На dev кластер была выкачена версия 1.15.7, которая уже считается beta, а не alpha как 1.15.4, но на деве нет такого трафика в DNS (40k RPS). Печально.

Суровые парни ставят альфу в дев и ловял лулзы. Мощно.
В момент внедрения nodelocaldns у нас, оно существовало лишь в альфе, а эксплуатировало в проде этот софт очень мало народу. Этот дивный новый мир.
который всё еще находится в beta
С 1.18 уже stable. Поправьте пожалуйста.
А чем ДомКлик пользуется для гео-запросов? Сколько записей в БД?

Могу ответить, но это не точно: postgis, не больше 800GiB

На это место у меня ругался staticcheck

не вижу ругани на github.com/miekg/dns/blob/v1.0.14/server.go#L260
проблемная версия
git checkout v1.0.14
staticcheck | grep server.go
server.go:543:21: argument should be pointer-like to avoid allocations (SA6002)
server.go:626:19: argument should be pointer-like to avoid allocations (SA6002)
server.go:722:19: argument should be pointer-like to avoid allocations (SA6002)

текущая версия из master
git checkout v1.1.29
staticcheck | grep server.go
server.go:474:21: argument should be pointer-like to avoid allocations (SA6002)
server.go:586:20: argument should be pointer-like to avoid allocations (SA6002)
server.go:606:19: argument should be pointer-like to avoid allocations (SA6002)
server.go:647:19: argument should be pointer-like to avoid allocations (SA6002)

множество go-рутин создают некую структуру для TCP- соединений, помечает их как false и никогда не удаляет

По коду v1.0.14 не вижу, почему не удаляются, есть строка github.com/miekg/dns/blob/v1.0.14/server.go#L581. И причем здесь false, если создается map[interface{}]struct{}?
На скриншотах виден и проблемный участок кода и структура, которая не очищалась при закрытии UDP-сессии.

Причем тут UDP, если мы говорим про TCP, причем для UDP объект conns не используется.
А на скриншоте мы видим, что len(conns)=1.152.090 (то есть открытых TCP соединений) — это нормально вообще?

И вообще — почему версии не обновлялись вовремя? Обновили бы, может и не увидели проблему вообще. А так начали глубоко копать, а по факту в чем конкретно проблема и как ее пофиксить не разобрались до конца.
Прошу прощения, хотел тогда детально разобрать ваш столь интересный комментарий, но что-то меня отвлекло и я просто забыл. А жаль — было бы очень интересно подискутировать. :(

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

К сожалению у меня обновился GoLand на MacOS и в нем перестал открываться мой старый coredump (как и любой coredump, снятый в Linux). Теперь не могу вернуть всё как было и посмотреть заново. Поэтому, я сейчас не смогу пальцем тыкнуть в точное место в коде, в котором не очищается структура с TCP-соединениями.
Мало того, сейчас я наверное и не в состоянии был бы это сделать, так как уже месяца 3 не писал ничего на Golang.

Про UDP — оговорка вышла. nodelocaldns с «апстримами» соединяется по TCP.

И вообще — почему версии не обновлялись вовремя?


Потому что у нас исторически случился Kubespray, в который был «вшит» nodelocaldns. Обновляем сам Kubespray мы раз в 3-6 месяцев (вместе с обновлением Kubernetes). Пришлось выпилить из Kubespray nodelocaldns и перейти на собственный helm-чарт.
Обновление Kubespray — сомнительное удовольствие и каждую неделю обновлять его ну никак не получится. По сути — это задача сложнее чем обновить сам Kubernetes.
Ок, я просто сам контрибьютор в github.com/miekg/dns. И там реально могли быть проблемы типа утечки тредов, например. Дело в том, что там используется антипаттерн «пришел запрос — спавним рутину» для обработки (в стандартном http go тоже он используется, откуда и распространился по миру). Это все чревато тем, что под большой нагрузкой сервер получит OOM (в какой то момент спавн рутин начинает занимать намного больше времени, чем тратиться на обработку). Я пытался продвинуть идею пула воркеров (сначала фиксированное количество), но ее не приняли (потому что количество нужно было подбирать — а оно зависит от железа и ожидаемой нагрузки). Потом была попытка продвинуть идею динамических воркеров (прирост перфоманса был ~ 20% и меньшее потребление памяти), но в процессе codereview вся идея была испохаблена путем добавления логики до 10.000 рутин спавним воркеры, после этого лимита спавним рутины. Эта версия даже была некоторое время в репе (в v1.0.14 в частности), потом у них возникли проблемы с пиками потребления памяти, и все мои правки откатили (это opensource, детка) без попытки какого-либо анализа, с комментами типа: «у нас это работало 8 лет» и «мы потом разберемся» (уже год прошел) — это все, что нужно знать об opensource. У нас на проекте сейчас есть кастомный код с воркерами на базе github.com/panjf2000/ants, но лимит так и приходится подбирать, так что режим выключен по умолчанию (ждем OOM на проде, хе-хе).
Здорово, очень круто! ) У меня такой патч был для pgbouncer (пулер соединений для Postgres) — пул-реквесту уже 5 лет :)

А в куб что-либо контрибьютить вообще нет желания, глядя на срачи других контрибьютеров с «кураторами». Проще накатать фича-реквест, а для себя просто поправить несчастные 2 строчки кода и закрыть рабочую задачку.
Отличная обратная связь
Каюсь, забыл ответить вовремя. Ответил выше на ваш комментарий.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий