Под с обычным контейнером тратит на старт от сотен миллисекунд до нескольких секунд — pull образа, boot процесса, прогрев рантайма приложения. Для fan-out на edge, коротких HTTP-обработчиков и всплесков нагрузки это накладные расходы, которые накапливаются на каждом масштабировании HPA. SpinKube решает эту задачу иначе: WebAssembly-модуль планируется тем же kubectl apply, что и обычный под, но стартует как WASM-инстанс — без отдельной прослойки, без своего API, без нового способа деплоя. Разбираем, как это работает на уровне containerd и почему это не «контейнеры, но быстрее», а инструмент для конкретного профиля нагрузок.
Содержание
Открыть содержание
Почему об этом стоит знать в 2026
WebAssembly на сервере несколько лет был нишевой темой — сначала proof-of-concept, потом нишевые edge-платформы со своим закрытым рантаймом. Ситуация изменилась с двух сторон одновременно. Fermyon Spin — фреймворк и CLI для сборки WASM-микросервисов — после покупки Akamai обрабатывает на edge порядка 75 млн RPS, то есть это не игрушечный бенчмарк, а продовая нагрузка (источник). Параллельно проект SpinKube попал в CNCF Sandbox, а WASM-модули теперь планируются напрямую через containerd shim — то есть встроены в тот же путь, по которому Kubernetes уже планирует обычные контейнеры, а не через отдельный сайдкар или внешний контроллер.
Это меняет вопрос с «стоит ли вообще пробовать WASM на сервере» на «для какого процента моих сервисов это уже имеет смысл сегодня». Ответ — не «для всех», и дальше по тексту честно разберём, где граница.
Архитектура: один kubelet, два пути исполнения
Ключевая идея SpinKube в том, что WASM не заменяет контейнерный рантайм узла, а встраивается рядом с ним на уровне containerd shim. У containerd уже есть abstraction для этого — shim v2 API, через который планируется, какой процесс фактически исполняет workload. Обычный путь — containerd-shim-runc-v2, который запускает Linux-процесс в namespace из OCI-образа. containerd-shim-spin (построен на библиотеке runwasi) — второй, параллельный путь: он берёт .wasm-компонент и создаёт под него инстанс Wasmtime вместо процесса ОС.
Выбор пути делает не отдельный планировщик и не аннотация — это стандартное поле Kubernetes runtimeClassName в спеке пода. RuntimeClass с handler: spin говорит kubelet: для этого пода использовать не runc, а shim для Spin. Дальше весь остальной Kubernetes — Service, HPA, NetworkPolicy, зонды готовности — работает без изменений, потому что для контроллеров это по-прежнему обычный Pod с обычным статусом Running.
SpinKube-оператор и SpinApp CRD
Руками ставить RuntimeClass и следить за низкоуровневыми деталями shim на каждом узле неудобно — для этого в SpinKube есть оператор, который берёт на себя provisioning узлов и превращает декларативное описание Spin-приложения в обычные Kubernetes-объекты.
Установка — три Helm-релиза: kwasm-operator ставит бинарник shim на узлы (аннотацией kwasm.sh/node=true включается на нужных нодах, без пересборки образа узла), cert-manager — стандартная зависимость для вебхуков оператора, и сам spin-operator:
helm install kwasm-operator kwasm-operator \
--repo https://kwasm.sh/kwasm-operator/ \
--namespace kwasm --create-namespace \
--set kwasmOperator.installerImage=ghcr.io/spinkube/containerd-shim-spin/node-installer:v0.21.0
kubectl annotate node --all kwasm.sh/node=true
helm install cert-manager cert-manager \
--repo https://charts.jetstack.io \
--namespace cert-manager --create-namespace \
--set crds.enabled=true
helm install spin-operator \
--namespace spin-operator --create-namespace --wait \
oci://ghcr.io/spinkube/charts/spin-operator
kubectl apply -f - <<'EOF'
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: wasmtime-spin-v2
handler: spin
EOF
После этого приложение собирается CLI spin, пакуется как OCI-артефакт и описывается CRD SpinApp:
spin new -t http-rust hello-spin
cd hello-spin
spin build
spin registry push ttl.sh/hello-spin:1h
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinApp
metadata:
name: hello-spin
spec:
image: "ttl.sh/hello-spin:1h"
executor: containerd-shim-spin
replicas: 3
resources:
limits:
cpu: "100m"
memory: "128Mi"
requests:
cpu: "50m"
memory: "32Mi"
kubectl apply -f spinapp.yaml — и оператор реконсилирует это в Deployment с runtimeClassName: wasmtime-spin-v2 и Service перед ним. Для остального кластера — kubectl get pods, kubectl logs, HPA по CPU или кастомным метрикам — разницы с обычным контейнерным сервисом нет.
Честные ограничения WASI 0.2
Здесь важно не поддаться маркетинговой рамке «WASM — это контейнеры, только быстрее». Component model и WASI 0.2 (Preview 2), на которых построен Spin, — это песочница с намеренно урезанной поверхностью системных вызовов, а не полноценная замена Linux-процесса:
- нет полноценных потоков — модель Spin рассчитана на короткоживущие, в основном однопоточные обработчики запросов; предложения по WASI-threads существуют, но не являются стабильной частью экосистемы, на которую можно полагаться в проде;
- урезанный сетевой стек — исходящие HTTP-вызовы и Redis/Postgres-коннекторы работают через явно объявленные capability в манифесте приложения, произвольный
socket()недоступен; - не любой код компилируется в
wasm32-wasi— нативные C-биндинги, нестандартные системные библиотеки, тяжёлые ML-рантаймы часто просто не собираются в этот таргет без переписывания; - не для тяжёлого stateful backend — долгоживущие соединения, фоновые воркеры с собственным жизненным циклом, локальный диск как источник правды — не тот профиль, под который спроектирован Spin.
Иными словами: если сервис — это обвязка вокруг блокирующего С-драйвера или долгоживущий stateful процесс, WASM на Kubernetes ему не поможет и не должен рассматриваться как замена.
Где WASM реально выигрывает
Профиль нагрузок, для которых холодный старт в единицы миллисекунд и лёгкая песочница дают измеримый эффект:
- edge-функции — обработка запроса ближе к пользователю, где инстансы поднимаются и опускаются по трафику, а не живут постоянно;
- лёгкий роутинг и API-gateway логика — трансформация запроса, авторизация, маршрутизация без тяжёлых зависимостей;
- всплесковая бизнес-логика — обработчики вебхуков, короткие трансформации данных, где HPA должен успеть отреагировать за секунды, а не за десятки секунд ожидания pull образа;
- мультитенантные песочницы — изоляция WASI даёт более узкую поверхность атаки на процесс, чем обычный Linux-namespace, при сопоставимой операционной модели.
Здесь же — важное практическое свойство: WASM-поды и обычные контейнерные Deployment спокойно сосуществуют в одном кластере и даже в одном namespace. RuntimeClass — это поле уровня пода, а не свойство кластера или namespace, поэтому миграция не требует «всё или ничего»: можно перевести на Spin ровно те сервисы, которые попадают в профиль выше, оставив остальное на runc.
Что нужно, чтобы сравнить старт и память на практике
Простой способ увидеть разницу — задеплоить SpinApp рядом с обычным Deployment, отдающим тот же HTTP-ответ, и прогнать оба через одинаковую нагрузку.
kubectl scale deployment hello-container --replicas=0
kubectl scale spinapp hello-spin --replicas=0
kubectl scale deployment hello-container --replicas=1
kubectl get pod -l app=hello-container -o wide --watch
# ContainerCreating -> Running: обычно сотни миллисекунд —
# секунды, зависит от того, есть ли образ в кэше узла
kubectl scale spinapp hello-spin --replicas=1
kubectl get pod -l core.spinkube.dev/app-name=hello-spin -o wide --watch
# ContainerCreating -> Running: единицы миллисекунд после первого запроса
Нагрузочный прогон — hey или k6 на оба сервиса с одинаковыми параметрами:
hey -z 30s -c 50 http://hello-container.default.svc.cluster.local/
hey -z 30s -c 50 http://hello-spin.default.svc.cluster.local/
kubectl top pod -l app=hello-container
kubectl top pod -l core.spinkube.dev/app-name=hello-spin
Таблица сравнения
| Параметр | Обычный контейнер (runc) | WASM-приложение (SpinKube) |
|---|---|---|
| Холодный старт | сотни мс — секунды | единицы мс |
| Потребление памяти в простое | десятки — сотни МБ на процесс | единицы — десятки МБ на инстанс |
| Потоки / блокирующий I/O | полная поддержка ОС | ограничено WASI 0.2, без полноценных потоков |
| Сетевой доступ | произвольный socket() | капабилити из манифеста приложения |
| Совместимость с существующим кодом | любой Linux-бинарник | только то, что собирается в wasm32-wasi |
| Планирование в Kubernetes | стандартный путь | тот же kubectl apply, RuntimeClass |
| Сосуществование в кластере | — | да, рядом с обычными подами в том же namespace |
| Подходящий профиль | долгоживущие stateful-сервисы | короткоживущие stateless обработчики |
Как проверить, что всё работает
kubectl get spinapps
# NAME READY IMAGE
# hello-spin True ttl.sh/hello-spin:1h
kubectl get pods -l core.spinkube.dev/app-name=hello-spin
# NAME READY STATUS RESTARTS AGE
# hello-spin-6d8f9c7b6f-x2k9p 1/1 Running 0 4s
kubectl describe pod -l core.spinkube.dev/app-name=hello-spin | grep "Runtime Class"
# Runtime Class Name: wasmtime-spin-v2
curl -s http://hello-spin.default.svc.cluster.local/ -w '\n%{time_total}s\n'
Если под завис в ContainerCreating дольше пары секунд — почти всегда это значит, что shim не установлен на узле (проверить kwasm.sh/node аннотацию и логи kwasm-operator) или RuntimeClass не применился до создания пода.
Итог
SpinKube не заменяет контейнеры — он добавляет второй путь исполнения рядом с уже привычным runc, доступный через то же поле runtimeClassName, тот же kubectl apply, тот же HPA. Выигрыш реален и измерим для короткоживущих, stateless, I/O-light обработчиков: edge-функций, роутинга, лёгкой бизнес-логики, — там, где холодный старт в единицы миллисекунд и меньшая поверхность атаки WASI-песочницы окупают ограничения Preview 2. Для тяжёлого stateful backend, кода с нативными зависимостями или сервисов, которым нужны полноценные потоки, WASM пока не альтернатива — и не должен ей навязываться. Правильная стратегия на 2026 год — не мигрировать кластер целиком, а выбрать сервисы, которые уже попадают в этот профиль, и запустить их рядом с остальными подами без единого лишнего компонента в архитектуре.