Хабр, привет! Есть такой тип проектов, которые начинаются со слов «а что если» и заканчиваются тем, что ты в три часа ночи объясняешь kubelet-у, что таблица — это его новый control plane. Так вышло и тут, всё родилось из субботней шутки в одном телеграм-канале по кубернетесу.
Правила игры простые: раз весь мир можно создать в эксельке, то пускай всё состояние кластера живёт внутри электронной таблицы. Не «метаданные в таблице», не «экспорт в CSV» — а буквально: Deployments, Nodes, Pods, Events — это вкладки Google Sheets (или листы Excel). Планировщик читает и пишет ячейки. kubectl-подобная утилита ходит в таблицу. И — вот это ключевое — на другом конце крутятся настоящие Docker-контейнеры. Таблица не симулирует кластер. Таблица и есть кластер.
Проект называется Sheeternetes. Это, конечно, шутка. Но шутка, которая компилируется, проходит тесты и переживает падение ноды.
Ниже — как это устроено, как это поднять у себя за пять минут, как оно работает на bare metal без интернета (в Excel и LibreOffice), как связать несколько таблиц-кластеров в федерацию с живой миграцией, почему контейнеры можно хранить прямо в ячейках, — и что это внезапно выросло в целый фонд с 30 проектами, стандартом контейнеров и системой сертификации.
Дисклеймер на всякий случай
Не запускайте на этом прод. Если запустите — снимите на видео диалог сохранения. It reconciles.
Под капотом здесь планировщик с bin-packing и failover, HMAC-подписи, live-миграция make-before-break, свой формат образов. Просто субстрат выбран максимально еретический. Единственное жёсткое ограничение проекта, из которого растёт вся остальная эстетика: по возможности всё делать внутри таблиц.
Архитектура: таблица как источник истины
Kubernetes — это, если сильно упростить, «желаемое состояние в etcd + контроллеры, которые сводят реальность к желаемому». Замените etcd на вкладку Google Sheets, а контроллер — на Apps Script и bash-демон, и вы получите Sheeternetes.

Компоненты:
Таблица — durable store и single source of truth. Четыре вкладки:
Deployments,Nodes,Pods,Events. Строка = объект, шапка = схема.apiserver — процесс, который отдаёт данные по HTTP и запускает планировщик. В облачной версии это Google Apps Script, привязанный к таблице; в on-prem — маленький Python-сервер поверх
.xlsx.kubelet —
kubelet.sh, обычный bash-скрипт. Превращает любой Docker-хост в «ноду»: шлёт heartbeat в apiserver, получает список подов, которые должен запустить, и сводит локальный Docker к этому списку.skctl — крошечный
kubectl.curl+jqповерх контракта apiserver.
Контракт между всеми частями предельно简单:
GET ?token=<T>&kind=pods|nodes|deployments|events -> { "items": [ ... ] } POST { "token": T, "action": "apply|scale|delete", ... } POST { "token": T, "node": ..., "pods": [...] } # heartbeat кубелета -> desired pods
Вот и вся «плоскость управления». Дальше — только детали, которых, как обычно, вагон.
Пять минут до первого кластера
Разберём облачный вариант — на Google Sheets. Нужны Google-аккаунт, Docker-хост, bash, curl, jq. Весь код — в репозитории github.com/sncfoundation/sheeternetes.
Шаг 1. Control plane. Шаблона копировать не нужно — его намеренно нет: пустая таблица превращается в кластер сама.
Создаём пустую Google-таблицу.
Extensions → Apps Script, вставляем
Code.gsиз репозитория.В начале файла ставим свой
TOKEN.Один раз запускаем
setup()— он превращает пустой лист в полную структуру: четыре вкладки с заголовками, пара примеров-деплойментов и reconcile-триггер раз в минуту.Deploy → New deployment → Web app (Execute as: Me, Who has access: Anyone), копируем
/execURL.
Хочется сперва просто посмотреть на живой кластер, ничего не поднимая? На сайте есть референсное демо на трёх настоящих таблицах — открываете и читаете running-state прямо в ячейках.
Шаг 2. Поднимаем ноду — на любой машине с Docker:
export WEBAPP_URL=https://script.google.com/macros/s/XXXX/exec export TOKEN=secret NODE_NAME=node-a CPU_TOTAL=4000 MEM_TOTAL=8192 ./kubelet.sh # [kubelet] node=node-a ip=10.0.0.1 cpu=4000m mem=8192Mi -> ...
Нет трёх машин? ./local-cluster.sh поднимет node-a/b/c как фоновые кубелеты на одном Docker-демоне — удобно, чтобы посмотреть на планировщик и failover локально.
Шаг 3. Деплоим нагрузку. Манифест — обычный JSON:
{ "deployments": [ { "name": "web", "image": "nginx:alpine", "replicas": 2, "cpu_req": 100, "mem_req": 32 }, { "name": "hello", "image": "hashicorp/http-echo", "replicas": 1, "cpu_req": 50, "mem_req": 16, "command": "-text=hello-from-a-spreadsheet -listen=:8080" } ] }
skctl apply lab/hello-web.json skctl get pods
И вот тут начинается магия: вы открываете таблицу — и видите свой кластер как строки. Вкладка Deployments — это ваши деплойменты. Вкладка Pods — реально запущенные контейнеры с их нодами и статусами. Меняете replicas в ячейке руками — планировщик на следующем heartbeat досоздаёт поды. Таблица — это kubectl edit, только курсором.

А в терминале всё выглядит подозрительно похоже на «взрослый» оркестратор:

$ skctl get pods NAME DEPLOYMENT NODE PHASE CONTAINER web-1 web node-a Running a1b2c3d4e5f6 web-2 web node-b Running b2c3d4e5f6a7 hello-1 hello node-a Running c3d4e5f6a7b8
Контейнеры — настоящие. docker ps на ноде покажет их. Sheetlium (наш «Cilium») заодно кладёт их в общую Docker-сеть и вешает на имя деплоймента network-alias — вот вам и Service c DNS.
Планировщик: не игрушечный
Самое частое возражение: «ну это же просто список, какой там планировщик». А вот и нет. На каждый heartbeat apiserver честно раскладывает реплики по нодам, и это чистая, покрытая юнит-тестами функция. Что она умеет:
Bin-packing по ресурсам. Под с
cpu_req/mem_reqкладётся на ноду, где есть место. Не влезает никуда — становитсяUnschedulable.Sticky placement. Под не скачет между нодами без причины — остаётся там, где был, пока нода жива.
Failover. Нода перестала слать heartbeat дольше
NODE_TTL(30с) — становитсяNotReady, её поды переезжают на живых.cordon / drain / migrate. Ровно как в kubectl.
affinity + taints/tolerations. Полноценно:
node_selectorпротив лейблов ноды,NoSchedule-тейнты против толерейшенов.
Обслуживание ноды выглядит так:
skctl cordon node-a # новые поды сюда не едут, текущие работают skctl drain node-a # эвакуировать поды на других, затем cordon skctl uncordon node-a # вернуть в строй skctl migrate web-1 node-b # переselить конкретный под (make-before-break) skctl label node-a disk=ssd skctl taint node-a gpu=true:NoSchedule
Хотите закрепить GPU-нагрузку за нужными нодами? Обычный node_selector в манифесте:
{ "name": "ml-infer", "image": "sicf:tinyllm", "replicas": 1, "cpu_req": 800, "mem_req": 256, "node_selector": "accel=gpu", "tolerations": "gpu=true" }
Проверить, что failover работает, можно грубо: убиваете kubelet на node-a, ждёте TTL — и видите, как поды сами перечисляются на node-b. В таблице. В реальном времени. Это, честно, гипнотизирует.
SheetOS: операционка для ноды
Раз уж у нас есть оркестратор, ему нужна нода-ОС. Встречайте SheetOS — «Talos для таблиц»: минимальная, иммутабельная, конфигурируемая декларативно, и — самое важное — самораскатывающаяся.
Идея: sheetstrap берёт «образ» ноды из таблицы, поднимает поверх себя runtime, регистрируется как Node и — если сказать ему — сам ставит поверх себя Sheeternetes-кластер. То есть таблица-ОС бутстрапит таблицу-кластер. Матрёшка, которая при этом ещё и reconcile-ится.
# нода читает свой желаемый образ/конфиг из таблицы и собирает себя sheetstrap up --config nodeconfig.json # распределённый режим: несколько листов собираются в кластер по join-токену sheetstrap join --token <...>
SheetOS отвечает на любимый вопрос интервью «а ваша ОС распределённая?» — да, и она умеет ставить оркестратор поверх себя же. Дальше рекурсию оставим на усмотрение читателя.
Bare metal: Sheeternetes без интернета, в Excel и LibreOffice
Облако — это прекрасно, но настоящая мужская дисциплина — это air-gapped on-prem на десктопной таблице. Для этого есть отдельная редакция — sheeternetes-onprem.
Здесь apiserver — это локальный Python-процесс поверх .xlsx, с тем же самым контрактом, что и облачный. То есть kubelet.sh и skctl цепляются к нему без единой правки. Интернет не нужен: таблица — хранилище, процесс — apiserver.
pip install openpyxl cp .skctl.env.example .skctl.env # WEBAPP_URL=http://localhost:8787, TOKEN=... make up # терминал 1: apiserver поверх cluster.xlsx на :8787 make node # терминал 2: kubelet (этот хост становится нодой) make apply # терминал 3: применить lab/hello-web.json make pods # смотрим, как планировщик раскладывает и запускает поды
Всё, что нужно, лежит в одной репе: apiserver.py (control plane + планировщик), kubelet.sh, skctl, Makefile, лаб-манифест. Планировщик — тот же, что в облаке: bin-packing, failover, cordon/drain/migrate, affinity/taints. Мультинода — просто запускаете kubelet.sh на других машинах в LAN, указав им адрес apiserver.
Про Excel и LibreOffice честно: openpyxl читает .xlsx, поэтому в LibreOffice Calc делаем Save As → Excel 2007-365 (.xlsx). Нативный .ods через Python-UNO — в роадмапе. И да, у бэкендов есть жёсткие пределы (об этом ниже, когда дойдём до нагрузочного тестирования — спойлер: Excel начинает страдать задолго до формальных лимитов).
Вся эта редакция — с тестами (47 штук) и CI. Потому что «шутка» не значит «без тестов».
Федерация: связываем таблицы-кластеры между собой
Один кластер — это скучно. Настоящая цель — связать несколько таблиц в федерацию, в том числе разного типа: локальный Excel-кластер ↔ облачный Google Sheets-кластер.
On-prem нельзя достучаться снаружи, поэтому мост bridge.py звонит наружу сам: читает локальный apiserver и сводит его с удалённым пиром (Google Sheets-кластером или другой нодой). Получаем cross-substrate service discovery.
# федеративная картина сервисов (+ --push публикует локальные деплойменты пиру) python3 bridge.py status --local http://localhost:8787 --local-token secret \ --peer https://script.google.com/macros/s/XXXX/exec --peer-token secret2
И — то, ради чего всё затевалось — живая миграция между субстратами, make-before-break:
python3 bridge.py migrate web --from local --to peer --rollback-window 30 \ --local http://localhost:8787 --local-token secret \ --peer https://script.google.com/macros/s/XXXX/exec --peer-token secret2
Логика: скопировать спеку на target → дождаться, пока там поднимется → только потом снять с source. Не поднялось — source остаётся нетронутым, простоя ноль. А --rollback-window ещё и последит за target после переключения и откатит на source, если тот деградировал.
Плюс двусторонняя синхронизация как демон:
python3 bridge.py sync --interval 60 --local ... --peer ...
И безопасность поверх этого — опциональные HMAC-подписи запросов. Поднимаете apiserver с SIGNING_KEY, и каждый POST обязан нести X-SNCF-Timestamp + X-SNCF-Signature в пределах TTL (защита от подделки и replay):
SIGNING_KEY=shared-secret WORKBOOK=cluster.xlsx python3 apiserver.py python3 bridge.py sync --peer-signing-key shared-secret --local ... --peer ...
Да, у нас HMAC на таблицах. Мы тоже не до конца понимаем, как до этого дошли.
Два формата контейнеров: обычный и «нативный табличный»
Вот это, пожалуй, самая интересная часть. Sheeternetes работает с контейнерами в двух режимах, и они взаимозаменяемы.
1. OCI-режим (обычные контейнеры). В манифесте image: nginx:alpine — kubelet тянет образ из обычного реестра через Docker. Полная совместимость со всей экосистемой контейнеров, работает из коробки. То есть уже сегодня Sheeternetes гоняет самые настоящие OCI-образы — таблица держит только желаемое состояние, а байты образа приходят из Docker Hub.
2. SICF native — образ живёт прямо в таблице. image: sicf:web:v1 — и образ резолвится из табличного хранилища образов, без внешнего реестра. Как? Слои образа — это строки на вкладке Layers, разбитые на base64-чанки и адресуемые по sha256:-дайджесту; манифест — строка на вкладке Images. Puller собирает слои из ячеек, проверяет дайджест, разворачивает rootfs и запускает. Полностью air-gapped, «таблица — это реестр».
Между форматами — мост (sheetbuild), без потерь:
sheetbuild import nginx:alpine # тянет OCI-образ и записывает его в таблицу как SICF sheetbuild export web:v1 ghcr.io/you/web:v1 # обратно в OCI-реестр
Оба режима адресуют контент одними и теми же sha256:-дайджестами, поэтому import → export — это round-trip.
Есть, разумеется, физика. Google Sheets: 50 000 символов на ячейку и 10 млн ячеек на книгу. Поэтому нативный режим — для маленьких образов: статические Go/Rust-бинарники, scratch/alpine, и особенно WASM-модули (которые ложатся идеально). Многогигабайтный CUDA-образ — это OCI-режим с реестром, и точка.
Всё это оформлено не как «фича», а как стандарт — но об этом в главе про фонд.
Наблюдаемость: Prometheus и Grafana, но в ячейках
Куда же без дашбордов. Sheetheus скрейпит метрики (в таб Metrics), Sheetfana рисует их — прямо внутри таблицы, средствами условного форматирования и спарклайнов. Выглядит это неприлично прилично:

Панели «Pods Running», «Nodes Ready», «CPU Allocated», гистограмма подов по нодам, тайм-серия аллокации CPU — и всё это ячейки. SheetAIOps сверху смотрит на вкладку Events и делает вид, что предсказывает аномалии.
Sheet-Native Computing Foundation
В какой-то момент проектов стало так много, что они попросили себе фонд. Так появился SNCF — Sheet-Native Computing Foundation: вендор-нейтральный дом для «sheet-native computing», пародия на CNCF, которая… работает. За шуткой — реальный код: планировщик с failover, верифицируемая сертификация, живое демо на трёх настоящих Google-таблицах и собственный стандарт контейнеров.
Стандарт: Sheet Container Initiative (SCI)
Где две независимые реализации — там должна быть спецификация. У нас их две (Apps Script и Python), поэтому появился SCI — по образцу OCI, три спеки:
SCRI (Sheet Container Runtime Interface) — контракт apiserver↔kubelet. Уже v0.1, с уровнями conformance Core/Full.
SICF (Sheet-Native Image Container Format) — тот самый двухрежимный формат образов. Тоже v0.1.
SDS (Sheet Distribution Spec) — как образы ездят между таблицами и реестрами. В работе.
Спеки написаны с реального поведения (описывают то, что уже крутится), с нормативными MUST/SHOULD и исполняемым conformance-сьютом. Это, помимо прочего, честный учебник по тому, что такое CRI и OCI на самом деле.
Сертификация: с серийными номерами и рангами
Да, у нас есть верифицируемая сертификация инженеров. Открываете issue с формой экзамена → GitHub Action чеканит серийник (например, SFE000001) в публичный реестр. 19 программ (по одной на проект), у каждой свой префикс.

Собрал несколько сертификатов — можешь податься на ранг, и система проверит тебя по реестру:
🚀 SheetCadet — 1 сертификат
👨🚀 SheetAstronaut — 3
🛰️ SheetCommander — 6
🌌 SheetAdmiral — 10+

Всё проверяемо: у каждого сертификата и ранга есть серийник и запись в открытом реестре. Никакого «поверьте на слово» — только «сверьтесь с таблицей».
Другие проекты фонда
Sheeternetes — оркестрация контейнеров. Флагман.
SheetOS — нода-ОС (Talos).
sheeternetes-onprem — bare-metal редакция на Excel/LibreOffice + федерация.
Sheetlium — сеть (Cilium). Sheetmesh — service mesh (Istio).
Sheetlux CD — GitOps-доставка (Argo CD).
Sheetstor — распределённое блочное хранилище (LINSTOR).
SheetHub — DevOps-форж (GitLab). Уже сделан внешним контрибьютором — см. ниже.
Sheetheus / Sheetfana — метрики и дашборды (Prometheus/Grafana).
SheetFinOps — учёт стоимости инфраструктуры ($0, разумеется).
SheetAssembly — WASI/WASM-нагрузки. skctl-wasm — CLI в браузере.
Sheetelligence / SheetAIOps — LLM-оркестрация и AIOps.
Cloud connectors — запуск в managed-облаках (Cloud Run, ACI, Yandex, ECS…).
Data & Storage — Sheedis (Redis), Sheetgres (Postgres), MySheet (MySQL), SheetMongo (Mongo), Sheethouse (ClickHouse), Sheetka (Kafka), SheetWire (реальные клиенты БД к таблице).
Языковые клиенты — PowerShell, Lisp и, для отважных, Brainfuck.
SCI — стандарт контейнеров (SCRI/SICF/SDS).
Роадмап (кратко)
Sheeternetes: мульти-хост overlay-сеть, реальные Secrets (в скрытых листах), RBAC через protected ranges, HPA через условное форматирование, документированный DR через историю ревизий, conformance-сьют.
SheetVirt: аналог KubeVirt — VM как поды.
SICF/native: свой формат образов + нагрузочное тестирование бэкендов (сколько образов влезет в Excel, пока он не взмолится — отдельный открытый вопрос, которым мы всерьёз займёмся).
Безопасность: SheetFalco (runtime security), SheetSign (подпись образов), SheetPolicy (admission через формулы).
Экосистема миграции с настоящих платформ (импорт Kubernetes/Compose-манифестов).
Присоединяйтесь
Это open source, Apache 2.0, и он живой. Первый внешний контрибьютор недавно с нуля реализовал SheetHub (apiserver + web-UI + CLI + тесты), прошёл трёхлинзовое ревью с парой security-замечаний, починил всё за пять часов — и это смёржено. Ровно то, ради чего затевался open source.
Комьюнити — международное и англоязычное (но пока там один я), так что подключайтесь откуда угодно.
🎓 Пройдите сертификацию — с серийником, по 19 программам.
🛠️ Возьмите good-first-issue в любом из проектов.
💬 Каналы: Slack — sheetncf, Telegram — t.me/stncf, LinkedIn — company/sheetncf. Twitter — скоро.
🌐 Весь код: github.com/sncfoundation и sncfoundation.github.io.
✉️ Написать лично — Telegram @tym83.
Итого
Sheeternetes — это, конечно, доведённый до абсурда мысленный эксперимент: «а что если единственным ограничением будет — держать всё внутри таблиц». Но по дороге он оказался настоящим: реальные контейнеры (в двух форматах — обычном OCI и нативном табличном), планировщик с bin-packing и failover, on-prem без интернета, федерация с живой миграцией и HMAC, стандарт SCRI/SICF, тесты, CI и даже фонд с сертификацией.
Ничего из этого не нужно в проде. Но, кажется, именно поэтому этим так весело заниматься — и, что неожиданно, на этом отлично объясняются вполне серьёзные вещи: что такое control plane, чем reconcile отличается от императивного деплоя, как устроены CRI и OCI, зачем нужен make-before-break и почему у образов есть дайджесты.
И помните: It reconciles.

