Ключевые выводы из нашего журнала Kubernetes

Мы начали создавать наш первый кластер Kubernetes в 2017 году, версия 1.9.4. У нас было два кластера: один работал на виртуальных машинах RHEL без операционной системы, а другой - на AWS EC2.

Сегодня наш парк инфраструктуры Kubernetes состоит из более 400 виртуальных машин, распределенных в нескольких центрах обработки данных. На платформе размещены высокодоступные критически важные программные приложения и системы для управления массивной действующей сетью с почти четырьмя миллионами активных устройств.

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

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



1. Любопытный случай приложений Java

Когда дело доходит до микросервисов и контейнеризации, инженеры стараются избегать использования Java, в первую очередь из-за ее пресловутого управления памятью. Однако сейчас все изменилось, и совместимость контейнеров Java с годами улучшилась. В конце концов, такие вездесущие системы, как Apache Kafka и Elasticsearch запускают на Java.

Еще в 2017–2018 годах у нас было несколько приложений, работающих на Java версии 8. Они часто с трудом понимали контейнерные среды, такие как Docker, и вылетали из-за проблем с памятью кучи и необычных тенденций сбора мусора. Мы узнали, что это было вызвано неспособностью JVM поддерживать Linux cgroups и namespaces, которые лежат в основе технологии контейнеризации.

Однако с тех пор Oracle постоянно улучшает совместимость Java в мире контейнеров. Даже в последующих патчах Java 8 были представлены экспериментальные флаги JVM для решения этих проблем XX:+UnlockExperimentalVMOptions и XX:+UseCGroupMemoryLimitForHeap.

Но, несмотря на все улучшения, нельзя отрицать, что Java по-прежнему имеет плохую репутацию из-за чрезмерного использования памяти и медленного запуска по сравнению с аналогами, такими как Python или Go. Это в первую очередь вызвано управлением памятью JVM и загрузчиком классов.

Сегодня, если нам нужно выбрать Java, мы гарантируем, что это версия 11 или выше. И наши ограничения памяти Kubernetes установлены на 1 ГБ поверх максимальной памяти кучи JVM (-Xmx) для запаса. То есть, если JVM использует 8 ГБ для кучи, наши ограничения ресурсов Kubernetes для приложения будут 9 ГБ. С этим жизнь стала лучше.



2. Обновления жизненного цикла Kubernetes

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

В Kubernetes есть несколько движущихся частей, которые необходимо согласовать с обновлением. От Docker до подключаемых модулей CNI, таких как Calico или Flannel, вам нужно тщательно собрать все вместе, чтобы они работали. Хотя такие проекты, как Kubespray, Kubeone, Kops и Kubeaws, упрощают задачу, все они имеют недостатки.

Мы построили наши кластеры с помощью Kubespray на виртуальных машинах RHEL. Kubespray был великолепен, в нем были инструкции по созданию, добавлению и удалению новых узлов, обновлению версии и почти все, что нам нужно для работы Kubernetes в производственной среде. Но, тем не менее, в руководстве по обновлению прилагался отказ от ответственности, который не позволял нам пропускать даже второстепенные версии. Таким образом, чтобы достичь целевой версии, нужно было пройти все промежуточные версии.

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

3. Сборка и развертывание

Будьте готовы перепроектировать все конвейеры сборки и развертывания. Наш процесс сборки и развертывания должен был пройти полную трансформацию для мира Kubernetes. Произошла большая реструктуризация не только конвейеров Jenkins, но и использования новых инструментов, таких как Helm, разработки стратегии новых потоков и сборок git, добавления тегов к изображениям докеров и управления версиями диаграмм развертывания Helm.

Вам понадобится стратегия для поддержки не только кода, но и файлов развертывания Kubernetes, файлов Docker, образов Docker, диаграмм Helm и разработки способа связать все это вместе.

После нескольких итераций мы остановились на следующем дизайне.

  • Код приложения и его схемы управления находятся в отдельных репозиториях git. Это позволяет нам редактировать их по отдельности. (Семантическое версионирование)
  • Затем мы сохраняем карту версии диаграммы с версией приложения и используем ее для отслеживания выпуска. Так, например, app-1.2.0deployed с charts-1.1.0. Если бы изменился только файл значений Helm, то изменилась бы только патч-версия диаграммы. (например, от 1.1.0 до 1.1.1). Все эти версии были продиктованы примечаниями к выпуску в каждом репозитории, RELEASE.txt.
  • Системные приложения, такие как Apache Kafka или Redis, код которых мы не создавали и не изменяли, работали по-другому. То есть у нас не было двух репозиториев git, поскольку тег Docker был просто частью управления версиями диаграммы Helm. Если бы мы когда-либо изменили тег докера для обновления, мы увеличили бы основную версию в теге диаграммы.

4. Зонды живости и готовности (палка о двух концах)

Зонды активности и готовности Kubernetes - отличные возможности для автономной борьбы с системными проблемами. Они могут перезапускать контейнеры при сбоях и отвлекать трафик от неработоспособных экземпляров. Но в определенных условиях сбоя эти зонды могут стать палкой о двух концах и повлиять на запуск и восстановление вашего приложения, особенно приложений с отслеживанием состояния, таких как платформы обмена сообщениями или базы данных.

Наша система Kafka стала жертвой этого. Мы запустили 3 Broker 3 Zookeeper набор с отслеживанием состояния с replicationFactor of 3 и minInSyncReplica of 2.. Проблема возникала, когда Kafka запускался после случайных сбоев или сбоев системы. Это заставляло его запускать дополнительные сценарии во время запуска для исправления поврежденных индексов, что занимало от 10 до 30 минут в зависимости от серьезности. Из-за этого добавленного времени зонды живости постоянно выходили из строя, подавая Kafka сигнал о перезапуске. Это помешало Кафке когда-либо исправить индексы и вообще запустить.

Единственное решение - настроить initialDelaySeconds в настройках зонда живости, чтобы отложить оценку зонда после запуска контейнера. Но проблема, конечно, в том, что это сложно назвать цифрой. Некоторое восстановление занимает даже час, и нам нужно предоставить достаточно места, чтобы учесть это. Но чем больше вы увеличиваете initialDelaySeconds, тем медленнее ваша устойчивость, так как Kubernetes потребуется больше времени для перезапуска вашего контейнера во время сбоев при запуске.

Таким образом, золотая середина состоит в том, чтобы оценить значение поля initialDelaySeconds, чтобы оно лучше балансировало между устойчивостью, которую вы ищете в Kubernetes, и временем, которое требуется приложению для успешного запуска во всех условиях сбоя (сбои дисков, сбои сети, сбои системы и т. Д. .)

Обновление: если вы используете последние несколько последних выпусков, Kubernetes представил третий тип зонда, называемый« Startup Probe , для решения этой проблемы». Он доступен начиная с alpha from 1.16 и beta from 1.18.

Зонд запуска отключает проверки готовности и работоспособности до запуска контейнера, чтобы убедиться, что запуск приложения не прерывается.

5. Отображение внешних IP-адресов

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

Наш кластер работает на Calico for CNI и BGP в качестве протокола маршрутизации внутри Kubernetes, а также взаимодействует с граничными маршрутизаторами. Для Kubeproxy мы используем IP Tablesmode. Мы размещаем в нашем Kubernetes огромный сервис, доступный через внешний IP-адрес, который обрабатывает миллионы подключений каждый день. Из-за всех SNAT и маскировки, исходящих от программно определяемых сетей, Kubernetes необходим механизм для отслеживания всех этих логических потоков. Для этого он использует Conntrack and netfilter инструменты ядра для управления этими внешними подключениями к статическому IP-адресу, который затем преобразуется во внутренний IP-адрес службы, а затем на IP-адрес вашего модуля. Все это делается с помощью таблицыconntrack и таблиц IP.

Однако у этой conntrack таблицы есть свои пределы. Как только вы достигнете предела, ваш кластер Kubernetes (ядро ОС внизу) больше не сможет принимать новые подключения. В RHEL вы можете проверить это следующим образом.

$  sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_count = 167012
net.netfilter.nf_conntrack_max = 262144

Некоторые способы обойти это - установить одноранговую связь между несколькими узлами с граничными маршрутизаторами, чтобы входящее соединение с вашим статическим IP-адресом распределялось по всему кластеру. Поэтому, если в вашем кластере большой парк машин, в совокупности у вас может быть большая conntrack таблица для обработки массивных входящих соединений.

Когда мы начинали в 2017 году, это нас полностью сбило с толку, но недавно Calico опубликовала подробное исследование по этому поводу в 2019 году под метким названием Почему conntrack больше не ваш друг.



Вам абсолютно необходим Kubernetes?

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

Однако, если вы находитесь в облаке и можете использовать Kubernetes в качестве «услуги», это может избавить вас от большей части этих накладных расходов, связанных с обслуживанием платформы, например «Как мне расширить CIDR моей внутренней сети?» или «Как мне обновить мою версию Kubernetes?»

Сегодня мы поняли, что первый вопрос, который вам нужно задать себе, - это «Вам абсолютно нужен Kubernetes?» Это может помочь оценить вашу проблему и насколько серьезно Kubernetes решает ее.

Трансформация Kubernetes стоит недешево. Цена, которую вы платите за это, должна действительно оправдывать «ваш» вариант использования и то, как он использует платформу. Если это так, Kubernetes может значительно повысить вашу производительность.

Помните, технология ради технологии бессмысленна.