Стадия жизненного цикла модуля: Общедоступная версия

У модуля есть требования для установки

Для Deckhouse Commander доступно технологическое превью активно развивающегося Terraform-провайдера, который работает через API интеграции, описанный в этом разделе. Провайдер в настоящее время поддерживает создание, удаление, изменение кластеров и шаблонов кластеров в одном рабочем пространстве, поэтому используйте с ним API-ключ сервисного аккаунта рабочего пространства: такому ключу не нужен контекст рабочего пространства.

Обзор

Deckhouse Commander предоставляет внешний API для сценариев автоматизации и интеграции — CI/CD, self-service-порталов, IDP-платформ. Интерактивная документация OpenAPI (Swagger) встроена в веб-интерфейс и всегда соответствует версии запущенного бэкенда; чтобы её открыть, нажмите на иконку в виде шестерёнки в верхней панели:

  • на странице вне рабочих пространств, например в разделе «Рабочие пространства», пункт «Документация API» открывает справочник глобального API по адресу /documentation;
  • на странице рабочего пространства пункт «Документация API» открывает справочник API рабочего пространства по адресу /workspaces/<slug-рабочего-пространства>/auth-tokens/documentation.

Каждый запрос должен содержать API-ключ в HTTP-заголовке X-Auth-Token. Все пути указываются относительно https://$COMMANDER_HOST; в примерах используется переменная окружения $COMMANDER_HOST для адреса сервера и $COMMANDER_GLOBAL_TOKEN / $COMMANDER_TOKEN — для ключей.

GET /api/v1/workspaces HTTP/1.1
Host: $COMMANDER_HOST
X-Auth-Token: $COMMANDER_GLOBAL_TOKEN
Accept: application/json

API-ключ принадлежит сервисному аккаунту — идентичности, от имени которой выполняется запрос. Права даёт не ключ, а роли, назначенные аккаунту: все ключи одного аккаунта действуют с одинаковыми правами, а отзыв ключа права не меняет. Ключи выпускаются на вкладке «Сервисные аккаунты» раздела «Пользователи и права» (полная процедура — в разделе API интеграции и сервисные аккаунты руководства пользователя).

Есть два вида сервисных аккаунтов, и вид аккаунта определяет, какие методы API доступны его ключу:

  • Глобальный сервисный аккаунт — управляется в глобальном разделе «Пользователи и права» (пункт верхнего меню на страницах вне рабочих пространств). При наличии глобального назначения вызывает глобальные методы: рабочие пространства, глобальные роли, глобальные назначения ролей, назначения в рабочих пространствах и биллинг. Он также вызывает методы рабочего пространства в каждом РП, где у него есть назначение, — с явным контекстом рабочего пространства, см. Контекст рабочего пространства;
  • Сервисный аккаунт рабочего пространства — управляется в разделе «Пользователи и права» своего РП. Вызывает методы только этого рабочего пространства: кластеры, шаблоны кластеров, каталоги инвентаря, записи каталогов, хранилища образов контейнеров и проекты. Глобальные методы и любое другое рабочее пространство возвращают 403 Forbidden.

Запросы без ключа, а также с отозванным, просроченным или неизвестным ключом возвращают 401 Unauthorized. Права на перечисленные ниже ресурсы проверяются так, как описано в разделе Права доступа. Сервисные аккаунты и их ключи управляются только в веб-интерфейсе и не входят в API интеграции, поэтому аккаунт не может выпустить ключ сам себе. За исчерпывающими схемами запросов и ответов, перечислениями и HTTP-кодами для каждого метода всегда обращайтесь к Swagger UI.

Контекст рабочего пространства

Сервисному аккаунту рабочего пространства контекст не нужен: рабочее пространство определяется самим аккаунтом. Заголовок X-Commander-Workspace-ID принимается только тогда, когда он повторяет это же рабочее пространство.

Глобальный сервисный аккаунт указывает рабочее пространство в заголовке X-Commander-Workspace-ID, в котором передаётся UUID рабочего пространства:

GET /api/v1/clusters HTTP/1.1
Host: $COMMANDER_HOST
X-Auth-Token: $COMMANDER_GLOBAL_TOKEN
X-Commander-Workspace-ID: 7f3b9e6a-2c4d-4f8a-9b1e-5d7c6a8f0b2c
Accept: application/json

Правила одинаковы для всех методов:

Сервисный аккаунт Методы X-Commander-Workspace-ID Результат
Глобальный Глобальные Отсутствует Права из глобальных назначений аккаунта
Глобальный Глобальные Передан 400 invalid_workspace_context
Глобальный Рабочего пространства Корректный ID рабочего пространства Права аккаунта в выбранном рабочем пространстве
Глобальный Рабочего пространства Отсутствует или некорректен 400 invalid_workspace_context
Рабочего пространства Своего рабочего пространства Отсутствует или равен этому РП Права из назначений этого рабочего пространства
Рабочего пространства Другого РП или глобальные Любой 403 Forbidden

Объект другого рабочего пространства, объект несуществующего рабочего пространства и несуществующий объект возвращают 403 Forbidden: API не раскрывает, существует ли недоступный объект.

Доступные глобальные методы

Доступны глобальному сервисному аккаунту с соответствующими глобальными назначениями. Методы биллинга тоже относятся к глобальным и перечислены в разделе операции биллинга.

  1. Управление глобальными ролями:
    • GET /api/v1/global_roles;
    • POST /api/v1/global_roles;
    • GET /api/v1/global_roles/:id;
    • PUT /api/v1/global_roles/:id;
    • DELETE /api/v1/global_roles/:id.
  2. Управление глобальными назначениями ролей:
    • GET /api/v1/global_role_bindings;
    • POST /api/v1/global_role_bindings;
    • GET /api/v1/global_role_bindings/:id;
    • PUT /api/v1/global_role_bindings/:id;
    • DELETE /api/v1/global_role_bindings/:id.
  3. Управление рабочими пространствами:
    • GET /api/v1/workspaces;
    • POST /api/v1/workspaces;
    • PUT /api/v1/workspaces/:id;
    • DELETE /api/v1/workspaces/:id.
  4. Управление назначениями в рабочих пространствах:
    • GET /api/v1/workspace_role_bindings;
    • POST /api/v1/workspace_role_bindings;
    • GET /api/v1/workspace_role_bindings/:id;
    • PUT /api/v1/workspace_role_bindings/:id;
    • DELETE /api/v1/workspace_role_bindings/:id.

Операции биллинга

API интеграции позволяет получать рассчитанную стоимость, управлять лицевыми счетами и тарифами, читать и создавать финансовые операции и классы ресурсов. Готовые запросы собраны в разделе примеры биллинга.

Методы биллинга относятся к глобальному API, и условия доступа к ним отличаются от остальных методов:

  • биллинг должен быть включён;
  • вызывающий должен быть глобальным сервисным аккаунтом, а вызов выполняется в глобальном контексте, а не в контексте рабочего пространства;
  • роль billing-admin покрывает все методы биллинга, а ограниченный доступ задаётся глобальной ролью с нужными ресурсами и действиями — подробности в разделе права доступа к биллингу.

Полные схемы запросов и ответов, фильтры, заголовки CSV и ошибки доступны в глобальном Swagger UI по адресу /documentation. Глобальный API предоставляет следующие операции биллинга:

Операция Метод и путь
Детализация рассчитанной стоимости GET /api/v1/billing_costs
Список или создание счетов GET /api/v1/billing_accounts, POST /api/v1/billing_accounts
Чтение, изменение или архивация счёта GET /api/v1/billing_accounts/:id, PUT /api/v1/billing_accounts/:id, DELETE /api/v1/billing_accounts/:id
Список транзакций или финансовая операция GET /api/v1/billing_accounts/:id/transactions, POST /api/v1/billing_accounts/:id/transactions
Чтение одной транзакции GET /api/v1/billing_accounts/:id/transactions/:transaction_id
Список или создание тарифов GET /api/v1/billing_tariffs, POST /api/v1/billing_tariffs
Чтение, изменение или удаление тарифа GET /api/v1/billing_tariffs/:id, PUT /api/v1/billing_tariffs/:id, DELETE /api/v1/billing_tariffs/:id
Импорт новых тарифов POST /api/v1/billing_tariffs/import
Список или создание вычислительных классов GET /api/v1/billing_resources/compute_classes, POST /api/v1/billing_resources/compute_classes
Список или создание классов хранилища GET /api/v1/billing_resources/storage_classes, POST /api/v1/billing_resources/storage_classes
Список сформированных отчётов GET /api/v1/billing_reports

Стоимость и экспорт данных

Стоимость рассчитывается синхронно, по тем же правилам, что и на дашборде; доступные группировки и фильтры перечислены в Swagger. Параметры from и to передаются как время в UTC, соответствующее границам календарных суток в часовом поясе Europe/Moscow, причём граница to в период не входит.

По умолчанию API возвращает JSON. Выгрузка в CSV и разбиение на страницы устроены по-разному у детализации и у списков:

Данные CSV Пагинация
Детализация стоимости response_format=csv Нет, результат возвращается целиком
Списки счетов, транзакций и тарифов response_format=csv Курсорная, per_page от 1 до 9999 (по умолчанию 20)

Следующая страница запрашивается возвращённым курсором: в JSON он приходит в поле cursor, в CSV — в заголовке X-Next-Cursor. Последнюю страницу обозначает null в JSON или пустой заголовок X-Next-Cursor в CSV.

Набор расчётных величин по счёту зависит от того, каким методом его читают:

  • список счетов содержит проведённый баланс и paid_until, но текущий расход и прогноз не рассчитывает;
  • чтение одного счёта их добавляет;
  • поля forecast_days и forecast_until остаются пустыми, пока доступный остаток не положителен или потребления нет.

Успешный ответ не подтверждает, что метрики покрывают весь запрошенный период. При отсутствии метрик или наличии пропусков API возвращает частичный либо пустой результат с кодом 200, и признака покрытия метриками в ответе нет. Поэтому отсутствие строк или нулевой итог не доказывает отсутствие потребления.

Классы с положительным потреблением и нулевой рассчитанной стоимостью учитываются отдельно, если для периода действует тариф:

  • partial_tariff_unpriced_rows — общее количество, а unpriced_compute_classes и unpriced_storage_classes — разбивка по видам классов;
  • в CSV те же величины приходят в заголовках X-Unpriced-Classes, X-Unpriced-Compute-Classes и X-Unpriced-Storage-Classes.

Ненулевое значение может означать отсутствие цены класса или явно заданную нулевую цену. Периоды без действующего тарифа не формируют строки и не учитываются в этих счётчиках.

Таймаут каждого запроса к источнику метрик — 120 секунд, при этом один запрос API может выполнить несколько таких запросов, поэтому учитывайте это при настройке таймаутов клиента и прокси. Если источник метрик недоступен, API возвращает ошибку 503 в JSON с кодом error_code: prometheus_unavailable и без частичных данных. Повторите запрос позже, а при таймауте уменьшите период или сузьте выборку фильтрами.

Счета и финансовые операции

Лицевой счёт — то, на что относится потребление: он хранит баланс, привязанные рабочие пространства, держателей и историю пополнений и списаний. Удаление счёта архивирует его — привязки к рабочим пространствам и держателям снимаются, история транзакций сохраняется, а изменять архивный счёт и проводить по нему новые финансовые операции нельзя.

Пополнение и списание требуют заголовка Idempotency-Key. Ключ связан с неизменяемым ID сервисного аккаунта, поэтому ротация API-ключа не меняет область идемпотентности. Результат повтора зависит от тела запроса:

Запрос с заголовком Idempotency-Key Результат
Первый с этим ключом 201 и созданная транзакция
Повтор с тем же телом 200 и та же транзакция, второй раз ничего не проводится
Повтор с изменённым телом 409 idempotency_conflict

Два свойства методов счетов стоит учесть заранее:

  • ответ с транзакцией не содержит баланс счёта — при необходимости запросите счёт отдельно;
  • чтение одного счёта и первое списание по счёту обращаются к источнику метрик, поэтому при его недоступности оба возвращают 503.

Изменение и импорт тарифов

Тариф задаёт цену класса ресурсов начиная с указанной даты, а стоимость периода всегда считается по тарифам, действовавшим в этом периоде. Неизменяемых версий тарифа API не хранит, поэтому изменение или удаление тарифа меняет и результат повторного расчёта за период его действия. Чтобы изменить цены для будущего периода, создайте новый тариф с более поздним значением start_date, а не правьте действующий или исторический.

Импорт принимает JSON или CSV в UTF-8 и только создаёт тарифы — существующие он не изменяет. Точные поля и обязательные столбцы CSV перечислены в Swagger. Права для импорта нужны такие:

  • billingtariffs:create и billingresources:get — всегда;
  • billingresources:create — дополнительно, если указанного класса ещё нет.

Импорт идёт в два шага, и от того, на каком из них случилась ошибка, зависит состояние после неё:

Шаг Что делает Ошибка на этом шаге
Подготовка классов Создаёт недостающие вычислительные классы и классы хранилища Останавливает импорт до создания тарифов; уже созданные классы сохраняются
Создание тарифов Обрабатывает каждый тариф независимо Остальные тарифы обрабатываются; у каждого в ответе стоит created или error

Доступные методы рабочего пространства

Все методы работают с одним рабочим пространством: для сервисного аккаунта рабочего пространства — с его РП, для глобального — с РП из заголовка X-Commander-Workspace-ID. Методы, помеченные (архивируемые), принимают query-параметр archived=true, чтобы включить архивные сущности в ответ; по умолчанию они из ответа исключаются.

  1. Управление шаблонами кластеров:
    • GET /api/v1/cluster_templates (архивируемые);
    • POST /api/v1/cluster_templates;
    • GET /api/v1/cluster_templates/:id;
    • PUT /api/v1/cluster_templates/:id;
    • DELETE /api/v1/cluster_templates/:id;
    • DELETE /api/v1/cluster_templates/:id/permanent.
  2. Управление каталогами инвентаря:
    • GET /api/v1/catalogs (архивируемые);
    • POST /api/v1/catalogs;
    • GET /api/v1/catalogs/:id;
    • PUT /api/v1/catalogs/:id;
    • DELETE /api/v1/catalogs/:id.
  3. Управление записями каталогов:
    • POST /api/v1/records;
    • GET /api/v1/records (архивируемые);
    • GET /api/v1/records/:id;
    • PUT /api/v1/records/:id;
    • DELETE /api/v1/records/:id.
  4. Управление кластерами:
    • POST /api/v1/clusters;
    • GET /api/v1/clusters (архивируемые);
    • GET /api/v1/clusters/:id;
    • GET /api/v1/clusters/:id/status;
    • PUT /api/v1/clusters/:id;
    • DELETE /api/v1/clusters/:id;
    • DELETE /api/v1/clusters/:id/force;
    • DELETE /api/v1/clusters/:id/permanent.
  5. Управление хранилищами образов контейнеров:
    • POST /api/v1/registries;
    • GET /api/v1/registries;
    • GET /api/v1/registries/:id;
    • DELETE /api/v1/registries/:id.
  6. Управление проектами:
    • GET /api/v1/projects;
    • POST /api/v1/projects;
    • GET /api/v1/projects/:id;
    • PUT /api/v1/projects/:id;
    • DELETE /api/v1/projects/:id.
  7. Одобрение запросов на изменение кластера:
    • GET /api/v1/cluster_change_requests;
    • GET /api/v1/cluster_change_requests/:id;
    • POST /api/v1/cluster_change_requests/:id/approve.
  8. Чтение задач кластера и их логов:
    • GET /api/v1/cluster_tasks (архивируемые);
    • GET /api/v1/cluster_task_logs (архивируемые).

Исключение — запросы, созданные механизмом согласования создания кластеров: решения по ним принимаются только в веб-интерфейсе. Такой запрос помечен в ответе полем approval_flow: true, и POST /api/v1/cluster_change_requests/:id/approve возвращает для него 409 approval_flow_not_supported. Для подтверждения деструктивных изменений инфраструктуры эндпоинт /approve продолжает работать.

Ошибки

Ошибки приложения возвращаются в едином JSON-конверте:

{
  "error": "Человекочитаемое сообщение",
  "error_code": "invalid",
  "errors": { "name": ["can't be blank"] }
}

Опирайтесь в автоматизации на error_code для ошибок приложения — это стабильный контракт; сообщение error локализовано и может меняться. Ответы о превышении лимита могут формироваться ingress-контроллером до передачи запроса в приложение, поэтому для 429 Too Many Requests используйте HTTP-код и заголовок Retry-After, а не тело ответа. Используются стандартные HTTP-коды; полный список для каждого метода — в Swagger UI. Наиболее частые случаи:

  • 400 invalid_workspace_context — заголовок X-Commander-Workspace-ID отсутствует или некорректен там, где он нужен запросу, либо передан там, где он не допускается. См. Контекст рабочего пространства;
  • 401 no_token / invalid_token — заголовок X-Auth-Token отсутствует, либо его ключ отозван, просрочен или не распознан. Ключ также отклоняется после удаления его сервисного аккаунта;
  • 403 forbidden — у сервисного аккаунта нет прав на операцию либо запрос обращается к ресурсу вне доступного ему рабочего пространства;
  • 409 stale_object при PUT — ресурс изменён параллельным запросом; перечитайте его, слейте изменения и повторите запрос с новым current_revision;
  • 409 conflict на /cluster_change_requests/:id/approve (already_approved, already_completed, cluster_cannot_be_converged и аналогичные) — не повторяйте одобрение: перечитайте запрос на изменение и выберите дальнейшее действие;
  • 409 approval_flow_not_supported на /cluster_change_requests/:id/approve — запрос создан механизмом согласования создания кластеров (approval_flow: true), решение по нему принимается только в веб-интерфейсе; повторные попытки не помогут;
  • 422 invalid — ошибка валидации; детали по полям — в поле data;
  • 422 при создании кластера с registry_mode или изменении режима (direct_mode_unsupported, registry_mode_direct_prerequisites, registry_mode_switch_requires_direct, init_configuration_after_install, registry_mode_change_blocked, registry_mode_attached_without_template) — режим хранилища образов или переход недоступны в текущем состоянии кластера; сообщение error объясняет условие: «{Direct|Unmanaged} недоступен: разрешённая версия … (нужна 1.75+)», «Переход в режим Direct или Unmanaged (через ModuleConfig) доступен для кластеров на DKP 1.72+ с containerd v2», «При смене хранилища кластер переводится в режим Direct», «Режим «InitConfiguration (deprecated)» доступен только при создании кластера, до начала установки», «Режим хранилища образов можно изменить после подключения шаблона». Правила переходов — в разделе «Режим хранилища образов кластера» руководства пользователя;
  • 429 Too Many Requests — превышен лимит частоты запросов; перед повторной попыткой подождите количество секунд из заголовка ответа Retry-After. Тело ответа не является стабильным контрактом;
  • 500 internal_server_error — неожиданная ошибка бэкенда; безопасно повторять идемпотентные запросы с экспоненциальным backoff.

При опросе кластера учитывайте одновременно status и render_errors: ошибка рендеринга шаблона оставляет кластер в нетерминальном статусе, но заполняет render_errors.

Примеры

Во всех примерах предполагается, что заданы переменные окружения:

export COMMANDER_HOST="commander.example.com"
export COMMANDER_GLOBAL_TOKEN="..."  # API-ключ глобального сервисного аккаунта.
export COMMANDER_TOKEN="..."         # API-ключ сервисного аккаунта рабочего пространства.

Создание рабочего пространства

curl -X "POST" \
    "https://$COMMANDER_HOST/api/v1/workspaces" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_GLOBAL_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"name":"Рабочее пространство из API"}'

В ответ приходят данные рабочего пространства; id понадобится для последующих запросов.

{
    "id": "088b8e03-e7c0-4655-aae7-d11b7609c579",
    "slug": "sftpp",
    "name": "Рабочее пространство из API",
    "comment": null,
    "main": false,
    "current_revision": 0,
    "created_at": "2026-01-15T12:19:57.002+03:00",
    "updated_at": "2026-01-15T12:19:57.002+03:00"
}

Получение глобальной роли

Чтобы назначить глобальную роль пользователю или группе, получите id роли:

curl -s -X "GET" \
    "https://$COMMANDER_HOST/api/v1/global_roles" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_GLOBAL_TOKEN"

В ответе будут объекты ролей; нужное поле — id:

[
  {
    "id": "818c3814-4973-452e-9985-d75fbe35eb4c",
    "name": "commander-admin",
    "comment": null,
    "labels": {
      "commander.deckhouse.io/owned-by": "commander"
    },
    "current_revision": 0,
    "commander_rules": [
      {
        "id": "455ccf46-f34c-4c75-970d-4dbc361adc68",
        "verbs": ["*"],
        "resources": ["*"],
        "resource_names": null,
        "created_at": "2025-04-30T23:46:18.616+03:00",
        "updated_at": "2025-04-30T23:46:18.616+03:00"
      }
    ],
    "kubernetes_rules": [
      {
        "id": "9713527d-69df-4ecb-9db2-35df194e18b5",
        "verbs": ["*"],
        "resources": ["*"],
        "resource_names": null,
        "api_groups": ["*"],
        "created_at": "2025-06-06T16:56:32.208+03:00",
        "updated_at": "2025-06-06T16:56:32.208+03:00"
      }
    ],
    "created_at": "2025-04-30T23:46:18.567+03:00",
    "updated_at": "2025-04-30T23:46:18.567+03:00"
  }
]

Создание назначения

PAYLOAD="$(jq -nc '{
    "workspace_id": "088b8e03-e7c0-4655-aae7-d11b7609c579",
    "name": "workspace-admins",
    "comment": "Назначение из API",
    "role": {
        "type": "GlobalRole",
        "id": "818c3814-4973-452e-9985-d75fbe35eb4c"
    },
    "subjects": [
        {
            "kind": "User",
            "name": "commander-admin@deckhouse.io"
        }
    ]
}')"

curl -X "POST" \
    "https://$COMMANDER_HOST/api/v1/workspace_role_bindings" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_GLOBAL_TOKEN" \
    -H "Content-Type: application/json" \
    -d "$PAYLOAD"

Успешный ответ:

{
    "id": "05b3abdf-20a5-447b-823b-8e1a57b46158",
    "name": "workspace-admins",
    "comment": "Назначение из API",
    "current_revision": 0,
    "role_type": "GlobalRole",
    "role": {
        "id": "818c3814-4973-452e-9985-d75fbe35eb4c",
        "name": "commander-admin",
        "comment": null,
        "labels": {
            "commander.deckhouse.io/owned-by": "commander"
        },
        "current_revision": 0,
        "created_at": "2025-04-30T23:46:18.567+03:00",
        "updated_at": "2025-04-30T23:46:18.567+03:00"
    },
    "subjects": [
        {
            "kind": "User",
            "name": "commander-admin@deckhouse.io",
            "created_at": "2026-01-15T14:01:01.858+03:00",
            "updated_at": "2026-01-15T14:01:01.858+03:00"
        }
    ],
    "created_at": "2026-01-15T14:01:01.821+03:00",
    "updated_at": "2026-01-15T14:01:01.821+03:00"
}

Добавление группы пользователей в назначение

PUT /api/v1/workspace_role_bindings/:id целиком заменяет список subjects и требует current_revision для оптимистичной блокировки.

PAYLOAD="$(jq -nc '{
    "current_revision": 0,
    "subjects": [
        {
            "kind": "User",
            "name": "commander-admin@deckhouse.io"
        },
        {
            "kind": "Group",
            "name": "Administrators"
        }
    ]
}')"

curl -X "PUT" \
    "https://$COMMANDER_HOST/api/v1/workspace_role_bindings/05b3abdf-20a5-447b-823b-8e1a57b46158" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_GLOBAL_TOKEN" \
    -H "Content-Type: application/json" \
    -d "$PAYLOAD"

Получение актуальной версии шаблона

TEMPLATE_ID="fb999a72-efe7-4db7-af53-11b17bc0a687"

curl -s -X "GET" \
    "https://$COMMANDER_HOST/api/v1/cluster_templates/$TEMPLATE_ID?without_archived=true" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" |
    jq "del(.cluster_template_versions)"

Нужное поле — current_cluster_template_version_id:

{
  "id": "fb999a72-efe7-4db7-af53-11b17bc0a687",
  "name": "YC Dev",
  "current_cluster_template_version_id": "8e75210a-f05c-421d-84b3-fc0697814d6d",
  "comment": "Канал обновлений и версия K8s задаются параметрами",
  "current_revision": 12,
  "immutable": false,
  "created_at": "2024-02-05T17:35:44.318+03:00",
  "updated_at": "2024-04-10T18:00:57.835+03:00",
  "archived_at": null,
  "archive_number": null
}

Сохраним его для последующих шагов:

TEMPLATE_VERSION_ID="$(curl -s -X "GET" \
    "https://$COMMANDER_HOST/api/v1/cluster_templates/$TEMPLATE_ID?without_archived=true" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" |
    jq -r ".current_cluster_template_version_id")"

Поиск неиспользованной записи в каталоге

Посмотрим схему входных параметров выбранной версии шаблона:

curl -s -X "GET" \
    "https://$COMMANDER_HOST/api/v1/cluster_templates/$TEMPLATE_ID?without_archived=true" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" |
    jq -r --arg ver "$TEMPLATE_VERSION_ID" '
        .cluster_template_versions[]
        | select(.id == $ver)
        | .params'

Схема предусматривает три обязательных параметра; один из них — запись из каталога yandex-cloud-slot (поле catalog):

[
  { "header": "Параметры кластера" },
  {
    "key": "slot",
    "span": 4,
    "title": "Слот для кластера в Yandex Cloud",
    "catalog": "yandex-cloud-slot",
    "immutable": true
  },
  {
    "key": "releaseChannel",
    "enum": ["Alpha", "Beta", "EarlyAccess", "Stable", "RockSolid"],
    "span": 1,
    "title": "Канал обновлений",
    "default": "EarlyAccess"
  },
  {
    "key": "kubeVersion",
    "enum": ["Automatic", "1.25", "1.26", "1.27", "1.28", "1.29"],
    "span": 1,
    "title": "Версия Kubernetes",
    "default": "Automatic"
  }
]

Мультиселект с возможностью ввода произвольных значений требует type: "array" и items.suggestions в виде массива (может быть пустым). Значение поля — массив строк; перечисленные suggestions — опции в UI, допустимы и произвольные строки:

{
  "key": "kubeVersions",
  "type": "array",
  "title": "Версии Kubernetes",
  "items": {
    "type": "string",
    "suggestions": ["1.32", "1.33", "1.34"]
  }
}
{
  "key": "tags",
  "type": "array",
  "title": "Теги",
  "items": {
    "type": "string",
    "suggestions": []
  }
}

Строгий мультиселект задаётся через type: "array" и items.enum. Допустимы только перечисленные строки (скалярная строка отклоняется):

{
  "key": "harddrive",
  "type": "array",
  "title": "Выбор диска",
  "items": {
    "enum": ["sdk1", "sdk2"]
  }
}

Найдём каталог по его slug:

CATALOG_ID="$(curl -s -X "GET" \
    "https://$COMMANDER_HOST/api/v1/catalogs?without_archived=true" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" |
    jq -r '.[] | select(.slug == "yandex-cloud-slot") | .id')"

Возьмём первую запись из этого каталога, не занятую другим кластером (cluster_id == null), и подготовим её к использованию: в значения записи добавим специальное поле x-commander-record-id со ссылкой на ID записи:

SLOT_VALUES="$(curl -s -X "GET" \
    "https://$COMMANDER_HOST/api/v1/records?without_archived=true" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" |
    jq -c --arg cid "$CATALOG_ID" '
        [ .[]
          | select(.catalog_id == $cid and .cluster_id == null) ][0]
        | .values + { "x-commander-record-id": .id }')"

echo "$SLOT_VALUES"

Результат:

{
  "ip": "158.166.177.188",
  "name": "x",
  "x-commander-record-id": "5f6727e7-630c-4b18-bcf0-868ea96a27ee"
}

Имя ключа x-commander-record-id выбрано так, чтобы не конфликтовать с пользовательскими полями id, которые могут присутствовать в самих записях.

Создание кластера

PAYLOAD="$(jq -nc --argjson slot "$SLOT_VALUES" --arg ver "$TEMPLATE_VERSION_ID" '{
    "name": "Кластер из API",
    "cluster_template_version_id": $ver,
    "values": {
        "kubeVersion": "1.29",
        "releaseChannel": "EarlyAccess",
        "slot": $slot
    }
}')"

curl -X "POST" \
    "https://$COMMANDER_HOST/api/v1/clusters" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" \
    -H "Content-Type: application/json" \
    -d "$PAYLOAD"

В ответ придут данные кластера. Часть полей (отрендеренная конфигурация, timestamps) в примере ниже опущена для краткости:

{
    "id": "5436e6ef-d811-472f-9c9c-46cb9c6321d9",
    "name": "Кластер из API",
    "values": {
        "slot": {
            "ip": "158.166.177.188",
            "name": "x",
            "x-commander-record-id": "5f6727e7-630c-4b18-bcf0-868ea96a27ee"
        },
        "kubeVersion": "1.29",
        "releaseChannel": "EarlyAccess"
    },
    "cluster_template_version_id": "8e75210a-f05c-421d-84b3-fc0697814d6d",
    "was_created": false,
    "status": "new"
}

Изменение кластера

PUT /api/v1/clusters/:id заменяет параметры кластера целиком: поля, которых нет в запросе, сбрасываются. Поэтому соберите тело запроса из текущего состояния кластера и измените в нём нужное значение:

CLUSTER_ID="5436e6ef-d811-472f-9c9c-46cb9c6321d9"

PAYLOAD="$(curl -sS -X "GET" \
    "https://$COMMANDER_HOST/api/v1/clusters/$CLUSTER_ID" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" |
    jq -c '{name, current_revision, cluster_template_version_id, registry_id, registry_mode, values}
        | .values.releaseChannel = "Stable"')"

curl -X "PUT" \
    "https://$COMMANDER_HOST/api/v1/clusters/$CLUSTER_ID" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" \
    -H "Content-Type: application/json" \
    -d "$PAYLOAD"

Если между чтением и записью кластер изменился, PUT вернёт 409 stale_object: перечитайте кластер и повторите запрос с новым current_revision.

Создание managed-кластера

Kubeconfig целевого кластера передаётся в values как параметр шаблона. В предустановленном шаблоне «Managed Kubernetes» это kubeconfig. Здесь TEMPLATE_VERSION_ID — текущая версия этого шаблона (как её получить, описано в разделе «Получение актуальной версии шаблона»), а REGISTRY_ID — идентификатор хранилища образов из GET /api/v1/registries:

PAYLOAD="$(jq -nc --arg ver "$TEMPLATE_VERSION_ID" --arg registry "$REGISTRY_ID" \
    --arg kubeconfig "$(cat kubeconfig.yaml)" '{
    "name": "Managed-кластер из API",
    "cluster_template_version_id": $ver,
    "registry_id": $registry,
    "values": { "kubeconfig": $kubeconfig }
}')"

curl -X "POST" \
    "https://$COMMANDER_HOST/api/v1/clusters" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" \
    -H "Content-Type: application/json" \
    -d "$PAYLOAD"

Чтобы заменить kubeconfig, измените values.kubeconfig, как описано в разделе Изменение кластера.

Создание кластера с режимом хранилища образов

Параметр registry_mode задаёт режим хранилища образов кластера. Допустимые значения:

Значение Название в интерфейсе Конфигурация хранилища образов
Direct Direct ModuleConfig deckhouse, модуль registry в режиме Direct
ModuleUnmanaged Unmanaged ModuleConfig deckhouse, модуль registry в режиме Unmanaged
Manual Вручную (YAML) секцию хранилища образов описывает шаблон
Unmanaged InitConfiguration (deprecated) InitConfiguration; доступен только при создании кластера, до начала установки

Параметр нужен, когда автоматика не может определить версию DKP — например, для хранилища образов с dev-веткой, собранной на базе DKP ниже 1.75:

curl -X "POST" \
    "https://$COMMANDER_HOST/api/v1/clusters" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" \
    -H "Content-Type: application/json" \
    -d "{\"name\": \"legacy\", \"cluster_template_version_id\": \"$TEMPLATE_VERSION_ID\",
         \"registry_id\": \"$REGISTRY_ID\", \"registry_mode\": \"Unmanaged\"}"

В ответе поле registry_mode содержит выбранный режим (Direct, ModuleUnmanaged, Manual или Unmanaged), а поле registry_module_mode — фактический режим модуля registry в кластере по данным последней сверки (null, пока модуль его не сообщил, и у кластера без модуля — DKP до 1.72). Если параметр не передан, режим выбирается автоматически по тем же правилам, что и в веб-интерфейсе: если из хранилища образов разрешена версия 1.75 или выше — Direct, ниже 1.75 — Unmanaged (InitConfiguration); автоматика никогда не выбирает ModuleUnmanaged и Manual. Режимы Direct и ModuleUnmanaged отклоняются с кодом 422 (direct_mode_unsupported), если разрешённая версия ниже 1.75. Изменение режима через PUT /api/v1/clusters/:id проверяется по тем же правилам, что и в веб-интерфейсе; недопустимый переход отклоняется с кодом 422. При смене хранилища образов у установленного кластера кластер в Unmanaged (InitConfiguration) переводится в Direct, а кластер в Direct, ModuleUnmanaged или Manual остаётся в своём режиме. Переход установленного кластера в Direct или ModuleUnmanaged, в том числе при смене хранилища, требует DKP 1.72+ с containerd v2; иначе запрос отклоняется с кодом 422 (registry_mode_direct_prerequisites), а хранилище у такого кластера меняется только в режиме Manual. Переход в Unmanaged (InitConfiguration) после запуска установки невозможен. Режим присоединённого кластера без шаблона не меняется до подключения шаблона (registry_mode_attached_without_template).

Опрос статуса готовности кластера

После создания или обновления кластера нужно опрашивать GET /api/v1/clusters/:id до достижения статуса status: "in_sync". Учитывайте поле render_errors: непустой список означает, что шаблон не отрендерился и операция не продвинется, пока входные значения не будут исправлены через PUT /api/v1/clusters/:id.

CLUSTER_ID="5436e6ef-d811-472f-9c9c-46cb9c6321d9"
DEADLINE=$(( $(date +%s) + 60 * 60 ))  # таймаут 1 час

while : ; do
    RESP="$(curl -sS -X "GET" \
        "https://$COMMANDER_HOST/api/v1/clusters/$CLUSTER_ID" \
        -H "accept: application/json" \
        -H "X-Auth-Token: $COMMANDER_TOKEN")"

    STATUS="$(jq -r ".status" <<<"$RESP")"
    RENDER_ERRORS="$(jq -r ".render_errors // [] | length" <<<"$RESP")"

    echo "status=$STATUS render_errors=$RENDER_ERRORS"

    if [ "$STATUS" = "in_sync" ]; then
        echo "кластер готов"
        break
    fi

    if [ "$RENDER_ERRORS" != "0" ]; then
        echo "ошибка рендеринга шаблона, см. render_errors:"
        jq ".render_errors" <<<"$RESP"
        exit 1
    fi

    if [ "$(date +%s)" -gt "$DEADLINE" ]; then
        echo "таймаут ожидания in_sync"
        exit 1
    fi

    sleep 10
done

Типичная последовательность значений status: new → creating → in_sync. Застревание в creating вместе с непустым render_errors указывает на проблему с шаблоном или входными значениями; после обновления кластера через PUT опрос возобновится.

Пауза и возобновление кластера

Пауза останавливает сверку и изменения кластера со стороны Deckhouse Commander (см. раздел «Пауза обработки кластера» руководства пользователя):

curl -X "POST" "https://$COMMANDER_HOST/api/v1/clusters/$CLUSTER_ID/pause" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN"

В ответе — данные кластера с признаком паузы:

{
    "id": "5436e6ef-d811-472f-9c9c-46cb9c6321d9",
    "paused": true,
    "paused_at": "2026-09-26T10:00:00.000+03:00",
    "paused_by": "terraform"
}

Возобновление снимает паузу и сразу запускает сверку:

curl -X "POST" "https://$COMMANDER_HOST/api/v1/clusters/$CLUSTER_ID/resume" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN"

Обе операции идемпотентны: пауза приостановленного кластера и возобновление активного возвращают 200 и ничего не меняют. Пока кластер на паузе, PUT /api/v1/clusters/:id сохраняет конфигурацию без создания задач. Признак паузы (paused, paused_at, paused_by) есть и в ответе GET /api/v1/clusters/:id, и в облегчённом статусе. Выполняемая сверка или изменение прерывается паузой так же, как по «Остановить изменение». Ошибка 422 not_available — пауза недоступна (кластер не установлен, в архиве, удаляется или отсоединяется).

Получение облегчённого статуса кластера

Для частого опроса и дашбордов используйте GET /api/v1/clusters/:id/status. Эндпоинт возвращает компактный snapshot — текущий статус, активные алерты, сводку Upmeter и данные для подключения к master-узлу — и не содержит чувствительных полей, таких как отрендеренная конфигурация и API-ключ агента.

curl -s -X "GET" \
    "https://$COMMANDER_HOST/api/v1/clusters/$CLUSTER_ID/status" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" | jq .
{
  "id": "5436e6ef-d811-472f-9c9c-46cb9c6321d9",
  "status": "in_sync",
  "active_alerts": [
    { "name": "KubeletDown", "severity_level": 4 }
  ],
  "upmeter": {
    "status": "up",
    "components": [
      { "name": "control-plane", "status": "up" },
      { "name": "synthetic", "status": "up" }
    ]
  },
  "master_node": {
    "host": "203.0.113.10",
    "user": "ubuntu"
  }
}

active_alerts может быть пустым; upmeter и master_node могут быть null, пока агент кластера ещё не собрал данные.

Удаление кластера и ожидание его полного уничтожения

DELETE /api/v1/clusters/:id только инициирует удаление — в ответе возвращается кластер со статусом status: "delete" и флагом is_locked: true. Полное удаление подтверждается тем, что последующий GET возвращает 404 not_found.

CLUSTER_ID="5436e6ef-d811-472f-9c9c-46cb9c6321d9"

curl -sS -X "DELETE" \
    "https://$COMMANDER_HOST/api/v1/clusters/$CLUSTER_ID" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" | jq "{id, status, is_locked}"
{
  "id": "5436e6ef-d811-472f-9c9c-46cb9c6321d9",
  "status": "delete",
  "is_locked": true
}

Опрос до полного удаления:

DEADLINE=$(( $(date +%s) + 60 * 60 ))

while : ; do
    HTTP_CODE="$(curl -sS -o /dev/null -w "%{http_code}" -X "GET" \
        "https://$COMMANDER_HOST/api/v1/clusters/$CLUSTER_ID" \
        -H "accept: application/json" \
        -H "X-Auth-Token: $COMMANDER_TOKEN")"

    echo "http_code=$HTTP_CODE"

    if [ "$HTTP_CODE" = "404" ]; then
        echo "кластер полностью удалён"
        break
    fi

    if [ "$(date +%s)" -gt "$DEADLINE" ]; then
        echo "таймаут ожидания удаления"
        exit 1
    fi

    sleep 10
done

Если кластер заблокирован другой операцией (например, идёт converge), DELETE вернёт 409 Conflict:

{
  "error": "Cluster is locked",
  "error_code": "conflict",
  "errors": null
}

Повторите DELETE после завершения блокирующей операции или проверьте ожидающие запросы на изменение кластера через GET /api/v1/cluster_change_requests?cluster_id=$CLUSTER_ID и одобрите/отмените их.

Deckhouse Commander переносит managed-кластер в архив сразу, без задания на удаление: ответ на DELETE уже содержит status: "deleted", ждать завершения удаления не нужно. Сам кластер и установленный в нём Deckhouse при этом не удаляются.

Принудительное удаление кластера

Используйте DELETE /api/v1/clusters/:id/force, если инфраструктура уже уничтожена вне Deckhouse Commander либо кластер находится в состоянии, из которого обычный DELETE /api/v1/clusters/:id не может восстановиться. Принудительное удаление убирает запись кластера из Deckhouse Commander без запуска dhctl destroy и не затрагивает внешние ресурсы — очистите инфраструктуру самостоятельно.

curl -sS -X "DELETE" \
    "https://$COMMANDER_HOST/api/v1/clusters/$CLUSTER_ID/force" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" | jq "{id, status, is_locked}"

Для штатного удаления используйте DELETE /api/v1/clusters/:id; принудительное удаление оставьте как инструмент ручного восстановления

Безвозвратное удаление архивного кластера

Используйте DELETE /api/v1/clusters/:id/permanent, чтобы необратимо удалить кластер, который уже находится в архиве. При успехе метод возвращает 204 No Content. Для кластера не из архива возвращается 409 Conflict с error_code: not_archived.

curl -sS -X "DELETE" \
    "https://$COMMANDER_HOST/api/v1/clusters/$CLUSTER_ID/permanent" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" \
    -w "\n%{http_code}\n"

Безвозвратное удаление убирает запись кластера из Deckhouse Commander и не запускает dhctl destroy. История изменений кластера сохраняется.

Безвозвратное удаление архивного шаблона кластера

Используйте DELETE /api/v1/cluster_templates/:id/permanent, чтобы необратимо удалить шаблон кластера, который уже находится в архиве и на который не ссылается ни один кластер. При успехе метод возвращает 204 No Content.

curl -sS -X "DELETE" \
    "https://$COMMANDER_HOST/api/v1/cluster_templates/$CLUSTER_TEMPLATE_ID/permanent" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_TOKEN" \
    -w "\n%{http_code}\n"

Если шаблон не в архиве, API возвращает 409 Conflict с error_code: not_archived. Если на шаблон ещё ссылаются кластеры, API возвращает 409 Conflict с error_code: used_by_clusters.

Управление проектами

API позволяет получать список проектов, создавать, изменять и удалять проекты типа commander. Проекты типа deckhouse — это проекты DKP (ресурсы Project модуля multitenancy-manager), созданные непосредственно в прикладном кластере и не управляемые Deckhouse Commander; Deckhouse Commander их обнаруживает и отображает, но изменение и удаление таких проектов через API запрещено — попытка возвращает 403. Подробнее о типах проектов, статусах и коллизиях имён — раздел Проекты руководства пользователя.

Все примеры используют токен рабочего пространства (заголовок X-Auth-Token: $COMMANDER_TOKEN).

Получение списка проектов

curl -s -X 'GET' \
    "https://$COMMANDER_HOST/api/v1/projects" \
    -H 'accept: application/json' \
    -H "X-Auth-Token: $COMMANDER_TOKEN"

В ответе возвращается массив проектов рабочего пространства. Ниже приведён сокращённый пример (часть полей опущена для краткости):

[
  {
    "id": "1a0c4f1b-8f24-4d0a-9a21-0c2d7e1a2b3c",
    "name": "web-frontend",
    "kind": "commander",
    "description": "Публичный сайт команды",
    "resource_quota": {
      "requests": {
        "cpu": "2",
        "memory": "4Gi",
        "storage": "50Gi"
      }
    },
    "cumulative_status": "deployed",
    "status_data": {},
    "kubernetes_project_id": "b7c1d1d8-9d0e-4cbb-8d9e-2b77b6b9f9c1",
    "cluster_id": "5436e6ef-d811-472f-9c9c-46cb9c6321d9",
    "cluster_name": "prod-eu-1",
    "administrators": [
      { "kind": "User",  "name": "alice@example.com" },
      { "kind": "Group", "name": "platform-admins" }
    ],
    "error_messages": [],
    "created_at": "2026-04-10T12:00:00Z",
    "synced_at":  "2026-04-23T18:15:32Z"
  }
]

Ключевые поля ответа:

  • id — идентификатор проекта в Deckhouse Commander;
  • name — имя проекта (совпадает с именем namespace в прикладном кластере);
  • kind — тип проекта: commander или deckhouse;
  • cluster_id, cluster_name — прикладной кластер, в котором создан проект;
  • cumulative_status — итоговый статус проекта (Статусы проекта);
  • kubernetes_project_id — идентификатор объекта Project в прикладном кластере; заполняется после успешного применения агентом;
  • resource_quota — установленные ресурсные квоты проекта;
  • administrators — пользователи и группы, назначенные администраторами проекта;
  • error_messages — сообщения об ошибках применения проекта, если они есть.

Создание проекта

curl -s -X 'POST' \
    "https://$COMMANDER_HOST/api/v1/projects" \
    -H 'accept: application/json' \
    -H "X-Auth-Token: $COMMANDER_TOKEN" \
    -H 'Content-Type: application/json' \
    -d '{
  "name": "web-frontend",
  "cluster_id": "5436e6ef-d811-472f-9c9c-46cb9c6321d9",
  "description": "Публичный сайт команды",
  "resource_quota": {
    "requests": {
      "cpu": "2",
      "memory": "4Gi",
      "storage": "50Gi"
    }
  },
  "administrators": [
    { "kind": "User",  "name": "alice@example.com" },
    { "kind": "Group", "name": "platform-admins" }
  ]
}'

Параметры запроса:

  • name (обязательный) — имя проекта. Должно соответствовать правилам именования namespace в Kubernetes, зарезервированные системные имена (в частности, default и deckhouse) использовать нельзя, уникальность проверяется в пределах кластера.
  • cluster_id (обязательный) — идентификатор прикладного кластера. После создания проекта менять нельзя.
  • administrators (обязательный, минимум один элемент) — массив объектов {"kind": "User"|"Group", "name": "..."}. Значение kind принимает только User или Group.
  • description — произвольное описание проекта.
  • resource_quota — ресурсные квоты; поддерживаются ключи requests.cpu, requests.memory, requests.storage. Ограничения (limits) в текущей итерации не задаются.

В успешном ответе 201 Created возвращается тело объекта проекта, идентичное формату в получении списка. Сразу после создания cumulative_status обычно равен initialized и со временем переходит в synchronization, а затем в deployed.

При нарушении правил валидации возвращается 422 Unprocessable Entity, например при попытке сохранить проект без администраторов:

{
  "errors": {
    "administrators": ["В проекте должен быть назначен хотя бы один администратор"]
  }
}

Изменение проекта

curl -s -X 'PUT' \
    "https://$COMMANDER_HOST/api/v1/projects/1a0c4f1b-8f24-4d0a-9a21-0c2d7e1a2b3c" \
    -H 'accept: application/json' \
    -H "X-Auth-Token: $COMMANDER_TOKEN" \
    -H 'Content-Type: application/json' \
    -d '{
  "name": "web-frontend",
  "description": "Публичный сайт команды (прод)",
  "resource_quota": {
    "requests": {
      "cpu":     "4",
      "memory":  "8Gi",
      "storage": "100Gi"
    }
  },
  "administrators": [
    { "kind": "User",  "name": "alice@example.com" },
    { "kind": "User",  "name": "bob@example.com" },
    { "kind": "Group", "name": "platform-admins" }
  ]
}'

Тело запроса такое же, как при создании, за исключением поля cluster_id — изменить прикладной кластер у существующего проекта нельзя. В ответе возвращается обновлённый объект проекта.

Попытка изменить проект типа deckhouse возвращает 403 Forbidden.

Удаление проекта

Удаление проекта через API — необратимая операция. Вместе с проектом в прикладном кластере удаляются все созданные в нём ресурсы (namespace, рабочие нагрузки, конфигурации и секреты). Отменить удаление нельзя, и ни сам проект, ни его ресурсы восстановлению не подлежат. Перед выполнением DELETE-запроса убедитесь, что проект и все его данные больше не нужны — в интеграционном сценарии требуйте явное подтверждение намерения удалить проект на стороне вызывающей системы.

curl -s -X 'DELETE' \
    "https://$COMMANDER_HOST/api/v1/projects/1a0c4f1b-8f24-4d0a-9a21-0c2d7e1a2b3c" \
    -H 'accept: application/json' \
    -H "X-Auth-Token: $COMMANDER_TOKEN"

Deckhouse Commander снимает с проекта служебные лейблы и запрашивает у агента удаление ресурса Project в прикладном кластере. В ответе возвращается тело удаляемого проекта с обновлённым статусом.

Удаление проектов типа deckhouse через API недоступно и возвращает 403 Forbidden; такие проекты удаляются непосредственно в Deckhouse с помощью модуля multitenancy-manager.

Управление участниками проекта (ресурсы ProjectRoleBinding) и пользовательскими шаблонами проектов через API интеграции в текущем релизе не поддерживаются и появятся в следующих релизах.

Примеры биллинга

Все примеры используют глобальный токен (заголовок X-Auth-Token: $COMMANDER_GLOBAL_TOKEN) и не передают контекст рабочего пространства.

Получение детализации стоимости за период

Запрос возвращает стоимость CPU за 1 сентября 2026 года с группировкой по кластерам:

$ curl -s -G "https://$COMMANDER_HOST/api/v1/billing_costs" \
    --data-urlencode "from=2026-08-31T21:00:00Z" \
    --data-urlencode "to=2026-09-01T21:00:00Z" \
    --data-urlencode "resources[]=cpu" \
    --data-urlencode "group_by=cluster_id" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_GLOBAL_TOKEN"

Каждая строка содержит измеренный объём, цену из действовавшего тарифа и получившуюся стоимость:

{
  "from": "2026-08-31T21:00:00Z",
  "to": "2026-09-01T21:00:00Z",
  "group_by": "cluster_id",
  "entity_type": "pods",
  "total": 120.0,
  "partial_tariff_unpriced_rows": 0,
  "unpriced_compute_classes": 0,
  "unpriced_storage_classes": 0,
  "items": [
    {
      "from": "2026-08-31T21:00:00Z",
      "to": "2026-09-01T21:00:00Z",
      "resource": "cpu",
      "entity_type": "pod",
      "metrics_kind": "usage",
      "class_kind": "compute",
      "unit": "core*hour",
      "class_name": "standard",
      "quantity": 96.0,
      "price": 1.25,
      "cost": 120.0,
      "tariff": {
        "id": "8f2b1c4e-5a9d-4f13-8c6b-2d7e0a935114",
        "name": "Стандартный 2026",
        "start_date": "2026-01-01"
      },
      "group": {
        "cluster_id": "3c81a2f7-6b54-4e29-9d10-7a5f8c2b40e6"
      }
    }
  ]
}

Выгрузка детализации и постраничное чтение списка

Для выгрузки этой детализации в CSV добавьте response_format=csv:

$ curl -s -D - -o billing_costs.csv -G "https://$COMMANDER_HOST/api/v1/billing_costs" \
    --data-urlencode "from=2026-08-31T21:00:00Z" \
    --data-urlencode "to=2026-09-01T21:00:00Z" \
    --data-urlencode "resources[]=cpu" \
    --data-urlencode "response_format=csv" \
    -H "X-Auth-Token: $COMMANDER_GLOBAL_TOKEN"
HTTP/1.1 200 OK
Content-Type: text/csv; charset=utf-8
Content-Disposition: attachment; filename="billing_costs.csv"
X-Total: 120.0
X-Billing-From: 2026-08-31T21:00:00Z
X-Billing-To: 2026-09-01T21:00:00Z
X-Billing-Group-By: cluster_id
X-Billing-Entity-Type: pods
X-Unpriced-Classes: 0
X-Unpriced-Compute-Classes: 0
X-Unpriced-Storage-Classes: 0

Пример следующей страницы истории транзакций с одной записью:

$ curl -s -G "https://$COMMANDER_HOST/api/v1/billing_accounts/$ACCOUNT_ID/transactions" \
    --data-urlencode "per_page=1" \
    --data-urlencode "cursor=6d1f0b83-9c2a-4e57-b840-1f93c7de5a20" \
    -H "accept: application/json" \
    -H "X-Auth-Token: $COMMANDER_GLOBAL_TOKEN"
{
  "data": [
    {
      "id": "b47c9e02-3d18-4a6f-95be-0c2a71d84f35",
      "account_id": "5e90d7a1-8c34-4b26-a1df-93f70e6c2b58",
      "kind": "deduction",
      "source": "auto",
      "amount": "-181.44",
      "period_from": "2026-09-01T00:00:00+03:00",
      "period_to": "2026-09-02T00:00:00+03:00",
      "description": "Списание за сентябрь",
      "idempotency_key": null,
      "created_at": "2026-09-02T09:15:44.318+03:00"
    }
  ],
  "per_page": 1,
  "cursor": "b47c9e02-3d18-4a6f-95be-0c2a71d84f35",
  "total": 37
}

Пополнение счёта и безопасный повтор

Используйте уникальный идентификатор исходной операции в качестве Idempotency-Key, например номер счёта на оплату:

$ curl -s -X "POST" \
    "https://$COMMANDER_HOST/api/v1/billing_accounts/$ACCOUNT_ID/transactions" \
    -H "accept: application/json" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: invoice-2026-0917" \
    -H "X-Auth-Token: $COMMANDER_GLOBAL_TOKEN" \
    -d '{"kind": "top_up", "amount": "100.00", "description": "Оплата по счёту"}'

Ответ 201:

{
  "id": "1a6f2d70-4c85-4b19-9e33-8d02fb71c9a4",
  "account_id": "5e90d7a1-8c34-4b26-a1df-93f70e6c2b58",
  "kind": "top_up",
  "source": "integration",
  "amount": "100.0",
  "period_from": null,
  "period_to": null,
  "description": "Оплата по счёту",
  "idempotency_key": "invoice-2026-0917",
  "created_at": "2026-09-17T12:04:03.921+03:00"
}

Если изменить amount на 250.00, сохранив прежний ключ, API вернёт 409 idempotency_conflict.

Импорт тарифов

Права доступа и обработка ошибок описаны в разделе изменение и импорт тарифов. Пример создания тарифа из JSON:

$ curl -s -X "POST" \
    "https://$COMMANDER_HOST/api/v1/billing_tariffs/import" \
    -H "accept: application/json" \
    -H "Content-Type: application/json" \
    -H "X-Auth-Token: $COMMANDER_GLOBAL_TOKEN" \
    -d '{
          "tariffs": [
            {
              "name": "Стандартный 2027",
              "start_date": "2027-01-01",
              "aggregation": "average",
              "metrics_kind": "usage",
              "tariff_bindings": [
                {"label_kind": "compute", "label_name": "standard", "cpu_price": "1.25", "ram_price": "2.50"}
              ]
            }
          ]
        }'

Сокращённый пример успешного ответа (200):

[
  {
    "index": 0,
    "name": "Стандартный 2027",
    "status": "created",
    "tariff": {
      "id": "c0a4e18b-77d3-4f52-b6ac-9e1d3820f4a7",
      "name": "Стандартный 2027",
      "start_date": "2027-01-01"
    }
  }
]

В CSV строки с одинаковым tariff_key объединяются в один тариф с несколькими привязками. Сохраните следующий пример в tariffs.csv:

tariff_key,name,start_date,aggregation,metrics_kind,label_kind,label_name,cpu_price,ram_price,storage_price
standard-2027,Стандартный 2027,2027-01-01,average,usage,compute,standard,1.25,2.50,
standard-2027,Стандартный 2027,2027-01-01,average,usage,storage_class,fast-ssd,,,0.40
$ curl -s -X "POST" \
    "https://$COMMANDER_HOST/api/v1/billing_tariffs/import" \
    -H "accept: application/json" \
    -H "Content-Type: text/csv" \
    -H "X-Auth-Token: $COMMANDER_GLOBAL_TOKEN" \
    --data-binary @tariffs.csv