Продолжаем серию переводов статьей о контейнеризации OpenClaw и деплое в Kubernetes, и неочевидных проблемах переноса OpenClaw на рельсы автомасштабирования. В материале подробно разбирается каждый шаг пути от Dockerfile до продакшн-кластера, с рабочими конфигами, инструкциями и примерами сборок. Предложенные автором решения не всегда оптимальны, и мы добавили в материал комментарии нашего эксперта @Philipp451 там, где можно что-то улучшить или применить альтернативный подход.
Запущенный на сервере OpenClaw решает большинство задач, которые пользователи ставят перед агентами. Для личного использования, параллельных запусков и несложной автоматизации его возможностей хватит с запасом. Одного VPS перестает хватать, когда приходят они: пиковые нагрузки.
В продакшене пиковые нагрузки у OpenClaw появляются раньше, чем можно ожидать. Параллельные сессии, тяжелые повторяющиеся cron-задачи, тяжелые операции с памятью и входящие сообщения от нескольких коннекторов одновременно… Gateway начинает трещать по швам, упирается в лимиты CPU и RAM, а отказоустойчивость стремительно начинает падать
Когда это случается, варианта остается два: подбросить в печь больше вычислительных мощностей или пересмотреть архитектуру. Если второй вариант вам ближе, то эта статья для вас. Сегодня мы разберем контейнеризацию в Docker, отказоустойчивый деплой через Kubernetes, а также управление stateful-хранилищем, без которого стабильный запуск нескольких инстансов невозможен.
Я исхожу из предположения, что деплой одного инстанса вам знаком и понятен, и разбираем исключительно вопросы масштабирования системы.
Когда масштабирование — не оверхед?
Большинство персональных и небольших командных конфигураций прекрасно работают на одном сервере, а добавлять сложность оркестрации без реальной причины — просто множить объем обслуживания.
Как понять, что масштабирование назрело, и стоит прекратить пытаться нагружать единственный инстанс?
Нагрузка на CPU выше 80% при параллельных сессиях. Если шлюз упирается в процессор, когда активны две-три сессии одновременно, больше нет смысла мучать единственный инстанс.
Появляются OOM-киллы в системных логах. Сложные tool chains, индексация памяти, мультиагентный роутинг — все это может быстро исчерпать RAM. Если ОС убивает процесс шлюза, вы либо выросли из текущих ресурсов, либо пора распределять нагрузку.
Больше 10 одновременных сессий или много крон-задач. Шлюз изолирует сессии через внутренний механизм lane, но есть предел того, сколько сессий один процесс может тянуть без роста времени отклика
Требования к zero-downtime. Обновление systemd на одном инстансе вызывает кратковременный простой. Поэтому если в вашем сценарии перерывы недопустимы, нужны реплики и rolling-деплой.
Высокий поток входящих webhook-событий. Обработка сотни входящих событий в час быстро превращают одинокий gateway в бутылочное горлышко для трафика.
Контейнеризация OpenClaw с Docker
Пишем свой Dockerfile
OpenClaw поддерживает Docker «из коробки» и для него всегда доступны официальные Docker-образы openclaw/openclaw:latest. Но если хочется больше контроля, Dockerfile придется создавать под себя. В нашем случае, это позволит добавлять apt-пакеты, назначать пользователи с низкими привилегиями и добавлять кастомные шаги сборки.
Рабочая точка отсчета — Node 22 slim:
FROM node:22-bookworm-slim RUN corepack enable && \ curl -fsSL https://bun.sh/install | bash && \ mv /root/.bun/bin/bun /usr/local/bin/ WORKDIR /app # Кэшируем зависимости до копирования исходников (ускоряет пересборку) COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./ COPY ui/package.json ./ui/ RUN pnpm install --frozen-lockfile COPY . . RUN pnpm build && pnpm ui:build ENV NODE_ENV=production EXPOSE 18789 18793 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD node dist/index.js health || exit 1 CMD ["node", "dist/index.js"]
Примечане: Хелсчек в Dockerfile.
CMD node dist/index.js health || exit 1поднимает новый Node.js-процесс при каждой проверке. Это дорого, медленно и создает ощутимую нагрузку на рантайм — особенно если интервал проверки короткий. Лучше использовать легковесный HTTP-запрос к уже запущенному процессу, что, кстати, и сделано в манифесте Kubernetes через httpGet.
Что вы можете захотеть изменить под себя:
Если ваш сценарий включает медиа и требует установки ffmpeg и кастомных модулей, выполните
RUN apt-get update && apt-get install -y ffmpeg build-essentialпередCOPY. Кэширование на этом слое позволит не пересобирать при каждом изменении кода.Вы можете захотеть переключиться на непривилегированного пользователя. Привычка работать из root на частном сервере становится вредной, когда ваши агенты открыты для внешних сервисов. Для этого добавьте
USER nodeпередCMD, и убедитесь, что пользователю послеchownдоступны все нужные папки томов.Вам нужно сделать выбор между компактными Alpine-образами и Debian slim bookworm. Alpine могут конфликтовать с нативными Node-модулями, slim bookworm надежнее для продакшн-шлюза.
Три сервиса — один Compose
Для большинства деплоев нужны минимум три сервиса: gateway, управляющий контейнер для административных CLI-команд и реверс-прокси. Ниже Compose-файл для всех трех:
version: '3.8' services: openclaw-gateway: image: openclaw/openclaw:latest container_name: openclaw-gateway restart: unless-stopped ports: - "18789:18789" - "18793:18793" volumes: - openclaw-config:/root/.openclaw - openclaw-workspace:/root/workspace environment: - NODE_ENV=production - OPENCLAW_STATE_DIR=/root/.openclaw command: openclaw gateway deploy: resources: limits: cpus: '2' memory: 2G reservations: cpus: '1' memory: 1G healthcheck: test: ["CMD", "node", "dist/index.js", "health"] interval: 30s timeout: 10s retries: 3 openclaw-cli: image: openclaw/openclaw:latest volumes_from: [openclaw-gateway] entrypoint: openclaw nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - certs:/etc/nginx/certs depends_on: - openclaw-gateway volumes: openclaw-config: openclaw-workspace: certs:
Примечание: deploy.resources в Docker Compose. Секция deploy относится к Docker Swarm, в обычном Compose она не работает. Скорее всего, автор подразумевает запуск через docker compose --compatibility.
Обратите внимание на два именованных тома:
openclaw-configхранит все из~/.openclaw: конфиг, учетные данные, активные сессии и расписание задач изcron/jobs.json.В
openclaw-workspaceнаходятся MEMORY.md, директория memory/, инструменты и скиллы.
Оба тома должны переживать перезапуски контейнера и редеплои — эфемерное хранилище здесь не подходит.
Также мы подключаем сервис openclaw-cli. Он позволяет работать с теми же томами, которые использует шлюз, а значит — запускать административные CLI-команды через одноразовые процессы, не вмешиваясь в работу шлюза exec-запросами.
docker compose run --rm openclaw-cli channels login
docker compose run --rm openclaw-cli status --all
docker compose exec openclaw-gateway openclaw status
Для авторизации TTY добавьте:
docker compose run -it --rm openclaw-cli channels login
Изолируем агентов в песочницах
Песочницы однозначно заслуживают места в продакшн: при аномалиях tool-цепочки можно уменьшить «радиус взрыва» и локализовать ущерб. Sandbox в OpenClaw работает через изолированные субконтейнеры с инструментами и сессиями, которые координирует хост-шлюз. Настроить изоляцию можно в agents.defaults.sandbox.mode: non-main (изолировать всех агентов кроме главного) или all.
Sandbox-контейнеры монтируют директорию /workspace. По умолчанию они запускаются с network: none, с выборочными egress разрешениями для инструментов, которым необходим доступ в сеть. Неактивные песочницы удаляются каждые 24 часа, а с истекшим сроком — раз в 7 дней.
Примечание: песочницы и docker-in-docker. Автор описывает sandbox-контейнеры с сетевой изоляцией — отличный подход с точки зрения безопасности. Но такие песочницы часто запускаются через docker-in-docker (dind), а dind внутри Kubernetes — источник отдельного класса проблем: от конфликтов с cgroup до дыр в изоляции.
Деплой OpenClaw в Kubernetes
Docker файл собран и настало время выбрать способ оркестрации. Docker Compose хорош для одиночного хоста. Kubernetes — когда нужны несколько реплик, автоматический фейловер с переключением трафика, rolling-деплой и горизонтальное автомасштабирование.
Работа с Kubernetes приносит в продакшн весомую операционную сложность. Если OpenClaw живет на VPS и одного хорошо настроенного инстанса достаточно — оставайтесь там. Если у вас продакшен с SLA по аптайму — продолжаем готовиться к деплою.
Хранилище секретов и namespace
Для начала изолируем ресурсы в namespace. OpenClaw должен хранить логин-пароль и чувствительные данные в Secret, а не в ConfigMap и переменных окружения манифеста.
apiVersion: v1 kind: Namespace metadata: name: openclaw --- apiVersion: v1 kind: Secret metadata: name: openclaw-secrets namespace: openclaw type: Opaque data: OPENCLAW_TELEGRAM_TOKEN: <base64-encoded> ANTHROPIC_API_KEY: <base64-encoded> DISCORD_BOT_TOKEN: <base64-encoded>
Base64-значения генерируются через echo -n 'your-value' | base64. В боевом кластере лучше использовать что-то вроде External Secrets Operator, чтобы тянуть секреты из нормального хранилища, а не зашивать значения прямо в манифесты.
Манифест деплоя
apiVersion: apps/v1 kind: Deployment metadata: name: openclaw-gateway namespace: openclaw spec: replicas: 3 selector: matchLabels: app: openclaw-gateway strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 template: metadata: labels: app: openclaw-gateway spec: containers: - name: gateway image: openclaw/openclaw:latest ports: - containerPort: 18789 envFrom: - secretRef: name: openclaw-secrets volumeMounts: - name: config mountPath: /root/.openclaw - name: workspace mountPath: /root/workspace resources: limits: cpu: "2" memory: "2Gi" requests: cpu: "1" memory: "1Gi" livenessProbe: httpGet: path: /health port: 18789 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 3 readinessProbe: httpGet: path: /health port: 18789 initialDelaySeconds: 5 periodSeconds: 5 volumes: - name: config persistentVolumeClaim: claimName: openclaw-config-pvc - name: workspace persistentVolumeClaim: claimName: openclaw-workspace-pvc
Три запущенных реплики — разумный минимум для HA: если один под падает (на обслуживании или из-за сбоя), два других продолжают работать. Liveness-проба перезапускает контейнер, если шлюз перестал отвечать; readiness-проба убирает под из ротации балансировщика, пока тот реально не готов принимать трафик.
Персистентные тома
Вот тут многорепликовые деплои OpenClaw становятся интересными. Тома config и workspace требуют режима доступа ReadWriteMany, чтобы несколько подов могли монтировать их одновременно. Не все провайдеры хранилищ поддерживают RWX. Если в кластере нет storage class с поддержкой RWX, проще всего добавить NFS:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: openclaw-config-pvc namespace: openclaw spec: accessModes: - ReadWriteMany storageClassName: nfs-client resources: requests: storage: 10Gi --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: openclaw-workspace-pvc namespace: openclaw spec: accessModes: - ReadWriteMany storageClassName: nfs-client resources: requests: storage: 20Gi
Если вы используете продвинутые бэкенды памяти (QMD, Cognee, Mem0), каждому из этих сервисов лучше выделить собственные PVC и запускать их отдельными деплоями, а не пакетом вместе со шлюзом. Шлюзы подключаются к ним через внутренний DNS кластера: так хранилище остается разделенным, а масштабировать каждый компонент проще.
Service и Ingress
Увеличенные таймауты прокси важны: шлюз использует WebSocket для части коммуникаций между каналами, и дефолтные nginx-таймауты в 60 секунд будут обрывать эти соединения. Ставьте минимум 3600 секунд (один час).
apiVersion: v1 kind: Service metadata: name: openclaw-service namespace: openclaw spec: selector: app: openclaw-gateway ports: - name: gateway port: 18789 targetPort: 18789 type: ClusterIP --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: openclaw-ingress namespace: openclaw annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod" nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" spec: rules: - host: openclaw.example.com http: paths: - path: / pathType: Prefix backend: service: name: openclaw-service port: number: 18789
Horizontal Pod Autoscaler
Когда в Deployment заданы запросы и лимиты ресурсов, HPA может масштабировать реплики вверх и вниз по загрузке CPU.
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: openclaw-hpa namespace: openclaw spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: openclaw-gateway minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70
70% загрузки CPU как порог масштабирования — консервативное значение, и это намеренно: лучше добавить мощности до того, как все упрется в потолок, а не после. Минимум в две реплики гарантирует, что при падении одного пода всегда останется запасной.
Защита от простоя: Pod Disruption Budget
При плановом обслуживании кластера Kubernetes может гасить поды. Pod Disruption Budget задает количество подов, которые всегда остаются в строю, и деплой на кластер проходит с нулевым простоем:
apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: openclaw-pdb namespace: openclaw spec: minAvailable: 2 selector: matchLabels: app: openclaw-gateway
При трех запущенных репликах и minAvailable: 2, из работы выводится не более одного пода. Сервис остается доступным во время обслуживания.
Общее состояние между репликами
Общее состояние между репликами — самая сложная часть мультиреплицированного деплоя. Gateway не является stateless — в нем хранятся сессии, cron-задачи, данные авторизации и память агентов. Три реплики, одновременно пишущие в один NFS-том — это состояние гонки трех процессов с непредсказуемым результатом.
Архитектура OpenClaw спроектирована вокруг модели с одним шлюзом. Общее хранилище через RWX PV работает достаточно хорошо для конфигов и учетных данных (их в основном читают, редко пишут) и для файлов памяти в workspace (они записываются последовательно отдельными сессиями). Место, которое требует внимания, — cron/jobs.json: при нескольких работающих шлюзах можно получить дублирование крон-задач, если все реплики следят за одним jobs-файлом и каждая независимо запускает выполнение.
Примечание: Главный редфлаг. Фраза «архитектура OpenClaw спроектирована вокруг модели с одним шлюзом» является ключевой для всей статьи — и одновременно главным редфлагом. Если система не проектировалась как мультиинстансная, попытки запустить ее в таком режиме — это архитектурно плохой паттерн. Все, что дальше предлагается — RWX-тома, shared NFS, Redis-локи — это костыли поверх фундамента, который не рассчитан на такую нагрузку.
RWX PVC + общий NFS + несколько подов — на мой взгляд, опасная комбинация. Это чревато состояниями гонки, непредсказуемыми задержками и трудновоспроизводимыми багами. Автор, к его чести, сам признает проблему с дублированием крон-задач и предлагает
OPENCLAW_SKIP_CRON— но это ручное управление с плохим фейловером. Если крон-лидер упал, автоматического переключения нет.
Решений два.
Назначить одну реплику крон-лидером — включить крон только на одном поде (
OPENCLAW_SKIP_CRON=0), а остальным выставить пропуск (OPENCLAW_SKIP_CRON=1). Это проще, чем звучит: создайте отдельный Deployment для пода-крон-лидера с другим значением переменной окружения. Остальные реплики обрабатывают трафик каналов и сессии, а один под занимается планированием.Использовать Redis-сайдкар или внешний сервис блокировок для распределенной координации крона — сложнее, но чище при больших масштабах.
Бэкенды памяти (QMD, Cognee, Mem0) лучше запускать как выделенные сервисы внутри кластера. Каждый под шлюза подключается к одному и тому же эндпоинту сервиса памяти через внутренний DNS, поэтому результаты выборки одинаковы для всех реплик, независимо от того, какой под обрабатывает конкретную сессию.
Высокая доступность: health-чеки, rolling-обновления и фейловер
При правильной настройке деплоймента Kubernetes сам следит за здоровьем подов через liveness- и readiness-пробы, перезапускает и убирает их из ротации до того, как на них пойдет трафик. Rolling-обновления с maxUnavailable: 1 гарантируют, что минимум два пода обслуживают трафик во время деплоя. А PDB следит, чтобы операции обслуживания не погасили слишком много подов разом.
Kubernetes не поможет с фейловером на уровне каналов. Например, если у вас настроен токен Telegram-бота, все три реплики шлюза получат один и тот же токен, но Telegram доставит сообщения только на один активный вебхук-эндпоинт. Нужно убедиться, что Ingress или балансировщик настроен так, чтобы вебхук Telegram попадал на стабильный эндпоинт, маршрутизируемый на кластер, а не на IP конкретного пода. То же касается вебхуков Discord и колбэков WhatsApp.
Также есть нюансы с обновлением данных для авторизации. WhatsApp-сессии с авторизацией через QR привязаны к состоянию телефона и часто истекают. При этом нужно, чтобы данные обновлялись для всех подов, а не только того, где запущена сессия. Решение — использовать openclaw-cli для записи данных в общих том конфигурации.
Бэкапы в контейнеризованном сетапе
Именованные тома и PVC — не стратегия бэкапа. Они защищают от перезапуска контейнера, но не от повреждения хранилища, случайного удаления или катастрофы на уровне кластера. Для Docker Compose добавьте контейнер с кроном, который делает снапшоты томов в S3-совместимое хранилище или на удаленный сервер. Для Kubernetes стандартный инструмент — Velero: снапшоты PV и бэкапы состояния кластера.
Для Kubernetes стандартным инструментом для снапшотов PV и бэкапов состояния кластера является Velero. Принципы бэкапа в целом одинаковые для VPS и для K8s-кластера.
Антихрупкость. Автор описал, как запустить OpenClaw в кластере, но забыл несколько вещей, без которых продакшн-деплой будет хрупким:
Распределенное хранилище сессий. Вместо файлов на общем NFS — Redis. Сессии, блокировки, состояние — все это должно жить в быстром in-memory хранилище, а не на сетевой файловой системе с ее латентностью и рисками.
Очередь сообщений. При нескольких репликах входящие события от каналов нужно не раскидывать по подам через балансировщик, а складывать в очередь и обрабатывать упорядоченно.
Rate limiting. В статье ни слова о том, как защитить шлюз от перегрузки входящим трафиком — а при открытых вебхуках это вопрос времени.
Логирование и трейсинг. Для мультирепликового деплоя нужна централизованная система логов (ELK, Loki) и распределенный трейсинг (OpenTelemetry, Jaeger). Без них отладка инцидента на кластере из нескольких подов — гадание.
Стоит ли в это ввязываться?
Для большинства пользователей архитектура, которую мы обсуждали выше — оверкил. Грамотно настроенный VPS с systemd, мониторингом и бэкапами закроет подавляющее большинство реальных сценариев без операционной сложности Kubernetes.
Задумываться о контейнеризации/кластеризации стоит лишь когда вы упретесь в конкретные лимиты, в виде OOM-киллов, стабильно высокой нагрузки на CPU, даунтайм с реальными последствиями.
