Реестр контейнеров, который живёт внутри ячеек электронной таблицы

Привет, Хабр! Значю, что вам на это плевать и вы не хотите это читать, но я все равно продолжу это писать:) В общем, Sheet-Native Computing Foundation продолжает цвести и пахнуть и у нас даже есть сторонние контрибьюторы. А чего добились вы?
Итак, вот что теперь можно сделать в таблице: запушить в неё настоящий OCI-образ контейнера и вытянуть его обратно. Не ссылку на образ, не метаданные о нём — сами слои, хранящиеся как base64 по ячейкам, с адресацией по sha256, собирающиеся байт-в-байт на выходе. У SheetHub — нашего форжа в стиле GitLab, который работает на Google-таблице — появился реестр контейнеров, и он целиком живёт в ячейках.
Этот пост — про то, как это всё устроено.
Впервые тут? SheetHub — DevOps-форж (репозитории, issues, merge requests, релизы), который хранит всё во вкладках Google-таблицы. Это часть Sheet-Native Computing Foundation (SNCF) — работающей пародии на CNCF, где весь стек живёт в таблицах: оркестратор (Sheeternetes), формат образов (SICF), сертификация, живые демо. sncfoundation.github.io · github.com/sncfoundation
Что это
Реестр контейнеров, если говорить проще, — это content-addressed хранилище блобов плюс немного метаданных: слои образа под ключом их sha256-дайджеста и манифест, который говорит, из каких слоёв собран какой образ. Docker Hub держит эти блобы в объектном хранилище. Этот реестр держит их в ячейках таблицы.
Работу выполняют две вкладки:
Registry— по строке на образ:repo,name,tag,digest, размер, число чанков, кто запушил, таймстемпы. Метаданные.RegistryChunks— сами байты слоёв. Каждый слой кодируется в base64 и режется на куски по ≤30 000 символов, по одному на ячейку, и в каждой строке — дайджест слоя и индекс чанка.
Зачем резать? Потому что у ячейки таблицы жёсткий лимит на символы — 32 767 в Excel, 50 000 в Google Sheets — а настоящий слой куда больше, чем влезает в одну ячейку. Поэтому слой шардится по подряд идущим ячейкам и сшивается обратно на чтении. Дайджест считается по собранным байтам, так что одна битая ячейка проваливает проверку sha256, а не молча отдаёт вам сломанный образ.
Вот и весь фокус — тот же принцип, что в OCI (адресация по sha256), просто с максимально неуместным носителем.
Видно байты
Больше всего мне нравится, что хранилище не спрятано. Любой реестр — это просто ячейки, которые можно открыть и посмотреть, и UI на это опирается. Нажимаешь Inspect Shard Map на любом образе — и видишь, в каких именно ячейках лежит слой:

Это traefik/whoami — настоящий образ — лежит в ячейках D2 и D3 вкладки RegistryChunks, 49 152 байта в двух чанках, каждый с запасом под лимитом ячейки, у каждого показан base64-preview. Между «реестром» и «таблицей» нет никакой абстракции. Реестр и есть таблица, и можно ткнуть пальцем в ячейку, где живут байты.
Push и pull
Ведёт себя так, как и должен реестр. Есть CLI:
sheethub registry push sncf/hello-web traefik/whoami:latest layer.tar
sheethub registry pull sncf/hello-web traefik/whoami:latest out.tar
sheethub registry view sncf/hello-web traefik/whoami:latest
sheethub registry list sncf/hello-web
push кодирует слой в base64, считает его sha256, шардит по ячейкам и пишет строку манифеста. pull читает чанки обратно по порядку, склеивает, пересчитывает дайджест и проверяет его, декодирует бинарный слой. Дайджест авторитетный и считается на сервере, так что положить контент под несовпадающий digest нельзя. Та же гарантия, что даёт настоящий реестр, обеспеченная тем же хешем.
А поскольку всё хранилище — одна таблица, это по-настоящему air-gapped, self-hosted реестр: ни объектного хранилища, ни Harbor, ни внешнего сервиса. Одна таблица — и форж, и хранилище образов. Для маленьких образов, для которых это реально годится (статические бинарники, scratch/alpine, WASM-модули), — это настоящее свойство, а не просто панчлайн.
Пределы и ограничения
Пропустить их было бы нечестно (ну вы же знаете, клоды и чатыГПТ всегда трут за честность в сгенерированных ими текстах, пришлось идти у них на поводу и оставить здесь только свой искрометный остроумный комментарий, чтобы показать все, что я не просто генерирую эти тексты, а еще и читаю их перед публикацией), и мы уже провели для них нагрузочное тестирование: реестр на таблице — для маленьких образов. Лимит ячейки вынуждает шардинг; потолок в 10 млн ячеек на книгу и квота записи Sheets API (~60 запросов/мин на пользователя) ограничивают, сколько и как быстро можно пушить. Образ в пару сотен мегабайт технически влезает, но делает файл тяжёлым, а пуши медленными. Это реестр для того, что должно жить в ячейках, а не замена Docker Hub — и он это понимает.
Ещё приятная деталь, которую мы проверили по ходу: чанк base64 может законно начинаться с +, а таблица попыталась бы прочитать это как формулу. Реестр экранирует такие ячейки, чтобы таблица их не исполняла, и экранирование собирается обратно байт-в-байт — так что sha256 сходится. Content addressing держит в честности всех, включая слой хранения.
Куда это вписывается
Картинка крупнее: SNCF уже хранит образы в ячейках через SICF (наш Sheet-Native Image Container Format), и Sheeternetes умеет шедулить и запускать sicf:-образ прямо из таблицы. Этот реестр — «парадная дверь» форжа к той же идее: пушишь через SheetHub, а в планах — сделать эти образы доступными для sheetbuild и рантайма Sheeternetes напрямую, чтобы форж и кластер делили одно хранилище образов в таблице. Это выравнивание — следующий шаг.

Реестр приехал как внешний вклад от prateeekbuilds (который реализовал и сам SheetHub, существоваший до этого только в виде дизайн пропозала в ишьюсах) — он и правда постарался, провел 50+ тестов и всё такое. Спасибо. Не знаю, на кой пёс это ему сдалось и понимает ли что его вклад с Sheet-Native хоть и неоценим с точки зрения истории человечества, вряд ли будет оценен будущими работодателями. Но кто мы такие, чтобы его осуждать.

Не запускайте продовый реестр на таблице. Но откройте карту шардов — это самый наглядный взгляд на content-addressed storage, какой можно получить, потому что можно ткнуть пальцем в ячейку.
Присоединяйтесь
SheetHub: github.com/sncfoundation/sheethub
Стандарт контейнеров (SICF): github.com/sncfoundation/sci
Slack sheetncf, Telegram t.me/stncf, LinkedIn company/sheetncf
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.