Разговорились тут с коллегами и друзьями на тему контейнеров и их использования. Тема не то чтобы спорная но ее всегда интересно мусолить ибо у разных команд (Dev, QA, OPS) — разный взгляд на это.
Давайте я внесу свои 5 копеек, а вы меня поправите, если что!
Основная задача контейнера — решить проблему переносимости и воспроизводимости. Когда мы точно знаем, что приложение на конечный сервер доедет в том же виде, в котором его подготовил разработчик, с тем же форматом (подчеркиваю это слово) конфига (возможно с другими значениями, относящимися к Prod — другой ключ там, токен, entry point стороннего сервера и тп) и всеми зависимостями, в том же числе и тех версий.
Таким образом попутно решается проблема деплоймента / развертывания — просто донести контейнер и запусти. Не надо кастомизировать сервер, бояться что у тебя в репозитории Dev и Prod разные версии пакетов, ловить конфликты зависимостей и так далее. Но это как бы бонусом. Ещё бонус — при грамотном подходе мы экономим вычислительные ресурсы. теперь не под каждое приложение нужна своя VM, а это значит не нужно эмулировать железо (привет затраты на memory и compute), не надо хранить лишние файлы гостевой ос и ее компонент, более того, если два контейнера используют один и тот же слой, например базовый образ, он будет загружен лишь раз — что еще больше экономит место
Таким образом для окружений разработки и тестирования это решает проблему сложности поднять и сконфигурировать окружение — оно поднимается в несколько команд а конфигурация и описание инфраструктуры управляется в виде кода (привет IaC), что дружественно разработчикам, контролируемо и версионируемо. А значит это быстрей, проще, меньше ошибок. Для продакшена это решает совсем другие задачи — это решение анальной боли «dependency’s hell«, это 100% воспроизводимость, это решение проблемы «ничего не знаю, на моей машине все работает», это легкий выход новой версии и легкий ролбэк, это экономия ресурсов.
Но! Надо помнить что у всего есть своя цена. и ничего, НИЧЕГО не приходит бесплатно. Мы платим:
- Необходимостью получить экспертизу в +1 технологии. Простой и очевидной на первый взгляд, Но чем глубже в лес тем толще партизаны. Например ваше приложение может начать себя вести неадекватно. Cкажем до какой то версии (я не поленился и нашел статью, описывающую этот пример — начиная с java 10 это исправили.), jvm под Линукс не умело корректно получать значения cgroups по памяти из настроек контейнера и им следовать. И ваше приложение, отлично работавшее на bare metal или в VM начинало ловить OOM в контейнере. Это ещё надо понять что и где. Да, пример частный, но сколько из пользователей Docker смогут рассказать про 3 его основных столпа — cgroups, namespaces, unionfs? Я боюсь 5% (95% — docker run -d -p 666:8080 -name whatever shitpost/bugsoft:latest, 5% — ок гугл, что такое cgroups). Остальные просто делают docker run и когда в продакшене оно начинает вести себя не так, как ожидалось — делают лапки кверху
- Это плюс один уровень (а на самом деле нифига не один) абстракции, а значит надо понимать как оно вас приземляет на диск, в сеть и так далее по факту/
- Это новый сервис а значит это уязвимости и обновления. Да, да! Вот как минимум одна из «вкусных» CVE этого года, которая относится к самому docker engine — CVE-2019-5736. А так же стоит помнить что уязвимость может скрываться и в базовых образах: CVE-2019-5021
И список этот можно ещё продолжить.
Следующий вопрос — микросервисы. Если говорить про микросервисы, а именно паттерн архитектуры, так популярный в контексте докера и контейнеризации в целом — микросервисы могут жить и без контейнеров просто архитектура родилась как раз во время бурного интереса к ним и удачно на это легла. опять же, микросервисы микросервисами но ими тоже не надо обмазываться. То есть не надо плодить их ради самой идеи. Пример — у нас делали подсистему и изначально запланировали ее из 11 компонент. После проведения архитектурного совета их число сократили до 5. Ибо там были такие вещи как «этот компонент берет значение из базы, из таблицы А, считает хеш и кладет в таблицу Б». Я вам более того скажу, есть сферы где не то что контейнеры, виртуалки то не приедут. Только железо (только хардкор), но и там можно применить микросервисы как архитектурный паттерн — например банки, нефтяника, relaltime сферы, скажем диспетчерское управление, scada! Да, или даже круче — просто с теми же VM, в выводе утилиты top есть такой параметр — st (steal time) — это время которое гипервизор украл у вашей VM когда он перегружен. А если это вм с приложением которое управляет буровой и оно на пару секунд пропустило сигнал и закрыло клапан позже?) Или если в докере у вас крашнулся слой из base image и все контейнеры запущенные на его базе теперь могут выдать непонятно что?) С той же VM это не пройдёт (если только это не Linked clode — но даже если так, то в мире VM это опция а в мире Docker это стандарт дефакто), а с шаренным слоем — привет!
Более того, в контейнерах нельзя хранить данные (и вообще много чего не рекомендуется). Поэтому любые базы а них — только на тестовых средах. И я не про техническую возможность а про здравый смысл. Если у вас тестовый набор данных для разработки приложения, обучения вашей нейронной сети и тп — ради бога. это ваш набор данных который вы всегда можете заново загрузить. я про юзер данные которые появляются от юзеров и их страшно потерять.
Вот пример — есть кластер elasticksearch. Скажем аждая дата нода хранит данные в виде lucene файлов на диске ( ООООчень много небольших файлов) и индекс в памяти.
Когда это поднято на ВМ или железе все понятно — шарды раскиданы по дата нодам, с репликацией и пониманием того чтобы две шарды не приехали на ноды в одной стойке, VM кластере или AZ.
Все хранится на дисках, если нода падает, кластер перебалансирует ее шарды, если она поднимается, они синхронизировали разницу и балансируется опять взад.
Теперь представим это в виде контейнеров — да, мы можем все поднять в докере но использовать голый докер и прибивать гвоздями контейнеры к нодам мы не хотим — зачем нам тогда докер, он скорее лишняя прослойка чем помощник. Мы хотим его использовать со всеми плюшками а значит будет инструмент оркестровки. Например K8S. И вот у нас для такого контейнера на ноде K8S монтируется volume, все работает… И тут контейнер падает, или ноде на которой он был поплохело. Короче случилось что-то. Кластер переводит его на другую ноду и честно поднимает. Возможно даже создаст там ему volme только volume то будет пустой) И значит вместо того чтобы стянуть разницу он будет реплицировать всю шарду заново. А если у нас разом навернутся оба контейнера, державшие и мастер и слейв шарду? Данные тютю.
Да, я знаю пример так себе но показателен.
Поэтому мое имхо — только stateless приложения без хранения и без жестких запросов на realtime обработку данных можно (и нужно) упаковывать в контейнер!
П.С. Подписывайтесь на мой Telegram канал