Реестры образов и хранение артефактов
Поднял у себя реестр образов и опубликовал в него образ с 25 мегабайтами данных внутри - ушло 416 миллисекунд. Потом собрал вторую версию, отличающуюся ровно одной строкой, и опубликовал её: 103 миллисекунды. По сети уехала только разница, потому что общие слои у реестра уже были.
Стержень: реестр хранит слои по их содержимому, тег - подвижная бумажка, а отпечаток - надёжная ссылка.
// Формулировки: «зачем свой реестр?», «чем тег отличается от отпечатка?», «как не тащить секреты в образ?»
Как реестр экономит и время, и место
Реестр - это хранилище образов, откуда их скачивают машины. Образ в нём лежит не одним куском, а слоями: неизменяемыми наборами изменений файлов, у каждого своё имя-хэш. При публикации клиент сначала спрашивает, какие слои уже есть, и шлёт только недостающие.
Мой замер это и показывает: первая публикация 416 миллисекунд, вторая - 103, потому что тяжёлый слой с данными не изменился и повторно не поехал. Ровно та же экономия работает и в обратную сторону, при скачивании на машины: свежая версия приложения на общей базе тянет только свой верхний слой.
// Зачем свой реестр, а не публичный. Во-первых, скорость и независимость: сборка не встаёт из-за чужого лимита на скачивание. Во-вторых, приватность образов. В-третьих, контроль: можно запретить публикацию поверх существующего тега, включить проверку на уязвимости и подпись, вести срок хранения. Публичные образы при этом никуда не деваются - их обычно зеркалят, то есть держат у себя копию, и берут уже оттуда.
- реестр
- хранилище образов; хранит слои по их хэшу и не дублирует их
- слой
- неизменяемый набор изменений файлов со своим именем-хэшем
Тег двигается, отпечаток нет
Тег - это имя, которое можно перевесить на другой образ в любой момент. Отпечаток - хэш содержимого, и он меняется вместе с содержимым. Я взял отпечаток первой версии, удалил её локально и скачал обратно ПО ОТПЕЧАТКУ - получил ровно то, что публиковал, независимо от того, куда с тех пор переехал тег.
Отсюда правило для прода: либо ссылаться на отпечаток, либо давать каждой сборке свой неповторяющийся тег, например с датой и коротким именем коммита. Плавающие имена вроде latest или stable на нескольких машинах разъезжаются: где-то стоит вчерашняя сборка, где-то позавчерашняя, и на вопрос «что сейчас в проде» ответить нечем.
// Отдельно про то, что попадает в артефакт. Слой неизменяем, поэтому секрет, скопированный в образ и удалённый следующей строкой, из образа никуда не девается - он достаётся распаковкой за минуту, я это проверял. Значит, ключи в образ не кладут вовсе: на сборке их подкладывают как секрет сборки, а в работе передают снаружи. И перед публикацией полезно смотреть, что вообще уехало внутрь: локальные файлы, история репозитория, конфиги с паролями попадают туда чаще, чем хочется.
- тег
- подвижное имя образа; сегодня одно содержимое, завтра другое
- отпечаток
- хэш содержимого образа; ссылка, которая не переедет
- секрет сборки
- значение, доступное шагу сборки, но не попадающее в слой
Хранение и уборка
Артефакты копятся быстрее, чем кажется. Каждая сборка каждой ветки оставляет образ, а образ тянет за собой слои. Смотрел на своей машине: образов 41 общим весом 11,68 гигабайта, и 78% этого веса не используется ни одним контейнером. Плюс кэш сборки, то есть сохранённые промежуточные шаги прошлых сборок, на 3,97 гигабайта, и тома - хранилища данных, живущие отдельно от контейнеров, - ещё на 2,78, из которых 93% ни к чему не подключено.
Поэтому у артефактов должна быть политика хранения: сколько держать сборки веток, сколько релизные, что удалять автоматически. Обычно ветки чистят агрессивно (дни), релизы держат долго, а то, что помечено как выкаченное в прод, не трогают вовсе - иначе однажды окажется, что откатываться некуда.
// Уборка на боевой машине требует аккуратности. Команда чистки удаляет всё, на что нет ссылок, и в режиме с томами уносит и данные, которые сейчас никем не заняты, - а это чьё-то состояние. Поэтому на проде уборку делают точечно и осознанно, а не одной командой по расписанию.
- политика хранения
- правило, сколько и какие артефакты держать, остальное удалять
- неиспользуемый образ
- тот, на который не ссылается ни один живой контейнер
Как отвечать: «Чем тег отличается от отпечатка и что использовать в проде?»
Тег это подвижное имя: его можно перевесить на другой образ, и содержимое под тем же именем поменяется. Отпечаток это хэш самого содержимого, он меняется только вместе с содержимым. Я проверял: удалил образ локально и скачал его обратно по отпечатку - получил ровно то, что публиковал. В проде поэтому либо ссылаюсь на отпечаток, либо даю каждой сборке неповторяющийся тег с датой и коротким именем коммита, а плавающие вроде latest оставляю для локальных экспериментов. Иначе на разных машинах под одним именем оказываются разные сборки, и на вопрос «что сейчас работает» ответить нечем. Плюс в реестре включаю запрет публикации поверх существующего тега, чтобы имя нельзя было переставить задним числом.
Ответ различает механику и практику и заканчивается настройкой реестра. Последнее показывает, что человек хранилище настраивал руками, а не читал про отпечатки.
На чём валятся
- −Катят плавающий тег в прод и не могут сказать, что именно работает на каждой машине.
- −Кладут ключи внутрь образа и полагаются на удаление следующей строкой сборки.
- −Не заводят политику хранения и упираются в кончившееся место на машине сборки.
- −Чистят артефакты одной командой по расписанию и уносят то, к чему собирались откатываться.
- −Считают, что публичные образы всегда доступны, и получают вставшую сборку из-за чужого лимита.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 7, остальные разбираются в тренажёре.
- Зачем компании держать собственный реестр образов и пакетов вместо публичных?A)Публичные реестры не поддерживают приватные репозиторииB)Свой реестр автоматически сканирует код на ошибкиC)Права, скорость и независимость от чужих лимитовD)Свой реестр снимает необходимость версионировать образы
показать ответ и разбор
+C)Права, скорость и независимость от чужих лимитов// разбор: Свой реестр (Nexus, Harbor, облачный) решает три задачи: приватность и права, скорость выкатки, потому что образ рядом с нодами, и независимость — внешний реестр может ограничить число скачиваний, подорожать или стать недоступным. В российских контурах добавляется четвёртое: зеркала и проксирование внешних репозиториев, чтобы сборка не зависела от доступности зарубежных источников.
- Реестр разросся до терабайтов: каждая сборка каждой ветки кладёт образ. Как навести порядок?A)Хранить только последний образ каждого репозиторияB)Собирать образы лишь для main, ветки проверять без сборкиC)Перевести реестр на сжатие слоёв и включить дедупликациюD)Политика хранения: релизы держим долго, сборки веток — дни
показать ответ и разбор
+D)Политика хранения: релизы держим долго, сборки веток — дни// разбор: Разумная политика различает типы артефактов: релизные теги и всё, что крутится в проде, хранятся долго и не удаляются автоматически; сборки веток и pull request живут дни, потом уходят по расписанию. Правила задают в самом реестре, а не скриптом на коленке, и обязательно исключают дайджесты, на которые ссылаются работающие деплойменты — иначе автоуборка снесёт образ живого сервиса.
- Сборка образов идёт в контейнере CI. Чем плох проброс сокета демона внутрь джобы?A)Джоба получает управление демоном хостаB)Сборка станет медленнее из-за двойной виртуализацииC)Собранный образ не попадёт в кэш слоёв раннераD)Демон не умеет собирать образ из контейнера без привилегий
показать ответ и разбор
+A)Джоба получает управление демоном хоста// разбор: Сокет — это полный API демона без аутентификации: чужой код в джобе запускает контейнер с монтированием корня ноды, читает образы и секреты других сборок, ставит своё. По сути это root на раннере. Альтернативы: сборщики без демона (kaniko, buildah), удалённый BuildKit с ограниченным доступом или выделенные одноразовые раннеры, которые уничтожаются после джобы.
- Один и тот же сервис собирают заново на каждом окружении (dev/stage/prod) из исходников. Чем это плохо и как правильно?A)Пересборка на среду ускоряет выкатку за счёт кэшаB)Собирать раз, продвигать один артефакт по средамC)Так надёжнее: на каждой среде свежий бинарь под неёD)Разницы нет, если версии зависимостей закреплены
показать ответ и разбор
+B)Собирать раз, продвигать один артефакт по средам// разбор: Пересборка на каждой среде даёт формально разные артефакты (другое окружение сборки, время, недетерминизм) — на проде оказывается не тот бинарь, что проверяли на stage. Правильно: build once — собрать артефакт один раз, прогнать через среды один и тот же (promote by digest/версии). Тогда до прода доезжает ровно то, что тестировали, а среды отличаются только конфигом.
- Кэш зависимостей в CI иногда отдаёт устаревшие пакеты, и сборка ставит не то. Как задать ключ кэша правильно?A)Ключ по имени ветки, чтобы у каждой был свой кэшB)Один общий фиксированный ключ на весь репозиторийC)Ключ кэша — по хешу lock-файла зависимостейD)Ключ по дате сборки, чтобы кэш освежался каждый день
показать ответ и разбор
+C)Ключ кэша — по хешу lock-файла зависимостей// разбор: Кэш должен инвалидироваться ровно тогда, когда меняются зависимости. Поэтому ключ строят из хеша lock-файла (package-lock.json, poetry.lock, go.sum): правишь зависимости — меняется lock — меняется ключ — кэш пересобирается. Ключ по ветке/дате/константе рассинхронизируется с реальными зависимостями и отдаёт стухшее. Обычно добавляют и restore-keys как запасной префикс для частичного попадания.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.