Как стать автором
Обновить

Задаём виртуальной машине IP по MAC без использования DHCP

Время на прочтение9 мин
Количество просмотров35K

В статье рассказывается о использовании скриптов для CentOS и Windows XP, которые устанавливают IP в соответствии с MAC сетевого интерфейса VM, а также о сложностях управления сетевым интерфейсом в Windows

Система, которую мы разрабатываем, очень активно работает с виртуальными машинами. Когда ядру системы требуется очередная машина, копируется шаблонный образ, и запускается эта копия. Таким образом, одновременно может работать очень много копий по сути одной и той же машины.

Конечно, в момент запуска каждая виртуальная машина идентична шаблонной. В том числе наследуются и установленные параметры сети. Все виртуальные машины работают в одной подсети, а значит, они не должны пользоваться тем статическим IP, который достался им от шаблонной машины — иначе будут возникать конфликты. То есть каждая машина должна получить собственный IP. Казалось бы, решение очень простое — использовать DHCP сервер и динамические IP.

Однако, есть и другой вариант, о котором я расскажу в этой статье.

1. Для чего нам нужны виртуальные машины
2. Проблема одинаковых статических адресов при запуске нескольких клонов VM
3. Кодируем IP в MAC
4. Выделение IP по MAC: DHCP
5. Выделение IP по MAC: скрипт mac2ip
a) Linux
b) Windows
6. Замечания

Update
Update 2

Спойлер: собственно, самое интересное — в пунктах 3) и 5), остальное — для тех, кто захочет увидеть всю картину.

1. Для чего нам нужны виртуальные машины


Наш проект, Nerrvana, выполняет функциональные тесты сайтов в разных браузерах. Тесты эти работают со специальным фреймворком для функционального тестирования — Selenium, который позволяет эмулировать действия пользователя в брaузере (клики по элементам, движения мышью, ввод символов, чтение текста), делать скриншоты страниц и некоторые другие вещи.
Тест сайта представляет собой последовательность действий, которые мог бы сделать на сайте пользователь, и проверок, что результат этих действий — точно такой, как ожидается. Например, простейший тест — логин на сайт. Необходимо открыть страницу логина, ввести логин, пароль, нажать «Ввод», и убедиться, что мы залогинены — скажем, увидев стандартное приветствие. Все знают, что браузеры могут совершенно по-разному отображать и работать с одной и той же страницей, и поэтому имеет смысл выполнить одинаковые тесты в наиболее популярных браузерах. Как уже говорилось, именно этой работой и занимается наша система.

Тесты в выбранных браузерах выполняются одновременно и совершенно независимо друг от друга. Выполнение теста в одном из браузеров мы назвали спеком (speck). То есть, допустим, если я хочу выполнить тест логина на браузерах IE 8 и FF 3.6, наша система сделает два независимых спека — выполнит код тестов с использованием выбранных браузеров. Не сильно вдаваясь в подробности, скажу, что для работы каждого спека мы создаём как минимум две виртуальные машины. Одна машина, «хаб», будет заниматься собственно выполнением тестов — там есть Java и PHP, на которых должны быть написаны тесты. На второй машине, «тестере», работает Selenium RC и нужный браузер. Через Selenium RC происходит взаимодействие между тестами и браузером. После выполнения каждого спека виртуальные машины, на которых он работал, уничтожаются.

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

2. Проблема одинаковых статических адресов при запуске нескольких клонов VM


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

И тут появляется некоторая проблема.
Естественно, в момент запуска каждая виртуальная машина абсолютно идентична шаблонной — ведь все данные хранятся в образе, в который нельзя внести изменений. В том числе наследуются и установленные параметры сети. Так как все виртуальные машины работают в одной подсети, совершенно очевидно, что они не должны пользоваться тем статическим IP, который достался им от шаблонной машины — иначе будут возникать конфликты адресов. То есть каждая машина должна динамически получить собственный IP.
Казалось бы, решение очень простое — использовать DHCP сервер, который и выдаст каждой машине уникальный адрес.

Однако, не всё так просто. Дело в том, что ядро системы активно взаимодействует с виртуальными машинами. Оно должно проделать просто кучу работы с ними: например, убедиться, что виртуальные машины успешно стартовали, запустить Selenium RC на тестере, загрузить на хаб и выполнить сами тесты, следить за их выполнением, а потом получить обратно результаты (скриншоты, логи и т.д.). Работа ведётся через ssh.
Т.е. ядро системы, как ни крути, должно знать IP-адреса машин, которые только что были запущены по её требованию.

Мы видели два подхода к решению задачи:
1) После того, как виртуальная машина поднялась, она получает случайный адрес от DHCP, и затем каким-то образом регистрирует себя в базе — т.е. указывает, что я — машина такого-то типа, получила от DHCP такой-то адрес.
2) Каким-то образом ядро даёт понять виртуальной машине, какой адрес ей следует использовать, т.е. соответствие IP — виртуальная машина имеется ещё до запуска машины.

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

3. Кодируем IP в MAC


Как же воздействовать на ещё не запущенную машину, чтобы сообщить ей будущий ip-адрес? Мы нашли такой способ: при запуске виртуальной машины можно задать MAC-адрес виртуальной сетевой плате. А сама виртуальная машина может в любой момент его узнать. То есть MAC можно использовать как носитель информации о IP (или о чём-нибудь ещё).

Как именно мы будем кодировать IP в мак-адресе? Для виртуализации мы используем Xen, и виртуальные сетевые платы xen должны иметь MAC, который выглядит так: 00:16:3E:XX:XX:XX, где 00:16:3E — код производителя сетевой платы. Последние три байта мы можем использовать по своему усмотрению (конечно, помня о том, что MAC должен быть уникальным).
Предположим, что наша система будет работать в подсети 10.0.0.0/8, и поэтому логичное решение — использовать для трёх последних байтов MAC значения, соответствующие трём последним байтам IP адреса, который мы хотим закодировать.
То есть для адреса 10.1.1.3 мы будем использовать MAC 00:16:3E:01:01:03.

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

4. Выделение IP по MAC: DHCP


Первый способ довольно очевиден. Мы запустим DHCP сервер, который будет настроен так, чтобы выдавать IP по MAC-адресу в соответствии с описанным способом кодирования.

Сценарий работы будет выглядеть так:
1) Ядру требуется VM.
2) Ядро просматривает пул IP-адресов, выбирает первый незанятый IP (например, 10.4.0.15), и преобразует его в MAC (00:16:3E:04:00:0F)
4) Ядро копирует шаблонный образ VM нужного типа, подготавливает файл конфигурации VM (в котором указывает в том числе и полученный MAC)
5) Ядро запускает VM
6) VM обращается к DHCP за адресом, тот сверяется с таблицей соответствия MAC/IP, и выдаёт IP 10.4.0.15.
7) Ядро в это время периодически пингует 10.4.0.15, и, получив ответ, начинает работать с виртуальной машиной (конечно, предварительно дождавшись старта sshd)

- требуется DHCP сервер
- если мы переедем в другую подсеть или получим другой пул IP-адресов, придётся менять конфигурацию не только ядра, но и DHCP
+ используется стандартный подход к получению IP

5. Выделение IP по MAC: скрипт mac2ip


Второй способ менее очевиден и требует некоторой дополнительной работы.
Он заключается в том, что VM при старте выполнит специальный скрипт, который получит MAC, преобразует его в IP, и назначит сетевой плате. Сценарий работы, таким образом, будет практически таким же — изменится только пункт 6. Он будет выглядеть так:

6) VM вычисляет свой IP на основании своего MAC, и устанавливает его перед стартом интерфейса.

+ не требуется дополнительное звено в виде DHCP и хранения там таблицы соответствия MAC-IP.
- для каждой ОС потребуется свой скрипт mac2ip

Мы реализовали именно этот вариант.
Мы работаем с виртуальными машинами с CentOS 5.6 и Windows XP PRO SP3, и поэтому нам нужно было два скрипта mac2ip — для каждой из систем.
Рассмотрим оба скрипта.

a) Linux

#!/bin/bash

# первый байт всегда будет равен 10
IP1=10

IFCFG=/etc/sysconfig/network-scripts/ifcfg-eth0
NETWORK=/etc/sysconfig/network

case "$1" in
*start)
;;
*)
exit
;;
esac

# получаем MAC и проверяем, что мы его таки получили
MAC=$(ifconfig eth0|grep HWaddr|awk '{print $NF}'|grep ^00:16:3E)
if [[ -z "$MAC" ]] ; then
echo "Can't determine MAC address" >&2
exit 1
fi

# преобразуем MAC в IP
set -- $(echo $MAC|awk -F: '{print $4,$5,$6}')
IPADDR=${IP1}.$((0x$1)).$((0x$2)).$((0x$3))

# меняем настройки интерфейса
sed -i -e "s/^IPADDR.*/IPADDR=$IPADDR/" $IFCFG
sed -i -e "/^HWADDR/d" $IFCFG
sed -i -e "s/^HOSTNAME.*/HOSTNAME=localhost/" $NETWORK

И заставляем запускаться этот скрипт до запуска network.

b) Windows

Та же самая работа в Windows XP делается куда более заумными путями. Возможно, со временем найдётся более эффективный способ преобразования MAC в IP.

Первая проблема, с которой я столкнулся — это невозможность относительно лёгкими путями изменить адрес интерфейса ДО его включения. Таким образом, шаблонная VM должна иметь выключенный по умолчанию «сетевое подключение», иначе две одновременно запущенные копии Windows сразу после запуска попробуют использовать один и тот же адрес (он статический, т.к. для VM мы не используем DHCP).

Ок, это не проблема — выключить интерфейс. Однако утилита getmac, которую мы будем использовать для получения мак-адреса сетёвки, не может вернуть MAC для того интерфейса, который выключен! Поэтому нам придётся сначала присвоить интерфейсу случайный IP, включить его, узнать MAC, и уже тогда установить желаемый IP.

Для манипуляций с устройствами используется утилита devcon.
Вот как это выглядит:
@echo off
SET MAC=
SET IP=
SET MASK=255.255.255.0
SET GATEWAY=

rem получаем случайный IP. Этот IP будет использоваться в течении пары секунд,
rem и поэтому для обеспечения уникальности достаточно сгенерировать случайно два последних байта.
rem 100 - смещение, которое гарантирует, что мы в любом случае
rem не воспользуемся реально используемыми IP.
set /A TEMP_THIRD_BYTE=100+%RANDOM:~0,2%
set /A TEMP_FOURTH_BYTE=100+%RANDOM:~0,2%
set TEMP_IP="192.168.%TEMP_THIRD_BYTE%.%TEMP_FOURTH_BYTE%"
set TEMP_GATEWAY="192.168.%TEMP_THIRD_BYTE%.1"

rem устанавливаем параметры интерфейса
netsh interface ip set address local static %TEMP_IP% %MASK% %TEMP_GATEWAY% 1

rem включаем интерфейс. для этого мы используем
C:\devcon\i386\devcon enable *VEN_10E*

rem получаем три последних байта MAC
FOR /F "Tokens=4-6 Delims=- " %%d in ('getmac^|find "Device\Tcpip_"') do (
set /a dec_d=0x%%d
set /a dec_e=0x%%e
set /a dec_f=0x%%f
)

rem подготавливаем нужный IP и шлюз
SET IP=10.%dec_d%.%dec_e%.%dec_f%
SET GATEWAY=10.%dec_d%.0.1

rem меняем параметры интерфейса на реальные
netsh interface ip set address local static %IP% %MASK% %GATEWAY% 1
netsh interface ip set dns local static %GATEWAY%

Добавляем этот скрипт в автозагрузку — например, так (требуется перезагрузка):
reg ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /v "mac2ip" /t REG_SZ /d "c:\init\mac2ip.bat"

Таким образом, после выполнения этих скриптов виртуальная машина получит заданный ядром адрес, и будет готова к работе.
Ядро узнает об этом по заработавшему пингу к этому адресу.

Возможно, эта схема добавит наглядности (по клику — полный размер в новом окне):


6. Замечания


Используемый нами способ — со скриптом mac2ip в автозагрузке — оказался довольно медленным в Win XP. Во всяком случае, текущая его реализация делает свою работу 10-15 секунд. При этом время, которое уходит от старта виртуальной машины до начала выполнения скрипта mac2ip, составляет 15-20 секунд.

Однако мы не спешим перейти на использование первого способа (DHCP с привязкой IP к MAC), потому что:

— во-первых, VM у нас завершаются не штатным для них способом (т.е. centos/windows не выполняют завершение работы), а выполнением virsh destroy для VM (всё равно, что питание выдернуть). Это позволяет экономить много времени, а целостность использованной VM нас всё равно не интересует — она будет немедленно удалена после использования. Так вот, арендованный VM адрес не будет в этом случае освобождён сразу, а будет освобождён по истечении default-lease-time DHCP. Это значит, что мы не сможем сразу же запустить VM с таким же MAC (и таким же IP). Вряд ли установка default-lease-time слишком маленьким (секунды) — хорошая идея. Более реальный вариант — изучение и использование OMAPI/omshell, и с их помощью удалять ненужные записи DHCP сразу после остановки VM.

— во-вторых, для Linux получение адреса от DHCP будет происходить медленнее, чем текущий вариант с назначением статического адреса.

— в третьих — конечно, обнаружатся и другие подводные камни.

Так что относительно медленная работа текущей версии скрипта в Windows — недостаточная причина для того, чтобы перейти на использовать варинанта с DHCP.

Идеальным решением было бы ускорение работы скрипта mac2ip для Windows. Буду рад советам — поскольку это мой первый опыт в управлении сетевым интерфейсом windows из скриптов, то, возможно, уже имеется велосипед.


Update: Во-первых, The_Kf открыл глаза на банальный способ получения MAC на выключенном интерфейсе с помощью ipconfig.
Во-вторых, gribozavr провёл эксперимент, который показал, что заботиться о lease-time арендованного IP вообще не надо, потому что при привязке IP к MAC DHCP выдаст IP в любом случае машине с тем же MAC, даже ели аренда не истекла. Также он указал на stateless autoconfiguration из IPv6.
В-третьих, при общении с akshakirov я внезапно понял, почему сразу более пристально не смотрели в сторону DHCP.

Update 2: в-четвёртых, amarao предложил использовать xenstore для передачи IP в VM. В гостевой Linux-машине для этого просто надо установить xenstore-utils, однако для винды, возможно, потребуется написать утилиту для чтения из xenstore.
Теги:
Хабы:
Всего голосов 37: ↑33 и ↓4+29
Комментарии74

Публикации

Истории

Работа

Ближайшие события