Всем привет! Меня зовут Валерий Хорунжин, я инженер архитектурных решений в команде Deckhouse компании «Флант». И я поставил себе виртуализацию.

Начало моего пути к виртуализации связано с использованием Obsidian, у которого не было встроенной синхронизации. У меня есть небольшой арендованный VPS, но я столкнулся с тем, что запуск готового Docker-контейнера сильно нагружал процессор сервера, из-за чего даже подключение по SSH становилось затруднительным.

В поисках решения я понял, что хочу не просто хостить одно приложение, а легко переносить и масштабировать всю инфраструктуру с помощью декларативной конфигурации. Перспектива создания собственного Kubernetes-кластера выглядела очень заманчиво. 

Однако контейнеры, несмотря на свои преимущества, могут добавлять риски безопасности. Если одно из приложений будет скомпрометировано, злоумышленник может получить доступ к контейнерам и дальше, что особенно опасно, когда я не слежу за кластером.

Поэтому я пришёл к выводу, что мне необходима полноценная виртуализация. Для реализации я выбрал Open Source-платформу Deckhouse Virtualization Platform Community Edition (DVP CE). Она работает на базе Deckhouse Kubernetes Platform. Чтобы не путаться в похожих названиях, K8s-платформу и её модули далее по тексту буду называть просто Deckhouse.

В этой статье я покажу, как развернуть домашний кластер виртуализации на базе DVP CE: от выбора и подготовки оборудования до настройки сети, установки платформы и первых шагов по работе с виртуальными машинами и хранилищем данных.

Содержание:

Для тех, кто ничего не знает о Kubernetes

Нажмите, чтобы узнать подробнее о Kubernetes

Если вы никогда не использовали Kubernetes, то вот вам краткая справка, которая позволит легче усвоить материал статьи. Учитывайте, что в силу краткости здесь могут быть неточности. Также рекомендуем прочитать статью из блога Deckhouse «Кубернетес для бабушки».

Kubernetes — это система оркестрации кластеров серверов для запуска подов. В переводе на человеческий — это система, которая позволяет управлять ресурсами множества серверов без возникновения болезни, связанной с излишним загустением крови в области чуть ниже спины. 

Kubernetes позволяет управлять кластером декларативно, то есть мы описываем желаемое состояние, а внутренние механизмы приводят к нему за своими слоями абстракции вне поля нашего зрения. 

Взаимодействие с Kubernetes происходит через HTTP API. Стандартным клиентом для управления Kubernetes является kubectl, его мы и будем использовать в статье. При помощи kubectl мы управляем сущностями, которые и являются декларативным описанием того, что мы хотим получить от кластера. Основной язык описания сущностей — это YAML.

Сущности в Kubernetes могут располагаться в разных пространствах имён (namespaces), для указания конкретного namespace используется флаг kubectl -n. Базовыми операциями, применяемыми из kubectl, являются apply (применить) и delete (удалить), флаг -f позволяет указать этим командам файл с входными данными.

Основные сущности, встречающиеся в статье:

  • Контейнер — «песочница» запуска приложений со своей файловой системой, списком процессов, сетевым окружением и набором других слоёв изоляции от хост-системы.

  • Pod — базовая единица запуска контейнеров в Kubernetes, которая содержит один или несколько контейнеров.

  • Deployment (deploy) — один или несколько инстансов pod.

  • Service (svc) — объект, который создаёт постоянную сетевую точку доступа к группе подов внутри кластера.

  • Ingress — объект, который обеспечивает внешний доступ к сервисам внутри кластера, маршрутизируя HTTP- и HTTPS-трафик.

  • PersistentVolumeClaim (PVC) — сущность хранения данных (упрощение).

  • StorageClass (SC) — набор параметров, определяющих, какое физическое хранилище будет использоваться для физического размещения PVC, дисков и так далее.

  • Node (узел) — сервер в кластере.

  • Master node (master-узел, мастер-узел) — узел, на котором располагаются системные компоненты кластера (в том числе API кластера).

  • Worker node (worker-узел, воркер-узел) — узел, где размещаются пользовательские сущности (поды, виртуальные машины и так далее).

  • VirtualImage (vi) — виртуальный образ диска.

  • VirtualDisk (vd) — виртуальный диск.

  • VirtualMachine (vm) — виртуальная машина (ВМ).

  • Project (проект) — проект для запуска ВМ, представляет собой отдельное адресное пространство имён, набор квот по ресурсам (процессор, память, дисковое пространство), сетевые политики, и позволяет разграничить доступ для пользователей.

Подробнее о компонентах Kubernetes можно почитать в официальной документации.

Если вы хотите получше узнать о том, как концепции виртуализации в контейнерах соотносятся с виртуализацией VMware, прочитайте статью о KubeVirt — виртуализации виртуальных машин через Kubernetes.

Подготовка домашнего кластера

Параметры физических серверов

Для домашнего кластера я купил три мини-ПК (11 000 рублей за каждый) и гигабитный свитч (1 500 рублей) для связи между ними. В итоге всего заплатил 34 500 рублей.

Контейнеры в контейнере ?
Контейнеры в контейнере ?

Характеристики ПК:

  • Процессор: Intel N150 (4 ядра, 4 потока, 3.6 ГГц)

  • ОЗУ: 16 ГБ

  • Хранилище: 500 ГБ SSD

Предварительная настройка кластера 

Чтобы развернуть кластер, необходимо провести ряд предварительных шагов для настройки сети DNS и SSH-доступа.

На кластер будет установлена платформа DVP CE.

Узлы кластера должны обладать статическими IP-адресами в подсети, в которой они взаимодействуют друг с другом. Кластер будет использовать схему «1 master- и 2 worker-узла». В качестве хранилища используем модуль Deckhouse — sds-replicated-volume.

Концептуальная схема кластера представлена на рисунке:

Для тестирования работы кластера нужно прописать DNS-записи. Я выполняю установку с ОС Windows, поэтому в моём случае нужно прописать в файл hosts следующие домены с адресом master-узла:

api.homecluster.ru
argocd.homecluster.ru
dashboard.homecluster.ru
documentation.homecluster.ru
dex.homecluster.ru
grafana.homecluster.ru
hubble.homecluster.ru
istio.homecluster.ru
istio-api-proxy.homecluster.ru
kubeconfig.homecluster.ru
openvpn-admin.homecluster.ru
prometheus.homecluster.ru
status.homecluster.ru
upmeter.homecluster.ru

Также нужно создать SSH-ключ на компьютере, с которого будет ставиться кластер через ssh-keygen, и прописать его в authorized_keys на master-узле.   

Установка ОС

В качестве ОС для серверов кластера я использовал Ubuntu 24.04, которую нужно установить на каждый сервер. Не буду надолго останавливаться на этом шаге, но отмечу пару моментов:

  • При установке рекомендую сразу настроить статичный IP для сервера, чтобы потом не писать настройки руками. После установки конфиги будут находиться в /etc/netplan. В моём случае сервер использует два сетевых интерфейса: Wi-Fi для подключения к интернету и гигабитный свитч для связи между серверами.

Здесь я выставляю настройки сети
Здесь я выставляю настройки сети
  • Для распределённого хранилища нам понадобятся блочные устройства, ими могут выступать неразмеченные устройства, либо неотформатированные, но созданные разделы. В моём случае разделы разбиты следующим образом:

    • /boot - 1gb

    • / - ext4, 100gb

На остальном месте мы создаём раздел, у которого выбираем опцию leave unformatted, что позволит нам использовать его для распределённого хранилища sds-replicated-volume.

Развёртывание кластера

Развёртывание master-узла

Устанавливать платформу я буду с Windows. Для этого потребуется обеспечить доступ по SSH к master-узлу с использованием ключа.

Далее на втором шаге руководства homecluster по быстрому старту DVP нужно ввести шаблон доменных имён нашего кластера. У меня это %s.homecluster.ru:

После чего жмём «Далее: установка платформы» и получаем готовый конфиг с подставленным шаблоном доменных имён. 

Осталось только скопировать и изменить параметр internalNetworkCIDRs, где указываем подсеть, в которой находится кластер. Это необходимо в случае, если на наших серверах используется более одного сетевого интерфейса. У меня это 10.0.4.0/24, подсеть ethernet-соединения.

Обратите внимание на параметры, в которых прописаны подсети, резервируемые под нужды кластера. У вас не должно быть пересечения с другими сетями серверов. Если они есть, смените либо внешние настройки, либо данные параметры.

Конфиг получился таким, сохраним его в файл config.yml (нажмите, чтобы посмотреть)
# Общие параметры кластера.
# https://deckhouse.ru/products/virtualization-platform/reference/cr/clusterconfiguration.html
apiVersion: deckhouse.io/v1
kind: ClusterConfiguration
clusterType: Static
# Адресное пространство подов кластера.
podSubnetCIDR: 10.111.0.0/16
# Адресное пространство сети сервисов кластера.
serviceSubnetCIDR: 10.222.0.0/16
kubernetesVersion: "Automatic"
# Домен кластера.
clusterDomain: "cluster.local"
---
# Настройки первичной инициализации кластера Deckhouse.
# https://deckhouse.ru/products/virtualization-platform/reference/cr/initconfiguration.html
apiVersion: deckhouse.io/v1
kind: InitConfiguration
deckhouse:
  # Адрес Docker registry с образами Deckhouse
  imagesRepo: registry.deckhouse.ru/deckhouse/ce
  # Строка с ключом для доступа к Docker registry (сгенерировано автоматически для вашего токена доступа)
  registryDockerCfg: eyJhdXRocyI6IHsgInJlZ2lzdHJ5LmRlY2tob3VzZS5ydSI6IHt9fX0K
---
# Настройки модуля deckhouse.
# https://deckhouse.ru/products/virtualization-platform/documentation/v1/modules/deckhouse/configuration.html
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: deckhouse
spec:
  version: 1
  enabled: true
  settings:
    bundle: Default
    releaseChannel: EarlyAccess
    logLevel: Info
---
# Глобальные настройки Deckhouse.
# https://deckhouse.ru/products/virtualization-platform/documentation/v1/deckhouse-configure-global.html#%D0%BF%D0%B0%D1%80%D0%B0%D0%BC%D0%B5%D1%82%D1%80%D1%8B
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: global
spec:
  version: 2
  settings:
    modules:
      # Шаблон, который будет использоваться для составления адресов системных приложений в кластере.
      # Например, Grafana для %s.homecluster.ru будет доступна на домене 'grafana.homecluster.ru'.
      # Домен НЕ ДОЛЖЕН совпадать с указанным в параметре clusterDomain ресурса ClusterConfiguration.
      # Можете изменить на свой сразу, либо следовать шагам руководства и сменить его после установки.
      publicDomainTemplate: "%s.homecluster.ru"
---
# Настройки модуля user-authn.
# https://deckhouse.ru/products/virtualization-platform/documentation/v1/modules/user-authn/configuration.html
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: user-authn
spec:
  version: 2
  enabled: true
  settings:
    controlPlaneConfigurator:
      dexCAMode: DoNotNeed
    # Включение доступа к API-серверу Kubernetes через Ingress.
    # https://deckhouse.ru/products/virtualization-platform/documentation/v1/modules/user-authn/configuration.html#parameters-publishapi
    publishAPI:
      enabled: true
      https:
        mode: Global
        global:
          kubeconfigGeneratorMasterCA: ""
---
# Настройки модуля cni-cilium.
# https://deckhouse.io/products/virtualization-platform/reference/mc.html#cni-cilium
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: cni-cilium
spec:
  version: 1
  # Включить модуль cni-cilium
  enabled: true
  settings:
    tunnelMode: VXLAN
---
# Настройки модуля admission-policy-engine
# https://deckhouse.io/products/virtualization-platform/reference/mc.html#admission-policy-engine
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: admission-policy-engine
spec:
  enabled: true
  version: 1
---
# Настройки модуля multitenancy-manager
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: multitenancy-manager
spec:
  enabled: true
  version: 1
---
# Параметры статического кластера.
# https://deckhouse.ru/products/virtualization-platform/reference/cr/staticclusterconfiguration.html
apiVersion: deckhouse.io/v1
kind: StaticClusterConfiguration
# Список внутренних сетей узлов кластера (например, '10.0.4.0/24'), который
# используется для связи компонентов Kubernetes (kube-apiserver, kubelet...) между собой.
# Укажите, если используете модуль virtualization или узлы кластера имеют более одного сетевого интерфейса.
# Если на узлах кластера используется только один интерфейс, ресурс StaticClusterConfiguration можно не создавать.
internalNetworkCIDRs:
- 10.0.4.0/24

И начнётся установка DVP CE на master-узел:

После того как команда выполнится, развёртывание master-узла завершится.

Установка worker-узлов

Master-узел — это прекрасно, и на нём можно крутить системные компоненты, но без worker-узлов кластер не имеет смысла, так как на них разворачиваются пользовательские нагрузки (поды, ВМ и прочее). Мне нужно сконфигурировать два дополнительных узла.

Для начала необходимо создать NodeGroup с узлами worker:

sudo -i d8 k create -f - << EOF
apiVersion: deckhouse.io/v1
kind: NodeGroup
metadata:
  name: worker
spec:
  nodeType: Static
  staticInstances:
    count: 2
    labelSelector:
      matchLabels:
        role: worker
EOF

Обратите внимание на параметр count — он указывает на количество узлов, входящих в группу узлов.

Далее необходимо настроить worker-узлы, что Deckhouse делает самостоятельно, нужно только обеспечить взаимодействие master- и worker-узлов по SSH. Для этого на master-узле сгенерируем SSH-ключ с пустой парольной фразой:

ssh-keygen -t rsa -f /dev/shm/caps-id -C "" -N ""

И далее создадим на кластере ресурс SSHCredentials:

sudo -i d8 k create -f - <<EOF
apiVersion: deckhouse.io/v1alpha1
kind: SSHCredentials
metadata:
  name: caps
spec:
  user: caps
  privateSSHKey: "`cat /dev/shm/caps-id | base64 -w0`"
EOF

Публичный ключ сгенерированного ключа необходимо добавить в authorized_keys пользователя caps на worker-узлах. Выведём его для дальнейшего копирования:

cat /dev/shm/caps-id.pub

Дальше поочерёдно заходим по SSH на worker-узлы и под рутом выполняем следующие команды (подставьте ваше значение publickey вместо <SSH-PUBLIC-KEY>, которые создадут пользователя caps и пропишут ему выведённый ранее publickey):

export KEY='<SSH-PUBLIC-KEY>' # Укажите публичную часть SSH-ключа пользователя.
useradd -m -s /bin/bash caps
usermod -aG sudo caps
echo 'caps ALL=(ALL) NOPASSWD: ALL' | sudo EDITOR='tee -a' visudo
mkdir /home/caps/.ssh
echo $KEY >> /home/caps/.ssh/authorized_keys
chown -R caps:caps /home/caps
chmod 700 /home/caps/.ssh
chmod 600 /home/caps/.ssh/authorized_keys

Для добавления узла в кластер Deckhouse необходимо создать описание статического узла в кластере (StaticInstance) и дать доступ master-узлу к worker-узлам по SSH. Выполним данные шаги.

Возвращаемся на master-узел, так как далее команды выполняем снова на нём. Создадим StaticInstance по количеству worker-узлов, где укажем IP-адрес (используйте IP-адрес из внутренней сети узлов) устанавливаемого узла, а также имя создаваемой сущности (параметр name):

export NODE=<NODE-IP-ADDRESS> # Укажите IP-адрес узла, который необходимо подключить к кластеру.
sudo -i d8 k create -f - <<EOF
apiVersion: deckhouse.io/v1alpha1
kind: StaticInstance
metadata:
  name: dvp-worker
  labels:
    role: worker
spec:
  address: "$NODE"
  credentialsRef:
    kind: SSHCredentials
    name: caps
EOF

Введём команду получения сущностей StaticInstance с ожиданием изменения состояний:

d8 k get staticinstances.deckhouse.io -w

Когда узлы будут готовы, мы увидим следующий вывод:

Выполним d8 k get no:

Узлы развернулись. Приступаем к следующему шагу.

Установка программно-определяемого хранилища

Одна из причин, почему я выбрал конфигурацию из трёх узлов, — желание надёжного хранения данных. Основой этого является реплицируемость данных, то есть их хранение в нескольких копиях. На данном этапе нам необходимо настроить реплицируемое хранилище данных, воспользуемся модулем Deckhouse sds-replicated-volume.

Для начала включим соответствующие модули:

sudo -i d8 k create -f - <<EOF
---
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: sds-node-configurator
spec:
  version: 1
  enabled: true
---
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: sds-replicated-volume
spec:
  version: 1
  enabled: true
EOF

Дождёмся готовности модуля sds-replicated-volume:

sudo -i d8 k wait module sds-replicated-volume --for='jsonpath={.status.phase}=Ready' --timeout=1200s

В Deckhouse развёртыванием модулей, системных образов и управлением большинством вещей в кластере занимается deployment deckhouse, который находится в namespace d8-system. После включения модулей или изменения их конфигурации выполняется множество хуков, состояние выполнения которых можно посмотреть в очереди Deckhouse при помощи команды d8 platform queue list. Введём watch d8 platform queue list и дождёмся, когда в очереди не останется заданий:

Пустая очередь будет выглядеть так:

Получим список блочных устройств, который сможем использовать в хранилище. Для этого воспользуемся командой d8 k get bd:

В sds-replicated-volume существуют понятия thin-пулы и thick-пулы для размещения данных. Thick-пулы при выделении места занимают выделенный участок сразу целиком, в то время как thin-пулы позволяют занимать только необходимую в данный момент часть дискового пространства. 

Thick-пулы работают быстрее, однако выделение памяти может занять больше времени, и на thick-пулах не работают снимки. Thin-пулы позволяют экономить пространство, быстрее выделять место, но суммарно выделенное место может превышать фактическое место на хранилище. Соответственно, необходимо следить за фактическим заполнением места.

Создадим LVMVolume для каждого узла. Для этого нужно подставить в следующую команду имя узла и имя блочного устройства с этого узла:

d8 k apply -f - <<EOF
apiVersion: storage.deckhouse.io/v1alpha1
kind: LVMVolumeGroup
metadata:
  name: "vg-on-worker-0"
spec:
  type: Local
  local:
    # Замените на имя своего узла, для которого создаёте группу томов. 
    nodeName: "worker-0"
  blockDeviceSelector:
    matchExpressions:
      - key: kubernetes.io/metadata.name
        operator: In
        values:
          # Замените на имена своих блочных устройств узла, для которого создаёте группу томов. 
          - dev-ef4fb06b63d2c05fb6ee83008b55e486aa1161aa
  # Имя группы томов LVM, которая будет создана из указанных выше блочных устройств на выбранном узле.
  actualVGNameOnTheNode: "vg"
  thinPools:
    - name: thin-pool-0
      size: 70%
EOF

Далее создадим thin-пул:

d8 k apply -f - <<EOF
apiVersion: storage.deckhouse.io/v1alpha1
kind: ReplicatedStoragePool
metadata:
  name: thin-pool
spec:
  type: LVMThin
  lvmVolumeGroups:
    - name: vg-1-on-homecluster0
      thinPoolName: thin-pool-0
    - name: vg-1-on-homecluster1
      thinPoolName: thin-pool-0
    - name: vg-1-on-homecluster2
      thinPoolName: thin-pool-0
EOF

В sds-replicated-volume пользователь не конфигурирует StorageClass вручную, а конфигурирует сущность более высокого уровня ReplicatedStorageClass

Создадим ReplicatedStorageClass (чтобы выбрать параметр replication, ознакомьтесь с документацией):

d8 k apply -f - <<EOF
apiVersion: storage.deckhouse.io/v1alpha1
kind: ReplicatedStorageClass
metadata:
  name: replicated-storage-class
spec:
  # Указываем имя одного из пулов хранения, созданных ранее.
  storagePool: thin-pool
  # Режим поведения при удалении PVC.
  # Допустимые значения: "Delete", "Retain".
  # [Подробнее...](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reclaiming)
  reclaimPolicy: Delete
  # Реплики смогут размещаться на любых доступных узлах: не более одной реплики определенного тома на один узел.
  # В кластере нет зон (нет узлов с лейблами topology.kubernetes.io/zone).
  topology: Ignored
  # Режим репликации, при котором том остается доступным для чтения и записи, даже если одна из реплик тома становится недоступной. 
  # Данные хранятся в трех экземплярах на разных узлах.
  replication: ConsistencyAndAvailability
EOF

Убедимся, что всё создалось:

Установим созданный StorageClass классом по умолчанию:

DEFAULT_STORAGE_CLASS=replicated-storage-class
sudo -i d8 k patch mc global --type='json' -p='[{"op": "replace", "path": "/spec/settings/defaultClusterStorageClass", "value": "'"$DEFAULT_STORAGE_CLASS"'"}]'

Включение модуля виртуализации

Время пришло, включим модуль виртуализации:

sudo -i d8 k create -f - <<EOF
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: virtualization
spec:
  enabled: true
  settings:
    dvcr:
      storage:
        persistentVolumeClaim:
          size: 50G
        type: PersistentVolumeClaim
    virtualMachineCIDRs:
    # Укажите подсети, из которых будут назначаться IP-адреса виртуальным машинам.
    - 10.66.10.0/24
    - 10.66.20.0/24
    - 10.66.30.0/24
  version: 1
EOF

Дождёмся, пока модуль не перейдёт в состояние готовности:

После этого смотрим в очередь Deckhouse, пока она не станет пустой. Это может занять некоторое время:

Если на этапе настройки хранилища вы выбрали всё-таки thick-пул, убедитесь, что все поды в namespace d8-virtualization находятся в состоянии Running:

Настройка Ingress и DNS

Убедимся, что под Kruise controller manager запустился и находится в статусе Running:

d8 k -n d8-ingress-nginx get po -l app=kruise

Установим Ingress-контроллер:

sudo -i d8 k apply -f - <<EOF
# Параметры контроллера NGINX Ingress.
# https://deckhouse.ru/products/virtualization-platform/reference/cr/ingressnginxcontroller.html
apiVersion: deckhouse.io/v1
kind: IngressNginxController
metadata:
  name: nginx
spec:
  ingressClass: nginx
  # Способ поступления трафика из внешнего мира.
  inlet: HostPort
  hostPort:
    httpPort: 80
    httpsPort: 443
  # Описывает, на каких узлах будет находиться Ingress-контроллер.
  # Возможно, захотите изменить.
  nodeSelector:
    node-role.kubernetes.io/control-plane: ""
  tolerations:
  - effect: NoSchedule
    key: node-role.kubernetes.io/control-plane
    operator: Exists
EOF

Под контроллера должен быть в Running:

d8 k -n d8-ingress-nginx get po -l app=controller

Создание пользователя и мониторинг

Создадим пользователя для доступа к кластеру и веб-интерфейсу:

sudo -i d8 k apply -f - <<"EOF"
apiVersion: deckhouse.io/v1
kind: ClusterAuthorizationRule
metadata:
 name: admin
spec:
 # Список учётных записей Kubernetes RBAC
 subjects:
 - kind: User
   name: admin@deckhouse.io
 # Предустановленный шаблон уровня доступа
 accessLevel: SuperAdmin
 # Разрешить пользователю делать kubectl port-forward
 portForwarding: true
---
# Секция, описывающая параметры статического пользователя
# Используемая версия API Deckhouse
apiVersion: deckhouse.io/v1
kind: User
metadata:
 name: admin
spec:
 # e-mail пользователя
 email: admin@deckhouse.io
 # Это хэш пароля password, сгенерированного сейчас
 # Сгенерируйте свой или используйте этот, но только для тестирования
 # echo "password" | htpasswd -BinC 10 "" | cut -d: -f2
 # Возможно, захотите изменить
 password: $2y$10$5.7NBl2MtHbQNzpc4/NOGeBU8lO73qDrc1jMjo.DQz8.X.PuZB7Ji
EOF

Зайдём на grafana.homecluster.ru:

Становится ясно, что сеть кластера работает и отдаёт нужные запрашиваемые страницы, а на странице на скриншоте мы видим метрики кластера и можем смотреть конкретные дашборды.

Примечание

Чтобы кластер был доступен из интернета, можно воспользоваться обратным пробросом порта по SSH и утилитой autossh для поддержания соединения. Это хоть и не оптимальный способ, но простой. И в таком случае вам наверняка захочется поменять доменное имя кластера, для чего нужно изменить параметр .spec.settings.modules.publicDomainTemplate у сущности mc global (kubectl edit mc global).

Создание проекта и виртуальной машины

ВМ располагаются в проектах, поэтому пришло время создать проект и приступить к тому, для чего мы прошли столько шагов — создать, наконец, виртуальную машину.

Давайте создадим тестовый проект:

d8 k create -f - <<EOF
apiVersion: deckhouse.io/v1alpha2
kind: Project
metadata:
 name: test-project
spec:
 description: test-project
 projectTemplateName: default
 parameters:
  # Квоты проекта.
  resourceQuota:
   requests:
    cpu: 16
   limits:
    cpu: 16
  networkPolicy: NotRestricted
  # Администраторы проекта.
  administrators:
   - subject: User
     name: admin
EOF

И образ:

d8 k apply -f - <<EOF
apiVersion: virtualization.deckhouse.io/v1alpha2
kind: VirtualImage
metadata:
  name: ubuntu-22-04
  namespace: test-project
spec:
  # Сохраним образ в DVCR
  storage: ContainerRegistry
  # Источник для создания образа.
  dataSource:
    type: HTTP
    http:
      url: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
EOF

Убедимся, что образ создался, и будем ждать его готовности:

d8 k -n test-project get vi -w

Создадим диск из этого образа:

d8 k apply -f - <<EOF
apiVersion: virtualization.deckhouse.io/v1alpha2
kind: VirtualDisk
metadata:
  name: linux-vm-root
spec:
  # Настройки параметров хранения диска.
  persistentVolumeClaim:
    # Укажем размер больше, чем значение распакованного образа.
    size: 10Gi
    # Подставьте ваше название StorageClass.
    storageClassName: i-sds-replicated-thin-r2
  # Источник, из которого создается диск.
  dataSource:
    type: ObjectRef
    objectRef:
      kind: VirtualImage
      name: ubuntu-22-04
EOF

StorageClass, который мы создавали, имеет настройку WaitForFirstConsumer, то есть диск «не поедет», пока не существует потребителя (ВМ). Эта настройка позволяет создавать диск на том же узле, что и ВМ, что сокращает задержки по работе с диском. Создадим ВМ:

d8 k apply -f - <<"EOF"
apiVersion: virtualization.deckhouse.io/v1alpha2
kind: VirtualMachine
metadata:
  name: linux-vm
  namespace: test-project
spec:
  # Название класса ВМ.
  virtualMachineClassName: generic
  # Блок скриптов первичной инициализации ВМ.
  provisioning:
    type: UserData
    # Пример cloud-init-сценария для создания пользователя cloud с паролем cloud и установки сервиса агента qemu-guest-agent и сервиса nginx.
    userData: |
      #cloud-config
      package_update: true
      packages:
        - qemu-guest-agent
      run_cmd:
        - systemctl daemon-reload
        - systemctl enable --now qemu-guest-agent.service
      ssh_pwauth: True
      users:
      - name: cloud
        passwd: '$6$rounds=4096$saltsalt$fPmUsbjAuA7mnQNTajQM6ClhesyG0.yyQhvahas02ejfMAq1ykBo1RquzS0R6GgdIDlvS.kbUwDablGZKZcTP/'
        shell: /bin/bash
        sudo: ALL=(ALL) NOPASSWD:ALL
        lock_passwd: False
      final_message: "The system is finally up, after $UPTIME seconds"
  # Настройки ресурсов ВМ.
  cpu:
    # Количество ядер ЦП.
    cores: 1
    # Запросить 10% процессорного времени одного физического ядра.
    coreFraction: 10%
  memory:
    # Объём оперативной памяти.
    size: 1Gi
  # Список дисков и образов, используемых в ВМ.
  blockDeviceRefs:
    # Порядок дисков и образов в данном блоке определяет приоритет загрузки.
    - kind: VirtualDisk
      name: linux-vd
EOF

Остаётся только ждать старта ВМ:

 d8 k -n test-project get vm -w

Теперь можно подключиться к ВМ по SSH. Я это сделаю с помощью утилиты d8 v, которую предоставляет DVP:

Виртуальная машина доступна и готова к работе, можно пользоваться.

Сколько ресурсов в таком кластере есть для запуска ВМ

Каждый из worker-узлов кластера предоставляет примерно 10 ГБ оперативной памяти и 4 ядра CPU для запуска виртуальных машин. Таким образом, суммарно кластер имеет около 20 ГБ доступной оперативной памяти и 8 ядер CPU для ВМ.

Master-узел:

Один worker-узел (на нём была запущена, а затем остановлена ВМ):

Второй worker-узел:

Заключение

Мы получили домашний кластер виртуализации и выполнили первые шаги по его использованию — с декларативностью, мониторингом и репликацией данных. Так можно создать гибкую и отказоустойчивую среду, которую можно масштабировать и использовать для самых разных задач: тестирования, обучения, хобби-проектов или домашних сервисов. Всего на установку кластера на этой конфигурации я потратил примерно 1,5 часа, не считая установку ОС.

На данный момент конфигурация используется для личного облака Nextcloud, планирую развернуть GitLab-сервер. А ещё эксперименты с веб-приложениями и Telegram-ботами больше не причинят боль вопросом «где же мне захоститься». 

P. S.

Читайте также в нашем блоге:

Комментарии (7)


  1. fl64
    17.07.2025 09:48

    А на мастере ВМ можно запускать?


    1. Fearning Автор
      17.07.2025 09:48

      Нежелательно, но можно так как нагрузка не должна делить ресурсы с управляющими компонентами. По умолчанию не будет работать, в Kubernetes реализован механизм taints который не даст запуститься нагрузке на мастер узле, нужно удалить с него taint с ключом node-role.kubernetes.io/control-plane, но не делайте так на продакшене пожалуйста)


      1. fl64
        17.07.2025 09:48

        На домашнем продакшене можно)


  1. dsoastro
    17.07.2025 09:48

    Как-то перебор для синхронизации obsidian городить кубер кластер. Обычного гит хватит скорей всего. Ну или Syncthing какого-нибудь


    1. Fearning Автор
      17.07.2025 09:48

      Согласен, но это "заделка на будущее", чтобы один раз сделать и больше не думать где хостить тг ботов, мелкие веб приложения для себя. Но главное, конечно, это то что это домашний проект и все логические доводы падут под "мне так захотелось и я сделал")


  1. Testinghall
    17.07.2025 09:48

    В целом было интересно, жду продолжения, где ТС будет в контейнерах запускать серверы ла2 и Майнкрафта.


    1. Fearning Автор
      17.07.2025 09:48

      Мне кажется что в таких экспериментах не набёртся материала на отдельную статью, но спасибо)