mysurik.ru

Почему я перевёл свои Docker контейнеры на Alpine Linux

Alpine Linux в Docker

С чего всё началось

У меня на сервере крутится около 20 Docker контейнеров. Каждый на Ubuntu или Debian. И каждый весит 200-400 MB. Я посчитал суммарный размер образов на диске — почти 8 гигабайт! Для террабайтного NVMe это копейки, но меня бесила неэффективность.

Начал смотреть, какие образы можно уменьшить. Нашёл Alpine Linux — дистрибутив на musl и busybox, который весит около 5 MB. Да, 5 мегабайт против 200 у Ubuntu.

Решил перевести все свои Dockerfile на Alpine.

С чем столкнулся

Первая проблема — musl вместо glibc. Многие программы собираются под glibc и на Alpine падают с ошибками. Например, npm-пакет sharp не собирался на Alpine, потому что требует glibc. Пришлось ставить libc6-compat через apk.

Вторая — пакетный менеджер apk. Я привык к apt, а apk работает иначе: команда не apt install python3, а apk add python3. Пришлось переписывать Dockerfile, привыкать. Но apk быстрее apt — это факт.

Третья — Node.js на Alpine официально поддерживается, но есть нюанс: версии в apk часто устаревшие. Лучше ставить Node через официальный образ node:alpine, а не через apk.

Python тоже норм. Стандартные библиотеки работают, но некоторые модули (например, mysqlclient) требуют компиляции, и на Alpine нужно ставить musl-dev и gcc.

Что я получил

Размер образов уменьшился в 5-10 раз. Мой nginx на Alpine весит 12 MB вместо 150 на Ubuntu. Python-сервисы — 50 MB вместо 300. PHP-FPM — 30 MB вместо 200.

Билды стали быстрее. apk качает пакеты быстрее apt. Сборка контейнера с нуля на Alpine занимает 30 секунд, на Ubuntu — 2-3 минуты.

Поверхность атаки меньше. Alpine минимален — в нём меньше потенциальных уязвимостей в стандартной поставке.

Мой первый Alpine контейнер

Помню, как собрал первый образ на Alpine для Python-скрипта, который парсит RSS и шлёт в Telegram. Раньше он жил в контейнере на Ubuntu — 280 MB. После переезда на Alpine стало 35 MB. Я обалдел. Перезапустил контейнер — стартанул за секунду. Раньше на Ubuntu грузился секунды 3-4. Разница вроде небольшая, но когда 20 контейнеров перезагружаются после обновления ядра — экономия времени ощутимая.

Что меня бесило в Alpine

Первое — отладка. В Alpine нет bash, только sh (busybox). Если привык к стрелочкам вверх и автодополнению — будешь страдать. Я ставлю bash через apk в dev-контейнерах, но в прод я его не тащу.

Второе — DNS. Alpine использует musl, у которого свои тараканы с резолвом. Была ситуация: контейнер на Alpine не мог достучаться до другого контейнера по hostname, пока я не прописал dns-сервер явно в docker-compose. На Ubuntu эта проблема не всплывала.

Третье — логи. По умолчанию в Alpine нет rsyslog или syslog-ng. Пришлось настраивать логгирование в stdout через Docker, чтобы логи попадали в docker logs. Мелочь, но кто первый раз ставит — теряет время.

Главный аргумент для перехода

Знаете, что меня окончательно убедило? Я обновил все образы на сервере, запустил docker system df и увидел: 8.2 GB стало 1.3 GB. Семь гигабайт свободного места на диске. Для домашнего сервера с NVMe на 256 GB — это серьёзно.

Плюс билды в CI/CD стали проходить за 40 секунд вместо 3 минут. Когда ты пушишь фикс и ждёшь деплой, каждая секунда на счету.

Я не фанатик. Если Alpine глючит с какой-то библиотекой — я не мучаюсь, беру Ubuntu. Но для 80% моих контейнеров Alpine — идеал. Маленький, быстрый, безопасный.

Кстати, я даже nginx собираю из сорцов на Alpine, а не беру готовый образ от nginx. Потому что официальный nginx:alpine — 11 MB, а nginx:latest — 187 MB. Разница в 17 раз. Задумайтесь.

Где я оставил Ubuntu

Не все контейнеры перевёл. Тяжёлые сервисы с кучей зависимостей (например, базы данных) оставил на Debian — там стабильнее поддержка драйверов. MySQL и PostgreSQL официально рекомендуют Debian. Для n8n оставил Ubuntu, потому что на нём проще отлаживать.

Но для простых сервисов типа nginx, Python-скриптов, PHP-FPM — только Alpine.

Совет

Если ваш контейнер делает что-то простое — проксирует запросы, выполняет Python-скрипты, отдаёт статику — переводите на Alpine. Сэкономите место, время сборки и нервы.

Если используете сложные библиотеки с нативными расширениями — сначала проверьте совместимость в тестовом контейнере. MySQL, Redis, PostgreSQL лучше не трогать.

Расширю рассказ о переводе Docker-контейнеров на Alpine Linux дополнительными деталями. С чего всё началось. У меня на сервере крутится около 20 Docker контейнеров. Каждый на Ubuntu или Debian. И каждый весит 200-400 MB. Я посчитал суммарный размер образов на диске — почти 8 гигабайт! Для террабайтного NVMe это копейки, но меня бесила неэффективность. Контейнеры должны быть лёгкими — в этом их смысл. Когда образ весит 300 мегабайт, это не контейнер, а мини-система. Начал смотреть, какие образы можно уменьшить. Нашёл Alpine Linux — дистрибутив на musl и busybox, который весит около 5 MB. Да, 5 мегабайт против 200 у Ubuntu. Решил перевести все свои Dockerfile на Alpine.

Почему Alpine такой маленький. Секрет в трёх вещах: busybox вместо GNU coreutils, musl вместо glibc и минимальная базовая поставка. Busybox — это один бинарник, который заменяет сотни стандартных утилит. Musl — это компактная стандартная библиотека C, которая занимает в разы меньше glibc. И в Alpine по умолчанию нет лишнего: никаких демонов, менеджеров и предустановленного софта. Вы получаете ровно то, что нужно для запуска вашего приложения. Именно поэтому базовый образ Alpine весит 5 мегабайт, а Ubuntu — 200. Эта разница — результат философии: минимализм вместо универсальности.

С чем столкнулся — musl вместо glibc. Многие программы собираются под glibc и на Alpine падают с ошибками. Например, npm-пакет sharp не собирался на Alpine, потому что требует glibc. Пришлось ставить libc6-compat через apk. Это первая и самая частая проблема при переходе: бинарники, собранные под glibc, не работают на musl. Хорошая новость в том, что почти всегда есть обходной путь: пакет-совместимость libc6-compat или сборка из исходников. Для нативных модулей Python и Node.js нужно просто добавить build-зависимости в Dockerfile. Один раз настроили — и работает годами.

Вторая — пакетный менеджер apk. Я привык к apt, а apk работает иначе: команда не apt install python3, а apk add python3. Пришлось переписывать Dockerfile, привыкать. Но apk быстрее apt — это факт. У apk свой синтаксис и свои особенности: apk update, apk add, apk del. Переучиваться несложно, но на первых порах постоянно ошибаешься в командах. Зато apk реально быстрее: его дерево пакетов легче, зависимости устанавливаются за секунды. После месяца работы на Alpine я уже не хочу возвращаться к apt — apk стал привычным и быстрым.

Третья — Node.js на Alpine. Официально поддерживается, но есть нюанс: версии в apk часто устаревшие. Лучше ставить Node через официальный образ node:alpine, а не через apk. Python тоже норм. Стандартные библиотеки работают, но некоторые модули (например, mysqlclient) требуют компиляции, и на Alpine нужно ставить musl-dev и gcc. Эти требования описаны в документации большинства популярных библиотек. Если библиотека официально поддерживает Alpine — она даст инструкцию по установке build-зависимостей. Правило простое: для сложных проектов проверяйте совместимость заранее, для простых — переводьте смело.

Что я получил — размер образов уменьшился в 5-10 раз. Мой nginx на Alpine весит 12 MB вместо 150 на Ubuntu. Python-сервисы — 50 MB вместо 300. PHP-FPM — 30 MB вместо 200. Эти цифры меня поразили. Один и тот же сервис занимает в 5-10 раз меньше места. На диске это экономит гигабайты, а при передаче образов — трафик и время. Если вы пушите образы в registry и тянете их на серверы — вес имеет прямое значение. Каждый мегабайт образа — это скорость деплоя. Alpine делает деплой быстрее просто за счёт размера.

Билды стали быстрее. apk качает пакеты быстрее apt. Сборка контейнера с нуля на Alpine занимает 30 секунд, на Ubuntu — 2-3 минуты. Разница в 4-6 раз. Причина: пакеты Alpine меньше и их меньше, apk параллелит скачивание. В CI/CD это ощущается напрямую: пайплайн, который собирал образ 3 минуты, стал проходить за 40 секунд. Когда ты пушишь фикс и ждёшь деплой, каждая секунда на счету. Ускорение сборки — это ускорение всей разработки. Чем быстрее цикл сборка-тест-деплой, тем быстрее вы доставляете фичи и фиксы.

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

Мой первый Alpine контейнер. Помню, как собрал первый образ на Alpine для Python-скрипта, который парсит RSS и шлёт в Telegram. Раньше он жил в контейнере на Ubuntu — 280 MB. После переезда на Alpine стало 35 MB. Я обалдел. Перезапустил контейнер — стартанул за секунду. Раньше на Ubuntu грузился секунды 3-4. Разница вроде небольшая, но когда 20 контейнеров перезагружаются после обновления ядра — экономия времени ощутимая. Каждый контейнер стартует быстрее, каждый образ легче, каждая перезагрузка проходит быстрее. В сумме это часы сэкономленного времени.

Что меня бесило в Alpine — отладка. В Alpine нет bash, только sh (busybox). Если привык к стрелочкам вверх и автодополнению — будешь страдать. Я ставлю bash через apk в dev-контейнерах, но в прод я его не тащу. Когда вы входите в контейнер для отладки, вас встречает минималистичный sh без удобств. Нет истории команд, нет автодополнения, нет многих привычных утилит. Решение простое: ставьте bash в dev-контейнеры через apk add bash. В продакшене лишний bash не нужен — там контейнер должен быть минимальным.

Второе — DNS. Alpine использует musl, у которого свои тараканы с резолвом. Была ситуация: контейнер на Alpine не мог достучаться до другого контейнера по hostname, пока я не прописал dns-сервер явно в docker-compose. На Ubuntu эта проблема не всплывала. Musl реализует DNS-резолвинг немного иначе, чем glibc. В большинстве случаев всё работает, но при нестандартных настройках DNS могут быть сюрпризы. Решение — явно указывать DNS-серверы в конфигурации Docker. Один раз настроил — и забыл. Это не баг, а особенность, о которой нужно знать заранее.

Третье — логи. По умолчанию в Alpine нет rsyslog или syslog-ng. Пришлось настраивать логгирование в stdout через Docker, чтобы логи попадали в docker logs. Мелочь, но кто первый раз ставит — теряет время. Собственно, это и есть правильный подход: контейнерные приложения должны писать логи в stdout/stderr, а Docker уже собирает их в docker logs. На Ubuntu у вас есть иллюзия, что можно полагаться на системные лог-файлы. На Alpine такой иллюзии нет — сразу учишься делать правильно. Это даже плюс: правильная контейнерная практика.

Главный аргумент для перехода. Знаете, что меня окончательно убедило? Я обновил все образы на сервере, запустил docker system df и увидел: 8.2 GB стало 1.3 GB. Семь гигабайт свободного места на диске. Для домашнего сервера с NVMe на 256 GB — это серьёзно. Плюс билды в CI/CD стали проходить за 40 секунд вместо 3 минут. Цифры говорят сами за себя: семь гигабайт экономии и в четыре раза быстрее сборки. Это не маркетинг, а измеренный результат. Я не фанатик. Если Alpine глючит с какой-то библиотекой — я не мучаюсь, беру Ubuntu. Но для 80% моих контейнеров Alpine — идеал.

Про nginx. Я даже nginx собираю из сорцов на Alpine, а не беру готовый образ от nginx. Потому что официальный nginx:alpine — 11 MB, а nginx:latest — 187 MB. Разница в 17 раз. Задумайтесь. Один и тот же веб-сервер, разница в размере — семнадцатикратная. Для чего вам платить 187 мегабайт за образ, который делает то же самое за 11? Даже официальные образы — жирные, потому что собраны на Ubuntu-базе. Специальные alpine-варианты в 10-20 раз легче. Проверьте свои образы — какие alpine-версии доступны.

Где я оставил Ubuntu. Не все контейнеры перевёл. Тяжёлые сервисы с кучей зависимостей (например, базы данных) оставил на Debian — там стабильнее поддержка драйверов. MySQL и PostgreSQL официально рекомендуют Debian. Для n8n оставил Ubuntu, потому что на нём проще отлаживать. Но для простых сервисов типа nginx, Python-скриптов, PHP-FPM — только Alpine. Умный подход — не «всё на Alpine», а «каждый сервис на лучшей базе». Базы данных и тяжёлые сервисы с нативными зависимостями остаются на Debian, лёгкие сервисы — на Alpine. Так вы получаете максимум выгоды без боли совместимости.

Совет. Если ваш контейнер делает что-то простое — проксирует запросы, выполняет Python-скрипты, отдаёт статику — переводите на Alpine. Сэкономите место, время сборки и нервы. Если используете сложные библиотеки с нативными расширениями — сначала проверьте совместимость в тестовом контейнере. MySQL, Redis, PostgreSQL лучше не трогать. Начните с простых сервисов: nginx, PHP-FPM, Python-скрипты, статические файлы. Проверьте их на тестовом стенде, потом переводите прод. Через месяц вы увидите экономию в размере диска и скорости деплоев и не захотите возвращаться назад.

Ваш комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *