Мнение от Джастина Кормака, бывшего CTO Docker.
Почему вообще появились контейнеры?
Несколько лет назад я провёл немало времени, отвечая на вопросы Федеральной торговой комиссии (FTC) по поводу покупки VMware компанией Broadcom. Их интересовало, можно ли считать контейнеры конкурентами виртуальных машин - они пытались разобраться в конкурентной среде вокруг VMware.
Это напомнило мне первые пять лет работы в Docker, когда все только и делали, что сравнивали контейнеры с виртуальными машинами. Контейнеры - это просто “облегчённые” виртуалки? Или это небезопасная ерунда, от которой все скоро откажутся и вернутся к старым добрым VM?
Я рассказал FTC, что эти технологии родились в разные эпохи и решали разные проблемы.
Виртуальные машины появились в тот момент, когда у компаний внезапно стало слишком много компьютеров. Управлять ими было тяжело - процессы были ручными, а загрузка серверов низкой (часто меньше 15%). Установка новых систем занимала кучу времени. Виртуализация позволила консолидировать ресурсы, сэкономить на “железе” и лицензиях Windows Server.
В мире Linux ситуация была чуть лучше: мы умели запускать несколько приложений на одном сервере, но и там многие машины простаивали без дела.
Контейнеры, напротив, появились не из-за избытка компьютеров, а из-за избытка приложений. Компании нанимали всё больше разработчиков, а те писали всё больше кода. DotCloud, предшественник Docker, был PaaS-платформой, которая столкнулась с этой проблемой и создала Docker для упрощения деплоя приложений. Ключевой идеей была не изоляция, а упаковка.
Так я это объяснял. Для корпоративных клиентов контейнеры стали частью перехода в облако. Им не хотелось просто “поднять и перенести” неэффективные виртуалки в облако. На заре Microsoft звонила нашим клиентам и предлагала “превратить их датацентр в Azure-датацентр” - мол, вы даже не заметите разницы, все те же VM, только теперь в Azure.
Продвижение контейнеров вместе с облаком стало способом вынудить модернизацию - и нередко переход с Windows на Linux.
Бюджет на изменения
Docker было легко принять - он почти не требовал менять привычный способ работы с софтом.
Главное новшество заключалось в Docker Hub - реестре, где можно было делиться образами. По сути, это был GitHub, только для запускаемых артефактов. У виртуальных машин ничего подобного так и не появилось. Ближе всего к этому подошёл, пожалуй, Vagrant Cloud, но шаринг полноценных VM-образов работал плохо - они были огромные, а полностью сконфигурированные образы плохо подходили для повторного использования. Чем меньше в образе специфики под конкретный случай, тем шире он может применяться.
Позже появились такие инструменты, как Cloud Init, которые убирали часть конфигурации, и VM-образы стали немного “многоразовее”. Но они всё равно оставались слишком тяжёлыми и конкретными, особенно по сравнению с контейнерными образами, которые представляют собой более мелкие и независимые компоненты. И не стоит забывать: сети тогда были медленнее, а сами образы - большими. Сейчас LLM-модели ещё больше, но это уже другая история.
Помимо одного яркого новшества, у Docker было и одно табу: нельзя было обновлять приложение “на месте”. Нужно было пересобрать образ и заново задеплоить. Это работало, потому что Docker оперировал в рамках одного приложения, и управление этим процессом было вполне посильным. А может, просто потому, что мы никому не сказали, что можно обновлять контейнер прямо в продакшене. Меня всегда удивляло, что никто не сделал инструмент, позволяющий передавать обновления по ftp прямо в контейнер и обновить, например, PHP-приложение на лету.
Иммутабельность (неизменяемость) - отличная вещь, с кучей полезных свойств для безопасности (хотя большинство из них так и не раскрылись). Но главное - она упростила деплой, о чём мы ещё поговорим позже.
Ещё одно интересное следствие - Docker сделал язык Go уважаемым и популярным. Сейчас почти все современные языки включают TLS-стек прямо в стандартную библиотеку. До появления Docker основным пользователем Go был YouTube, а теперь этот язык занимает четвёртое место среди используемых в контейнерах - после Javascript, Java и Python.
Kubernetes
Помню, в первые годы контейнеров, ещё до появления Kubernetes (а потом, когда он только-только появился), все думали, что оркестрация контейнеров - это про планирование. Целые конференции были посвящены только планировщикам (schedulers). Но когда начинал говорить с реальными пользователями Kubernetes, оказывалось, что они просто пытаются написать скрипты для деплоя.
Docker Swarm не позволял писать такие скрипты. Команда безопасности решила, что если можно будет деплоить прямо изнутри кластера, то это “сломает модель безопасности”. В итоге коммерческая версия Docker имела, пожалуй, худший способ деплоя из всех возможных: ты буквально вставлял YAML в текстовое поле на веб-странице. Культура компании вообще игнорировала тему деплоя.
А ведь именно деплой - то, что все больше всего хотели делать с Kubernetes. Постепенно появились настоящие инструменты и философии деплоя, например GitOps.
Другой популярный вопрос тех времён:
“А будут ли вообще когда-нибудь запускать базы данных в контейнерах? Или на Kubernetes?”
И где-то в этот момент многие стали спрашивать себя:
“А зачем вообще мы запускаем базы данных сами?”
Решили, что если риск - потерять все данные, а выгода - чуть сэкономить, то проще отдать базу в облако. Не знаю, насколько это связано с тем, что контейнерное хранилище кажется таким ��егко удаляемым, но факт: эта эфемерность повлияла на отношение к данным.
Когда объект можно удалить без последствий, то управление жизненным циклом систем с состоянием превращается в совершенно другую задачу. А ещё тогда было слишком много вариантов для сетевых и распределенных систем хранения данных : NFS, блочные тома, Ceph, GlusterFS, EBS и т.п. - бесконечный выбор, бесконечные дебаты.
Что пошло не так
Фокус на деплое и растущая сложность Kubernetes убили DevOps в его изначальном смысле. Как человек, который раньше занимался администрированием, а потом вернулся в разработку, я всегда любил ту идею DevOps, что он объединяет культуры - разработчиков и операторов.
Но со временем всё это выродилось: DevOps стал просто технической должностью, где люди “ковыряются в Kubernetes и прочих инструментах деплоя”. Похоже, людям проще взаимодействовать с технологиями, а не с культурой - и технологии в итоге начали работать против самой идеи DevOps.
Docker на самом деле не изменил процесс разработки. Какое-то время казалось, что он может занять место Vagrant - в создании локальных окружений для разработки. Команда Docker проделала титаническую работу, чтобы сделать “разработку в контейнерах” удобной, но почти никто так и не стал этого делать - разве что в “облачных IDE”, которые на деле ближе к просто удалённым Linux-машинам.
Тем временем Python и Ruby привели в порядок свои инструменты виртуальных окружений, а если тебе нужен действительно воспроизводимая локальная разработка, то можно использовать Nix. На практике же большинство разработчиков используют Docker лишь для того, чтобы поднять базу данных или сервис, с которым можно работать или тестировать приложение.
За последние десять лет основным способом создания приложений стало составление их из open source-компонентов. Но это в основном поддерживается языковыми пакетными менеджерами, которые у каждого языка свои и сильно различаются.
Мы так и не получили универсального уровня сборки - “единого способа билдить всё”.
Иммутабельность контейнеров помогла понять, что именно сейчас запущено, но огромное количество зависимостей и микросервисов сделало это знание менее полезным, чем казалось сначала.
Где мы сейчас?
Всё начиналось с виртуализации, цель которой была - повысить эффективность: ведь железо тогда использовалось лишь на 15 %. Теперь, по отчёту Datadog “State of Cloud Costs” за 2024 год,
“83 % расходов на контейнеры связаны с простаивающими ресурсами.”
То есть теперь мы можем точнее измерять, сколько именно впустую тратим вычислительных мощностей - вот и вся разница.
Вычисления, которые мы сейчас “растрачиваем”, как минимум в десять раз дешевле, чем раньше, но теперь у нас есть автоматизация, которая помогает делать это в промышленных масштабах. Большая часть контейнерных нагрузок сегодня - это мобильные приложения. Иронично то, что теперь CPU из телефонов, адаптированные под серверы ARM, запускают эти самые приложения.
Мы пришли к миру, где есть островки эффективности - там, где масштабы действительно велики, и длинный хвост неэффективности, который не исчез за десять лет.
ИИ показал, что можно добиться огромных скачков производительности, если сделать вычисления достаточно дорогими: стоимость инференса падает стремительно, и это, пожалуй, лучшее доказательство того, как быстро мы можем оптимизироваться, когда вынуждены.
Что дальше?
Эссе “Choose Boring Technology” (“Выбирайте скучные технологии”) было написано ещё в 2015 году. Тогда контейнеры точно не были скучными - автор приводил в пример “не-скучные” технологии вроде Consul и MongoDB, а “скучными” считались MySQL, Postgres, PHP, Python, Memcached, Squid и Cron.
А что теперь? ChatGPT сказал мне, что Docker - “в основном скучный”, а Kubernetes - “движется в сторону скучного”. Похоже, культура “выбирайте скучное” наконец-то прижилась - на это ушло целое десятилетие. Может быть, всё потому, что весь “бюджет на изменения” ушёл в сторону ИИ, а вместе с концом эры “нулевых процентных ставок” (ZIRP) для стартапов cloud-native эпоха перемен тоже закончилась. ИИ хорошо справляется со “скучными” технологиями - ведь он обучен и на нашей культуре тоже.
Если мы хотим чего-то нового, придётся снова выделить бюджет на изменения.