- PVSM.RU - https://www.pvsm.ru -

Прим. перев.: Представляем вниманию мини-подборку из постмортемов о фатальных проблемах, с которыми столкнулись инженеры разных компаний при эксплуатации инфраструктуры на базе Kubernetes. Каждая заметка рассказывает о самой проблеме, её причинах и последствиях, а также, конечно, о решении, помогающем избегать подобных ситуаций в будущем.
Как известно, учиться на чужом опыте дешевле, а посему — пусть эти истории помогут быть готовыми к возможным неожиданностям. Кстати, большая и регулярно обновляемая подборка ссылок на такие «failure stories» публикуется на этом сайте [1] (по данным из этого Git-репозитория [2]).
Оригинал: Moonlight [3].
В период с 18 по 22 января сайт и API Moonlight испытывали периодические сбои в работе. Все началось со случайных ошибок API и закончилось полным отключением. Проблемы были решены, и приложение вернулось в нормальное состояние.
Moonlight использует программное обеспечение, известное как Kubernetes. Kubernetes запускает приложения на группах серверов. Эти серверы называются узлами. Копии приложения, работающие на узле, зовутся pod'ами. В Kubernetes есть планировщик (scheduler), который динамически определяет, какие pod'ы на каких узлах должны работать.
Первые ошибки в пятницу были связаны с проблемами с подключением к базе данных Redis. API Moonlight использует Redis для проверки сессий при каждом аутентифицированном запросе. Наш инструмент для мониторинга Kubernetes оповестил, что некоторые узлы и pod'ы не отвечают. В то же время Google Cloud сообщил о сбоях в работе сетевых служб [4], и мы решили, что именно они являются причиной наших проблем.
По мере сокращения трафика на выходных ошибки, казалось, разрешились в своей основной массе. Однако утром во вторник сайт Moonlight'а упал, а внешний трафик совсем не доходил до кластера. Мы обнаружили другого человека в Twitter [5] со сходными симптомами и решили, что на Google произошел сбой в работе сети. Мы связались с поддержкой Google Cloud, которая оперативно передала проблему в команду технической поддержки.
Команда техподдержки Google выявила некую закономерность в поведении узлов в нашем кластере Kubernetes. Загрузка CPU отдельных узлов достигала 100%, после чего в виртуальной машине происходил kernel panic и она падала.
Цикл, вызвавший сбой, оказался следующим:
Первоначально ошибка возникла в pod'е Redis, но в конечном итоге все pod'ы, работающие с трафиком, упали, что привело к полному отключению. Правила экспоненциальной задержки при повторном планировании приводили ко все более длительным периодам простоя.
Мы смогли восстановить работу сайта, добавив правила anti-affinity [7] во все основные Deployment'ы. Они автоматически распределяют pod'ы по узлам, повышая отказоустойчивость и производительность.
Сам Kubernetes спроектирован как отказоустойчивая хост-система. Moonlight использует три узла на разных серверах для обеспечения устойчивости, и мы запускаем три копии каждого приложения, обслуживающего трафик. Идея состоит в том, чтобы иметь по одной копии на каждом узле. В этом случае даже отказ двух узлов не приведет к простою. Тем не менее, Kubernetes иногда размещал все три pod'а с сайтом на одном узле, создавая таким образом узкое место в системе. При этом другие приложения, требовательные к мощности процессора (а именно — рендеринг на стороне сервера), оказывались на этом же узле, а не на отдельном.
Правильно настроенный и должным образом функционирующий кластер Kubernetes обязан справляться с продолжительными периодами высокой нагрузки на CPU и размещать pod'ы таким образом, чтобы максимально эффективно использовать доступные ресурсы. Мы продолжаем работать с поддержкой Google Cloud для выявления и устранения основной причины появления kernel panic на серверах.
Правила anti-affinity позволяют сделать приложения, работающие со внешним трафиком, более отказоустойчивыми. Если у вас есть подобный сервис в Kubernetes, задумайтесь над тем, чтобы их добавить.
Мы продолжаем работать с ребятами из Google над поиском и устранением причины сбоев в ядре ОС на узлах.
Оригинал: Phil Pearl из Ravelin [8].
Мы в компании Ravelin мигрировали на Kubernetes (на GKE). Процесс оказался очень успешным. Наши pod disruption budgets полны как никогда, statefulset'ы по-настоящему статны (с трудом переводимая игра слов: «our statefulsets are very stately» — прим. перев.), а скользящая замена узлов проходит как по маслу.
Последний кусочек головоломки — перенос слоя API со старых виртуальных машин в кластер Kubernetes. Для этого нам необходимо настроить Ingress, чтобы API был доступен из внешнего мира.
Поначалу задача казалась простой. Мы просто определим Ingress-контроллер, подправим Terraform, чтобы получить некоторое количество IP-адресов, а Google позаботится практически обо всем остальном. И все это заработает как по волшебству. Класс!
Однако со временем стали замечать, что интеграционные тесты периодически получают ошибки 502. С этого и началось наше путешествие. Впрочем, я сэкономлю вам время и перейду сразу к выводам.
Все говорят о graceful shutdown («изящном», постепенном отключении). Но на самом деле не стоит полагаться на него в Kubernetes. Или, по крайней мере, это должен быть не тот graceful shutdown, который вы впитали с молоком матери [9]. В мире Kubernetes подобный уровень «изящности» не нужен и грозит серьезными проблемами.
Вот как в представлении большинства проходит удаление pod'а из сервиса или балансировщика нагрузки в Kubernetes:
К сожалению, в действительности все обстоит совсем иначе.
Большая часть документации намекает, что все происходит несколько иначе, однако об этом нигде не пишут явно. Главная проблема состоит в том, что шаг 3 не следует за шагом 2. Они происходят одновременно. В обычных сервисах удаление endpoint'ов происходит настолько быстро, что вероятность столкнуться с проблемами крайне низка. Однако с Ingress'ами все иначе: обычно они реагируют гораздо медленнее, поэтому проблема становится очевидной. Pod может получить SIGTERM задолго до того, как изменения в endpoint'ах попадут в Ingress.
В итоге, graceful shutdown — вовсе не то, что требуется от pod'а. Он будет получать новые подключения и должен продолжать обрабатывать их, иначе клиенты начнут получать 500-е ошибки и вся чудесная история о беспростойных развертываниях и масштабировании начнет разваливаться.
Вот что происходит на самом деле:
Connection: close в заголовках ответов.Если это сторонний код и вы не можете изменить его поведение, тогда лучшее, что можно сделать — добавить хук pre-stop, который просто будет спать (sleep) в течение «изящного» периода, так что pod будет продолжать работать так, словно ничего не произошло.
Оригинал: Jetstack [10].
Jetstack предлагает своим клиентам multi-tenant-платформы на Kubernetes. Иногда возникают особые требования, которые мы не можем удовлетворить с помощью стандартной конфигурации Kubernetes. Чтобы их реализовать, с недавнего времени мы начали использовать Open Policy Agent [11] (подробнее о проекте мы писали в этом обзоре [12] — прим. перев.) в качестве контроллера доступа для внедрения особых политик.
В этом материале описывается сбой, вызванный неправильной настройкой этой интеграции.
Мы занимались обновлением мастера для dev-кластера, в котором различные команды тестировали свои приложения в течение рабочего дня. Это был региональный кластер в зоне europe-west1 на Google Kubernetes Engine (GKE).
Команды были предупреждены, что идет обновление, при этом простоя API не ожидалось. Ранее в тот день мы уже провели аналогичное обновление другой pre-production-среды.
К обновлению приступили с помощью своего GKE Terraform-пайплайна. Обновление мастера не завершилось до истечения таймаута Terraform'а, который мы установили на 20 минут. Это был первый тревожный звоночек о том, что что-то пошло не так, хотя в консоли GKE кластер по-прежнему значился как «upgrading» («обновляющийся»).
Повторный запуск пайплайна привел к следующей ошибке
google_container_cluster.cluster: Error waiting for updating GKE master version:
All cluster resources were brought up, but the cluster API is reporting that:
component "kube-apiserver" from endpoint "gke-..." is unhealthy
На этот раз связь с API-сервером стала периодически прерываться и команды не смогли деплоить свои приложения.
Пока мы пытались понять, что происходит, все узлы начали уничтожаться и воссоздаваться в бесконечном цикле. Это привело к неизбирательному отказу в обслуживании для всех наших клиентов.
С помощью поддержки Google мы смогли определить последовательность событий, которые привели к сбою:
extension-apiserver-authentication в kube-system. Сделать это не удалось, поскольку бэкенд для настроенного нами проверяющего вебхука Open Policy Agent (OPA) не отвечал.Итогом стали периодические сбои API, из-за которых kubelet'ы не смогли сообщить о работоспособности узла. В свою очередь, это привело к тому, что механизм автоматического восстановления узлов GKE (node auto-repair) начал перезапускать узлы. Эта особенность подробно описана в документации [15]:
Статус unhealthy может означать: В течение заданного времени (приблизительно 10 минут) узел вообще не выдает какой-либо статус.
Когда мы выяснили, что ресурс ValidatingAdmissionWebhook является причиной проблемы с прерывистым доступом к серверу API — удалили его и восстановили работу кластера.
С тех пор настроили ValidatingAdmissionWebhook для OPA на мониторинг только тех пространств имен, где применима политика и к которым имеют доступ команды разработчиков. Мы также ограничили вебхук ресурсами Ingress и Service — единственными, с которыми работает наша политика.
С тех пор, как мы впервые развернули OPA, документация была обновлена [16], чтобы отразить это изменение.
Мы также добавили liveness-тест, чтобы гарантировать перезапуск OPA в случае, если он становится недоступным (и внесли соответствующие поправки [17] в документацию).
Мы также рассматривали отключение механизма [15] автоматического восстановления узлов GKE, но все же решили отказаться от этой идеи.
Если бы мы подключили оповещения о времени отклика сервера API, то изначально смогли бы заметить его глобальное возрастание для всех запросов CREATE и UPDATE после развертывания вебхука для OPA.
Произошедшее подчеркивает важность настройки тестов для всех рабочих нагрузок. Оглядываясь назад, можно сказать, что развертывание OPA было настолько обманчиво простым, что мы даже не стали связываться с Helm-чартом [18] (хотя следовало бы). Чарт производит ряд корректировок за пределами базовых настроек, описанных в руководстве, включая настройку livenessProbe для контейнеров с admission-контроллером.
Мы не первыми столкнулись с этой проблемой: upstream issue [19] остается открытым. Функциональность в этом вопросе явно можно улучшить (и мы будем за этим следить).
Читайте также в нашем блоге:
Автор: Андрей Сидоров
Источник [24]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/sistemnoe-administrirovanie/337286
Ссылки в тексте:
[1] этом сайте: https://k8s.af/
[2] этого Git-репозитория: https://github.com/hjacobs/kubernetes-failure-stories
[3] Moonlight: https://updates.moonlightwork.com/outage-post-mortem-87370
[4] сбоях в работе сетевых служб: https://status.cloud.google.com/incident/cloud-networking/19002
[5] другого человека в Twitter: https://twitter.com/johanhaleby/status/1087792436884262922
[6] хостинге: https://www.reg.ru/?rlink=reflink-717
[7] правила anti-affinity: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
[8] Phil Pearl из Ravelin: https://philpearl.github.io/post/k8s_ingress/
[9] впитали с молоком матери: https://golang.org/pkg/net/http/#Server.Shutdown
[10] Jetstack: https://blog.jetstack.io/blog/gke-webhook-outage/
[11] Open Policy Agent: https://www.openpolicyagent.org/
[12] этом обзоре: https://habr.com/ru/company/flant/blog/353808/
[13] PostStartHook: https://github.com/kubernetes/kubernetes/blob/e09f5c40b55c91f681a46ee17f9bc447eeacee57/pkg/master/client_ca_hook.go#L43
[14] регистрации CA: https://github.com/kubernetes/kubernetes/blob/e09f5c40b55c91f681a46ee17f9bc447eeacee57/pkg/master/client_ca_hook.go#L121
[15] документации: https://cloud.google.com/kubernetes-engine/docs/how-to/node-auto-repair
[16] была обновлена: https://github.com/open-policy-agent/opa/pull/1435
[17] соответствующие поправки: https://github.com/open-policy-agent/opa/pull/1605
[18] Helm-чартом: https://github.com/helm/charts/tree/master/stable/opa
[19] upstream issue: https://github.com/kubernetes/kubernetes/issues/54522
[20] Как приоритеты pod'ов в Kubernetes стали причиной простоя в Grafana Labs: https://habr.com/ru/company/flant/blog/461807/
[21] Из жизни с Kubernetes: Как HTTP-сервер испанцев не жаловал: https://habr.com/ru/company/flant/blog/448420/
[22] 6 занимательных системных багов при эксплуатации Kubernetes [и их решение]: https://habr.com/ru/company/flant/blog/443458/
[23] 6 практических историй из наших SRE-будней: https://habr.com/ru/company/flant/blog/471892/
[24] Источник: https://habr.com/ru/post/475026/?utm_source=habrahabr&utm_medium=rss&utm_campaign=475026
Нажмите здесь для печати.