Вот и помер Docker Swarm

Введение

Да, можно сказать, что он еще не умер, его разработка не заморожена, и в некоторых местах он все еще используется. Но в нише web-scale он однозначно был вытеснен Kubernetes. Предлагаю провести “ретроспективу”, и сказать, что он умел лучше конкурента, а где он проиграл.

Установка

В том, что касается деплоя, Docker Swarm на много порядков проще, чем Kubernetes. В качестве “движущихся частей” есть только dockerd, а все администрирование делается через docker. Создать кластер, установив docker на каждую машину можно очень просто: на будущем мастере запускается docker swarm init, который выдаст команду, с помощью которой можно присоединить воркеров. На этом установка завершается.

Сравним это с Kubernetes: установка “по старинке” настолько длинная, что пальцы успевают устать от одного пролистывания. Вместо этого предлагается использовать Ansible репозиторий, или хотя бы kubeadm. После успешной установки методом “установил и забыл” через год вас ждет сюрприз: все сертификаты истекли, и их надо заменять. Сами себе злобные буратины, что не учли? Да. Сложно ли узнать, что сертификаты надо ротировать? Да, если вы использовали готовые утилиты. В связи с этим рекомендуется хотя бы раз помучаться и поставить его своими руками полностью вручную. Почему-то с докером такого делать не надо. В его копилку за это отправляется большой плюс.

Конфиги

Взглянем (не без слез) самую интересную для разработчика часть: написание конфигов для каждой из систем.

Docker Swarm

Docker Swarm официально поддерживает идею “стаков”, где в одном файле хранятся описания сервисов, сетей, секретов, и конфигов. Формат достаточно компактный, и использовать его чтобы поиграться и “потыкать” Swarm очень даже приятно. Только стоит дойти до его реального использования, как все очень быстро разваливается. Проблема заключается в том, что одно и то же приложение обычно установлено как минимум в трех вариациях:

  • Прод
  • Стейджинг
  • Локальная разработка1

Можно взять и положить рядом docker-compose.dev.yml, docker-compose.staging.yml, docker-compose.prod.yml, но уже на третий раз, когда вы в одном из окружений забудете добавить, скажем, переменную окружения, и при выкатке прода станет очень грустно, захочется вырывать свою роскошную шевелюру. Но не стоит отчаиваться! Docker Swarm позволяет склеивать ямлики, и можно просто сделать docker-compose.yml, docker-compose.dev.override.yml, docker-compose.prod.override.yml, docker-compose.staging.override.yml. А если у вас есть, скажем, несколько стейджингов, просто используйте интерполяцию переменных окружения! После такой склейки и мешка интерполяций окружения конфигурация становится не только хрупкой и легко ломаемой, но еще и совершенно непонятной. Красивый конфиг вида “привет, я сервис, я слушаю на порту 80”, который используется в маркетинговых примерах, обрастает достаточно жуткими волосатостями. А еще определение стека не получишь обратно из кластера, чтобы сравнить и что-то из него понять, не говоря уже про “понять, какие были переменные окружения”. Спасает жизнь только Portainer, который хранит текст и переменные окружения в нетронутом виде.

Еще одна мелочь, а неприятно: каждый конфигурационный файл - это отдельный объект, и его надо отдельно указывать в стеке. Если кажется, что это мелочи жизни, то вспомним, что конфиги не являются мутабельными (как и секреты), и надо при изменении конфига обновлять стек. Самым лучшим решением (до которого дошел я) было называть все конфигурационные файлы по следующей схеме: stack-name-my-cool-config-${MY_COOL_CONFIG_VERSION:-v1}. Тогда для обновления версии файла достаточно было просто:

  • Распарсить конфигурационный файл стека
  • Выкусить оттуда название переменной окружения и ее текущее значение
  • Залить новый конфиг (секрет) с увеличенной циферкой
  • Обновить стек, поменяв циферку в нем

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

Kubernetes

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

Отличия окружений решаются двумя способами: kustomize (встроен в kubectl) и Helm (не официальный инструмент, но стандарт индустрии). kustomize чем-то похож на склеивание ямликов, только более умное, и позволяет отделить котлеты (неотъемлемые части конфига, которые менять нельзя) от мух (переменные, специфичные для окружения). Helm же позволяет писать шаблоны YAML конфигурационных файлов, и паковать их в аккуратный .tar. Все особенности между окружениями помещаются в values.yml. Поговорим побольше про второй.

Я не знаю, кто придумал Helm, и чем он руководствовался, и уж тем более кто решил, что это хорошая идея и стал продвигать, но кажется, что он поигрался с Go templates и решил, что болью надо делиться. Вдумайтесь: вы берете машиночитаемый формат YAML, и работаете с ним с помощью текстовых шаблонов. Это примерно как забивать молотком шуруп - да, это более универсальное решение, только доска в итоге ломается. Как и мозг каждого, кому это приходится после написания такое чудо поддерживать. В Helm чартах есть свои конвенции, про которые вам никто не расскажет, но которые хочется соблюдать. Никакого автоматического инструмента для проверки того, доступна ли вся конфигурация, которая должна быть у любого уважающего себя чарта (например, навесить на Pod свои лейблы, подменить образ), естественно нет. Потому что после того, как вы изуродуете YAML (будем честны, с этим помощи ему не надо) с помощью Go templates, распарсить это может только высшая сила. Ну, и helm(1). Примерно как писать на плохом языке программирования в блокноте без поддержки подсветки и проверки синтаксиса.

Но не будем излишне очернять этот инструмент: потреблять чарты достаточно удобно. Представьте себе, установка MariaDB с репликацией простой командой helm install mariadb bitnami/mariadb5. Главное - не заглядывайте внутрь этого ужаса. Ах да, так как обычно портянки опций для чартов невозможно читать в силу их обширности и неточности, придется почитать сами шаблоны для наглядности. Молитвы опциональны, все равно не помогут.

Инструментарий

Благодаря волне хайпа, для k8s есть вагон и маленькая тележка различных инструментов, часть из которых стала даже общепринятыми (тот же Helm). Всей этой радости для Docker Swarm нет, за исключением нескольких дашбордов на выбор, включая тот самый Portainer. Ничего похожего на Helm нет, и не будет. Благо в качестве Ingress для самых бедных (пока что) можно использовать Traefik, который пусть уже не самый простой в использовании, но вполне приемлемый, а для сбора логов есть Filebeat6. Жить можно. Плохо и недолго, но можно.

Сама разработка инструментария для Docker Swarm с точки зрения безопасности представляет собой какой-то кошмар. Да, Docker умеет делать AuthN/AuthZ, только плагинов, чтобы это делать почти нет7. Поэтому любой инструмент, который будет общаться с dockerd через сокет будет иметь root доступ к ноде (в случае доступа к воркеру), или ко всему кластеру разом (в случае доступа к ноде). Kubernetes научились на этой ошибке, и теперь там хитрая структура с правами и ролями. Можно ли запутаться в новой горе YAML? Да. Есть ли риск, что компрометация вашего Ingress вскроет весь кластер? Не то чтобы.

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

Выводы

Docker Swarm ужасен. Kubernetes ужасны. Деплой прямо на виртуальной машине ужасен. Но все же если хочется страдать не в гордом одиночестве, то крутите семиконечный роль и радуйтесь (?) жизни. Работать в контейнерах мне нравится гораздо больше, чем страдать с виртуальными машинами8. Я не буду скучать по Docker Swarm.

  1. Разработка в контейнерах это боль. Еще большая боль - объяснять разработчикам, как сетапить окружение для разработки без контейнеров. 

  2. Не файлов, потому что YAML позволяет хранить много документов внутри одного файла

  3. Ладно, ладно, есть объект List, где можно все объекты в виде списка разместить в одном документе. 

  4. Тут тоже все немного нетривиально

  5. Только helm repo add bitnami https://charts.bitnami.com/bitnami и helm repo update не забудьте. Решение не делать репозитория по умолчанию достаточно интересно, возможно, docker.io/quay.io их чему-то научили. 

  6. Только будьте осторожны с амазоновским OpenDistro For Elasticsearch, они сейчас посередине войны с Elastic, и без клятвы верности одной из сторон вы эти два форка не подружите. 

  7. Безусловно, хочешь поменять мир - начни с себя, но я не умею ни воровать слова из греческого, ни рисовать минималистичные синие лого, так что модный Open Source продукт мне не создать. 

  8. Да, Packer, Terraform, и Ansible могут вас избавить от этих болей и без контейнеров. Только локальная разработка будет тем не менее страдать.