Привет, друзья! Разговоры у вечернего костра, у палатки, у реки, в спокойный тихий вечер и в располагающей к технической честности и объективности атмосфере, а не «у пепелища дата-центра», как некоторым может показаться! ? Приготовьтесь погрузиться в захватывающую историю как, но важнее, почему мы сознательно пришли к активному использованию контейнеров и «доросли» до внедрения Kubernetes в высоконагруженном проекте «BI-конструктор». Но про Kubernetes в посте не будет ни слова, будет только про контейнеры, но мы подготовим мозг к следующему посту, уже исключительно про Kubernetes, но тоже максимально доступно. Однако, я буду все рассказывать очень простыми (иногда техническими) словами, без ныряния в многоуровневый мат, уж простите. Я убежден, что когда ты все прочувствовал и выстрадал умом и сердцем, то сможешь этот опыт передать доступно и понятно другим, а когда сам не понимаешь, о чем говоришь, то и остальных просто запутаешь. И еще один момент – технических картинок и графиков по теме тоже не будет, они вызывают головную боль от растекания абстракций и их легко найти самостоятельно и в конце я дам рецепт - где. Но, даже без них не сомневайтесь - вы все поймете с первого раза. Итак, наливайте кофе, насыпайте попкорн, кладите в карман таблетку от головной боли (иногда будет сложно, но ради вашего же блага) и ныряйте «под кат».

Разработчик и сисадмин в одном флаконе – редко, но полезно

Так сложилось, что я половину времени, уже больше 20 лет, пишу код на Python, Java, Rust, PHP, а половину времени изучаю и настраиваю серверный софт, а также читаю много технической документации к нему, чтобы проектировать и запускать большие и высоконагруженные проекты в нашей компании. Это помогает комбинировать плюсы двух точек зрения, двух «вселенных», и именно это и сыграло решающую роль в выборе контейнеризации и дальнейшем развертывании нашего проекта в Kubernetes – т.к. сначала были видны плюсы «со стороны разработчика», а потом стали хорошо видны плюсы «со стороны системного администрирования» и потом пошли только одни плюсы, а минусы просто взяли и закончились.

Я буду также постепенно рассказывать о нашем большом проекте и начну, пожалуй, с его интерфейсной части. Картинка ниже, конечно, не про его интерфейс, а про жизнь архитектур "до контейнеризации" :-)

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

Apache Superset «не запускается стабильно» без контейнера

Мы решили сделать продукт для BI-аналитики данных из порталов Битрикс24: Лиды, Сделки, Компании, Контакты, Товары, Счета, Телефония – с использованием открытого софта и на открытых технологиях, чтобы дать его клиентам, которых у нас десятки тысяч компаний, в дополнение к известным Power BI и Google Looker Studio. Самым подходящим кандидатом на роль «внешнего интерфейса» с графиками и дашбордами оказался Apache Superset на python. Формально его можно установить через команду «pip install» (https://pypi.org/project/apache-superset/), но он не устанавливается правильно ни на Windows, ни на CentOS, начинается выклянчивание каких-то дополнительных библиотек, при установке которых ломаются предварительно установленные (и так до бесконечности). В итоге, после нескольких недель «борьбы с зависимостями», пришло в голову простое решение – а давайте запустим его «в докере»? Да, в контейнере, про которые активно рассказывают уже много лет на многочисленных конференциях в докладах про «докер-кубернетес-попрыгали-попили-смузи-улыбнулись-счастье-наступило». И вдруг, о, чудо, через 2 минуты Apache Superset прекрасно запустился и на Windows, и на CentOS, и запустится в еще десятке операционных систем разных версий и будет работать «практически» одинаково.

Виртуальные машины – дорого, да еще и тормозят

Как работает виртуальная машина, скажем в Windows, знают, похоже, уже дети в детском саду. Запускаете VirtualBox, выбираете операционную систему или ставите свою из образа (весом в несколько сотен мегабайт, как минимум) и вот у вас Linux внутри Windows. Но есть проблемка – виртуальная машина на самом деле - полноценный виртуальный компьютер (!) с диском на несколько десятков (сотен) гигабайт, с кучей запущенных внутри процессов и серверов, регулярных задач (cron jobs), обособленными сетевыми и дисковыми интерфейсами и т.п. Это все не только громоздко, но и заметно нагружает внешнюю операционную систему. А чтобы зайти вовнутрь, надо это делать через SSH, как будто заходите на отдельный сервер (так ведь оно и есть, у вас запускается внутри отдельный сервер!). А если вы заходите запустить рядом еще одну виртуальную машину, то все, вы приехали – все будет страшно тормозить и больше двух виртуальных машин вряд ли получится запустить на среднем ноутбуке. Да, сейчас пытаются сделать промежуточные решения типа WLS2, но запах костылей не дает ночами спать.

«Легкие» виртуальные машины - контейнеры

Многие обожглись с тяжелыми виртуальными машинами и стали искать альтернативный вариант с «легкими» и «очень легкими» «виртуальными машинами», и в начале нулевых инженеры из Google, в числе, конечно, и других, предложили в ядро Linux серию патчей для cgroups, которые, наряду с namespaces, позволяли красиво решить эту задачу (а кто-то вроде обещал не ругаться многоэтажным техническим матом).

Контейнеризованный процесс в Linux, общаясь с ядром операционной системы, думает, что он один, что у него свой сетевой интерфейс, свой диск и никого нет вокруг (но он может создать дочерние процессы, если захочет, но так редко делают; чаще – один процесс = один контейнер). Как будто он совсем один во вселенной, как Маленький Принц, и окружение создано исключительно для него.

Контейнер "думает", что он один во вселенной и вся операционная система - для него!
Контейнер "думает", что он один во вселенной и вся операционная система - для него!

Важно понять, что на «диск» этого изолированного процесса кладется только то, что ему одному нужно. Т.е. для контейнера с процессом Python кладется только то, что нужно Python для запуска – библиотеки и конфиги, и там не будет Java, других языков программирования, регулярных системных задач (cron jobs), оконных сервисов и демона sshd. Это, думаю, самое главное, что нужно понять в слове «контейнер». Образ контейнера, т.е. файлы, которые видит запускаемый процесс контейнера, обычно на порядки меньше «обычных» виртуальных машин (типа VirtualBox) и может занимать мегабайты, десятки мегабайт и, довольно редко, сотни мегабайт и больше.

Таким образом, можно легко запустить несколько контейнеров одновременно, скажем веб-сервер на Python, базу данных, Redis и запустить среди контейнеров еще и свое приложение, тоже добавленное в контейнер. И получить нагрузку, как от запущенных нескольких программ, что не сравнить с нагрузкой от нескольких запущенных «обычных»
виртуальных машин!

Trino тоже оказалось удобнее запускать в контейнере

Для нашего проекта мы выбрали «базу данных» для обработки SQL-запросов из Apache Superset под названием Trino. К ней есть книжка увесистая на несколько сотен страниц, возможно, приводящая к временной потере сознания при ударе по голове, система эта кластерная, но … мы решили повторить успех с контейнером Apache Superset и запустили на ноутбуке разработчика контейнер с сервером Trino за, опять-таки, пару минут. Пару часов дальше потребовалось, чтобы написать свой Dockerfile, подложить свой конфигурационный файл для Trino и модифицировать его работу для нужд нашего проекта. Все это, разумеется, мы сразу добавили в репозиторий кода. Теперь, чтобы запустить Trino и показать его работу с Apache Superset, достаточно запустить на ноутбуке сотрудника два контейнера, которые через свои конфигурационные файлы уже знают, как по именам найти друг друга и наслаждаться работой Apache Superset с данными через Trino. Не зря, возможно, люди пьют смузи на конференциях про «докер-кубернетес-попрыгали-счастье-наступило» и мы стали погружаться вниз, почти до самого железа.

Быстрые прототипы – группы контейнеров в Docker Compose

Опьянев, в хорошем смысле, без похмелья, от удобства использования контейнеров для запуска Apache Superset и Trino, мы решили запустить сразу рядом контейнеры MySQL, Nginx, Redis, и, конечно, без настраивания операционной системы или «обычной» виртуальной машины. Все проще - поставили Docker Desktop на Windows на ноутбук разработчика, скачали консольной командой «docker тра-та-там» образы Apache Superset, MySQL, Nginx, Redis, Trino с Dockerhub, стартовали вручную 5 контейнеров и получили работающее в браузере решение, работающее как одно целое, которое сразу и обсудили с коллегами.

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

Для запуска двух и более контейнеров, что нам вскоре и понадобилось, оказался удобным консольный инструмент Docker Compose. В текстовом файле конфигурации, в простом формате, мы описываем нужные нам контейнеры, папки, которые подключаются внутри контейнеров как диски, при необходимости создаем новые сети и одной командой запускаем группу контейнеров, обычно внутри их локальной сети, как единое целое. Таким образом, мы стали запускать сборку BI-решения, внутри которой уже были настроенные контейнеры: Apache Superset, MySQL, Nginx, Redis, Trino.

В такой группе контейнеров, по умолчанию, своя локальная сеть - контейнеры могут ходить друг ко другу на открытые порты и адресуют друг друга по именам контейнеров в очень простом текстовом конфигурационном файле Docker Compose. Таким образом, кроме созданной на лету локальной сети, уже из коробки работает DNS-разрешение имен и именно поэтому в конфигурационных файлах контейнеров прописываются короткие имена типа “mysql”, “nginx” и вовсе не нужно заниматься любовью с поднятием локального DNS-сервера.
Это очень удобно и дешево – прописал нужные контейнеры/образы, дал им имена, внес имена в конфиги и все заводится как часы за 30 секунд на ноутбуке любого сотрудника. Представьте, что это нужно было бы разворачивать в облаке как группу виртуальных машин каждый раз – небо и земля.

Конфигурация контейнеров – в системе контроля версий

Контейнер описывается в простом, текстовом файле Dockerfile. Из него он и создается на базе образа (про «образ» чуть ниже) одной командой. Группа контейнеров описывается в таком же простом текстовом файле docker-compose.yaml. Таким образом, разработчик создает эти файлы, поднимающие ему работающее приложение из кучи сервисов на локальном, скажем, ноутбуке, а затем добавляет их в систему контроля версий. Любое изменение конфигурации в этих файлах фиксируется в системе контроля версий. Т.е. все конфигурационные файлы Nginx, MySQL, Apache Superset, Trino (десяток конфигурационных файлов) и т.п., а их десятки, если не больше, под контролем разработчика и все изменения в них отслеживаются в журнале изменения системы контроля версий. Если что-то работает не оптимально/неправильно, разработчик сразу это видит по ошибкам в логах работы контейнеров (на самом деле логов нет - есть история сообщений в stdout и stderr каждого контейнера, но термин «смотри ошибки в логах» привычен). Необыкновенно удобно и надежно. У каждого разработчика перед глазами вся настроенная система из десятка контейнеров целиком, все «логи» на виду, легко что-то изменить и увидеть, как это работает во всех цепочке бизнес-сценариев.

В общем, как вы видите, в нашем проекте разработчики ощутили большой приток адреналина и начали активно экспериментировать на своих рабочих ноутбуках по схеме:

- Скачиваешь нужные для решения задачи контейнеры
- Создаешь для каждого контейнера модифицированное окружение, чуть поменяв настройки в Dockerfile
- Запускаешь их группой внутри их локальной виртуальной сети
- Контейнеры видят друг друга, ходят друг ко другу на порты (открыты по умолчанию) внутри их локальной сети
- Сверху разработчик запускает свое приложение, тоже имеющее Dockerfile, скажем, python flask web server с REST API, решает задачи и демонстрирует функционал бизнесу

В итоге получилось так, что через короткое время после перехода на разработку в контейнерах скорость экспериментов очень резко возросла. А ведь чем больше мы делаем экспериментов, тем быстрее приходим к бизнес цели!

Автотесты – в контейнерах

Написать код и решить задачу – это, к сожалению, полдела. Еще половина, хорошо протестировать его своим же кодом (а не руками, как кому-то могло показаться). А как это сделать, если приложение обращается к БД, к кэшу в Redis, к другим сервисам. Можно, конечно, потратить время и написать «заглушки» (mocks), и мы так часто и делали, но, оказалось, что нередко удобно тестировать, я имею в виду, скорее, интеграционные тесты, поднимая контейнеры рядом и обращаясь к ним из автотестов. Например, мы тестируем совместимость работы кода с разными версиями MySQL. Для этого прямо на ноутбуке разработчика поднимается несколько контейнеров с разными версиями MySQL и быстро проверяется работа в каждой из версий. При необходимости, разумеется, можно подсунуть в контейнер одной из версий MySQL модифицированный конфигурационный файл и стартануть контейнер с ним. Это оказалось и очень полезно и удобно и такой трюк нельзя провести, подняв несколько виртуальных машин с разными версиями MySQL – это бы очень сильно тормозило и поднималось долго, если бы поднялось вообще. Т.е. тестировать наш проект в контейнерах стало сильно удобнее и появилась возможность проверять его работу одновременно в разных версиях баз данных, кэша и т.п. – на ноутбуке одного сотрудника.

Тестировщики – тоже в восторге

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

Менеджеры по продукту – оценили пользу

Чтобы запустить группу контейнеров и получить работающее приложение у себя на ноутбуке, достаточно установить Docker Desktop (для Windows, Mac OS, Linux) и запустить внутри команду «docker compose -f docker-compose.yml up» из репозитория проекта. Если приложению чего-то не хватает, оно автоматически затянет нужные образы из централизованного репозитория образов контейнеров Dockerhub (или можно оперативно поднять свой локальный репозиторий образов контейнеров, это открытый софт).

Сначала приложение таким способом стали запускать разработчики, потом тестировщики и, довольно быстро, дело дошло до продактов и менеджеров и всех неравнодушных. И мы стали получать больше качественной обратной связи, а чем ее лучше, тем меньше рисков написать то, что хочется, а не то, что нужно людям. Легкость запуска контейнеризованного приложения любым сотрудником на его персональном компьютере, без инсталляции – еще один большой, нет, БОЛЬШОЙ, плюс.

Подключаются безопасники

Чтобы покопаться в архитектуре приложения и понять ее слабые места, нужно посмотреть не только исходный код самого приложения, но и конфигурационные файлы его вспомогательных сервисов (база данных, кэш, обратный прокси и т.п.). При контейнерной разработке, все конфигурационные файлы контейнеров лежат в репозитории и их можно не только посмотреть, но и запустить, зайти в каждый контейнер в его консоль (sh/bash), полистать процессы, пощупать дыхание системы, увидеть открытые порты, еще раз заглянуть в конфигурационные файлы уже внутри контейнеров, попытаться зайти из одного контейнера в другой и т.п. Возможности для аудита безопасности, когда у тебя приложение запущено на ноутбуке со всеми сервисами, которые будут практически так же запущены на боевых серверах, расширяются драматически. А ведь известно, что чем больше глаз разных экспертов внутри компании смотрят на работающее приложение, тем меньше ошибок и изъянов в нем остается.

Промежуточные итоги выстроенного процесса

На данный момент, мы настроили процесс разработки, тестирования, аудита безопасности и демонстрации функционала контейнеризованного «BI-конструктора» менеджерам по продукту и бизнес-менеджерам. Процесс выглядел простым, очень удобным, гибким, открытым к быстрым и смелым экспериментам, и мы запустили внутри контейнеров самые последние, стабильные версии необходимого нам софта и то, что мы разработали в рамках проекта, мы запустили рядом тоже в контейнерах. Не нужно ждать системных администраторов и конфигурации облаков – берешь последний стабильный релиз нужного тебе открытого образа контейнера, скажем, Postgres, и запускаешь через минуту на ноутбуке локально, даже внутри Windows. Процесс запуска всей конфигурации из почти десятка контейнеров занимал пару-тройку десятков секунд: открыл ноутбук, скачал изменения из репозитория кода, запустил «docker compose -f docker-compose.yml up» и работаешь. Вечером запускаешь «docker compose down» и все дела, никаких инсталляций, заявок, многочасовые объяснения сисадмину, почему у него "там в облаке" не работает, а у тебя на ноутбуке работает и т.п.

Технические детали для построения процесса

В сухом остатке, для получения того же у вас нужно две вещи: настроить доступ с машин сотрудников к системе контроля версий (у нас mercurial) и установить на машины Docker Desktop (для Windows, Mac OS, Linux), что занимает несколько минут. Интерфейс Docker Desktop простой, интуитивно понятный, но для начала можно только поднимать проект утром и гасить вечером не вникая в детали, что такое контейнер, том, сеть.

И все же расскажу про «том». В самом простом случае, которого должно хватить в 99% сценариев, том («volume» в словаре контейнеров) это папка на диске, скажем, на вашем рабочем столе Windows, которая монтируется внутрь запущенного контейнера как внутренняя папка (технически, это системный вызов mount, если папка и контейнер внутри одной операцинной системы) и когда контейнер останавливается или удаляется, данные никуда не теряются и могут быть использованы другим контейнером, если при его запуске указать ему эту папку. Мы так делаем для контейнеров MySQL и Redis, а также других баз данных, чтобы не потерять созданные там данные.

Как работает docker, dockerd и Docker Desktop изнутри?

Возможно, вас уже начало раздирать любопытство изнутри – как же работает Docker, dockerd и Docker Desktop внутри? Помогу составить простую ментальную модель их работы в мозгу, на примере Windows. Разберу одну из самых популярных схем (есть более «сложные», например WSL, их оставляю за кадром). Итак:

- Docker это, можно сказать, набор консольных команд, для управления контейнерами. Они позволяют скачивать образы контейнеров, запускать на из базе контейнеры, создавать свои образы (напомню, что «образ» -  это "zip-архив с файлами", нужные для работы контейнера). Ничего сложного, студенты пишут такие утилиты, вместо языка Go для из написания можно было использовать Basic, но Билл Гейтс был во время создания Docker занят, видимо, чем-то более интересным.  

- Внутри Windows добавляется виртуальная машина (настоящая, «тяжелая» которая) с Linux внутри, на основе, обычно, VirtualBox.

- Именно внутри этой виртуальной машины Linux, если вы работаете на Windows, будут создаваться наши docker-контейнеры. Это ведь ожидаемо – контейнеры создавались для работы в Linux (хотя появились и контейнеры для Windows, но это в 99% не основной сценарий, и он остается за кадром)

- Для облегчения запуска, останова, паузы контейнеров, создания своих образов контейнеров на базе существующих образов, внутри Linux поднимается сервис, написанный на языке Go, который называется dockerd. Если его даже аварийно остановить, запущенные контейнеры продолжат свою работу, если их предварительно попросить так делать (в настройках dockerd), т.к. контейнеризация это технология, прежде всего, внутри ядра Linux, а dockerd-сервис всего лишь элемент обвязки.

- Docker Desktop это графическая оболочка, отображающая контейнеры, их образы, тома, сети, позволяющая заходить в контейнеры, выполнять внутри них команды и т.п. Все это можно сделать и через консольную утилиту docker, но для начинающих иногда полезно покопаться с красивыми картинками и понажимать чекбоксы.

- Важно знать и хорошо понимать, что создавать контейнеры и их образы может разработчик на Windows на своем ноутбуке, а запускать эти контейнеры и использовать созданные им образы может кто угодно. Для этого нужно на Windows, Linux или Mac OS запустить
или dockerd или другой, совместимый с этим открытым стандартом, менеджер контейнеров, например cri-o или containerd. Более того, мы были «шокированы», когда внезапно узнали, что наши контейнеры, созданные на ноутбуках разработчиков под Windows c Docker Desktop запускаются на боевых серверах в Kubernetes в одних облаках внутри cri-o, а в других внутри containerd и работают, при этом, одинаково правильно! Промышленный стандарт на контейнеризацию во всей красе.

Где узнать больше деталей, желательно бесплатно?

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

- Контейнер и образ. Если я пишу новый сервис или приложение, я создаю для него контейнер на базе, скажем, образа python необходимой версии. Обратите внимание, не на базе CentOS123456, а на базе образа «python3.12» (миллионы образов, созданные в том числе создателями языков или продуктов, доступны на Dockerhub, но это не единственный открытый репозиторий образов контейнеров), внутри которого будет лежать бинарник python, связанные с ним библиотеки и пару библиотек и конфигурационных файлов из операционной системы, которые нужны именно ему. Там не будет веб-сервера Apache, библиотек базы данных MySQL, языка Basic или астрологических эфемерид за 20 столетие. А вот если бы вы запустили обычную, старую добрую виртуальную машину (VirtualBox) с Linux, то там подобного лишнего добра с избытком. Обязательно прочувствуйте эту громадную разницу в размере контейнера (десятки мегабайт, реже сотни мегабайт и очень редко больше) и размере образа операционной системы Linux (сотни мегабайт - гигабайты). И еще момент, в запущенной "тяжелой" виртуальной машине Linux будут работать процессы sshd, ntpd, другие сервера, регулярные задачи, а в контейнере python будет запущен только один процесс python.

- Том. Если вы запускаете базу данных в контейнере, скажем, MySQL, то, по умолчанию, все может писаться в файловую систему контейнера (а это не быстрый процесс) и пропадет при удалении контейнера. А если вы сделаете папку «снаружи», подключите ее во внутрь контейнера ключом консольной команды docker, то контейнер будет сохранять данные как бы внутри своей файловой системы, а на самом деле в папке снаружи! И работает это очень быстро, практически идентично папке в операционной системе без контейнеризации.

- IP-адрес, порты и сети. Для контейнеров вы можете легко создать сколько угодно локальных сетей и при запуске контейнеры получат временные IP-адреса, но лучше адресовать их по их имени. Это работает автоматически, если вы запускаете группу контейнеров командой «docker compose up». Ключ в команде запуска контейнера «docker run» позволяет привязать открытый порт контейнера к порту внешней операционной системы, так что можно обращаться на сервер на порт, скажем, 443, а попадать в веб-сервер внутри контейнера. Но хочу отметить, что на боевых серверах лучше выбирать тип для сетевой подсистемы docker-контейнера как «host», т.к. тип «bridge» обычно немного медленнее на единицы процентов.

- Чего нет в контейнере. Там нет файервола, нет регулярных задач cron-jobs, нет ротации логов и самих логов, нет сервера sshd, нет сервера синхронизации времени. Там запускается один процесс, скажем python, который выполняет скрипт, и который получает ID процесса 1. Нет никакого процесса init с ID = 1, как в мире операционных unix и обычных тяжелых виртуальных машин. Самый первый процесс, запущенный внутри контейнера, будет ваш скрипт на python c ID = 1, и он будет думать, что «вся операционная система, сеть, место на диске для него одного и вокруг он не видит не души»!

- А куда пишутся логи контейнера?. Это самый популярный вопрос системных администраторов, незнакомых с технологией контейнеризации. А логов – нет! ? Процесс, запущенный внутри контейнера, обычно с ID = 1, пишет, по соглашению, не в файл внутри контейнера (забудьте про это как про кошмарный сон, файловая система контейнеров для записи очень медленная, что не скажешь про скорость чтения из нее), а в стандартный поток вывода (stdout), а ошибки в (stderr). И эти потоки да, сохраняются некоторые время и их можно полистать консольной командой «docker logs». Этот функционал обеспечивает уже менеджер контейнеров, такой как dockerd, cri-o или containerd.

- Приложение = группа контейнеров. Ваше приложение запускается как группа связанных контейнеров внутри созданной во время запуска локальной сети (сетей) командой «docker compose up». Контейнеры хотят друг ко другу по сети по именам, имя контейнера можно использовать в качестве dns-имени, прописать его в конфигурации приложения, скажем «mysql». Имя «mysql» преобразуется автоматически (!) во временный IP-адрес контейнера с MySQL. Возникает вопрос, а как же имена внутри контейнера резолвятся в IP-адрес? Это отдельная тема, но это работает и пока можно о другом не думать и не лезть под капот без нужды.

Но если вам все же любопытно, как же «оно там внутри» работает, я рекомендую набрать в поисковике на Youtube фразу «docker и контейнеры за 8 часов» и, скорее всего, как и я, вы найдете видеокурс вводный за 30 минут, средний за 1-2 часа и продвинутый «pro level» за 8 часов. Я совершенно серьезно. Мы, внедряя контейнеризацию в компании, конечно, изучили практически всю техническую документацию к Docker, Docker Desktop, dockerd, Kubernetes, потратив на это пару месяцев и ища подводные камни, но, время было выброшено, практически, впустую. Достаточно было посмотреть 8-часовое погружение, информации в котором хватает в 99% случаев.

Системные администраторы, первое знакомство с технологией

И вот, друзья, начинается еще более интересная, вторая часть нашего поста, а именно, как пробиться с нашим отлично работающим процессом и продуктом через системных администраторов. Я опишу самые частые возражения от разных системных администраторов, с кем мне приходилось немало общаться на протяжении нескольких лет в разных компаниях, и дам ответы на них:

- Контейнеры придумали в Google для создания видимости работы и прогресса, обпившишь смузи. На самом деле контейнеризация (cgroups, namespaces) это технологии встроенные (да, с помощью патчей Google, но не только их) в ядро Linux, и они уже там с начала нулевых. Мы же все давно пользуемся Linux, а контейнеры – это часть того же Linux. Хостеры и раньше умели ими пользоваться (разными технологиями, не только контейнеризации, но и виртуализации) для экономии ресурсов физических серверов, не сообщая это часто клиентам ? Просто не хватало «обвязки» для простых смертных. И вот она появилась в очень доступном для людей виде набора инструментов Docker (хотя была еще технология LXC, но она оказалось не такой популярной, хотя до сих пор жива и развивается).

- Docker это игрушка для разработчиков, а серьезные системные администраторы разворачивают высоконагруженные проекты на серверах без контейнеризации. Docker это название группы утилит командной строки и нескольких сервисов: можно создать образы для контейнеров, можно подмонтировать папку снаружи внутрь контейнера и т.п. Теперь любой смертный, не отвлекая системного администратора, просмотрев 30 минутное введение на Youtube, сможет запустить внутри Windows (или Linux, или MaсOS), одновременно, 5 контейнеров разных версий Postgres и 4 контейнера разных версий (от очень старой, до последней) MySQL и протестирует работу своего приложения одновременно со всеми в риалтайме, прямо на встрече с бизнес-менеджерами, принимающими решения. Понимание этой мощи возможностей для разработчика нивелирует 30 минут (в крайнем случае, 8 часов), потраченных на просмотр видео по настройке Docker Desktop с большой корзинкой попкорна. А на боевых серверах для запуска этой же красоты достаточно установить dockerd, cri-o или containerd – созданные разработчиком контейнеры поддерживаются всеми этими менеджерами контейнеров одинаково, т.к. стандарт на образ контейнеров и Dockerfile считается уже промышленным. При этом системный администратор не сможет, скорее всего, установить в одну операционную систему на сервере без контейнеров 5 разных версий PHP и 4 разных версий Python, т.к. начнется кошмар с совместимостью версий библиотек и будет работать или одно или другое, и будет потрачен не один месяц и в пустую (и всех еще уволят). А с контейнерами такую задачу можно решить быстро и очень дешево. Поэтому Docker это уже давно не игрушка, а инструмент для бизнеса.

- Как это все потом поддерживать и обновлять? Для системного администратора все упрощается. Все сложное переносится разработчиком во внутрь контейнеров, а снаружи для системного администратора остается практически чистая операционная система с самыми базовыми системными библиотеками и менеджером контейнеров. И обновлять нужно будет лишь это небольшое число библиотек и лишь один менеджер контейнеров. Представьте себе, если бы вы гипотетически установили в эту операционную систему одновременно 5 версий Java и 4 версии Rust и начали обновлять это кровавое месиво? Страшно даже подумать о таком! ? А именно этим часто и приходится, к сожалению, заниматься, на боевых серверах, если не использовать технологию контейнеризации.

- Ваши контейнеры тормозят, нужно запускать софт без них. Звучит как байка, что нужно весь софт срочно переписать на «С», чтобы он работал и быстро и был наименее требователен к железу. Да, говорят что несколько процентов производительности при использовании контейнеров (как одной из технологий ядра Linux) можно в некоторых сценариях и потерять (например при видео-стриминге), но зато сколько плюсов можно получить сразу из коробки: на порядки выше скорость разработки, экспериментов и развертывания, качество тестирования и аудита безопасности возрастает тоже на порядок, уходят проблемы с совместимостью библиотек на боевых серверах и, что важно, боевые сервера не страшно теперь регулярно обновлять, устанавливая последние патчи безопасности, боясь сломать кучу запущенных legacy-проектов (пишу и плачу кровавыми слезами, вспоминая прошлую жизнь без контейнеризации).

Кроме этого, можно гибко ограничить ресурсы процессоров и памяти для контейнеров, не давая им заметно влиять друг на друга. А также контейнеры, т.к. они легкие и каждый решает свою задачу, позволяют «запихнуть» на сервер (а) гораздо больше проектов – у нас сейчас для BI-аналитики клиентов уже запущены десятки тысяч контейнеров c Apache Superset на сотнях серверов. Сложно представить, сколько бы стоило запустить вместо контейнеров (у нас 1 контейнер = 1 клиент-компания, с, грубо, 1 виртуальным CPU и 512МБ ОЗУ) десятки тысяч виртуальных машин в облаке. Чувствуете теперь, насколько контейнеры и гибче, и дешевле, и легче классических виртуальных серверов?

- А как это все мониторить? Контейнеризорованные процессы видны в дереве процессов Linux, как обычные процессы, но выполняющиеся с ограничениями cgroups и namespaces. Ну и существуют отдельные метрики для них, и есть немало уже готовых решений в этой сфере. Ничего сложного, все как обычно и начинайте как мы, с базового мониторинга операционной системы, внутри которого запущен менеджер контейнеров и углубляйтесь при необходимости. Из самого популярного в мониторинге, это нехватка некоторым контейнерам CPU и памяти (если их предварительно консервативно ограничить, что рекомендуется делать).

 В общем, подытожу, при использовании контейнеризации, на самом деле, жизнь системного администратора сильно упрощается. Обновлять программное обеспечение на сервере становится нестрашно, это можно делать регулярно (смех с сарказмом), ведь на серверах установлен минимум библиотек и менеджер контейнеров, а все самое сложное и интересное находится внутри изолированных друг от друга контейнеров, внутри которых могут одновременно работать десятки версий Postgres, MySQL, PHP, Python, Java, Rust и т.п., совершенно не мешая друг другу. Логи ротировать для каждого проекта не нужно, этим тоже занимается менеджер контейнеров. Системный администратор начинает помогать разработчикам собирать образы контейнеров, консультирует их, какие базовые образы Linux взять (скажем легкий Alpine с минимальными системными библиотеками, а не более «жирный» образ Ubuntu), пишет с ними Dockerfile и docker-compose.yml, больше занимается архитектурой и отказоустойчивостью, меньше молится Ктулху после слепого обновления "этого всего легаси".

А что с отказоустойчивостью и масштабированием контейнеров под нагрузкой?

Пока мы научились в этом посте запускать один (внутри dockerd, cri-o или containerd) или несколько контейнеров на ноутбуке или сервере (docker compose, как самое простое из доступных решений, хотя на пятки уже начинает наступать Docker Swarm и другие оркестраторы). И на удивление часто, этого оказывается достаточно. Когда-то еще было модно говорить, что запускать базы данных внутри контейнеров не рекомендуется, но эти времена тоже ушли, просадка сетевой производительности в единицы процентов в некоторых сценариях не повод для того, чтобы отказываться от многочисленных плюсов контейнеров для БД. Если контейнер упал, а точнее упал процесс в контейнере (например segfault), то можно попросить менеджер контейнеров его перезапустить автоматически, и так часто и делают. Но, к сожалению, более сложные сценарии отказоустойчивости, а тем более кластерные сценарии, где нужно обеспечить, чтобы был жив один и всегда один контейнер (или не меньше N контейнеров) и начинается алгебра протоколов консенусуса Raft (Paxos) – увы, уже не настроить без кластерных оркестраторов типа Docker Swarm или Kubernetes. Но нужно это не всем и не сразу. А для тех, кому это реально приспичило, я уже готовлю следующий пост.

Итоги

В итоге, друзья, теперь вы обладаете простой, полной и эффективной ментальной моделью контейнеризации, знаете ее многочисленные плюсы и несколько небольших особенностей и, уверен, сможете начать или продолжить более эффективно ее применять в своих проектах. Не переживайте, как мы раньше и не проваливайтесь в водянистую техническую документацию по теме контейнеризации и Kubernetes – это выброшенное время. Главное понять суть – я надеюсь, пост вам помог в этом. Технология необыкновенно проста и доступна всем! Разработка станет на порядок быстрее, эксперименты с новыми технологиями, языками и библиотеками на порядок дешевле и поддержка и обновление подобных, особенно сложных проектов с разным софтом разных версий больше не будет выглядеть кошмарным сном для системных администраторов. Один раз запущенные в виде контейнеров проекты, будут работать стабильно годами, иногда обновляясь (чем меньше контейнер, тем меньше библиотек в нем требуют обновления и тем реже его можно делать), не замечая, что под ними постоянно обновляется и увеличивает свою версию операционная система. Ощущение сильного упрощения, снижения рисков и стабильного развития вашего программного проекта больше не покинет вас никогда, поверьте.

Я хорошо помню внутреннее ощущение, когда мы за несколько дней после релиза раскатали несколько десятков тысяч контейнеров для клиентов (и регулярно обновляем их теперь новыми фичами) – с контейнерами стало сильно проще, появились четкие этапы, линейная зависимость одной задачи от другой, и разработке и тестированию и администрированию стало сильно легче, и голова перестала лопаться от сложности. Вместо одного большого распределительного щитка с перепутанными проводами, выпадающими наизнанку и дымом по выходным, перед нами теперь красивый щит с зелеными лампочками и промышленно выполненным cable-менеджментом.

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

Удачи, друзья и до новых встреч!

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


  1. saboteur_kiev
    21.08.2024 14:12
    +1

    Очень странные у вас системные администраторы, которые такое говорят.

    Может быть дело в том, что системные администраторы, которые настраивают инфраструктуру для компании, в которой идет разработка, как раз должны знать все эти CI/CD инструменты, быть знакомыми и с докером и кубером хотя бы на Вы, мониторинг именно подобных систем, и называться девопс-инженерами =)


    1. Dimly
      21.08.2024 14:12

      Обычные администраторы, привыкшие работать как раньше. Даже разрабы далеко не все начали использовать контейнеры.

      Но не путайте админа и девопса, этим вы только новые проблемы порождаете. Админы не должны знать CI/CD, это заблуждение из разряда что "компьютерщик" должен и комп собрать и домен настроить и в 1С написать новую регуляторную отчетность.


      1. saboteur_kiev
        21.08.2024 14:12
        +3

        по опыту, лучшие девопсы - те которые пришли из админов, а не из разрабов.
        Решения выходят более надежные, простые, легко поддерживающиеся =)


  1. Elaugaste
    21.08.2024 14:12
    +3

    Шел 2024 год, ребята из битрикс узнали про существование контейнеров. Может быть через пару лет увидим замену bitrixvm на helm чарт.


    1. AlexSerbul Автор
      21.08.2024 14:12

      Мы BI-конструктор сделали через helm chart. Каждый клиент - установка helm-приложения в кубер. Число подов больше 25к в одном кластере кубера. Ингресс-контроллер заменили с Ingress-nginx на Contour/Envoy, т.к. при 5к виртуальных хостов первый уже не справлялся. Helm тоже перестал справлятся при 15к инсталляций helm-chart, поэтому перешли на Postgres-бекенд для хранения релизов helm там. У нас уже не скучно :-)