company_banner

Резервирование в Kubernetes: оно существует

    Меня зовут Сергей, я из компании ITSumma, и я хочу вам рассказать, как мы подходим к резервированию в Kubernetes. В последнее время я много занимаюсь консультативной работой по внедрению разнообразных devops-решений для различных команд, и, в частности, плотно работаю по проектам с использованием K8s. На конференции Uptime day 4, которая была посвящена резервированию в сложных архитектурах, я выступал с докладом о резервировании «кубика», и вот его вольный пересказ. Только заранее предупрежу, что он является не непосредственным руководством к действию, а скорее, обобщением размышлений на указанную тему.



    В принципе мониторинг и резервирование — это два основных инструмента повышения отказоустойчивости любого проекта. Но ведь в кубере всё балансируется само, скажете вы, всё масштабируется само, и если что-то произойдёт — поднимется само… То есть, при первом поверхностном исследовании темы, на вопрос, кто как подходит к резервированию K8s, интернет ответил мне «а зачем?» Многие думают, что кубер представляет собой такую магическую штуку, которая избавляет от всех инфраструктурных проблем и делает так, что проект никогда не упадет. Но… мир не то, чем кажется.

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

    1. синхронизация кода и статики
    2. синхронизация конфигураций
    3. репликация бд

    И вуаля: в любой момент мы переключаемся на резервную площадку, все счастливы, встаём-расходимся.



    А что нам предлагают для увеличения постоянной доступности нашего kubernetes-приложения? Первое, о чём говорит неофициальная документация — это поставить много машин, сделать много мастеров — их количество должно удовлетворять условиям достижения кворума внутри кластера, и чтобы на каждом из мастеров был поднят etcd, api, MC, scheduler… И, вроде как, всё замечательно: при выходе нескольких рабочих нод или мастеров из строя наш кластер перебалансируется, и приложение продолжит работать. Опять выглядит, как волшебство! Но часто наш кластер находится в рамках одного центра обработки данных и это может вызвать определённые вопросы. Что если приехал экскаватор и раскопал кабель, ударила молния, произошел вселенский потоп? Всё накрылось, нашего кластера больше нет. Как подойти к резервированию с учётом этой стороны проблемы?

    Прежде всего, у вас должен быть ещё один кластер в горячем резерве, то есть кластер, на который вы можете переключиться в любой момент. При этом с точки зрения кубера, инфраструктуры должны быть полностью идентичными. То есть если есть какие-то нестандартные плагины для работы с файловой системой, кастомные решения для ingress, они должны быть полностью идентичными на ваших двух (или трёх, или десяти, тут уж на сколько хватит денег и сил админов) кластерах. Необходимо чётко определить два набора приложений (deployment’ов, statefulset’ов, daemonset’ов, cronjob’ов и т д): какие из них могут работать на резерве постоянно, а какие лучше не запускать до непосредственного переключения.

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

    Например, начнем с базовых сущностей кубернетеса — deployments – они должны быть идентичны. Должны быть запущены приложения, которые в любой момент могут перехватить обработку трафика и позволят нашему проекту продолжить жить. Если мы говорим о файлах конфигурации, то здесь нужно смотреть, должны ли они быть идентичны или нет. То есть если мы, умные люди, не употребляем никаких запрещенных веществ и не держим базу в K8s, то у нас в configmaps’ах должны быть настройки доступов до боевой базы (процесс резервирования которой построен отдельно). Соотвественно для обеспечения доступов до резервного экземпляра базы данных мы должны иметь отдельный файл конфигурации (configmap). Ровно так же мы работаем и с secret’ами: паролями для доступа в базу, api-ключами; в любой момент времени у нас может работать либо боевой secret, либо резервный. Итого мы имеем уже две сущности kubernetes, резервные версии которых не должны быть идентичны боевым. Следующая сущность на которой стоит остановиться — cronjob. Cronjob’ы на резерве ни в коем случае не должны быть идентичны набору cronjob’ов продакшен-кластера! Если мы поднимаем резервный кластер и поднимаем его полностью со всем включенными cronjob'ами — то, например, люди будут получать у вас по два письма одновременно вместо одного. Либо какая-то синхронизация данных с внешними источниками будет проходить два раза, соответственно мы начинаем болеть, плакать, кричать и ругаться.



    А как же нам предлагают организовать резервирующий кластер люди из интернета? Второй по популярности ответ после «а зачем?» — использование Kubernetes Federation.

    Что это такое? Это, скажем так, большой мета-кластер. Если мы представим архитектуру кубера — где у нас есть мастер, несколько нод, — то с точки зрения федерации у нас тоже есть мастер и несколько нод, только каждая нода — это отдельный кластер. То есть мы работаем с теми же сущностями, с теми же примитивами, как и с единичным кубером, только крутим-вертим не нашими физическими машинами, а целыми кластерами. В рамках федерации у нас идет полная синхронизация федеративных ресурсов от родителей к потомкам. Например, если мы запустили какой-то деплоймент через федерацию — он у нас задеплоится на каждый наш дочерний кластер. Если мы возьмем какой-либо configmap, секрет, раскатим его на федерации — оно растечется во все наши дочерние кластеры; при этом федерация позволяет кастомизировать наши ресурсы на детях. То есть мы взяли какой-нибудь configmap, задеплоили его через федерацию и затем уже, если нам что-то нужно подправить на конкретных кластерах, мы идем править на отдельном кластере, и это изменение уже никуда не будет синхронизироваться.

    Kubernetes Federation — не так давно существующий инструмент, и он поддерживает далеко не весь набор ресурсов, который предоставляет сам K8s: на момент публикации одной из первых версий документации там говорилось о поддержке только конфиг-мапов, деплоймента под реплика-сет, ingress. Секреты не поддерживались, работа с volume также не поддерживалась. Слишком ограниченный набор. Особенно если мы любим развлекаться, — например, через custom resource definition передавать свои собственные ресурсы кубернетесу, — в федерацию мы их уже не затолкаем. То есть как бы… очень похожее на правду решение, но заставляет нас периодически стрелять себе в ногу. С другой стороны, федерация позволяет гибко управлять нашим replicaset'ом. Например, мы хотим, чтобы было запущено 10 реплик нашего приложения, по умолчанию федерация поделит это число пропорционально между количеством кластеров. И это все можно ещё и конфигурировать! То есть можно указать, что на боевом кластере нужно держать 6 реплик нашего приложения, а на резервирующем кластере, для экономии ресурсов, либо для собственных каких-то развлечений — только 4 реплики нашего приложения. Что тоже достаточно удобно. Но с федерацией нам приходится использовать какие-то новые решения, что-то додеплоивать на ходу, заставлять себя чуть-чуть больше думать…

    Можно ли подойти к процессу резервирования кубера как-то попроще? Какие инструменты у нас вообще есть?

    Во-первых, у нас всегда есть некая ci/cd система, то есть мы вручную не ходим, не пишем на серверах create/apply. Система генерирует yaml’ики для наших контейнеров.

    Во-вторых, есть несколько кластеров, у нас есть либо одно, либо несколько (если мы умные) registry, которое мы тоже взяли и зарезервировали. И есть замечательная утилита kubectl, которая может работать с несколькими кластерами одновременно.



    Так вот: на мой взгляд, самое простое и верное решение для построения резервного кластера — это примитивный параллельный деплой. Есть какой-то пайплайн в ci/cd системе; сначала билдим наши контейнеры, тестируем и раскатываем приложения через kubectl на несколько независимых кластеров. Мы можем строить одновременные выкладки на несколько кластеров. Соответственно, доставку конфигураций мы тоже решаем на этом этапе. Можно заранее определить набор конфигураций для нашего боевого кластера, набор конфигураций для резервного кластера и на уровне ci/cd системы раскатывать продовое окружение в продовый кластер, резервное окружение — в резервный кластер. По сравнению с федерацией не надо ходить после определения федеративного ресурса на каждый дочерний кластер и что-то переопределять. Мы это сделали заранее. Какие мы молодцы.

    Но… есть… я было написал, есть «корень всех зол», но их на самом деле два. Во-первых, файловая система. Есть какой-то PV, либо мы используем внешнее хранилище. Если мы храним файлы внутри кластера, то тут надо действовать по старым практикам, оставшимся со времён железных инфраструктур: например, синхронизировать lsync'ом. Ну или любым другим предпочитаемым лично вами костылём. Раскатываем всё на другие тачки и живем.

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

    А теперь о том, как, в принципе, у нас будет происходить процесс переключения на резервную площадку в случае пожара. Мы, во-первых, параллельно деплоим stateless-приложения. Они не влияют на бизнес-логику наших приложений, нашего проекта, мы можем постоянно держать два набора запущенных аппликейшнов, и они могут начать принимать трафик. Очень важно в процессе переключения на резервную площадку обязательно посмотреть — нужно ли переопределять конфигурации? Например, у нас есть продовый кластер кубернетес, есть резервный кластер кубернетес, есть внешняя база данных мастер, есть резервная база данных мастер. У нас четыре варианта, как эти приложения в проде могут начать между собой взаимодействовать. У нас может переключиться база, и получится, что надо в продовом кластере переключить трафик на новую базу, либо у нас может навернуться кластер — и мы переехали на резерв, но продолжаем работать с продовой базой, ну и третий вариант, когда у нас навернулось это, навернулось то, и мы переключаем оба приложения, переопределяем нашу конфигурацию, для того чтобы новые приложения уже работали с новой базой данных.

    Ну и, собственно говоря, какие выводы из всего этого можно сделать?



    Вывод первый: с резервом жить хорошо. Но дорого. Но в идеале жить не с одним резервом. В идеале вообще нужно жить с несколькими резервами. Во-первых, резерв должен быть как минимум, не в одном ДЦ, и, во-вторых, как минимум, у другого хостера. Часто бывало — и на моей практике это было. Проекты я, к сожалению, не могу назвать, как раз когда произошел пожар в дата-центре… Я такой: переключаемся на резерв! А резервные серваки в этой же стойке стояли…

    Или представьте себе, что Amazon забанили в России (а такое было). И всё: толку от того, что в другом amazon лежит наш резерв? Он тоже недоступен. Так что повторюсь: держим резерв, как минимум, в другом ДЦ, а желательно — у другого хостера.

    Второй вывод: если у вас в кубере приложение общается с какими-то внешними источниками (это может быть как база данных, так и какое-то внешнее api), обязательно определите его как сервис с внешним Endpoint'ом, чтобы в момент переключения не передеплоивать 15 ваших приложений, которые стучатся в ту же самую базу. Определите базу отдельным сервисом, стучитесь в нее, как будто она у вас внутри кластера: если у вас навернётся база, вы в одном месте меняете ip и продолжаете жить счастливо.

    И напоследок: я «кубик» люблю, как и эксперименты с ним. А ещё люблю делиться результатами этих экспериментов и вообще своим личным опытом. Поэтому я записал серию вебинаров про K8s, велком в наш youtube-канал за подробностями.
    • +25
    • 4,6k
    • 5
    ITSumma
    278,90
    Собираем безумных людей и вместе спасаем интернет
    Поделиться публикацией

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

      +1
      А что если разделять поды из деплоймента/даемонсета/стейтфулсета по разным node labes? Каждый лейбл соответсвует своей зоне доступности где находится нода. Использвать nodeSelector и nodeAffinity. В таком случае если упадет одна из зон, будут доступны поды из других зон. Или варианты с несколькими зонами не рассматриваются?
        0
        Ну, по факту, примерно такой расклад и предлагается. Только деплоить нужно не всё, определённые вещи, типа кронджобов придётся держать отдельно. А так да, раскатываем релизы параллельно в два разных ведра.
        +1
        Привет. Спасибо за статью. Очень хотелось бы небольшую статью-спинофф с более подробным обоснованием «Если мы умные люди и не держим базу в кубере». Сейчас все больше людей, которые хотят держать БД в контейнере… как им объяснить, почему это плохо?
          +4
          Мы из другой компании, но на эту тему не так давно был довольно подробный доклад (на HighLoad++ 2018): «Базы данных и Kubernetes».
            0
            На самом деле нет ничего плохого в держании базы в контейнере. Всё самое плохое от того, как люди такие контейнеры используют.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое