UMGUM.COM 

KVM + Bash-Supervisor ( Пример управления виртуальными машинами Qemu-KVM через простейшие BASH-скрипты. )

21 июля 2011  (обновлено 28 октября 2018)

OS: "Linux Debian 5/6/7/8", "Linux Ubuntu 12.04/14.04 LTS".

Ориентировочно с 2000-го по 2005-й я много баловался со всеми попадающими под руки системами виртуализации. Кроме готовых решений вроде "Parallels", "Microsoft PC" и "VMware" меня в то время заинтересовали системы эмуляции "Bochs" и "QEmu", последняя из которых постепенно вышла на уровень, когда в ней можно было запускать неадаптированные для этого специально произвольные операционные системы. После дополнения "QEmu" модулем аппаратной виртуализации и покупки проекта компанией "RedHat" система стала называться KVM (Kernel-based Virtual Machine) и уже в 2010-м году я полностью отказался от всех остальных систем виртуализации на этапах тестирования, а в 2012-м перевёл около сотни физических и виртуальных серверов на эту платформу.

Так получилось, что активное использование "Qemu/KVM" для меня началось ещё до выхода на стабильный уровень таких библиотек управления, как "LibVirt" - потому до определённого момента управление виртуальными машинами было реализовано через простейшие BASH-скрипты, которые я здесь и привожу. Это не "продакшн"-решение! Это просто немного истории.


Первым делом проверяем, включена ли в процессоре и BIOS поддержка аппаратной виртуализации (обидно бывает, знаете ли, затеять перестройку на удалённой по сети машине и узнать о невозможности её завершить):

# egrep -c '(vmx|svm)' /proc/cpuinfo

Понятно, что если что-то насчиталось - то есть надежда на светлое будущее.

Особо обращаю внимание на то, что поддержки процессором инструкций аппаратной виртуализации маловато - как правило, следует дополнительно активировать возможность использовать таковые в BIOS компьютера (на моей практике в половине случаев поддержка виртуализации была отключена в BIOS).

Устанавливаем непосредственно средство виртуализации:

# aptitude install kvm

Не уловил точный момент, но вроде бы после "QEMU v2" пакет стал называться уже иначе:

# aptitude install qemu-kvm

Разработчики Qemu-KVM предусмотрели скрипт инициализации поддержки аппаратного ускорения при запуске системы; он нормально отрабатывает, важно только не пытаться стартовать виртуальные машины до момента инициирования модуля "kvm" - для этого достаточно установить соответствующую очерёдность скриптов "/etc/init.d/" в дальнейшем.

Используйте KSM (Kernel Samepage Merging), если процессоры несущего сервера достаточно производительны, что бы на оптимизацию использования областей памяти позволительно было бы оттянуть 5-10% ресурсов CPU. KSM в ядре Linux доступно с версии 2.6.32 и поддерживается KVM начиная с версии 0.12.

# uname -a

Linux 3.2.0-4-amd64 SMP

# kvm -version

В "Debian Wheeze":

QEMU version 1.1.2

В "Debian Jessie" уже следующий минорный релиз:

QEMU emulator version 2.1.2, Copyright (c) 2003-2008 Fabrice Bellard

Если комплекс системного и прикладного ПО удовлетворяет требованиям, то включаем KSM (неплохо бы ознакомится с документацией, но в целом подсистема работоспособна и полезна сразу после включения). В "Linux Debian Wheezy" для этого всё уже готово:

# echo 1 > /sys/kernel/mm/ksm/run

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

В дистрибутивах специально доработанных для использования в роли виртуализаторов имеются подсистемы контроля за использованием памятью несущей и гостевыми машинами, анализирующие общую картину и принимающие решения о включении, выключении и настройке средств оптимизации таковой в зависимости от ситуации. В "Linux Debian" ничего этого пока нет - будем просто включать KSM, если процессоры реально мощные, а оперативной памяти маловато (мой случай - куча двух-процессорных "HP DL380 G3/G4/G5/G6" имеющих на борту от 4-ёх до 8-ми гигабайт ОЗУ с тремя-пятью виртуальными машинами на каждом сервере). Далее мы автоматизируем активацию KSM в соответствующем скрипте подготовки окружения виртуализатора.

Устанавливаем набор утилит для организации "прозрачного" сетевого взаимодействия виртуальной машины с реальным окружением:

# aptitude install bridge-utils uml-utilities libcap2-bin socat

Сразу после установки пакетов окружения запретим автоматическое инициирование "виртуального коммутатора", которое производится по умолчанию скриптом пакета "uml-utilities", путём правки конфигурационного файла "/etc/default/uml-utilities":

# /etc/init.d/uml-utilities stop

# vi /etc/default/uml-utilities

....
UML_SWITCH_START="false"
....

Рассказываем Linux-у, что мы хотим создать "виртуальные мосты" (network bridge), к которым после будем прикреплять tap-интерфейсы виртуальных машин. Необходимо заранее определится с количеством виртуальных "мостов" и интерфейсов, которые будут к этим "мостам" прикреплены:

# vi /etc/network/interfaces

....
# Первым делом объявляем состояние готовности для всех задействуемых в схеме интерфейсов

# The primary network interface
auto eth0
iface eth0 inet manual

# The primary network interface
auto eth1
iface eth1 inet manual
....

# После инициирования задействованных интерфейсов описываем непосредственно "мосты"

# The primary bridge network interface
auto br0
iface br0 inet static
  #
  # Перед созданием "моста" явно останавливаем задействованные в нём сетевые интерфейсы
  pre-up ifconfig eth0 0.0.0.0 down
  #
  # При введении сетевого интерфейса в "мост" перевод в режим "promisc" производится автоматически, но на всякий случай можно это продублировать
  pre-up ifconfig eth0 promisc
  #
  # Блок описания параметров создаваемого "моста"
  bridge_stp      off
  bridge_maxwait  0
  bridge_fd       0
  bridge_ports    eth0
  #
  # Блок описания IP-параметров интерфейса, для обеспечения его доступности по сети, плюс к функционалу "моста"
  address 10.10.3.10
  netmask 255.255.255.248
  network 10.10.3.8
  broadcast 10.10.3.15
  gateway 10.10.3.9

# The primary bridge network interface
auto br1
iface br1 inet manual
  #
  # Перед созданием "моста" явно останавливаем задействованные в нём сетевые интерфейсы
  pre-up ifconfig eth1 0.0.0.0 down
  #
  # При введении сетевого интерфейса в "мост" перевод в режим "promisc" производится автоматически, но на всякий случай можно это продублировать
  pre-up ifconfig eth1 promisc
  #
  # Блок описания параметров создаваемого "моста"
  bridge_stp      off
  bridge_maxwait  0
  bridge_fd       0
  bridge_ports    eth1
....

Выше я помогаю, если можно так выразится, системному скрипту, создающему сетевую конфигурацию отрабатывая конфигурационный файл "/etc/network/interfaces", явно указывая операндом "pre-up" проделать особо важные операции, такие как предварительная остановка и изменение режима работы интерфейсов, задействуемых в "мостах". Оно вроде как и само собой подразумевается (и делается обычно автоматически, без особого на то указания), однако на практике у меня были проблемы в "много-мостовых" схемах, которые, как я подозреваю, были вызваны не совсем верной последовательностью сборки "моста" системными скриптами.

И да, полагаю, что важно объявить задействованные в "мостах" сетевые интерфейсы до описания "мостов", в которых они применяются. Интерфейсы, которые присоединены к "мосту", переводятся в режим "promiscuous" и лишаются некоторых обычных для полноценных сетевых интерфейсов атрибутов. Если оставить описание сетевого интерфейса, задействованного в "мосте", ниже описания "моста" как такового, запросто может случится так, что он будет выведен из режима "promiscuous" и его работа в составе "моста" будет невозможна.

# /etc/init.d/networking restart

# dmesg

....
Bridge firewalling registered
device eth0 entered promiscuous mode
br0: port 1(eth0) entering forwarding state
device br0 entered promiscuous mode
....
device eth1 entered promiscuous mode
br1: port 1(eth1) entering forwarding state
device br1 entered promiscuous mode
....

# ifconfig -a

br0 Link encap:Ethernet  HWaddr MAC
    inet addr:10.10.3.10 ....
....

br1 Link encap:Ethernet  HWaddr MAC
....

eth0 Link encap:Ethernet  HWaddr MAC
....

eth1 Link encap:Ethernet  HWaddr MAC
....

Создаём окружение виртуализации:

# mkdir -p /etc/kvm/fnc.d
# mkdir -p /usr/local/etc/kvm/cnf.d
# mkdir -p /var/lib/kvm
# mkdir -p /var/log/kvm
# mkdir -p /mnt/storage0/kvm

Группа "kvm" уже создана в системе, с инсталляцией дистрибутива Qemu-KVM. Если нет, то создаём её:

# groupadd kvm

Добавляем пользователя, от имени которых будет работать система виртуализации:

# useradd --system --shell /bin/false --home-dir /var/lib/kvm --gid kvm kvm

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

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

# usermod --append --groups kvm kvm

Явно добавляем пользователя kvm в группу disk, что бы предоставить ему возможность работать с дисковыми разделами:

# usermod --append --groups disk kvm

Проще всего определить ряд переменных, которые предположительно будут неизменными от машины к машине, в одном конфигурационном файле, расположив её в месте, определённом политикой Debian:

# vi /etc/default/qemu-kvm-custom

# Default configuration file for /etc/init.d/qemu-kvm-control

# Адрес директории динамически включаемых в тело скрипта управления описаний функций
FNCDIR="/etc/kvm/fnc.d"

# Адрес директории индивидуальных конфигураций виртуальных машин
CNFDIR="/usr/local/etc/kvm/cnf.d"

# Адрес директории временных ресурсов виртуальных машин
TMPDIR="/tmp/kvm"

# Адрес файла журнала
LOG="/var/log/kvm/kvm.log"

# Имя и группа пользователя, запускающего виртуальные машины
USER="kvm"
GROUP="kvm"

# Время ожидания (в секундах) между запуском виртуальных машин в пакетном режиме (необходимое для корректного выделения ОЗУ, спада первоначального потока IO-запросов и тому подобного)
RUNTIMEOUT="30"

Вообще, по тщательности проработки окружения Qemu-KVM заметно, что среда виртуализации находится в разработке - многие важные мелочи оставлены "на потом". Например, при инсталляции создаётся соответствующая группа для пользователей подсистемы, однако на субъекты подсистемы право использования таковых этой группой не распространяются - хотя, казалось бы, что может быть логичнее?

Исправим это дополнительным скриптом, необходимость в котором возможно скоро отпадёт:

# vi /etc/init.d/qemu-kvm-preset && chmod ugo+rx /etc/init.d/qemu-kvm-preset

#!/bin/bash

### BEGIN INIT INFO
# Provides:          qemu-kvm-preset
# Required-Start:    $local_fs $syslog $network qemu-kvm
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Script management of virtual machines
# Description:       Script management of virtual machines
### END INIT INFO

# Подключаем первичный конфигурационный файл
source "/etc/default/qemu-kvm-custom"
[ ${?} -ne 0 ] && { echo "Fatal error: missing default custom Qemu-KVM configuration file. Operation aborted."; exit 1; }

# Создаём директорию временных файлов, если таковая отсутствует, и задаём правила доступа
mkdir -p "${TMPDIR}"
chown ${USER}:${GROUP} "${TMPDIR}"
chmod ug+rw "${TMPDIR}"
chmod o-rw "${TMPDIR}"

# Проверяем поддержку аппаратной виртуализации и сетевого виртуального моста путём проверки наличия соответствующих символьных устройств
if [ -c /dev/kvm -a -c /dev/net/tun ]; then
  # Подправляем разрешения для доступа к ресурсам подсистемы виртуализации
  chown root:${GROUP} /dev/kvm
  chmod ug+rw /dev/kvm
  chown root:${GROUP} /dev/net/tun
  chmod ug+rw /dev/net/tun
else
  echo "Fatal error. Missing Qemu-KVM hardware acceleration device or Qemu-KVM virtual device network bridge (TUN). Operation aborted." | tee -a "${LOG}"
  exit 1
fi

# Предоставляем обычному пользователю право запускать утилиты манипулирования сетевыми интерфейсами от имени суперпользователя (прикрепляя утилите suid-бит)
# !!! (слишком грубо, следует использовать setcap) !!!
# # find /sbin /usr/sbin -name "ifconfig" -print0 | xargs --null chmod ug+s
# # find /sbin /usr/sbin -name "tunctl" -print0 | xargs --null chmod ug+s
# # find /sbin /usr/sbin -name "brctl" -print0 | xargs --null chmod ug+s
#
# # find /sbin /bin /usr/sbin /usr/bin -name "kill" -print0 | xargs --null chmod ug+s

# Добавляем KVM возможностей с помощью расширения стандарта POSIX "capabilities"
setcap CAP_NET_ADMIN=ep /usr/bin/kvm
#
setcap CAP_KILL=ep /bin/kill

# Включаем, если таковая доступна, поддержку несущей системой KSM (Kernel Samepage Merging)
[ -f /sys/kernel/mm/ksm/run ] && { echo 1 > /sys/kernel/mm/ksm/run; }

exit 0

Прописываем наш скрипт для нужных уровней исполнения в системах Linux Debian Squeeze/Wheezy:

# update-rc.d qemu-kvm-preset start 10 2 3 4 5 . stop 10 0 1 6 .

Для "Linux Debian Jessie" и Fedora с Systemd способ инициализирования автозапуска уже иной:

# systemctl enable qemu-kvm-preset

Synchronizing state for qemu-kvm-preset.service with sysvinit using update-rc.d...
Executing /usr/sbin/update-rc.d qemu-kvm-preset defaults
Executing /usr/sbin/update-rc.d qemu-kvm-preset enable

В стандарте POSIX имеется механизм так называемых "возможностей" (capabilities), более или менее полноценно реализованный в Linux начиная с ядра "2.6.24", который позволяет наделять приложения, работающие с правами обычных пользователей, рядом привилегий root, не давая им полных прав суперпользователя. Смысл заключается в том, чтобы позволить определенным приложениям, запускаемым с правами обычных пользователей и не имеющим серьезных полномочий в системе, совершать серьёзные и несущие угрозу действия, на которые раньше был способен только root. Такая возможность позволяет отказаться от использования SUID-бита для приложений и в то же время оставить за ним право на выполнение каких-либо привилегированных действий. Поэтому, если приложение когда-нибудь окажется скомпрометированным, злоумышленник не сможет нанести особого вреда системе.

Индивидуальные конфигурации виртуальных машин будем описывать не прямым указанием переменных среды командного интерпретатора BASH, а в файлах с расширением ".cnf", строками формата сходного с тем, в котором задаются системные переменные (sysctl):

# vi /usr/local/etc/kvm/cnf.d/mashine0.cnf

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

....
# Условное имя виртуальной машины, используется в дальнейшем как идентификатор (без пробелов, избегать спецсимволов)
name=mashine0
....

За несколько лет развития BASH-скрипт разросся до невоспринимаемых в один подход размеров и был разбит на автоматически включаемые в основной каркас нижеследующие логические блоки:


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

# vi /etc/init.d/qemu-kvm-control && chmod ugo+rx /etc/init.d/qemu-kvm-control

#!/bin/bash

### BEGIN INIT INFO
# Provides:          qemu-kvm-control
# Required-Start:    $local_fs $syslog $network qemu-kvm-preset
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Script management of virtual machines
# Description:       Script management of virtual machines
### END INIT INFO

# На случай, если скрипт управления запускается от непривелигированного пользователя, объявляем расширенный набор путей для поиска запрашиваемых ресурсов
export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin

# Переопределяем "маску" для создаваемых в рамках управления виртуальными машинами файлами, предписывая доступ на чтение и запись как запускающему скрипт пользователю, так и членам его группы
umask 0007

# Подключаем первичный конфигурационный файл
source "/etc/default/qemu-kvm-custom"
[ ${?} -ne 0 ] && { echo "Fatal error: missing default custom Qemu-KVM configuration file. Operation aborted."; exit 1; }

# Проверяем наличие директории для временных файлов, создавая её в случае необходимости
[ ! -d "${TMPDIR}" ] && { mkdir -p "${TMPDIR}"; }

# Проверяем поддержку аппаратной виртуализации и сетевого окружения путём проверки наличия соответствующих символьных устройств
[ ! -c /dev/kvm -o ! -c /dev/net/tun ] && { echo "Fatal error. Missing Qemu-KVM hardware acceleration device or Qemu-KVM virtual device network bridge (TUN). Operation aborted." | tee -a "${LOG}"; exit 1; }

# Принимаем в переменные с "говорящими" именами входящие аргументы
INSTANCE=${0}
OPERATION=${1}
TARGET=${2}
DATE=`date +"%Y-%m-%d %H:%M:%S"`

# Проверяем, имеется ли конфигурационный файл для целевой виртуальной машины
[ "${TARGET}" != "" -a ! -f "${CNFDIR}/${TARGET}.cnf" ] && { echo "${DATE}: Отсутствует конфигурационный файл ${CNFDIR}/${TARGET}.cnf целевой виртуальной машины ${TARGET}." | tee -a "${LOG}"; exit 1; }

# Перебираем в цикле все объекты (по маске) в целевой директории динамически подключаемых описаний функций
cd "${FNCDIR}"
# for OBJECT in *\.fnc ; do
for OBJECT in $(ls --format=single-column | grep --extended-regexp "*\.fnc$") ; do
  # Включаем в тело скрипта текст обнаруженных фрагментов описаний функций
  [ -f "${FNCDIR}/${OBJECT}" ] && source "${FNCDIR}/${OBJECT}"
done

# Описываем функцию формирования опций окружений виртуальной машины и непосредственного её запуска
function start() {

  # Получаем условное имя виртуальной машины
  NAME=`grep --ignore-case "^name=" "${CNF}" | awk -F = '{print $2}'`

  # Проверяем наличие условного имени виртуальной машины, которое в дальнейшем будет использоваться как её идентификатор
  if [ "${NAME}" == "" ]; then
    echo "${DATE}: Отсутствует условное имя запускаемой виртуальной машины. Операция прервана." | tee -a "${LOGT}"
    return 1
  fi

  # Вызываем функцию предварительной проверки возможности и необходимости запуска
  declare -f start-precheck >/dev/null || { echo "${DATE}: Fatal error! Не определена функция предварительной проверки возможности и необходимости запуска. Operation aborted." | tee -a "${LOGT}"; return 1; }
  #
  start-precheck
  if [ ${?} -ne 0 ]; then
    echo "${DATE}: Виртуальная машина ${NAME} не будет запущена. Операция прервана." | tee -a "${LOGT}"
    return 0
  fi

  # Вызываем функцию предварительной инициализации
  declare -f start-preset >/dev/null || { echo "${DATE}: Fatal error! Не определена функция предварительной инициализации. Operation aborted." | tee -a "${LOGT}"; return 1; }
  #
  start-preset
  if [ ${?} -ne 0 ]; then
    echo "${DATE}: Ошибка предварительной инициализации виртуальной машины ${NAME}." | tee -a "${LOGT}"
    return 1
  fi

  # Вызываем функцию инициализации виртуальных устройств хранения данных
  declare -f start-hdd >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "${DATE}: Notice. Не определена функция инициализации виртуальных дисков. Disk drives will not be available." | tee -a "${LOGT}"
  else
   start-hdd
   if [ ${?} -ne 0 ]; then
     echo "${DATE}: Ошибка инициализации виртуальных дисков машины ${NAME}." | tee -a "${LOGT}"
     return 1
   fi
  fi

  # Вызываем функцию инициализации виртуальных сетевых интерфейсов
  declare -f start-network >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "${DATE}: Notice. Не определена функция инициализации виртуальных сетевых интерфейсов. Virtual network devices will not be available." | tee -a "${LOGT}"
  else
   start-network
   if [ ${?} -ne 0 ]; then
     echo "${DATE}: Ошибка инициализации виртуальных сетевых интерфейсов машины ${NAME}." | tee -a "${LOGT}"
     return 1
   fi
  fi

  # Вызываем функцию инициализации средств управления виртуальной машиной
  declare -f start-management >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "${DATE}: Notice. Не определена функция инициализации средств управления виртуальной машиной. Controls the virtual machine will not be available." | tee -a "${LOGT}"
  else
   start-management
   if [ ${?} -ne 0 ]; then
     echo "${DATE}: Ошибка инициализации средств управления виртуальной машиной ${NAME}." | tee -a "${LOGT}"
     return 1
   fi
  fi

  # Вызываем функцию запуска виртуальной машины
  declare -f start-run >/dev/null || { echo "${DATE}: Fatal error! Не определена функция запуска виртуальной машины. Operation aborted." | tee -a "${LOGT}"; return 1; }
  #
  start-run
  if [ ${?} -ne 0 ]; then
    echo "${DATE}: Ошибка запуска виртуальной машины ${NAME}." | tee -a "${LOGT}"
    return 1
  fi

return ${?}
}

# Определяем функцию остановки виртуальной машины
function stop() {

  # Получаем условное имя виртуальной машины
  NAME=`grep --ignore-case "^name=" "${CNF}" | awk -F = '{print $2}'`

  # Проверяем наличие условного имени виртуальной машины, которое в дальнейшем будет использоваться как её идентификатор
  if [ "${NAME}" == "" ]; then
    echo "${DATE}: Отсутствует условное имя останавливаемой виртуальной машины. Операция прервана." | tee -a "${LOGT}"
    return 1
  fi

  # Вызываем функцию предварительной проверки возможности и необходимости остановки
  declare -f stop-precheck >/dev/null || { echo "${DATE}: Fatal error! Не определена функция предварительной проверки возможности и необходимости остановки. Operation aborted." | tee -a "${LOGT}"; return 1; }
  #
  stop-precheck
  if [ ${?} -ne 0 ]; then
    return 0
  fi

  # Вызываем функцию остановки виртуальной машины путём подачи сигнала ACPI
  declare -f stop-acpi >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "${DATE}: Notice. Не определена функция остановки виртуальной машины путём подачи сигнала ACPI." | tee -a "${LOGT}"
  else
    stop-acpi
    if [ ${?} -ne 0 ]; then
      echo "${DATE}: Notice. Ошибка остановки виртуальной машиной ${NAME} путём подачи сигнала ACPI. Продолжаем попытки остановить виртуальную машину." | tee -a "${LOGT}"
    else
      return 0
    fi
  fi

  # Вызываем функцию остановки виртуальной машины путём подачи сигнала SMB
  declare -f stop-smb >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "${DATE}: Notice. Не определена функция остановки виртуальной машины путём подачи сигнала SMB." | tee -a "${LOGT}"
  else
    stop-smb
    if [ ${?} -ne 0 ]; then
      echo "${DATE}: Notice. Ошибка остановки виртуальной машиной ${NAME} путём подачи сигнала SMB. Продолжаем попытки остановить виртуальную машину." | tee -a "${LOGT}"
    else
      return 0
    fi
  fi

  # Вызываем функцию остановки виртуальной машины путём уничтожения исполняемого процесса (KILL)
  declare -f stop-kill >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "${DATE}: Notice. Не определена функция остановки виртуальной машины путём подачи сигнала KILL." | tee -a "${LOGT}"
  else
    stop-kill
    if [ ${?} -ne 0 ]; then
      echo "${DATE}: Notice. Ошибка остановки виртуальной машиной ${NAME} путём уничтожения исполняемого процесса." | tee -a "${LOGT}"
      return 1
    else
      return 0
    fi
  fi

return ${?}
}

# Определяем функцию предварительной инициализации конфигурационного файла запускаемой виртуальной машины
function start-init() {

  # Выжидаем время, необходимое для корректного выделения ОЗУ, спада первоначального потока IO-запросов и тому подобного, в случае если виртуальные машины запускаются в пакетном режиме и эта - не первая в очереди
  if [ ${RUNNING} -ne 0 ] ; then
    echo "${DATE}: Information. Wait ${RUNTIMEOUT} seconds after running previous virtual mashine..." | tee -a "${LOGT}"
    sleep ${RUNTIMEOUT};
  fi

  # Создаём временный файл журнала событий
  LOGT=$(mktemp "${TMPDIR}/log.XXXXXXXX")

  # Получаем время старта
  DATE=`date +"%Y-%m-%d %H:%M:%S"`

  # Передаём управление функции непосредственного запуска виртуальной машины
  start

  # Если запуск виртуальной машины завершился неудачно, то отсылаем об этом отчёт администратору
  if [ "${?}" -ne "0" ] ; then
    send-report "Error. Virtual mashine running problem!" "$(cat ${LOGT})"
  fi

  # Включаем временный журнал событий в основной и удаляем временный
  cat "${LOGT}" >> "${LOG}"
  rm --force "${LOGT}"

return ${?}
}

# Определяем функцию предварительной инициализации конфигурационного файла останавливаемой виртуальной машины
function stop-init() {

  # Создаём временный файл журнала событий
  LOGT=$(mktemp "${TMPDIR}/log.XXXXXXXX")

  # Получаем время остановки
  DATE=`date +"%Y-%m-%d %H:%M:%S"`

  # Передаём управление функции непосредственной остановки виртуальной машины
  stop

  # Если запуск виртуальной машины завершился неудачно, то отсылаем об этом отчёт администратору
  if [ ${?} -ne 0 ]; then
    echo "${DATE}: Critical error. Работа виртуальной машины ${NAME} не была прекращена. Все попытки остановки были безуспешными." | tee -a "${LOGT}"
    send-report "Error. Virtual mashine stoping problem!" "$(cat ${LOGT})"
  else

    # Если виртуальная машина успешно остановлена, то выключаем её виртуальный сетевой интерфейс
    declare -f stop-network >/dev/null
    if [ ${?} -ne 0 ]; then
      echo "${DATE}: Warning! Не определена функция деактивации виртуальных сетевых интерфейсов." | tee -a "${LOGT}"
    else
      stop-network
    fi
  fi

  # Включаем временный журнал событий в основной и удаляем временный
  cat "${LOGT}" >> "${LOG}"
  rm --force "${LOGT}"

return ${?}
}

# Блок выбора варианта исполнения скрипта
case "$1" in
  "start"|"install")

    # Инициируем переменную счётчика успешно запущенных виртуальных машин
    RUNNING="0"

    if [ "${TARGET}" == "" ]; then
      # Переходим в директорию конфигурационных файлов
      cd "${CNFDIR}"

      # Перебираем в цикле все объекты (по маске) в целевой директории
      for OBJECT in $(ls --format=single-column | grep --extended-regexp "*\.cnf$") ; do

        # Определяем путь к очередному конфигурационному файлу
        CNF="${CNFDIR}/${OBJECT}"

        # Передаём управление функции подготовки окружения переменных виртуальной машины
        start-init

      done
    else
      # Определяем путь к конфигурационному файлу
      CNF="${CNFDIR}/${TARGET}.cnf"

      # Передаём управление функции подготовки окружения переменных виртуальной машины
      start-init
    fi

  ;;
  "stop")

    if [ "${TARGET}" == "" ]; then
      # Переходим в директорию конфигурационных файлов
      cd "${CNFDIR}"

      # Перебираем в цикле все объекты (по маске) в целевой директории
      for OBJECT in $(ls --format=single-column | grep --extended-regexp "*\.cnf$") ; do

        # Определяем путь к очередному конфигурационному файлу
        CNF="${CNFDIR}/${OBJECT}"

        # Передаём управление функции подготовки окружения переменных виртуальной машины
        stop-init

      done
    else
      # Определяем путь к конфигурационному файлу
      CNF="${CNFDIR}/${TARGET}.cnf"

      # Передаём управление функции подготовки окружения переменных виртуальной машины
      stop-init
    fi

  ;;
  "restart")
    echo "${DATE}: Received command to restart the virtual machine."  | tee -a "${LOG}"
    stop-init
    echo "${DATE}: Wait..." | tee -a "${LOG}"
    sleep 1
    start-init
  ;;
  *)
    echo "Usage $0 {install|start|stop|restart} [target]" >&2
    exit 1
  ;;
esac

exit 0

Прописываем наш скрипт для нужных уровней исполнения в системах "Debian Squeeze/Wheezy":

# update-rc.d qemu-kvm-control start 10 2 3 4 5 . stop 10 0 1 6 .

Прописываем наш скрипт для нужных уровней исполнения в системе "Debian Jessie" и Fedora с Systemd:

# systemctl enable qemu-kvm-control

Естественно, перед тем, как запускать виртуальную машину, нужно иметь дисковый раздел, LVM-том или файл, который будет использоваться как виртуальное дисковое устройство.

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

# dd if=/dev/zero of=/mnt/storage0/kvm/mashine0/mashine0.raw bs=1G count=100

В ядре Линукса от v2.6.23 включён патч для поддержки нового системного вызова "fallocate", который позволяет запросить непрерывный кусок пространства в файловой системе для избежания фрагментации. Для начала эта возможность доступна в "ext4" и XFS. Таким образом в некоторых случаях виртуальный RAW-диск можно создать мгновенно:

# truncate -s 100G /mnt/storage0/kvm/mashine0/mashine0.raw

...или так:

# fallocate --length 100GiB /mnt/storage0/kvm/mashine0/mashine0.raw

Как правило, операционные системы семейства "MS Windows" (до 2003 точно) не распознают раздел, если его тип явно не указан заранее, например как FAT или NTFS - потому важно сделать это до начала установки её на виртуальную машину.

Заранее генерируем MAC для нашего виртуального интерфейса (далее его будем использовать везде при запуске целевой виртуальной машины):

$ MACADDR="52:54:$(dd if=/dev/urandom count=1 2>/dev/null | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4/')"; echo $MACADDR

Должно получится что-то вроде этого:

52:54:0c:b8:71:b0

После запуска виртуальной машины можно удостоверится в том, что KVM прослушивает порты, предназначенные для доступа к VNC-серверу и к контрольной консоли KVM:

# netstat -apn

tcp  0  0 0.0.0.0:5901  0.0.0.0:*  LISTEN  5398/kvm
unix  2  [ ACC ]  STREAM  LISTENING  41146  5398/kvm  /tmp/kvm/monitor/mashine0.socket
unix  2  [ ACC ]  STREAM  LISTENING  41147  5398/kvm  /tmp/kvm/monitor/mashine0.local.socket

И да, подключив текущий STDIO к "сокету", на который прицеплена контрольная консоль виртуальной машиной, можно таковой поуправлять:

$ socat STDIO UNIX-CONNECT:/tmp/kvm/monitor/mashine0.socket

QEMU 0.12.5 monitor - type 'help' for more information
(qemu) info version
0.12.5 (qemu-kvm-0.12.5)
(qemu) ^C

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

Также можно будет увидеть, что в списке активных интерфейсов появились новые, через которые виртуальные машины подключатся к "мосту", привязанному, в свою очередь, к физическому интерфейсу eth0:

# dmesg

....
tun: Universal TUN/TAP device driver, 1.6
device tap0 entered promiscuous mode
br0: port 2(tap0) entering forwarding state
device tap1 entered promiscuous mode
br0: port 3(tap1) entering forwarding state
....

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

# brctl show

bridge name bridge id          STP enabled  interfaces
br0         8000.9c8e99f9c7b2  no           eth0
                                            tap0
br1         8000.9c8e99f9c7b4  no           eth1
                                            tap1

Сетевые интерфейсы, созданные для наших виртуальных машин не имеет IP-адресов, так как предназначены только для трансляции трафика между "мостом" и виртуальной машиной:

# ifconfig -a

....
tap0  Link encap:Ethernet  HWaddr MAC
....
tap1  Link encap:Ethernet  HWaddr MAC
....

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

Устанавливаем приложение ротации текстовых файлов с одновременным их сжатием:

# aptitude install logrotate gzip

Создаём конфигурационный файл ротации журнальных файлов для Lotus Notes Domino:

# mkdir -p /etc/logrotate.d
# vi /etc/logrotate.d/kvm

# шаблон указывающий объекты подлежащие "ротации"
/var/log/kvm/*.log {
  # размер журнального файла, после которого он обрабатывается утилитой
  size 1M
  # отсутствие файла не вызывает ошибку
  missingok
  # количество хранимых отработанных резервных копий
  rotate 10
  # указание сжимать отрабатываемые резервные копии
  compress
  # указание не сжимать первую резервную копию, делать это при повторном проходе
  delaycompress
  # указание не отрабатывать пустые файлы
  notifempty
  # добавлять к наименованию файла резервной копии даты в формате "-YYYYMMDD"
  dateext
  # задать права доступа, владельца и группу создаваемого журнального файла
  create 660 kvm kvm
}

Проверяем корректность конфигурационного файла:

# logrotate -d /etc/logrotate.d/kvm

Наводим порядок с правами доступа к ресурсам:

# chown -R kvm:kvm /usr/local/etc/kvm /var/lib/kvm /var/log/kvm /mnt/storage0/kvm
# chmod -R ug+rw /usr/local/etc/kvm /var/lib/kvm /var/log/kvm /mnt/storage0/kvm
# chmod -R o-rwx /usr/local/etc/kvm /var/lib/kvm /var/log/kvm /mnt/storage0/kvm


Заметки и комментарии к публикации:


Оставьте свой комментарий ( выразите мнение относительно публикации, поделитесь дополнительными сведениями или укажите на ошибку )