Для создания виртуальной машины используется ресурс VirtualMachine, его параметры позволяют сконфигурировать:

  • класс виртуальной машины;
  • ресурсы, требуемые для работы виртуальной машины (процессор, память, диски и образы);
  • правила размещения виртуальной машины на узлах кластера;
  • настройки загрузчика и оптимальные параметры для гостевой ОС;
  • политику запуска виртуальной машины и политику применения изменений;
  • сценарии начальной конфигурации (cloud-init);
  • перечень блочных устройств.

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

Ниже представлен пример простой конфигурации виртуальной машины, запускающей ОС Ubuntu 22.04. В примере используется сценарий первичной инициализации виртуальной машины (cloud-init), который устанавливает гостевого агента qemu-guest-agent и сервис nginx, а также создает пользователя cloud с паролем cloud:

Пароль в примере был сгенерирован с использованием команды mkpasswd --method=SHA-512 --rounds=4096 -S saltsalt и при необходимости вы можете его поменять на свой:

  1. Создайте виртуальную машину с диском:

    d8 k apply -f - <<"EOF"
    apiVersion: virtualization.deckhouse.io/v1alpha2
    kind: VirtualMachine
    metadata:
      name: linux-vm
    spec:
      # Название класса ВМ.
      virtualMachineClassName: host
      # Блок скриптов первичной инициализации ВМ.
      provisioning:
        type: UserData
        # Пример cloud-init-сценария для создания пользователя cloud с паролем cloud и установки сервиса агента qemu-guest-agent и сервиса nginx.
        userData: |
          #cloud-config
          package_update: true
          packages:
            - nginx
            - qemu-guest-agent
          run_cmd:
            - systemctl daemon-reload
            - systemctl enable --now nginx.service
            - 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-vm-root
    EOF
    

    После создания ресурс VirtualMachine может находиться в следующих состояниях:

    • Pending — ожидание готовности всех зависимых ресурсов, требующихся для запуска виртуальной машины.
    • Starting — идет процесс запуска виртуальной машины.
    • Running — виртуальная машина запущена.
    • Stopping — идет процесс остановки виртуальной машины.
    • Stopped — виртуальная машина остановлена.
    • Terminating — виртуальная машина удаляется.
    • Migrating — виртуальная машина находится в состоянии онлайн-миграции на другой узел.
  2. Проверьте состояние виртуальной машины после создания:

    d8 k get vm linux-vm
    

    Пример вывода:

    NAME       PHASE     NODE           IPADDRESS     AGE
    linux-vm   Running   virtlab-pt-2   10.66.10.12   11m
    

    После создания виртуальная машина автоматически получит IP-адрес из диапазона, указанного в настройках (блок virtualMachineCIDRs).

Жизненный цикл виртуальной машины

Виртуальная машина (ВМ) проходит через несколько этапов своего существования — от создания до удаления. Эти этапы называются фазами и отражают текущее состояние ВМ. Чтобы понять, что происходит с ВМ, нужно проверить её статус (поле .status.phase), а для более детальной информации — блок .status.conditions. Ниже описаны все основные фазы жизненного цикла ВМ, их значение и особенности.

Жизненный цикл виртуальной машины

  • Pending — ожидание готовности ресурсов

    ВМ только что создана, перезапущена или запущена после остановки и ожидает готовности необходимых ресурсов (дисков, образов, IP-адресов и т.д.).

    • Возможные проблемы:
      • не готовы зависимые ресурсы: диски, образы, классы ВМ, секрет со сценарием начальной конфигурации и пр.
    • Диагностика: В .status.conditions стоит обратить внимание на условия *Ready. По ним можно определить, что блокирует переход к следующей фазе, например, ожидание готовности дисков (BlockDevicesReady) или класса ВМ (VirtualMachineClassReady).

      d8 k get vm <vm-name> -o json | jq '.status.conditions[] | select(.type | test(".*Ready"))'
      
  • Starting — запуск виртуальной машины

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

    • Возможные проблемы:
      • Нет подходящего узла для запуска.
      • На подходящих узлах недостаточно CPU или памяти.
      • Превышены квоты неймспейса или проекта.
    • Диагностика:
      • Если запуск затягивается, проверьте .status.conditions, условие type: Running

        d8 k get vm <vm-name> -o json | jq '.status.conditions[] | select(.type=="Running")'
        
  • Running — виртуальная машина запущена

    ВМ успешно запущена и работает.

    • Особенности:
      • При установленном в гостевой системе qemu-guest-agent, условие AgentReady будет истинно,а в .status.guestOSInfo будет отображена информация о запущенной гостевой ОС.
      • Условие type: FirmwareUpToDate, status: False информирует о том, что прошивку ВМ требуется обновить.
      • Условие type: ConfigurationApplied, status: False информирует о том, что конфигурация ВМ не применена для запущенной ВМ.
      • Условие type: AwaitingRestartToApplyConfiguration, status: True отображает информацию о необходимости выполнить вручную перезагрузку ВМ, т.к. некоторые изменения конфигурации невозможно применить без перезагрузки ВМ.
    • Возможные проблемы:
      • Внутренний сбой в работе ВМ или гипервизора.
    • Диагностика:
      • Проверьте .status.conditions, условие type: Running

        d8 k get vm <vm-name> -o json | jq '.status.conditions[] | select(.type=="Running")'
        
  • Stopping — ВМ останавливается или перезагружается

  • Stopped — ВМ остановлена и не потребляет вычислительные ресурсы

  • Terminating — ВМ удаляется.

    Данная фаза необратима. Все связанные с ВМ ресурсы освобождаются, но не удаляются автоматически.

  • Migrating — живая миграция ВМ

    ВМ переносится на другой узел кластера (живая миграция).

    • Особенности:
      • Миграция ВМ поддерживается только для нелокальных дисков, условие type: Migratable отображает информацию о том может ли ВМ мигрировать или нет.
    • Возможные проблемы:
      • Несовместимость процессорных инструкций (при использовании типов процессоров host или host-passthrough).
      • Различие версиях ядер на узлах гипервизоров.
      • На подходящих узлах недостаточно CPU или памяти.
      • Превышены квоты неймспейса или проекта.
    • Диагностика:
      • Проверьте .status.conditions условие type: Migrating, а также блок .status.migrationState
      d8 k get vm <vm-name> -o json | jq '.status | {condition: .conditions[] | select(.type=="Migrating"), migrationState}'
      

Условие type: SizingPolicyMatched, status: False отображает несоответствие конфигурации ресурсов политике сайзинга используемого VirtualMachineClass. При нарушении политики сохранить параметры ВМ без приведения ресурсов в соответствие политике — невозможно.

Условия отображают информацию о состоянии ВМ, а также на возникающие проблемы. Понять, что не так с ВМ можно путем их анализа:

d8 k get vm fedora -o json | jq '.status.conditions[] | select(.message != "")'

Агент гостевой ОС

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

Чем поможет агент?

  • Обеспечит создание консистентных снимков дисков и ВМ.
  • Позволит получать информацию о работающей ОС, которая будет отражена в статусе ВМ. Пример:

    status:
      guestOSInfo:
        id: fedora
        kernelRelease: 6.11.4-301.fc41.x86_64
        kernelVersion: '#1 SMP PREEMPT_DYNAMIC Sun Oct 20 15:02:33 UTC 2024'
        machine: x86_64
        name: Fedora Linux
        prettyName: Fedora Linux 41 (Cloud Edition)
        version: 41 (Cloud Edition)
        versionId: "41"
    
  • Позволит отслеживать, что ОС действительно загрузилась:

    d8 k get vm -o wide
    

    Пример вывода (колонка AGENT):

    NAME     PHASE     CORES   COREFRACTION   MEMORY   NEED RESTART   AGENT   MIGRATABLE   NODE           IPADDRESS    AGE
    fedora   Running   6       5%             8000Mi   False          True    True         virtlab-pt-1   10.66.10.1   5d21h
    

Как установить QEMU Guest Agent:

Для Debian-based ОС:

sudo apt install qemu-guest-agent

Для Centos-based ОС:

sudo yum install qemu-guest-agent

Запуск службы агента:

sudo systemctl enable --now qemu-guest-agent

Автоматическая конфигурация топологии CPU

Топология CPU виртуальной машины (ВМ) определяет, как ядра процессора распределяются по сокетам. Это важно для обеспечения оптимальной производительности и совместимости с приложениями, которые могут зависеть от конфигурации процессора. В конфигурации ВМ вы задаете только общее количество ядер процессора, а топология (количество сокетов и ядер в каждом сокете) рассчитывается автоматически на основе этого значения.

Количество ядер процессора указывается в конфигурации ВМ следующим образом:

spec:
  cpu:
    cores: 1

Далее система автоматически определяет топологию в зависимости от заданного числа ядер. Правила расчета зависят от диапазона количества ядер и описаны ниже.

  • Если количество ядер от 1 до 16 (1 ≤ .spec.cpu.cores ≤ 16):
    • Используется 1 сокет.
    • Количество ядер в сокете равно заданному значению.
    • Шаг изменения: 1 (можно увеличивать или уменьшать количество ядер по одному).
    • Допустимые значения: любое целое число от 1 до 16 включительно.
    • Пример: Если задано .spec.cpu.cores = 8, то топология: 1 сокет с 8 ядрами.
  • Если количество ядер от 17 до 32 (16 < .spec.cpu.cores ≤ 32):
    • Используется 2 сокета.
    • Ядра равномерно распределяются между сокетами (количество ядер в каждом сокете одинаковое).
    • Шаг изменения: 2 (общее количество ядер должно быть четным).
    • Допустимые значения: 18, 20, 22, 24, 26, 28, 30, 32.
    • Ограничения: минимум 9 ядер в сокете, максимум 16 ядер в сокете.
    • Пример: Если задано .spec.cpu.cores = 20, то топология: 2 сокета по 10 ядер каждый.
  • Если количество ядер от 33 до 64 (32 < .spec.cpu.cores ≤ 64):
    • Используется 4 сокета.
    • Ядра равномерно распределяются между сокетами.
    • Шаг изменения: 4 (общее количество ядер должно быть кратно 4).
    • Допустимые значения: 36, 40, 44, 48, 52, 56, 60, 64.
    • Ограничения: минимум 9 ядер в сокете, максимум 16 ядер в сокете.
    • Пример: Если задано .spec.cpu.cores = 40, то топология: 4 сокета по 10 ядер каждый.
  • Если количество ядер больше 64 (.spec.cpu.cores > 64):
    • Используется 8 сокетов.
    • Ядра равномерно распределяются между сокетами.
    • Шаг изменения: 8 (общее количество ядер должно быть кратно 8).
    • Допустимые значения: 72, 80, 88, 96 и так далее до 248
    • Ограничения: минимум 9 ядер в сокете.
    • Пример: Если задано .spec.cpu.cores = 80, то топология: 8 сокетов по 10 ядер каждый.

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

Максимально возможное количество ядер — 248.

Текущая топология ВМ (количество сокетов и ядер в каждом сокете) отображается в статусе ВМ в следующем формате:

status:
  resources:
    cpu:
      coreFraction: 10%
      cores: 1
      requestedCores: "1"
      runtimeOverhead: "0"
      topology:
        sockets: 1
        coresPerSocket: 1

Подключение к виртуальной машине

Для подключения к виртуальной машине доступны следующие способы:

  • протокол удаленного управления (например SSH), который должен быть предварительно настроен на виртуальной машине;
  • серийная консоль (serial console);
  • протокол VNC.

Пример подключения к виртуальной машине с использованием серийной консоли:

d8 v console linux-vm

Пример вывода:

Successfully connected to linux-vm console. The escape sequence is ^]
linux-vm login: cloud
Password: cloud

Для завершения работы с серийной консолью нажмите Ctrl+].

Пример команды для подключения по VNC:

d8 v vnc linux-vm

Пример команды для подключения по SSH.

d8 v ssh cloud@linux-vm --local-ssh

Политика запуска и управление состоянием виртуальной машины

Политика запуска виртуальной машины предназначена для автоматизированного управления состоянием виртуальной машины. Определяется она в виде параметра .spec.runPolicy в спецификации виртуальной машины. Поддерживаются следующие политики:

  • AlwaysOnUnlessStoppedManually — (по умолчанию) после создания ВМ всегда находится в рабочем состоянии. В случае сбоев работа ВМ восстанавливается автоматически. Остановка ВМ возможна только путем вызова команды d8 v stop или создания соответствующей операции.
  • AlwaysOn — после создания ВМ всегда находится в работающем состоянии, даже в случае ее выключения средствами ОС. В случае сбоев работа ВМ восстанавливается автоматически.
  • Manual — после создания состоянием ВМ управляет пользователь вручную с использованием команд или операций.
  • AlwaysOff — после создания ВМ всегда находится в выключенном состоянии. Возможность включения ВМ через команды\операции - отсутствует.

Состоянием виртуальной машины можно управлять с помощью следующих методов:

  • Создание ресурса VirtualMachineOperation (vmop).
  • Использование утилиты d8 с соответствующей подкомандой.

Ресурс VirtualMachineOperation декларативно определяет действие, которое должно быть выполнено на виртуальной машине.

Пример операции для выполнения перезагрузки виртуальной машины с именем linux-vm:

d8 k create -f - <<EOF
apiVersion: virtualization.deckhouse.io/v1alpha2
kind: VirtualMachineOperation
metadata:
  generateName: restart-linux-vm-
spec:
  virtualMachineName: linux-vm
  # Тип применяемой операции = применяемая операция.
  type: Restart
EOF

Посмотреть результат действия можно с использованием команды:

d8 k get virtualmachineoperation
# или
d8 k get vmop

Аналогичное действие можно выполнить с использованием утилиты d8:

d8 v restart linux-vm

Возможные операции:

d8 vmop type Действие
d8 v stop Stop Остановить ВМ
d8 v start Start Запустить ВМ
d8 v restart Restart Перезапустить ВМ
d8 v evict Evict Мигрировать ВМ на другой узел

Изменение конфигурации виртуальной машины

Конфигурацию виртуальной машины можно изменять в любое время после создания ресурса VirtualMachine. Однако то, как эти изменения будут применены, зависит от текущей фазы виртуальной машины и характера внесённых изменений.

Изменения в конфигурацию виртуальной машины можно внести с использованием следующей команды:

d8 k edit vm linux-vm

Если виртуальная машина находится в выключенном состоянии (.status.phase: Stopped), внесённые изменения вступят в силу сразу после её запуска.

Если виртуальная машина работает (.status.phase: Running), то способ применения изменений зависит от их типа:

Блок конфигурации Как применяется
.metadata.labels Сразу
.metadata.annotations Сразу
.spec.liveMigrationPolicy Сразу
.spec.runPolicy Сразу
.spec.disruptions.restartApprovalMode Сразу
.spec.affinity EE, SE+ : Сразу, CE: Требуется перезапуск ВМ
.spec.nodeSelector EE, SE+ : Сразу, CE: Требуется перезапуск ВМ
.spec.* Требуется перезапуск ВМ

Рассмотрим пример изменения конфигурации виртуальной машины:

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

d8 v ssh cloud@linux-vm --local-ssh --command "nproc"

Пример вывода:

1

Примените следующий патч к виртуальной машине, чтобы изменить количество ядер с 1 на 2.

d8 k patch vm linux-vm --type merge -p '{"spec":{"cpu":{"cores":2}}}'

Пример вывода:

virtualmachine.virtualization.deckhouse.io/linux-vm patched

Изменения в конфигурацию внесены, но ещё не применены к виртуальной машине. Проверьте это, повторно выполнив:

d8 v ssh cloud@linux-vm --local-ssh --command "nproc"

Пример вывода:

1

Для применения этого изменения необходим перезапуск виртуальной машины. Выполните следующую команду, чтобы увидеть изменения, ожидающие применения (требующие перезапуска):

d8 k get vm linux-vm -o jsonpath="{.status.restartAwaitingChanges}" | jq .

Пример вывода:

[
  {
    "currentValue": 1,
    "desiredValue": 2,
    "operation": "replace",
    "path": "cpu.cores"
  }
]

Выполните команду:

d8 k get vm linux-vm -o wide

Пример вывода:

NAME        PHASE     CORES   COREFRACTION   MEMORY   NEED RESTART   AGENT   MIGRATABLE   NODE           IPADDRESS     AGE
linux-vm    Running   2       100%           1Gi      True           True    True         virtlab-pt-1   10.66.10.13   5m16s

В колонке NEED RESTART мы видим значение True, а это значит что для применения изменений требуется перезагрузка.

Выполните перезагрузку виртуальной машины:

d8 v restart linux-vm

После перезагрузки изменения будут применены и блок .status.restartAwaitingChanges будет пустой.

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

d8 v ssh cloud@linux-vm --local-ssh --command "nproc"

Пример вывода:

2

Порядок применения изменений виртуальной машины через «ручной» рестарт является поведением по умолчанию. Если есть необходимость применять внесенные изменения сразу и автоматически, для этого нужно изменить политику применения изменений:

spec:
  disruptions:
    restartApprovalMode: Automatic

Сценарии начальной инициализации

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

В качестве сценариев начальной инициализации поддерживаются:

Сценарий CloudInit можно встраивать непосредственно в спецификацию ВМ, но этот сценарий ограничен максимальной длиной в 2048 байт:

spec:
  provisioning:
    type: UserData
    userData: |
      #cloud-config
      package_update: true
      ...

При более длинных сценариях и/или наличии приватных данных, сценарий начальной инициализации виртуальной машины может быть создан в ресурсе Secret. Пример Secret со сценарием CloudInit приведен ниже:

apiVersion: v1
kind: Secret
metadata:
  name: cloud-init-example
data:
  userData: <base64 data>
type: provisioning.virtualization.deckhouse.io/cloud-init

Фрагмент конфигурации виртуальной машины при использовании скрипта начальной инициализации CloudInit хранящегося в ресурсе Secret:

spec:
  provisioning:
    type: UserDataRef
    userDataRef:
      kind: Secret
      name: cloud-init-example

Примечание: значение поля .data.userData должно быть закодировано в формате Base64.

Для конфигурирования виртуальных машин под управлением ОС Windows с использованием Sysprep, поддерживается только вариант с Secret.

Пример Secret с сценарием Sysprep:

apiVersion: v1
kind: Secret
metadata:
  name: sysprep-example
data:
  unattend.xml: <base64 data>
type: provisioning.virtualization.deckhouse.io/sysprep

Примечание: Значение поля .data.unattend.xml должно быть закодировано в формате Base64.

Фрагмент конфигурации виртуальной машины с использованием скрипта начальной инициализации Sysprep в ресурсе Secret:

spec:
  provisioning:
    type: SysprepRef
    sysprepRef:
      kind: Secret
      name: sysprep-example

Размещение ВМ по узлам

Для управления размещением виртуальных машин по узлам можно использовать следующие подходы:

  • Простое связывание по меткам — nodeSelector;
  • Предпочтительное связывание — Affinity;
  • Избежание совместного размещения — AntiAffinity.

Параметры размещения виртуальных машин можно изменить в реальном времени (доступно только в Enterprise-редакции). Однако, если новые параметры размещения не совпадают с текущими, виртуальная машина будет перемещена на узлы, соответствующие новым требованиям.

Простое связывание по меткам — nodeSelector

nodeSelector — это простейший способ контролировать размещение виртуальных машин, используя набор меток. Он позволяет задать, на каких узлах могут запускаться виртуальные машины, выбирая узлы с необходимыми метками.

spec:
  nodeSelector:
    disktype: ssd

nodeSelector

В этом примере виртуальная машина будет размещена только на узлах, которые имеют метку disktype со значением ssd.

Предпочтительное связывание — Affinity

Affinity предоставляет более гибкие и мощные инструменты по сравнению с nodeSelector. Он позволяет задавать «предпочтения» и «обязательности» для размещения виртуальных машин. Affinity поддерживает два вида: nodeAffinity и virtualMachineAndPodAffinity.

nodeAffinity позволяет определять, на каких узлах может быть запущена виртуальная машина, с помощью выражений меток, и может быть предпочтительным (preferred) или обязательным (required).

Пример использования nodeAffinity:

spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: disktype
                operator: In
                values:
                  - ssd

nodeAffinity

В этом примере виртуальная машина будет размещена только на узлах, которые имеют метку disktype со значением ssd.

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

Пример:

spec:
  affinity:
    virtualMachineAndPodAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 1
          virtualMachineAndPodAffinityTerm:
            labelSelector:
              matchLabels:
                server: database
            topologyKey: "kubernetes.io/hostname"

virtualMachineAndPodAffinity

В этом примере виртуальная машина будет размещена, если будет такая возможность (так как используется метка preferred), только на узлах на которых присутствует виртуальная машина с меткой server и значением database.

Избежание совместного размещения — AntiAffinity

AntiAffinity — это противоположность Affinity, которая позволяет задавать требования для избегания размещения виртуальных машин на одних и тех же узлах. Это полезно для распределения нагрузки или обеспечения отказоустойчивости.

Термины Affinity и AntiAffinity применимы только к отношению между виртуальными машинами. Для узлов используемые привязки называются nodeAffinity. В nodeAffinity нет отдельного обратного термина, как в случае с virtualMachineAndPodAffinity, но можно создать противоположные условия, задав отрицательные операторы в выражениях меток. Чтобы акцентировать внимание на исключении определенных узлов, можно воспользоваться nodeAffinity с оператором, таким как NotIn.

Пример использования virtualMachineAndPodAntiAffinity:

spec:
  affinity:
    virtualMachineAndPodAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchLabels:
              server: database
          topologyKey: "kubernetes.io/hostname"

AntiAffinity

В данном примере создаваемая виртуальная машина не будет размещена на одном узле с виртуальной машиной с меткой server: database.

Статические и динамические блочные устройства

Блочные устройства можно разделить на два типа по способу их подключения: статические и динамические (hotplug).

Статические блочные устройства

Блочные устройства и их особенности представлены в таблице:

Тип блочного устройства Комментарий
VirtualImage подключается в режиме для чтения, или как cd-rom для iso-образов
ClusterVirtualImage подключается в режиме для чтения, или как cd-rom для iso-образов
VirtualDisk подключается в режиме для чтения и записи

Статические блочные устройства указываются в спецификации виртуальной машины в блоке .spec.blockDeviceRefs в виде списка. Порядок устройств в этом списке определяет последовательность их загрузки. Таким образом, если диск или образ указан первым, загрузчик сначала попробует загрузиться с него. Если это не удастся, система перейдет к следующему устройству в списке и попытается загрузиться с него. И так далее до момента обнаружения первого загрузчика.

Изменение состава и порядка устройств в блоке .spec.blockDeviceRefs возможно только с перезагрузкой виртуальной машины.

Фрагмент конфигурации VirtualMachine со статически подключенными диском и проектным образом:

spec:
  blockDeviceRefs:
    - kind: VirtualDisk
      name: <virtual-disk-name>
    - kind: VirtualImage
      name: <virtual-image-name>

Динамические блочные устройства

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

Для подключения динамических блочных устройств используется ресурс VirtualMachineBlockDeviceAttachment (vmbda). На данный момент для подключения в качестве динамического блочного устройства поддерживается только VirtualDisk.

Создайте ресурс, который подключит пустой диск blank-disk к виртуальной машине linux-vm:

d8 k apply -f - <<EOF
apiVersion: virtualization.deckhouse.io/v1alpha2
kind: VirtualMachineBlockDeviceAttachment
metadata:
  name: attach-blank-disk
spec:
  blockDeviceRef:
    kind: VirtualDisk
    name: blank-disk
  virtualMachineName: linux-vm
EOF

После создания VirtualMachineBlockDeviceAttachment может находиться в следующих состояниях:

  • Pending — ожидание готовности всех зависимых ресурсов.
  • InProgress — идет процесс подключения устройства.
  • Attached — устройство подключено.

Диагностика проблем с ресурсом осуществляется путем анализа информации в блоке .status.conditions.

Проверьте состояние вашего ресурса:

d8 k get vmbda attach-blank-disk

Пример вывода:

NAME                PHASE      VIRTUAL MACHINE NAME   AGE
attach-blank-disk   Attached   linux-vm              3m7s

Подключитесь к виртуальной машине и удостоверьтесь, что диск подключен:

d8 v ssh cloud@linux-vm --local-ssh --command "lsblk"

Пример вывода:

NAME    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sda       8:0    0   10G  0 disk <--- статично подключенный диск linux-vm-root
|-sda1    8:1    0  9.9G  0 part /
|-sda14   8:14   0    4M  0 part
`-sda15   8:15   0  106M  0 part /boot/efi
sdb       8:16   0    1M  0 disk <--- cloudinit
sdc       8:32   0 95.9M  0 disk <--- динамически подключенный диск blank-disk

Для отключения диска от виртуальной машины удалите ранее созданный ресурс:

d8 k delete vmbda attach-blank-disk

Подключение образов, осуществляется по аналогии. Для этого в качестве kind указать VirtualImage или ClusterVirtualImage и имя образа:

d8 k apply -f - <<EOF
apiVersion: virtualization.deckhouse.io/v1alpha2
kind: VirtualMachineBlockDeviceAttachment
metadata:
  name: attach-ubuntu-iso
spec:
  blockDeviceRef:
    kind: VirtualImage # Или ClusterVirtualImage.
    name: ubuntu-iso
  virtualMachineName: linux-vm
EOF

Миграция виртуальной машины в реальном времени (живая миграция)

Живая миграция виртуальных машин (ВМ) — это процесс перемещения работающей ВМ с одного физического узла на другой без её отключения. Эта функция играет ключевую роль в управлении виртуализованной инфраструктурой, обеспечивая непрерывность работы приложений во время технического обслуживания, балансировки нагрузки или обновлений.

Как работает живая миграция

Процесс живой миграции включает несколько этапов:

  1. Создание нового экземпляра ВМ

    На целевом узле создаётся новая ВМ в приостановленном состоянии. Её конфигурация (процессор, диски, сеть) копируется с исходного узла.

  2. Первичная передача памяти

    Вся оперативная память ВМ копируется на целевой узел по сети. Это называется первичной передачей.

  3. Отслеживание изменений (Dirty Pages)

    Пока память передаётся, ВМ продолжает работать на исходном узле и может изменять некоторые страницы памяти. Такие страницы называются «грязными» (dirty pages), и гипервизор их помечает.

  4. Итеративная синхронизация

    После первичной передачи начинается повторная отправка только изменённых страниц. Этот процесс повторяется в несколько циклов:

    • Чем выше нагрузка на ВМ, тем больше «грязных» страниц появляется, и тем дольше длится миграция.
    • При хорошей пропускной способности сети объём несинхронизированных данных постепенно уменьшается.
  5. Финальная синхронизация и переключение

    Когда количество «грязных» страниц становится минимальным, ВМ на исходном узле приостанавливается (обычно на 100 миллисекунд):

    • Оставшиеся изменения памяти передаются на целевой узел.
    • Состояние процессора, устройств и открытых соединений синхронизируется.
    • ВМ запускается на новом узле, а исходная копия удаляется.

Живая миграция

Скорость сети играет важную роль. Если пропускная способность низкая, итераций становится больше, а время простоя ВМ может увеличиться. В худшем случае миграция может вообще не завершиться.

Механизм AutoConverge

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

Принципы работы механизма AutoConverge:

  1. Замедление процессора ВМ.

    Гипервизор постепенно снижает частоту процессора исходной ВМ. Это уменьшает скорость появления новых «грязных» страниц. Чем выше нагрузка на ВМ, тем сильнее замедление.

  2. Ускорение синхронизации.

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

  3. Автоматическое завершение.

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

AutoConverge — это своего рода «страховка», которая гарантирует, что миграция завершится, даже если сеть не справляется с передачей данных. Однако замедление процессора может повлиять на производительность приложений, работающих на ВМ, поэтому его использование нужно контролировать.

Настройка политики миграции

Для настройки поведения миграции используйте параметр .spec.liveMigrationPolicy в конфигурации ВМ. Допустимые значения параметра:

  • AlwaysSafe — миграция всегда выполняется без замедления процессора (AutoConverge не используется). Подходит для случаев, когда важна максимальная производительность ВМ, но требует высокой пропускной способности сети.
  • PreferSafe (используется в качестве политики по умолчанию) — миграция выполняется без замедления процессора (AutoConverge не используется). Однако можно запустить миграцию с замедлением процессора, используя ресурс VirtualMachineOperation с параметрами type=Evict и force=true.
  • AlwaysForced — миграция всегда использует AutoConverge, то есть процессор замедляется при необходимости. Это гарантирует завершение миграции даже при плохой сети, но может снизить производительность ВМ.
  • PreferForced — миграция использует AutoConverge, то есть процессор замедляется при необходимости. Однако можно запустить миграцию без замедления процессора, используя ресурс VirtualMachineOperation с параметрами type=Evict и force=false.

Виды миграции

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

  • Обновление «прошивки» виртуальной машины.
  • Перераспределение нагрузки в кластере.
  • Перевод узла в режим технического обслуживания (Drain узла)
  • При изменении параметров размещения ВМ (не доступно в Community-редакции).

Триггером к живой миграции является появление ресурса VirtualMachineOperations с типом Evict.

В таблице приведены префиксы названия ресурса VirtualMachineOperations с типом Evict, создаваемые для живых миграций вызванных системными событиями:

Вид системного события Префикс имени ресурса
Обновлении «прошивки» firmware-update-*
Перераспределение нагрузки evacuation-*
Drain узла evacuation-*
Изменение параметров размещения nodeplacement-update-*

Данный ресурс может находится в следующих состояниях:

  • Pending — ожидается выполнение операции.
  • InProgress — живая миграция выполняется.
  • Completed — живая миграция виртуальной машины завершилась успешно.
  • Failed — живая миграция виртуальной машины завершилась неуспешно.

Посмотреть активные операции можно с использованием команды:

d8 k get vmop

Пример вывода:

NAME                    PHASE       TYPE    VIRTUALMACHINE      AGE
firmware-update-fnbk2   Completed   Evict   linux-vm            1m

Прервать любую живую миграцию пока она находится в фазе Pending, InProgress можно удалив соответствующий ресурс VirtualMachineOperations.

Как выполнить живую миграцию виртуальной машины с использованием VirtualMachineOperations

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

d8 k get vm

Пример вывода:

NAME       PHASE     NODE           IPADDRESS     AGE
linux-vm   Running   virtlab-pt-1   10.66.10.14   79m

Виртуальная машина запущена на узле virtlab-pt-1.

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

d8 v evict -n <namespace> <vm-name>

Выполнение данной команды приводит к созданию ресурса VirtualMachineOperation.

Запустить миграцию можно также создав ресурс VirtualMachineOperation (vmop) с типом Evict вручную:

d8 k create -f - <<EOF
apiVersion: virtualization.deckhouse.io/v1alpha2
kind: VirtualMachineOperation
metadata:
  generateName: evict-linux-vm-
spec:
  # Имя виртуальной машины.
  virtualMachineName: linux-vm
  # Операция для миграции.
  type: Evict
  # Разрешить замедление процессора механизмом AutoConverge, для гарантии, что миграция выполнится.
  force: true
EOF

Для отслеживания миграции виртуальной машины сразу после создания ресурса vmop, выполните команду:

d8 k get vm -w

Пример вывода:

NAME       PHASE       NODE           IPADDRESS     AGE
linux-vm   Running     virtlab-pt-1   10.66.10.14   79m
linux-vm   Migrating   virtlab-pt-1   10.66.10.14   79m
linux-vm   Migrating   virtlab-pt-1   10.66.10.14   79m
linux-vm   Running     virtlab-pt-2   10.66.10.14   79m

Также для выполнения миграции можно использовать команду:

d8 v evict <vm-name>

Живая миграция виртуальной машины при изменении параметров размещения (недоступно в CE редакции)

Рассмотрим механизм миграции на примере кластера с двумя группами узлов (NodeGroups): green и blue . Допустим, виртуальная машина (ВМ) изначально запущена на узле группы green , а её конфигурация не содержит ограничений на размещение.

Шаг 1. Добавление параметра размещения Укажем в спецификации ВМ требование к размещению в группе green :

spec:
  nodeSelector:
    node.deckhouse.io/group: green

После сохранения изменений ВМ продолжит работать на текущем узле, так как условие nodeSelector уже выполняется.

Шаг 2. Изменение группы размещения Изменим требование на размещение в группе blue :

spec:
  nodeSelector:
    node.deckhouse.io/group: blue

Теперь текущий узел (группы green) не соответствует новым условиям. Система автоматически создаст объект VirtualMachineOperations типа Evict, что инициирует живую миграцию ВМ на доступный узел группы blue.

Пример вывода ресурса

NAME                         PHASE       TYPE    VIRTUALMACHINE      AGE
nodeplacement-update-dabk4   Completed   Evict   linux-vm            1m

Режим обслуживания

При выполнении работ на узлах с запущенными виртуальными машинами существует риск нарушения их работоспособности. Чтобы этого избежать, узел можно перевести в режим обслуживания и мигрировать виртуальные машины на другие свободные узлы. Для этого необходимо выполнить следующую команду:

d8 k drain <nodename> --ignore-daemonsets --delete-emptydir-dat

где <nodename> — узел, на котором предполагается выполнить работы и который должен быть освобожден от всех ресурсов (в том числе и от системных).

Если есть необходимость вытеснить с узла только виртуальные машины, выполните следующую команду:

d8 k drain <nodename> --pod-selector vm.kubevirt.internal.virtualization.deckhouse.io/name --delete-emptydir-data

После выполнения команд d8 k drain — узел перейдет в режим обслуживания и виртуальные машины на нем запускаться не смогут. Чтобы вывести его из режима обслуживания выполните следующую команду:

d8 k uncordon <nodename>

Режим обслуживания

IP-адреса виртуальных машин

Блок .spec.settings.virtualMachineCIDRs в конфигурации задает список подсетей для назначения IP-адресов виртуальным машинам (общий пул IP-адресов). Все адреса в этих подсетях доступны для использования, за исключением первого (адрес сети) и последнего (широковещательный адрес).

Ресурс VirtualMachineIPAddressLease (vmipl): кластерный ресурс, который управляет временным выделением IP-адресов из общего пула, указанного в virtualMachineCIDRs.

Чтобы посмотреть список временно выделенных IP-адресов (vmipl), используйте команду:

d8 k get vmipl

Пример вывода:

NAME             VIRTUALMACHINEIPADDRESS                             STATUS   AGE
ip-10-66-10-14   {"name":"linux-vm-7prpx","namespace":"default"}     Bound    12h

Ресурс VirtualMachineIPAddress (vmip) — это ресурс проекта или пространства имен, который отвечает за резервирование выделенных IP-адресов и их привязку к виртуальным машинам. IP-адреса могут выделяться автоматически или по запросу.

Проверить назначенный IP-адрес можно с помощью команды:

d8 k get vmip

Пример вывода:

NAME             VIRTUALMACHINEIPADDRESS                             STATUS   AGE
ip-10-66-10-14   {"name":"linux-vm-7prpx","namespace":"default"}     Bound    12h

Алгоритм автоматического присвоения IP-адреса виртуальной машине выглядит следующим образом:

  • Пользователь создает виртуальную машину с именем <vmname>.
  • Контроллер автоматически создает ресурс vmip с именем <vmname>-<hash>, чтобы запросить IP-адрес и связать его с виртуальной машиной.
  • Для этого vmip создается ресурс аренды vmipl, который выбирает случайный IP-адрес из общего пула.
  • Как только ресурс vmip создан, виртуальная машина получает назначенный IP-адрес.

По умолчанию IP-адрес для виртуальной машины назначается автоматически, из подсетей, и закрепляется за ней до её удаления. После удаления виртуальной машины ресурс vmip также удаляется, но IP-адрес временно остается закрепленным за проектом/пространством имен и может быть повторно запрошен.

Запрос требуемого IP-адреса

Создайте ресурс vmip:

d8 k apply -f - <<EOF
apiVersion: virtualization.deckhouse.io/v1alpha2
kind: VirtualMachineIPAddress
metadata:
  name: linux-vm-custom-ip
spec:
  staticIP: 10.66.20.77
  type: Static
EOF

Создайте новую или измените существующую виртуальную машину и в спецификации укажите требуемый ресурс vmip явно:

spec:
  virtualMachineIPAddressName: linux-vm-custom-ip

Сохранение IP-адреса, присвоенного виртуальной машине

Чтобы автоматически выданный IP-адрес виртуальной машины не удалился вместе с самой виртуальной машиной, выполните следующие действия.

Получите название ресурса vmip для заданной виртуальной машины:

d8 k get vm linux-vm -o jsonpath="{.status.virtualMachineIPAddressName}"

Пример вывода:

linux-vm-7prpx

Удалите блоки .metadata.ownerReferences из найденного ресурса:

d8 k patch vmip linux-vm-7prpx --type=merge --patch '{"metadata":{"ownerReferences":null}}'

После удаления виртуальной машины, ресурс vmip сохранится и его можно будет использовать во вновь созданной виртуальной машине:

spec:
  virtualMachineIPAddressName: linux-vm-7prpx

Даже если ресурс vmip будет удален, он остается арендованным для текущего проекта/пространства имен еще 10 минут и существует возможность вновь его занять по запросу:

d8 k apply -f - <<EOF
apiVersion: virtualization.deckhouse.io/v1alpha2
kind: VirtualMachineIPAddress
metadata:
  name: linux-vm-custom-ip
spec:
  staticIP: 10.66.20.77
  type: Static
EOF