Контейнеризація AI‑агентів (щоб вони не помирали на деплої)

Як контейнеризувати й оперувати агентів: runtime‑config, секрети, timeouts, health checks і rollouts. Є Python + JS приклади.
На цій сторінці
  1. Проблема (агент працював… доки ти його не задеплоїв)
  2. Чому це ламається в проді
  3. Діаграма: що ти реально деплоїш
  4. Реальний код: entrypoint, який не соромно деплоїти (Python + JS)
  5. Dockerfile (multi-stage, без секретів у image)
  6. Реальний інцидент (з цифрами)
  7. Компроміси
  8. Коли НЕ контейнеризувати
  9. Copy‑paste чекліст
  10. Safe default config (YAML)
  11. Implement in OnceOnly (опційно)
  12. FAQ (3–5)
  13. Пов’язані сторінки (3–6 лінків)

Проблема (агент працював… доки ти його не задеплоїв)

Notebook‑агент: ок.

Деплой‑агент: тут живе біль:

  • немає мережі, яку ти “припускав”
  • OOMKill, бо хтось увімкнув “full trace logging”
  • ретраї множаться і стають rate‑limit storm
  • секрети запечені в image (не треба)

Контейнеризація — це не Dockerfile‑театр. Це момент, коли ти змушуєш агента бути сервісом.

Чому це ламається в проді

Агенти — незручні ворклоади:

  • bursty трафік (spikes = spikes токенів)
  • I/O (tools) і зависання на timeouts
  • довгі хвости (p95 норм, p99 хаос)

Якщо контейнер не enforce’ить бюджети і timeouts, це зробить прод. Просто через 504, OOMKills і рахунок.

Діаграма: що ти реально деплоїш

Реальний код: entrypoint, який не соромно деплоїти (Python + JS)

Нудно, але правильно:

  • конфіг з env
  • бюджети/timeout enforced
  • health endpoint
PYTHON
import os
import time
from dataclasses import dataclass
from typing import Any, Dict


@dataclass(frozen=True)
class Budgets:
  max_steps: int
  max_tool_calls: int
  max_seconds: int


def load_budgets() -> Budgets:
  return Budgets(
      max_steps=int(os.getenv("AGENT_MAX_STEPS", "25")),
      max_tool_calls=int(os.getenv("AGENT_MAX_TOOL_CALLS", "12")),
      max_seconds=int(os.getenv("AGENT_MAX_SECONDS", "60")),
  )


def run_request(task: str, *, budgets: Budgets) -> Dict[str, Any]:
  t0 = time.time()
  steps = 0
  tool_calls = 0

  while True:
      steps += 1
      if steps > budgets.max_steps:
          return {"output": "", "stop_reason": "max_steps"}
      if tool_calls > budgets.max_tool_calls:
          return {"output": "", "stop_reason": "max_tool_calls"}
      if time.time() - t0 > budgets.max_seconds:
          return {"output": "", "stop_reason": "max_seconds"}

      return {"output": "ok", "stop_reason": "finish"}


def health() -> Dict[str, str]:
  return {"ok": "true"}
JAVASCRIPT
export function loadBudgets() {
return {
  maxSteps: Number(process.env.AGENT_MAX_STEPS ?? 25),
  maxToolCalls: Number(process.env.AGENT_MAX_TOOL_CALLS ?? 12),
  maxSeconds: Number(process.env.AGENT_MAX_SECONDS ?? 60),
};
}

export function runRequest(task, { budgets }) {
const t0 = Date.now();
let steps = 0;
let toolCalls = 0;

while (true) {
  steps += 1;
  if (steps > budgets.maxSteps) return { output: "", stop_reason: "max_steps" };
  if (toolCalls > budgets.maxToolCalls) return { output: "", stop_reason: "max_tool_calls" };
  if ((Date.now() - t0) / 1000 > budgets.maxSeconds) return { output: "", stop_reason: "max_seconds" };

  return { output: "ok", stop_reason: "finish" };
}
}

export function health() {
return { ok: true };
}

Dockerfile (multi-stage, без секретів у image)

DOCKERFILE
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["npm","run","start"]

Реальний інцидент (з цифрами)

Ми задеплоїли сервіс з “debug logging” увімкненим за замовчуванням. Він логував повні tool results.

Імпакт за один день:

  • памʼять росла → OOMKill
  • ретраї множили навантаження
  • ~12% failure rate
  • онкол: ~3 години (бо логи величезні і все одно не допомагають)

Фікс:

  1. sampled logging + редакція (/uk/observability-monitoring/agent-logging)
  2. enforce’ити бюджети в runtime
  3. kill switch, щоб вимикати дорогі tools під час інциденту

Компроміси

  • Жорсткі timeouts зменшують хвости і можуть знижувати якість.
  • Більше логів = легше дебажити, важче по privacy/вартості.
  • “Один контейнер на агента” — просто і дорого. “Спільний сервіс” — дешевше і важче.

Коли НЕ контейнеризувати

Якщо ти не оперуєш це як сервіс — не overbuild. Але якщо реальний юзер може це тригернути, ти вже оперуєш сервіс.

Copy‑paste чекліст

  • [ ] Budgets з env + enforced у runtime
  • [ ] Tool gateway: timeouts/retries/allowlists
  • [ ] Health + readiness checks
  • [ ] Secrets через платформу (не baked)
  • [ ] Kill switch config
  • [ ] Структуровані логи + sampling; PII‑редакція за замовчуванням

Safe default config (YAML)

YAML
runtime:
  env:
    AGENT_MAX_STEPS: 25
    AGENT_MAX_TOOL_CALLS: 12
    AGENT_MAX_SECONDS: 60
tools:
  allowlist: ["search.read", "http.get"]
  timeouts_ms: { default: 8000 }
  retries: { max: 2, backoff_ms: [200, 800] }
observability:
  sampled_tool_results: true
  result_sample_rate: 0.01
rollout:
  canary_percent: 10
  rollback_on_error_rate: 0.05

Implement in OnceOnly (опційно)

Implement in OnceOnly
Budgets + tool gateway defaults, які переживають деплой.
Use in OnceOnly
# onceonly-python: tool allowlist + governed tool call
import os
from onceonly import OnceOnly

client = OnceOnly(
    api_key=os.environ["ONCEONLY_API_KEY"],
    timeout=5.0,
    max_retries_429=2,
)

agent_id = "billing-agent"

client.gov.upsert_policy({
    "agent_id": agent_id,
    "allowed_tools": ["search.read", "http.get"],
    "max_actions_per_hour": 200,
    "max_spend_usd_per_day": 10.0,
})

res = client.ai.run_tool(
    agent_id=agent_id,
    tool="http.get",
    args={"url": "https://example.com/health"},
    spend_usd=0.001,
)
if not res.allowed:
    raise RuntimeError(res.policy_reason)

FAQ (3–5)

Чи варто запікати промпти/моделі в image?
Код — так, секрети — ні. Промпти можуть бути в репо (versioned). Модель — це конфіг. Секрети — тільки runtime.
Найчастіший фейл деплою?
Timeouts + retries, які взаємно підсилюються. 504 і storm. Ретраї — в одному місці, бюджети — capped.
Чи потрібен Kubernetes?
Не обовʼязково. Потрібні бюджети, observability і rollback. Це можна зробити і без K8s.
Як робити safe rollback?
Kill switch + попередній image/версія промпта. Ролбек по error‑rate і spikes витрат.

Пов’язані сторінки (3–6 лінків)

Не впевнені, що це ваш кейс?

Спроєктувати агента →
⏱️ 5 хв читанняОновлено Бер, 2026Складність: ★★★
Інтегровано: продакшен-контрольOnceOnly
Додай guardrails до агентів з tool-calling
Зашип цей патерн з governance:
  • Бюджетами (кроки / ліміти витрат)
  • Дозволами на інструменти (allowlist / blocklist)
  • Kill switch та аварійна зупинка
  • Ідемпотентність і dedupe
  • Audit logs та трасування
Інтегрована згадка: OnceOnly — контрольний шар для продакшен агент-систем.
Автор

Цю документацію курують і підтримують інженери, які запускають AI-агентів у продакшені.

Контент створено з допомогою AI, із людською редакторською відповідальністю за точність, ясність і продакшн-релевантність.

Патерни та рекомендації базуються на постмортемах, режимах відмов і операційних інцидентах у розгорнутих системах, зокрема під час розробки та експлуатації governance-інфраструктури для агентів у OnceOnly.