Pull to refresh

Comments 50

Переписать на python - это уменшит размер до 10кБ, если размер так волнует. ;)

Контейнера? Я уже молчу о том, сколько будет весить сам интерпретатор с его пакетами, но только runC с AuFS займет намного больше.

Если уж цель сделать контейнер минимального размера, то это только C, так как libc*.so уже есть в контейнере. Всё остальное потянет за собой свой runtime. В случае упомянутых в статье Go или Rust - это свыше 10 МБ довеска. Python - свыше 600 МБ. Alpine заметно меньше, но 90 МБ - тоже не мало

В случае упомянутых в статье Go или Rust - это свыше 10 МБ довеска.

Откуда взялись 10 мегабайт? Такой скрипт переписанный на Go или Rust получится ну от силы в мегабайт (я думаю раз в 10 меньше) - естественно с учётом оптимизаций размера в духе статьи.

Даже минимальный guessing_game из туториала на Rust утянет за собой ~17 МБ библиотек при сборке его с -Cprefer-dynamic в config.toml, эти 17 МБ в контейнер все равно потребуется положить.

С Go ситуация чуть лучше, но меньше 10 МБ его библиотеки точно не займут.

Статически скомпилированный бинарник не зависящий от динамических библиотек - 577 килобайт. Где здесь 17 мегабайт нашлось я так и не понял. А ведь можно уменьшить размер ещё сильнее.

Статически скомпилированный

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

Нет конечно. Но я это сделал, чтобы проиллюстрировать, что если статический бинарник занимает 577 килобайт, значит динамический бинарник будет ещё меньше? И никакими непонятными мне 17 мегабайтами там и не пахнет. Тем более, что автор всё равно тянет с собой в образе библиотеки.

чтобы проиллюстрировать

Какой смысл иллюстрировать то, что в рамка обсуждаемого k8s и контейнеров не имеет смысла?

динамический бинарник будет ещё меньше

Но потребует наличия всех динамически загружаемых библиотек в контейнере, что и займет в этом контейнере свыше 10 МБ. ЧТД.

автор всё равно тянет с собой в образе библиотеки.

На что я ответил, что libc в контейнере есть всегда, так как необходим runc. А значит, если писать на C, то его runtime уже всегда есть в контейнере.

в рамка обсуждаемого k8s

Чел, у тебя всё в порядке? Тебе сказали что нет никаких 17MB, показали пример с маленьким бинарём.

А я популярно объяснил, почему статическая линковка для k8s недопустима.

Давай по порядку.

ptr128: ... Всё остальное потянет за собой свой runtime. В случае упомянутых в статье Go или Rust - это свыше 10 МБ довеска
V1tol: Откуда взялись 10 мегабайт?
ptr128: Даже минимальный guessing_game из туториала на Rust утянет за собой ~17 МБ
V1tol: Статически скомпилированный бинарник не зависящий от динамических библиотек - 577 килобайт. Где здесь 17 мегабайт нашлось я так и не понял

Тебя ткнули в твою неправоту, ты начал какие-то левости про динамическую и статическую линковку собирать, потом ещё и k8s приплёл.

Ещё раз - с чего ты взял, что "runtime" (в кавычках, потому что применимо к расту термин спорный, но да ладно) rust будет весить 10MB?

Напоследок: аргументов против статической линковки в кластере не увидел. Какая такая специфика кластера является противопоказанием статической линковки?

это не ответ на вопрос, "с чего ты взял". Тебе возражали именно на 10MB рантайма, если у тебя нет подтверждения своих слов, то можешь сколько угодно минусить, аргументов у тебя нет.

это не ответ на вопрос, "с чего ты взял"

А я на вопросы в такой грубой форме не отвечаю. Я ответил на вопрос:

Какая такая специфика кластера является противопоказанием статической линковки?

А я на вопросы в такой грубой форме

Как говорится, красота в глазах смотрящего. Я не несу ответственности за твоё восприятие мира, но на всякий случай, намерений грубить у меня не было. Хочешь оскорбляться, твоё дело. Будем считать, что вопрос на тему размера рантайма ты тупо слил, ибо не умеешь признавать свою неправоту.

В твоих аргументах против статической линковки не было никакой специфики кластера. Ты просто описал свойства dynamic vs static linking, которые работают как в кластере, так и на десктопе.

Я не отрицаю пользу динамической линковки. Но чтобы разные процессы использовали одну и ту же память для одинакой so-шки на десктопе - не надо ничего делать. В кластере, тебе надо следить за тем, чтоб процессы выполнялись в контейнере, образ которого эти самые библиотеки содержит в одном и том же слое, иначе mmap положит их в разные участки памяти, потому что у этих библиотек не будет совпадать device ids и inode. Поэтому польза эта зависит от гомогенности, если у тебя везде debian-slim и используется sha а не тег, то да.

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

И самое главное. Помним классиков! Premature optimization is the root of all evil. Думаешь, что статическая линковка приносит проблему - измерь сначала, потом меняй на динамическую. А то вдруг окажется, что тебе KSM уже и так всё в ноль задедуплицировал, и твои старания коту под хвост.

один и тот же код библиотек в каждом исполняемом файле занимает место в оперативке

Это если они запущены одновременно. Если нет.. ну занимает. приложение отработает и перестанет занимать.

А у Вас что ли в k8s по одному контейнеру на хосте или в контейнерах не aufs/overlay, которые одинаковые so не дублируют в памяти?

У меня helloworld на bash, какой такой k8s ? ;) Я и слов таких не знаю! Вот как понодобится - так я сразу начну байты считать ;)

это свыше 10 МБ довеска.

Это означает что автор просто не умеет их готовить, также как не умеет готовить гит, судя по назначению скрипта. Дефолтный ржавый hello world до недавнего времени был размером в 5мб т.к. включал в себя дебаг инфу и здоровенный куско стандартной либы, которая в итоге не используется. В последних релизах это пофиксили, так что дефолтный размер сейчас должен исчисляться килобайтами, хотя возможно и больше чем Си с musl. Встроенщики уже давно наладили ржавый пайплайн и способы сокращения размера бинаря, так что 10 Мб это какие-то сказки.

Дефолтный ржавый hello world

Это не удачный пример. Потому что HelloWorld не тащит за собой вообще ни одного crate.

дефолтный размер сейчас должен исчисляться килобайтами

Когда в реальном проекте потянутся еще с десяток crates, то даже вычистка debug информации, ценой потери адекватной диагностики исключений, не позволит снизить размер этих библиотек ниже 10 МБ. Проверено.

Хотите доказать обратное? Ну так возьмите, например, простейший gRPC сервис на Rust и напишите здесь, как его собрать, чтобы он в контейнере занял меньше 10 МБ.

Пользуйтесь вот таким гайдом. Там же есть ссылки на докер образа < 10 MB.

Из очевидного - пересесть с жирных крейтов умеющих в nostd, старый добрый serde поменять на какой-нибудь miniserde, включить всякие сборочные оптимизации а ля thin-lto и `opt-level="z"`.

Чтоб вы понимали - ржавые драйвера и системы сейчас вполне себе крутятся на системах, у которых и памяти килобайты и флешки не шибко большие, так что все эти трюки при желании можно проворачивать и внутри докера. Вопрос насколько это действительно необходимо.

Прошу прощения, а зачем уходить в демагогию подменяя тему?

Между k8s и "системах, у которых и памяти килобайты" есть две принципиальные разницы. Во-первых, все shared object не только в одном контейнере, но и во всех контейнерах, запущенных на одном хосте (если руки не кривые), загружаются в память только один раз и совместно используются всеми сервисами во всех контейнерах. Во-вторых, сервисы в k8s решают несопоставимо более сложные задачи, чем сервисы в "системах, у которых и памяти килобайты". Например, типовой gRPC сервер в килобайтах Вы не поднимете, хоть убейтесь.

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

"системах, у которых и памяти килобайты"

Это было в качестве примера того, что уменьшение размеров таки возможно. Если хотите динамическую сборку - собирайте либы с API, который будете шарить между контенерами. gRPC, как в общем и большинстов прочих сетевых протоколов, будут достаточно жирными хотя бы из-за сериализации структур (которые ещё и валидируются). Насколько - вопрос разлапистости вашего API и используемых подходов к сериализации. Как с этим бороться я также выше пример дал.

Ваш кейс понял, но на слово в 10мб оверхеда не поверю.

Это было в качестве примера того, что уменьшение размеров таки возможно.

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

Ваш кейс понял, но на слово в 10мб оверхеда не поверю.

А я и не прошу верить. Мы же на профессиональном сайте. Эксперимент легко повторяем. Можете сами собрать хотя бы простейший gRPC сервер на Rust с -Cprefer-dynamic и посчитать размер runtime, который необходимо будет положить в контейнер, чтобы он запустился.

> shell-скрипт
> docker

Но зачем?

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

переносимые баш скрипты писать тяжело

Да. Нет повода их писать. Когда хочется написать простыню на bash - нужно попросить, чтобы кто-то ударил по руках.

я таки согласен

просто ТС пытается это сделать (писать баш скрипт, и ещё сделать его переносимым) и я объясняю его мотивацию использовать докер для пакетирования баш скрипта.

Транслятор из языка высокого уровня в код Bash Virtual Machine помог бы. Но настоящих буйных мало ;)

Знакомые мытарства. Мы докеризировали ruby приложение под astra linux в контейнере. В лоб контейнер выходил в 1 Гб. А спустя месяц смогли уменьшить до 200Мб. Приходилось изучать содержимое каждого слоя, что он добавляет. Естественно мультисейдж, но больше бесила кривота рук gem-писателей. То доку туда закинут на пару мегов, то исходники. Выделился модуль генерации pdf, он загружает 4 вида бинарников по 60Мб для разных типов linux, но использует потом один, пришлось удалять, тк у нас debian 10, просите, astralinux.

мультисейдж

что это?

я правильно понимаю что 500Кб скрипт засунули в какой-то контейнер чтобы обойти проблему линукса с зависимостями? После этого фраза

  • Linux — потрясающая операционная система. Всё в нём, каждое архитектурное решение, каждый инструмент наследует это великолепие.

звучит как гимн мазохиста.

Нет, я люблю Линукс, но зависимости это то, что меня уже 25 лет выбешивает.

500Кб скрипт

20 кб скрипт завернули в 17 mb контейнер.

ну я с запасом взял 1000 символов на строку.

в 500 кб можно переписать на bash все зависимости ;). Кроме git, но он и так есть.

  1. Выбрать sh для того, чтобы написать код

  2. Написать кучу кода, в котором не можешь самостоятельно разобраться и отрефакторить

  3. Решить, что переписывать на что-то, что позволит запускать твою программу корректно на разных платформах - уже поздно

  4. Заюзать контейнеризацию ДЛЯ SHELL-СКРИПТА, добавив огромный оверхед и необходимость людей установливать докер и запускать hello-world утилиту в контейнерах только чтобы отменять действия git (которые можно отменять через сам git)

Да там и сама утилита тоже для сомнительног округа пользователей.

В образах файлы не могут удаляться - это особенность образов. Можете RUN rm /myfile сделать, но файл останется!
Единственный действенный способ уменьшить размер - пересобрать Tar образа с docker image.

Мой код на Python для этого: https://gist.github.com/Roffild/ab27f4f8d05649d5fe666748f7d27f8f

В образах файлы не могут удаляться

Могут, если удалять в том же слое где создавали.

Эта особенность не позволяет уменьшить образ после объединения и копировании в другом слое. Не всё возможно в один RUN запихнуть.

я бы ещё `chmod +x ugit && mv ugit /usr/local/bin/` заменил на что нибудь вроде `install -Dm 777 ugit /usr/local/bin/`

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

Я сжал до 11мб потратив одну минуту, чтоб прикрутить upx)

А зачем, память кончилась - добавил, зря что-ли продают сервера с 2tb оперативки и сотней tb ssd

кто-то может объяснить, как удаление шебанга сохранило 1,9МБ размера?

Предположу, что это - эффект удаления самого бинаря /usr/bin/env и связанных с ним зависимостей.

Возможно, выйдет меньше, если какие-то бинарники заменить на BusyBox. Или вообще строить всё не из alpine, а из busybox. Правда, тогда с установкой туда пакетов придется повозиться.

Я так и не понял, зачем все эти приседания. Ну уменьшил он размер с 31 до 18 мб, но зачем ? Чтобы что ?

Чтобы статью написать, не уменьшил бы с 31 до 18 - не было бы статьи)

Sign up to leave a comment.

Articles