Как устроен Deckhouse Commander
Компоненты Deckhouse Commander
У Deckhouse Commander есть внешняя зависимость — база данных PostgreSQL.
Сервер API является центральным компонентом. Данные хранятся в PostgreSQL. Варианты установки Deckhouse Commander с СУБД указаны в разделе «Установка».
Сервер API предоставляет как внешние API — веб-приложения и для внешней интеграции - так и внутренние API для работы с кластерами.
Веб-приложение использует API для управления кластерами и другими сущностями Deckhouse Commander.
Для управления кластерами используются асинхронные операции — задания. Менеджер кластеров — это сервис, который отслеживает задания и выполняет их. Заданиями могут быть установка кластера, удаление кластера или сверка состояния кластера с заданной конфигурацией.
Менеджер кластеров отвечает за запуск установщиков — серверов dhctl. Менеджер кластеров запускает реплику сервера dhctl только нужной версии для каждого кластера Deckhouse Kubernetes Platform (DKP).
В каждом кластере Deckhouse Commander автоматически устанавливает модуль commander-agent. Этот
модуль отвечает за синхронизацию ресурсов Kubernetes в прикладном кластере, а так же за отправку
телеметрии в сервер API Deckhouse Commander. Телеметрия включает в себя основные метрики (общее
количество ядер, общий объем памяти, количество узлов и общий объем хранилищ), версию DKP, версию
Kubernetes и доступность компонентов DKP.
Требования к сетевой доступности компонентов
Компоненты Deckhouse Commander требуют сетевой доступности с прикладными кластерами. Эта доступность не нужна все время. Однако во время сетевого доступа происходит сравнение целевой и фактической конфигурации кластера, а так же только при сетевом доступе может быть запущено приведение кластера к целевому состоянию. Какой сетевой доступ требуется для полной работоспособности:
- 
22/TCP от Deckhouse Commander к прикладному кластеру
- менеджер кластеров (dhctl) подключается по SSH к мастер-узлу для первочной настройки или для уничтожения кластера.
 
 - 
443/TCP или 80/TCP от прикладного кластера к Deckhouse Commander
- 443 или 80 или другое значение — зависит от параметров ингресс-контроллера в управляющем
кластере и от параметра 
https.modeв управляющем кластере, который указывается в глобальных настройках или в настройках модуля Deckhouse Commander; - агент собирает конфигурацию, чтобы ее актуализировать, а так же отправляет телеметрию на сервер;
 - агент создает TCP-туннель, через который менеджер кластеров контролирует инфраструктурную часть конфигурации.
 
 - 443 или 80 или другое значение — зависит от параметров ингресс-контроллера в управляющем
кластере и от параметра 
 
По умолчанию Deckhouse Commander настраивает commander-agent на использование HTTPS. Чтобы изменить протокол на HTTP, нужно явно задать конфигурацию модуля commander-agent в шаблоне кластера:
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: commander-agent
spec:
  enabled: true
  version: 1
  settings:
    # http задан явно ↓
    commanderUrl: "http://{{ .dc_domain }}/agent_api/{{ .dc_clusterUUID }}"
Шифрование данных
Deckhouse Commander шифрует чувствительные данные в базе данных. Для шифрования используются ключи из
секрета commander-envs, которые генерируются автоматически при включении модуля.
Внимание: Крайне важно сохранить ключи в надежное место, чтобы иметь возможность восстановить базу данных в случае каких-либо проблем. При отсутствии ключей восстановить данные будет невозможно!
$ kubectl -n d8-commander get secret commander-envs -oyaml
apiVersion: v1
data:
  ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY: YVBBNVh5QUxoZjc1Tk5uTXphc3BXN2FrVGZacDBsUFk=
  ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT: eEVZMGR0NlRaY0FNZzUySzdPODR3WXpranZiQTYySHo=
  ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY: RUdZOFdodWxVT1hpeHlib2Q3Wld3TUlMNjhSOW81a0M=
kind: Secret
metadata:
...
  name: commander-envs
  namespace: d8-commander
type: Opaque
Сбор логов истории изменений
В Deckhouse Commander, начиная с версии 1.9, события из «Истории изменений» направляются в стандартный вывод и помечаются тегом ["audit"]. Сбор и отправку таких логов можно настроить при помощи модуля log-shipper
Пример логов:
{"level":"INFO","time":"2025-06-18 14:22:15 +0300","request_id":"ea09d409dc3c95dcf658fc2c2838084b","pid":19,"tags":["audit"],"auditable_type":"ClusterSettings","auditable_id":"8a0041ef-6c30-48bc-b3ca-e9db3e22be47","action":"create","user_type":"User","remote_address":"82.150.57.81","request_uuid":"ea09d409dc3c95dcf658fc2c2838084b","workspace_slug":"xcjtd","user_name":"admin@company.my","audited_changes":{"cluster_manager":{"sync":{"mode":"auto"},"check_interval":1}}}
{"level":"INFO","time":"2025-06-18 14:22:15 +0300","request_id":"ea09d409dc3c95dcf658fc2c2838084b","pid":19,"tags":["audit"],"auditable_type":"Cluster","auditable_id":"056f7fe5-7d22-4a76-b5e2-f225c0a99613","action":"create","user_type":"User","remote_address":"82.150.57.81","request_uuid":"ea09d409dc3c95dcf658fc2c2838084b","workspace_slug":"xcjtd","user_name":"admin@company.my","audited_changes":{"name":"mycluster","archived_at":null}}
{"level":"INFO","time":"2025-06-18 14:23:57 +0300","request_id":"a1eaf50bbc87a8cca4cd17d8be8fffdb","pid":12,"tags":["audit"],"auditable_type":"ClusterSettings","auditable_id":"707c46b1-b2c8-4fab-9392-8216a2058219","action":"create","user_type":"AuthToken","remote_address":"238.106.231.86","request_uuid":"a1eaf50bbc87a8cca4cd17d8be8fffdb","workspace_slug":"bfqcc","user_name":"api-user","audited_changes":{"cluster_manager":{"sync":{"mode":"auto"},"check_interval":1}}}
{"level":"INFO","time":"2025-06-18 14:23:57 +0300","request_id":"a1eaf50bbc87a8cca4cd17d8be8fffdb","pid":12,"tags":["audit"],"auditable_type":"Cluster","auditable_id":"42d432aa-8250-4ef0-b260-51639e1445d0","action":"create","user_type":"AuthToken","remote_address":"238.106.231.86","request_uuid":"a1eaf50bbc87a8cca4cd17d8be8fffdb","workspace_slug":"bfqcc","user_name":"api-user","audited_changes":{"name":"15731486914-1-con-1-30","archived_at":null}}
{"level":"INFO","time":"2025-06-18 14:28:56 +0300","request_id":"069566a46c004e53b686189587d484a9","pid":19,"tags":["audit"],"auditable_type":"ClusterSettings","auditable_id":"402a4d4d-5c14-4466-a1f3-3d990d7cf35a","action":"create","user_type":"User","remote_address":"30.231.184.26","request_uuid":"069566a46c004e53b686189587d484a9","workspace_slug":"xcjtd","user_name":"user@company.my","audited_changes":{"cluster_manager":{"sync":{"mode":"auto"},"check_interval":1}}}
{"level":"INFO","time":"2025-06-18 14:28:56 +0300","request_id":"069566a46c004e53b686189587d484a9","pid":19,"tags":["audit"],"auditable_type":"Cluster","auditable_id":"9ee687d4-18fe-423c-bbaa-e8e46ea47e67","action":"create","user_type":"User","remote_address":"30.231.184.26","request_uuid":"069566a46c004e53b686189587d484a9","workspace_slug":"xcjtd","user_name":"user@company.my","audited_changes":{"name":"mycluster2","archived_at":null}}
{"level":"INFO","time":"2025-06-18 14:29:06 +0300","request_id":"d29b248fbce414db8b71f821a3b1886e","pid":12,"tags":["audit"],"auditable_type":"Cluster","auditable_id":"e0f3c3de-2129-4b75-b927-72a8eb26902b","action":"update","user_type":"User","remote_address":"30.231.184.26","request_uuid":"d29b248fbce414db8b71f821a3b1886e","workspace_slug":"xcjtd","user_name":"user@company.my","audited_changes":{"archived_at":[null,"2025-06-18T14:29:05.943+03:00"]}}
Пример конфигурации:
apiVersion: deckhouse.io/v1alpha2
kind: ClusterLoggingConfig
metadata:
  name: commander-audit-logs
spec:
  destinationRefs:
  - loki-example
  kubernetesPods:
    labelSelector:
      matchLabels:
        app: backend
    namespaceSelector:
      labelSelector:
        matchLabels:
          kubernetes.io/metadata.name: d8-commander
  labelFilter:
  - field: message
    operator: Regex
    values:
    - .*\[\"audit\"\].*
  type: KubernetesPods
---
apiVersion: deckhouse.io/v1alpha1
kind: ClusterLogDestination
metadata:
  name: loki-example
spec:
  type: Loki
  loki:
    endpoint: http://loki-example.loki.svc:3100
Более подробную информацию по настройке смотрите в документации модуля log-shipper.
Изменение класса хранилища для БД (StorageClass)
Эта инструкция нужна только в том случае, если для СУБД используется модуль operator-postgres. На
это указывает режим подключения к БД Internal в ModuleConfig/commander
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: commander
spec:
  enabled: true
  version: 1
  settings:
    postgres:
      mode: Internal
    ...
Вариант 1 (предпочтительный)
- 
Выполняем резервное копирование экземпляра БД
kubectl -n d8-commander exec -t commander-postgres-0 -- su - postgres -c "pg_dump -Fc -b -v -d commander" > commander.dump - 
Изменяем storageClass в настройках модуля, заменив
<NEW_STORAGECLASS_NAME>на название необходимого класса храненияСписок доступных классов хранения можно узнать при помощи команды
kubectl get storageclasseskubectl patch moduleconfig commander --type=merge -p '{"spec":{"settings":{"postgres":{"internal":{"storageClass":"<NEW_STORAGECLASS_NAME>"}}}}}' moduleconfig.deckhouse.io/commander patchedДожидаемся, пока очередь deckhouse окажется пустой
kubectl -n d8-system exec svc/deckhouse-leader -c deckhouse -- deckhouse-controller queue main Queue 'main': length 0, status: 'waiting for task 5s'Проверяем логи оператора postgres на отсутствие ошибок
kubectl -n d8-operator-postgres logs deployments/operator-postgres {"cluster-name":"d8-commander/commander-postgres","level":"info","msg":"cluster has been updated","pkg":"controller","time":"2024-05-19T20:36:22Z","worker":0} - 
Увеличиваем количество реплик БД PostgreSQL (опционально)
Этот шаг необходимо пропустить, если в кластере активен режим HighAvailability и PostgreSQL имеет 2 реплики
kubectl -n d8-commander patch postgresqls.acid.zalan.do commander-postgres --type=merge -p '{"spec":{"numberOfInstances":2}}' postgresql.acid.zalan.do/commander-postgres patchedПроверяем логи оператора и экземпляра postgres
kubectl -n d8-operator-postgres logs deployments/operator-postgres {"cluster-name":"d8-commander/commander-postgres","level":"info","msg":"cluster has been updated","pkg":"controller","time":"2024-05-19T20:36:22Z","worker":0}kubectl -n d8-commander logs commander-postgres-1 2024-05-19 20:38:15,648 INFO: no action. I am (commander-postgres-1), a secondary, and following a leader (commander-postgres-0) - 
Выполняем переключение мастера
kubectl -n d8-commander exec -it commander-postgres-0 -- patronictl failover Current cluster topology + Cluster: commander-postgres --------+---------+---------+----+-----------+ | Member | Host | Role | State | TL | Lag in MB | +----------------------+--------------+---------+---------+----+-----------+ | commander-postgres-0 | 10.111.3.167 | Leader | running | 5 | | | commander-postgres-1 | 10.111.2.239 | Replica | running | 5 | 0 | +----------------------+--------------+---------+---------+----+-----------+ Candidate ['commander-postgres-1'] []: commander-postgres-1 Are you sure you want to failover cluster commander-postgres, demoting current leader commander-postgres-0? [y/N]: y 2024-05-19 20:40:52.63041 Successfully failed over to "commander-postgres-1" + Cluster: commander-postgres --------+---------+---------+----+-----------+ | Member | Host | Role | State | TL | Lag in MB | +----------------------+--------------+---------+---------+----+-----------+ | commander-postgres-0 | 10.111.3.167 | Replica | stopped | | unknown | | commander-postgres-1 | 10.111.2.239 | Leader | running | 5 | | +----------------------+--------------+---------+---------+----+-----------+Убеждаемся, что оба экземпляра БД находятся в состоянии
runningkubectl -n d8-commander exec -t commander-postgres-0 -- patronictl list + Cluster: commander-postgres --------+---------+---------+----+-----------+ | Member | Host | Role | State | TL | Lag in MB | +----------------------+--------------+---------+---------+----+-----------+ | commander-postgres-0 | 10.111.3.167 | Replica | running | 6 | 0 | | commander-postgres-1 | 10.111.2.239 | Leader | running | 6 | | +----------------------+--------------+---------+---------+----+-----------+Проверяем, что диск новой реплики БД создался с необходимым
storageClasskubectl -n d8-commander get pvc --selector application=spilo NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pgdata-commander-postgres-0 Bound pvc-fd80fde4-d0e2-4b5f-9e3a-eac998191f11 2Gi RWO network-hdd 36h pgdata-commander-postgres-1 Bound pvc-7af2f442-3097-4fe3-a795-5ad18bb11351 2Gi RWO network-ssd 2m54s - 
Удаляем диск и под первого экземпляра
kubectl -n d8-commander delete pvc pgdata-commander-postgres-0 --wait=false kubectl -n d8-commander delete po commander-postgres-0Проверяем логи
kubectl -n d8-commander logs commander-postgres-0 2024-05-19 20:43:33,293 INFO: Lock owner: commander-postgres-1; I am commander-postgres-0 2024-05-19 20:43:33,293 INFO: establishing a new patroni connection to the postgres cluster 2024-05-19 20:43:33,357 INFO: no action. I am (commander-postgres-0), a secondary, and following a leader (commander-postgres-1)Проверяем, что диск создался с правильным
storageClasskubectl -n d8-commander get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pgdata-commander-postgres-0 Bound pvc-fd80fde4-d0e2-4b5f-9e3a-eac998191f11 2Gi RWO network-ssd 2m6s pgdata-commander-postgres-1 Bound pvc-7af2f442-3097-4fe3-a795-5ad18bb11351 2Gi RWO network-ssd 7m11s - 
Выполняем обратное переключение мастера
kubectl -n d8-commander exec -it commander-postgres-0 -- patronictl failover Current cluster topology + Cluster: commander-postgres --------+---------+---------+----+-----------+ | Member | Host | Role | State | TL | Lag in MB | +----------------------+--------------+---------+---------+----+-----------+ | commander-postgres-0 | 10.111.3.189 | Replica | running | 6 | 0 | | commander-postgres-1 | 10.111.2.239 | Leader | running | 6 | | +----------------------+--------------+---------+---------+----+-----------+ Candidate ['commander-postgres-0'] []: commander-postgres-0 Are you sure you want to failover cluster commander-postgres, demoting current leader commander-postgres-1? [y/N]: y 2024-05-19 20:46:11.69855 Successfully failed over to "commander-postgres-0" + Cluster: commander-postgres --------+---------+---------+----+-----------+ | Member | Host | Role | State | TL | Lag in MB | +----------------------+--------------+---------+---------+----+-----------+ | commander-postgres-0 | 10.111.3.189 | Leader | running | 6 | | | commander-postgres-1 | 10.111.2.239 | Replica | stopped | | unknown | +----------------------+--------------+---------+---------+----+-----------+Убеждаемся, что оба экземпляра БД находятся в состоянии
runningkubectl -n d8-commander exec -t commander-postgres-0 -- patronictl list + Cluster: commander-postgres --------+---------+---------+----+-----------+ | Member | Host | Role | State | TL | Lag in MB | +----------------------+--------------+---------+---------+----+-----------+ | commander-postgres-0 | 10.111.3.189 | Leader | running | 6 | 0 | | commander-postgres-1 | 10.111.2.239 | Replica | running | 6 | | +----------------------+--------------+---------+---------+----+-----------+ - 
Уменьшаем количество реплик БД PostgreSQL (опционально)
Этот шаг необходимо пропустить, если в кластере активен режим HighAvailability и PostgreSQL имеет 2 реплики
kubectl -n d8-commander patch postgresqls.acid.zalan.do commander-postgres --type=merge -p '{"spec":{"numberOfInstances":1}}' postgresql.acid.zalan.do/commander-postgres patchedПроверяем логи оператора
kubectl -n d8-operator-postgres logs deployments/operator-postgres {"cluster-name":"d8-commander/commander-postgres","level":"info","msg":"cluster has been updated","pkg":"controller","time":"2024-05-19T20:50:22Z","worker":0} - 
Удаляем диски
 
- 
Удаляем диск и под первого экземпляра (если в кластере активен режим HighAvailability и PostgreSQL имеет 2 реплики)
Этот шаг необходимо пропустить, если в кластере не используется режим HighAvailability
kubectl -n d8-commander delete pvc pgdata-commander-postgres-1 --wait=false kubectl -n d8-commander delete po commander-postgres-1Проверяем логи
kubectl -n d8-commander logs commander-postgres-1 2024-05-19 20:53:33,293 INFO: Lock owner: commander-postgres-0; I am commander-postgres-1 2024-05-19 20:53:33,293 INFO: establishing a new patroni connection to the postgres cluster 2024-05-19 20:53:33,357 INFO: no action. I am (commander-postgres-1), a secondary, and following a leader (commander-postgres-0)Проверяем, что диск создался с правильным
storageClasskubectl -n d8-commander get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pgdata-commander-postgres-0 Bound pvc-fd80fde4-d0e2-4b5f-9e3a-eac998191f11 2Gi RWO network-ssd 7m6s pgdata-commander-postgres-1 Bound pvc-7af2f442-3097-4fe3-a795-5ad18bb11351 2Gi RWO network-ssd 1m11sУбеждаемся, что оба экземпляра БД находятся в состоянии
runningkubectl -n d8-commander exec -t commander-postgres-0 -- patronictl list + Cluster: commander-postgres --------+---------+---------+----+-----------+ | Member | Host | Role | State | TL | Lag in MB | +----------------------+--------------+---------+---------+----+-----------+ | commander-postgres-0 | 10.111.3.189 | Leader | running | 6 | 0 | | commander-postgres-1 | 10.111.2.239 | Replica | running | 6 | | +----------------------+--------------+---------+---------+----+-----------+ - 
Удаляем неиспользуемый диск, оставшийся от временной реплики БД (если режим HighAvailability не используется)
Этот шаг необходимо пропустить, если в кластере активен режим HighAvailability и PostgreSQL имеет 2 реплики
kubectl -n d8-commander delete pvc pgdata-commander-postgres-1 persistentvolumeclaim "pgdata-commander-postgres-1" deleted 
Вариант 2
- 
Выполняем резервное копирование экземпляра БД
kubectl -n d8-commander exec -t commander-postgres-0 -- su - postgres -c "pg_dump -Fc -b -v -d commander" > commander.dump - 
Выключаем модуль
commanderkubectl patch moduleconfig commander --type=merge -p '{"spec":{"enabled":false}}' moduleconfig.deckhouse.io/commander patchedДожидаемся, пока очередь deckhouse окажется пустой
kubectl -n d8-system exec svc/deckhouse-leader -c deckhouse -- deckhouse-controller queue main Queue 'main': length 0, status: 'waiting for task 5s'Проверяем, что неймспейс
d8-commanderудаленkubectl get namespace d8-commander Error from server (NotFound): namespaces "d8-commander" not found - 
Указываем необходимый класс хранилища и включаем модуль
commanderkubectl patch moduleconfig commander --type=merge -p '{"spec":{"enabled":true,"settings":{"postgres":{"internal":{"storageClass":"<NEW_STORAGECLASS_NAME>"}}}}}' moduleconfig.deckhouse.io/commander patchedДожидаемся, пока очередь deckhouse окажется пустой
kubectl -n d8-system exec svc/deckhouse-leader -c deckhouse -- deckhouse-controller queue main Queue 'main': length 0, status: 'waiting for task 5s'Проверяем, что экземпляр БД в статусе
Runningkubectl -n d8-commander get po commander-postgres-0 NAME READY STATUS RESTARTS AGE commander-postgres-0 1/1 Running 0 2m4s - 
Восстанавливаем ранее сохраненную резервную копию БД
kubectl -n d8-commander exec -it commander-postgres-0 -- su - postgres -c "pg_restore -v -c --if-exists -Fc -d commander" < commander.dump