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

Я и сам замешан в перемывании косточек на эту тему.

Конечно, могу иногда поворчать по поводу Kubernetes, но, справедливости ради, это технологический шедевр. Настоятельно рекомендую всем моим конкурентам им пользоваться.
— Paul Butler (@paulgb) September 9, 2022

Несмотря на такое ёрничанье, я искренне считаю Kubernetes «технологическим шедевром». В сентябре 2022 года я также написал о том, насколько в самом деле обоснована сложность Kubernetes с учётом тех задач, которые он решает.

Мы в компании Jamsocket вот уже несколько лет используем Kubernetes в продакшене, и я вполне втянулся в работу с ним. Внутри компании нам удалось привить спокойное отношение к Kubernetes. Самое важное, что мы для этого сделали — вычленили небольшой фрагмент фич Kubernetes, а остальные научились игнорировать.

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

Зачем вообще нужен Kubernetes?


На мой взгляд, Kubernetes — это столбовая дорога к цели, если вы стремитесь добиться сразу трёх следующих вещей:

  1. Одновременно выполнять множество процессов/серверов/назначенных заданий.
  2. Выполнять их с избыточностью и при этом балансировать нагрузку между ними.
  3. Конфигурировать их в коде и выражать отношения между ними в виде кода.

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

Мне доводилось слышать, что пункт #2 из списка выше — это уже слишком, стартапы не должны стремиться к развёртыванию с нулевыми задержками или к высокой доступности. Но нам зачастую требуется развёртывать код по несколько раз в день, а когда продукт ломается — от этого страдают, прежде всего, наши пользователи. Даже минуту недоступности кто-нибудь, да заметит. При работе в режиме скользящего развёртывания мы уверены, что можем выполнять такие операции без церемоний и с нужной частотой.

Как мы используем Kubernetes


Для контекста: Jamsocket— это сервис для динамического поднятия процессов, с которыми может «общаться» веб-приложение. Чем-то похоже на AWS Lambda, но время жизни процесса увязывается с соединением WebSocket, а не с отдельно взятым запросом/откликом.

При помощи Kubernetes мы выполняем долгоиграющие процессы, необходимые для поддержки таких операций. Сервер API, реестр контейнеров, контроллер, сборщик логов, некоторые сервисы DNS, сбор метрик и т.д.

Вот несколько видов операций, в которых мы обходимся без Kubernetes:

  • Эфемерные процессы как таковые. В самом начале работы мы успели активно ими попользоваться, но вскоре обнаружили, что они скорее нас сковывают (подробнее об этом ниже)
  • Статические/маркетинговые сайты. Для этой цели мы пользуемся Vercel. Он дороже, но, что ж, час времени разработчика на маленьком стартапе тоже стоит дорого, и в нашем случае Vercel вполне себя окупает.
  • В любых контекстах, где непосредственно сохраняются такие данные, о потере которых мы бы очень горевали. Мы используем персистентные тома для кэширования или производных данных, но, как правило, мы предпочитаем работать с управляемой базой данных Postgres DB вне кластера и хранилища блоб-данных.

При этом немаловажно, что сами мы не администрируем Kubernetes. Его главное преимущество заключается в возможности аутсорсить его эксплуатацию на уровне инфраструктуры! Нас вполне устраивает Google Kubernetes Engine, и, пусть фиаско Google Domains поколебало мою веру в Google Cloud, я, как минимум, сплю спокойно, полностью отдавая себе отчёт в том, что при необходимости нам не составит труда мигрировать на Amazon EKS.

Вещи, которыми мы активно пользуемся


В k8s есть несколько типов ресурсов, которыми мы пользуемся без колебаний. Здесь я перечислю только те ресурсы, которые мы явно создаём. Большинство из них сами неявно создают другие ресурсы (например, поды), которые я не стану упоминать, но которыми мы, конечно же (косвенно) пользуемся.

  • Развёрнутые инстансы: большинство наших подов мы специально развёртываем. Каждый развёрнутый инстанс, критичный для работы нашего сервиса, существует в нескольких репликах, и к нему применяются скользящие обновления.
  • Сервисы, а именно: ClusterIP для внутренних сервисов и LoadBalancer для внешних. Мы избегаем пользоваться сервисами NodePort и ExternalName, предпочитаем, чтобы наша конфигурация DNS находилась вне Kubernetes.
  • CronJobs для скриптов очистки и тому подобных вещей.
  • ConfigMaps и Secrets: для передачи данных вышеупомянутым ресурсам.

Вещи, которыми мы пользуемся с осторожностью


  • StatefulSet и PersistentVolumeClaim: да, нам доводилось время от времени пользоваться StatefulSet. Эта конфигурация посложнее, чем при развёртывании обычных инстансов, зато позволяет сохранять персистентный том от перезапуска к перезапуску. Важные данные мы предпочитаем долговременно хранить в управляемых сервисах вне k8s. У нас нет табу на использование томов, так как иногда бывает удобно сохранить, скажем, кэш между перезапусками. Но я предпочитаю обходиться без томов, так как при скользящем подходе к развёртыванию между ними возможны патологические взаимодействия (взаимные блокировки).
  • RBAC (управление доступом на основе ролей): таким подходом мы пару раз пользовались, например, чтобы дать сервису право обновить секрет. Но такой подход слишком усложняет наш маленький кластер, поэтому я его в основном избегаю.

Вещи, которых мы активно избегаем


  • Писать YAML вручную. В YAML достаточно вариантов, как выстрелить себе в ногу, поэтому я предпочитаю не иметь с ним дел. Напротив, определения наших ресурсов Kubernetes мы создаём из TypeScript при помощи Pulumi.
  • Невстроенные ресурсы и операторы. Ранее я писал о том, почему паттерн цикла управления — это палка о двух концах. Да, это ключевой фактор, обеспечивающий надёжность K8s, но он же — источник лишней опосредованности и сложности. При помощи паттерна оператор и собственных ресурсов можно позволить сторонним программам использовать надёжную инфраструктуру Kubernetes в их собственных управляющих циклах, и в теории эта идея отличная, а на практике, как оказалось — довольно неудобная. Мы не работаем с cert-manager, а автоматизируем работу с сертификатами при помощи Caddy.
  • Helm. Helm для нас не вариант, так как он требует работать с операторами и иметь дело с YAML без правил, но я к тому же полагаю, что, используя шаблонизацию неструктурированных строк для генерации данных, которые затем попадали бы под машинный парсинг, мы сами повышаем хрупкость нашей системы, ничего не выигрывая. Мне кажется, что nindent — это как «железом по стеклу», извините.
  • Что угодно с “mesh” в названии. Полагаю, кому-то они полезны, но точно не мне и, кстати, ему тоже не нравятся.
  • Ingress-ресурсы. Лично у меня рубцов от них нет, и я даже знаю людей, умеющих продуктивно пользоваться такими ресурсами, но наш успешный опыт работы с Kubernetes подсказывает, что нужно избегать лишних уровней косвенности. Нам вполне хватает сконфигурировать Caddy, так и действуем.
  • Попытки реплицировать целый стек k8s на локальной машине. Мы не пользуемся такими вещами как k3s или ему подобными для точного воспроизведения продакшен-среды, а обходимся Docker Compose или нашими собственными скриптами для запуска того подмножества системы, которое нас в настоящий момент интересует.

Человек не должен дожидаться пода


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

Признаю, что здесь я пиарю мою книгу, но она хотя бы опенсорсная: в ней рассказывается о лицензированном в MIT оркестраторе на Rust, который называется Plane. Этот оркестратор предназначен для быстрого назначения и выполнения процессов, обслуживающих интерактивные рабочие нагрузки (в частности, такие, которых приходится дожидаться человеку).

Более высокоуровневые абстракции


Для полноты картины также должен упомянуть, что уже появившиеся альтернативы достаточно хороши. В частности, если для вас не актуально требование #3 из вышеприведённого списка (возможность расписывать инфраструктуру в коде). В одном из наших продуктов мы решили воспользоваться Railway вместо кластера k8s, в основном для обслуживания превью-сред. Некоторые коллеги, которых я очень уважаю, топят за Render (я им тоже баловался, но мне кажется, что предлагаемая в Railway модель среды всё-таки чище). Кроме того, я выступаю за принятый в Flight Control подход «приходи со своим облаком».

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



Возможно, захочется почитать и это:


Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале

Комментарии (10)


  1. SUNsung
    31.03.2024 14:14

    Хорошо, статья интересная но так и не увидел сравнения с докером и обьяснений почему кубик лучше докера.

    Много чего было, но зачастую контейнеры всегда делаются через докер.

    Даже сложные вещи спокойно собираются через doker-compouse, а кубик реально переусложнен как по мне


    1. Stanislavvv
      31.03.2024 14:14
      +3

      Он просто не для случая десятка-другого контейнеров в кластере. Плюс — всякие GKE и EKS позволяют не заботиться об инфраструктуре и, как и в swarm, конфигурировать сервисы через yaml.

      А если вы не про кластер docker swarm, а про докер на единственном сервере, то, извините, вы сравниваете не те уровни абстракции.


    1. f_s_b_37
      31.03.2024 14:14
      +3

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

      А вот если вы запилили оверлейную сеть, service discovery, что-напоминающее оператор кубера на питоне и т.д. - тогда уже можно задумываться об оркестраторе


      1. SUNsung
        31.03.2024 14:14

        Жизнь оркетстров за пределами амазона и подобных площадок полна подводных камней)

        А относительно кубика - он все равно упирается в ведомый/ведущий и асинхронную дублированую сеть синхронизованых нод на нем сложно поднять без костылей.

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

        Кубик хорош для централизованого кластера, когда разные контейнеры на разных серверах, но не более. Увы.

        Может быть я не прав, было бы интересно услышать в чем именно. Работаю в основном с докером, кубер несколько раз пробовали и не то явно. От задачи зависит понятное дело, но в рамках малых виртуализаций (локально или иное) докер на коне. В рамках распределенного кластера и быстрой развертки докер так же на коне.


        1. computerix
          31.03.2024 14:14
          +2

          Просто ваше приложение ещё не вышло на такой уровень сложности вероятно, когда требуется более сложная логика управления контейнерами. Вроде автоскейлинга по какой-нибудь определенной метрики у приложения. Или логики размещения подов на нодах, чтобы например инстансы приложения не попадали на одну ноду для отказоустойчивости, ну или каким-то приложениям необходимо наличие видеокарты на хосте и прочего. Ну и сервис дискаверинг тот же. Придется какой-нибудь consul прикручивать. Я тоже считаю, что кубер сильно переусложнили в погоне за универсальностью и нужно внедрять его когда он нужен, и ты четко понимаешь плюсы и минусы. А не гнаться за модой и хайпом.


      1. Owleyeinnose
        31.03.2024 14:14

        уже есть оператор куба на питоне) и кажется не один(


      1. mayorovp
        31.03.2024 14:14

        А вот если вы запилили оверлейную сеть, service discovery, что-напоминающее оператор кубера на питоне и т.д. - тогда уже можно задумываться об оркестраторе

        …то уже немного поздно задумываться об оркестраторе


    1. vitaly_il1
      31.03.2024 14:14

      Корректно сравнивать не с докером, а с оркестраторами - docker compose, docker swarm, Nomad, AWS ECS.


  1. ggruno
    31.03.2024 14:14

    А caddy это что если не ингресс ?

    Такой же прокси

    А что за оператор нужен для helm ?

    С 3 версии он работает как и нативный kubectl ничего лишнего не нужно

    И как то все пришли к тому что это стандартный пакетный менеджер для куба а вам не нравиться

    Повторять все в компоузе тоже не очень практика

    Ту же стратегию апдецтов не протестить

    Хелс пробы тоже у докера конечно тоже есть но это далеко не то же самое

    А если есть интеграция с внешними сервисами то вообще досвидос ( какой нибудь vault и инжект сикретов)

    Да и как раз хельм и ямл одинаковый и там и там и косяк можно словить заранее


    1. mayorovp
      31.03.2024 14:14

      А caddy это что если не ингресс? Такой же прокси

      Я так понял, они конфигурируют его "родными" файлами конфигурации, и не используют для этого ingress ресурсы в кубе.

      И как то все пришли к тому что это стандартный пакетный менеджер для куба а вам не нравиться

      Возможно, это как-то связано с "качеством" сторонних пакетов. Или же задача написания качественных пакетов не имеет решения вовсе...