Статьи

Запуск виртуальной машины с XenServer, Java и Apache CloudStack

Apache CloudStack — это сервер управления с открытым исходным кодом для запуска инфраструктуры частного облака. Обладая поддержкой Citrix, CloudStack имеет поддержку корпоративного класса для масштабирования виртуальных машин на хостах XenServer. Сервер управления CloudStack управляет экземплярами хоста XenServer, используя привязки Java API управления XenServer . Поскольку исходный код CloudStack находится на github , он служит поучительным примером использования Java API XenServer. В этой статье мы сделаем проверку кода в CloudStack и изучим механизм масштабирования частного облака с помощью Xen и Java.

Предполагаемая аудитория для этой статьи — программисты на Java, интересующиеся облаками на основе Java, а также системные администраторы виртуализации, рассматривающие свои системы CloudStack и / или XenServer. Вам не нужно, чтобы CloudStack или XenServer были установлены, чтобы выполнить эту проверку кода, но если вы хотите попробовать запустить программу с использованием Java API XenServer, то вам определенно потребуется установка XenServer и шаблона виртуальной машины. Настройка XenServer и шаблона виртуальной машины выходит за рамки этой статьи. Кроме того, обратите внимание: приведенные здесь выдержки из кода значительно упрощены по сравнению с исходным кодом CloudStack, а не завершенными программами Java для прямой компиляции. Реальный код CloudStack довольно длинный, поэтому я сильно отредактировал оригинал, чтобы получить что-то достаточно короткое, чтобы понять суть.

Итак, с этим введением давайте посмотрим на некоторый код CloudStack.

Когда CloudStack необходимо масштабировать на гипервизоре XenServer, он определяет, какую виртуальную машину он хочет запустить, и встраивает спецификацию в StartCommand . Поэтому наша проверка кода начинается с метода execute(StartCommand) , который находится в исходном файле Java CitrixResourceBase.java . Код CloudStack находится в пакете Java com.cloud и использует библиотеку управления XenServer в пакете Java com.xensource.

Ниже приведены основные принципы метода execute(StartCommand) CloudStack.

 package com.cloud.hypervisor.xen.resource; . import com.xensource.xenapi.Connection; import com.xensource.xenapi.Host; import com.xensource.xenapi.VM; . import com.cloud.agent.api.StartCommand; import com.cloud.agent.api.StartAnswer; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.agent.api.to.VolumeTO; . public class CitrixResourceBase { . public StartAnswer execute(StartCommand cmd) { VirtualMachineTO vmSpec = cmd.getVirtualMachine(); Connection conn = getConnection(); Host host = Host.getByUuid(conn, _host.uuid); VM vm = createVmFromTemplate(conn, vmSpec, host); for (VolumeTO disk : vmSpec.getDisks()) createVbd(conn, disk, vmName, vm, vmSpec.getBootloader()); for (NicTO nic : vmSpec.getNics()) createVif(conn, vmName, vm, nic); startVM(conn, host, vm, vmName); return new StartAnswer(cmd); } . } 

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

Получить подключение Xen и войти

Строка 17 выше показывает, что первым шагом является установление соединения с XenServer. Чтобы подготовить безопасное зашифрованное соединение Xen, просто создайте новый объект Connection через HTTPS, используя IP-адрес экземпляра XenServer:

 import java.net.URL; import com.xensource.xenapi.Connection; ... Connection conn = new Connection(new URL("https://" + ipAddress), NUM_SECONDS_TO_WAIT); 

Имея соединение в руке, затем вы перейдете к входу в систему, что и делает первую передачу XML-RPC на XenServer. С обычными учетными данными XenServer вы можете войти так:

 import com.xensource.xenapi.APIVersion; import com.xensource.xenapi.Connection; import com.xensource.xenapi.Session; ... Session session = Session.loginWithPassword(conn, "username", "password", APIVersion.latest().toString()); 

Вход в систему несколько сложнее, если в пуле ресурсов Master / Slave настроено несколько экземпляров XenServer. Обычно вы должны подключаться только к мастеру, используя метод loginWithPassword , который создает сеанс, действительный на любом хосте в пуле. Если вам нужно подключиться к конкретному ведомому экземпляру, вы должны использовать slaveLocalLoginWithPassword , который дает вам сеанс «аварийного режима», который можно использовать только на этом подчиненном хосте. Обладая пулом XenServer, намерение CloudStack состоит в том, чтобы установить постоянное соединение с мастером. В целях безопасности предполагается, что IP-адрес, к которому он был подключен, может быть подчиненным, поэтому сначала он выполняет локальный вход подчиненного, затем получает IP-адрес главного хоста из этого имени и повторно аутентифицируется на главном. (Для получения дополнительной информации о пулах ресурсов XenServer Master / Slave см. Руководство по восстановлению системы XenServer.)

Код входа в CloudStack находится в XenServerConnectionPool.java . Вот:

 package com.cloud.hypervisor.xen.resource; . import java.net.URL; . import com.xensource.xenapi.APIVersion; import com.xensource.xenapi.Connection; import com.xensource.xenapi.Host; import com.xensource.xenapi.Pool; import com.xensource.xenapi.Session; . public class XenServerConnectionPool { . protected Map<String, Connection> _conns; . public Connection connect(String hostUuid, String poolUuid, String ipAddress, String username, String password, int wait) { Host host = null; Connection sConn = null; Connection mConn = _conns.get(poolUuid); // Cached? if (mConn != null) { try { host = Host.getByUuid(mConn, hostUuid); } catch (SessionInvalid e) { Session.loginWithPassword(mConn, mConn.getUsername(), mConn.getPassword(), APIVersion.latest().toString()); } } else { Connection sConn = new Connection( new URL("https://" + ipAddress), wait); Session.slaveLocalLoginWithPassword(sConn, username, password); Pool.Record pr = Pool.getAllRecords(sConn) .values().iterator().next(); //Just 1 pool, 1 record String masterIp = pr.master.getAddress(sConn); mConn = new Connection(masterIp, wait); Session.loginWithPassword(mConn, username, password, APIVersion.latest().toString()); _conns.put(poolUuid, mConn); } return mConn; } . } 

Обратите внимание, что хост и пул ресурсов XenServer идентифицируются по UUID (универсальному уникальному идентификатору), который является соглашением об именовании объектов, используемым как в XenServer, так и в CloudStack.

Создать виртуальную машину Xen

В строке 19 приведенного выше метода execute(StartCommand) показано, что следующим шагом является создание виртуальной машины из шаблона, указанного в команде. CloudStack StartCommand содержит специфичный для vmSpec типа VirtualMachineTO . («TO» означает объект переноса, который применяется к записям данных, передаваемым в команду api и из нее. Объекты переноса не сохраняются в базе данных.) vmSpec — это спецификация сервера управления облаком, который выполняет запрос на виртуальную vmSpec с определенным характеристики, такие как количество процессоров, тактовая частота, мин / макс RAM. Метод createVmFromTemplate createVmFromTemplate применяет спецификацию vmSpec и создает объект Java VM XenServer. Класс Xen VM представляет гостевую виртуальную машину, которая может работать на экземпляре XenServer. ВМ, возвращенная этим методом, еще не запущена.

Вот основные принципы метода createVmFromTemplate createVmFromTemplate .

 import com.xensource.xenapi.Connection; import com.xensource.xenapi.Host; import com.xensource.xenapi.Types; import com.xensource.xenapi.VM; . import com.cloud.agent.api.to.VirtualMachineTO; . public class CitrixResourceBase { . protected VM createVmFromTemplate(Connection conn, VirtualMachineTO vmSpec, Host host) { String guestOsTypeName = getGuestOsType(vmSpec.getOs()); VM template = VM.getByNameLabel(conn, guestOsTypeName) .iterator().next(); //Just 1 template VM vm = template.createClone(conn, vmSpec.getName()); . vm.setIsATemplate(conn, false); vm.setAffinity(conn, host); //Preferred host vm.removeFromOtherConfig(conn, "disks"); vm.setNameLabel(conn, vmSpec.getName()); vm.setMemoryStaticMin(conn, vmSpec.getMinRam()); vm.setMemoryDynamicMin(conn, vmSpec.getMinRam()); vm.setMemoryDynamicMax(conn, vmSpec.getMinRam()); vm.setMemoryStaticMax(conn, vmSpec.getMinRam()); vm.setVCPUsMax(conn, (long)vmSpec.getCpus()); vm.setVCPUsAtStartup(conn, (long)vmSpec.getCpus()); . Map<String, String> vcpuParams = new HashMap<String, String>(); Integer speed = vmSpec.getSpeed(); vcpuParams.put("weight", Integer.toString( (int)(speed*0.99 / _host.speed * _maxWeight))); vcpuParams.put("cap", Long.toString( !vmSpec.getLimitCpuUse() ? 0 : ((long)speed * 100 * vmSpec.getCpus()) / _host.speed)); vm.setVCPUsParams(conn, vcpuParams); . vm.setActionsAfterCrash(conn, Types.OnCrashBehaviour.DESTROY); vm.setActionsAfterShutdown(conn, Types.OnNormalExit.DESTROY); vm.setPVArgs(vmSpec.getBootArgs()); //if paravirtualized guest . if (!guestOsTypeName.startsWith("Windows")) { vm.setPVBootloader(conn, "pygrub"); } } . // Current host, discovered with getHostInfo(Connection) protected final XsHost _host = ... ; . } 

Обратите внимание, что для каждого метода Xen VM требуется аргумент Connection. Поскольку гость фактически является компьютером, к нему может быть много подключений одновременно, поэтому подключение не является собственностью виртуальной машины. С другой стороны, виртуальная машина может одновременно находиться только на одном хосте XenServer, поэтому тип VM.Record имеет поле residentOn типа Host . Вышеупомянутый метод CloudStack не устанавливает residentOn , но он устанавливает другое поле типа Host , называемое affinity , что является подсказкой для CloudStack, что виртуальная машина «предпочла бы» запускаться на определенном хосте. CitrixResourceBase также имеет поле _host типа XsHost , вспомогательную структуру CloudStack, которая инициализируется с информацией о хосте XenServer и UUID в методе getHostInfo , называемом getHostInfo , отдельно от того, что показано в этой проверке кода.

Вызов removeFromOtherConfig ссылается на параметр карты Citrix other-config , который является объектом карты, который предоставляет произвольные аргументы ключ / значение для команд хоста XenServer. (См. Справочник по командам xe для некоторых команд хоста, в которых используется параметр other-config .) В этом случае необходимо отбросить любые первоначальные предположения о дисках, связанных с виртуальной машиной.

weight и cap VCPUsParams определены в статье Citrix CTX117960 . Более высокий weight приводит к тому, что XenServer выделяет больше виртуальных процессоров этой виртуальной машине, чем другим гостям на хосте cap — это процентное ограничение на количество процессоров, которое может использовать виртуальная машина.

Создайте VBD для каждого диска

Строка 21 метода execute(StartCommand) вызывает метод createVbd для создания VBD (виртуального блочного устройства) в гостевой системе XenServer для каждого диска в vmSpec . VBD — это декоратор для VDI (образа виртуального диска). CloudStack распознает четыре типа томов диска: Root (основной диск), Swap, Datadisk (больше локального хранилища), ISO (CD-ROM). Информация vmSpec диске vmSpec передается параметру метода volume ниже.

Ниже приведен метод createVbd . Это не очень много, полагаясь в основном на уже существующий VDI. Если вы выполнили базовую установку CloudStack, то у вас уже есть VDI. Ключевым вызовом интереса здесь является VBD.create , который распределяет ресурсы на XenServer.

 import com.xensource.xenapi.Connection; import com.xensource.xenapi.VBD; import com.xensource.xenapi.VDI; import com.xensource.xenapi.VM; . import com.cloud.agent.api.to.VolumeTO; import com.cloud.storage.Volume; import com.cloud.template.VirtualMachineTemplate.BootloaderType; . public class CitrixResourceBase { . protected VBD createVbd(Connection conn, VolumeTO volume, String vmName, VM vm, BootloaderType bootLoaderType) { VDI vdi = VDI.getByUuid(conn, volume.getPath()); VBD.Record vbdr = new VBD.Record(); vbdr.VM = vm; vbdr.VDI = vm; vbdr.userdevice = Long.toString(volume.getDeviceId()); vbdr.mode = Types.VbdMode.RW; vbdr.type = Types.VbdType.DISK; if (volume.getType() == Volume.Type.ROOT && bootLoaderType == BootloaderType.PyGrub) vbdr.bootable = true; if (volume.getType() != Volume.Type.ROOT) vbdr.unpluggable = true; VBD vbd = VBD.create(conn, vbdr); return vbd; } . } 

Хотя createVbd возвращает VBD, выполнение вызывающей стороны не сохраняет его. Предположительно, это связано с тем, что CloudStack может позже найти его снова из XenServer, используя статический метод VBD.getAllRecords(Connection) .

Для простоты я отредактировал логику в createVbd включающую Volume.Type.ISO , который предназначен для приводов CD-ROM.

Создайте VIF для каждого сетевого интерфейса

Строка 23 метода execute(StartCommand) вызывает метод createVif для создания нового XenServer Java VIF (виртуальный сетевой интерфейс) для сетевых карт (контроллеров сетевого интерфейса, также называемых сетевыми адаптерами), указанных на гостевой виртуальной машине. Метод кажется поначалу довольно простым:

 import com.xensource.xenapi.Connection; import com.xensource.xenapi.Network; import com.xensource.xenapi.VIF; import com.xensource.xenapi.VM; . import com.cloud.agent.api.to.NicTO; . public class CitrixResourceBase { . protected VIF createVif(Connection conn, String vmName, VM vm, NicTO nic) { VIF.Record vifr = new VIF.Record(); vifr.VM = vm; vifr.device = Integer.toString(nic.getDeviceId()); vifr.MAC = nic.getMac(); vifr.network = getNetwork(conn, nic); VIF vif = VIF.create(conn, vifr); return vif; } . } 

Но на самом деле нет ничего проще, когда дело доходит до сети. Метод getNetwork подвергается нетривиальным попыткам собрать воедино Network объект Xen. Общая процедура по сути такова:

 import com.xensource.xenapi.Connection; import com.xensource.xenapi.Network; import com.xensource.xenapi.VLAN; . import com.cloud.agent.api.to.NicTO; import com.cloud.network.Networks.BroadcastDomainType; . public class CitrixResourceBase { . protected Network getNetwork(Connection conn, NicTO nic) { BroadcastDomainType nicType = nic.getBroadcastType(); Network network = null; Network.Record nwr = new Network.Record(); if (nic.getBroadcastType() == BroadcastDomainType.Native || nic.getBroadcastType() == BroadcastDomainType.LinkLocal) { network = ... } else if (nicType == BroadcastDomainType.Vlan) { network = ... } else if (nicType == BroadcastDomainType.Vswitch) { network = ... } return network; } . } 

Выше приведены основные сетевые сценарии: Native / Link-Local, Vlan и Open vSwitch.

Собственная сеть означает, что гостевая виртуальная машина будет передавать простой трафик на сетевой адаптер. В этом случае CloudStack просто принимает к сведению uuid XenServer для гостевого трафика на этой виртуальной машине и передает его в качестве аргумента конструктору Xen Network . Как упоминалось выше, CitrixResourceBase вызвал getHostInfo для получения UUID XenServer и сохранил их в поле с именем _host .

Link-Local network, формально определенная в RFC 3927 , предназначена для настройки сети, при которой две машины напрямую соединяются кабелями Ethernet без промежуточного коммутатора или маршрутизатора. Обычно вы не конфигурируете свое оборудование таким образом, но на самом деле CloudStack использует локальную адресацию для подключения хоста XenServer к специальной управляющей виртуальной системе, называемой вторичной виртуальной памятью. Когда эта виртуальная машина запускается, она попадает в этот случай, и, поскольку CloudStack уже имеет свой uuid сетевого адаптера, хранящийся в _host имеет смысл обрабатывать случай Link-Local в том же блоке кода, что и для Native network:

 protected Network getNetwork(Connection conn, NicTO nic) { ... if (nic.getBroadcastType() == BroadcastDomainType.Native || nic.getBroadcastType() == BroadcastDomainType.LinkLocal) { String uuid = null; switch (nic.getType()) { case Guest: uuid = _host.guestNetwork; break; case Control: uuid = _host.linkLocalNetwork; break; case Management: uuid = _host.privateNetwork; break; //other cases not shown } network = Network.getByUuid(conn, uuid); } ... 

Благодаря Vlan CloudStack делает имя сети уникальным, добавляя тег, позволяющий виртуальным локальным сетям сосуществовать в одной сети. CloudStack использует дополнительный прием добавления временной метки к имени Vlan в случае кластерных хостов XenServer, одновременно пытающихся создать тот же Vlan; они будут выбраны для выбора Vlan, созданного с самой ранней отметкой времени.

 protected Network getNetwork(Connection conn, NicTO nic) { ... else if (nicType == BroadcastDomainType.Vlan) { long tag = Long.parseLong(nic.getBroadcastUri().getHost()); nwr.nameLabel = "VLAN-" + network.getNetworkRecord(conn).uuid + "-" + tag; nwr.tags = new HashSet<String>(); nwr.tags.add(generateTimeStamp()); network = Network.create(conn, nwr); VLAN.create(conn, network.getPif(conn), tag, network); } ... 

Open vSwitch , как и сам CloudStack, является еще одним продуктом виртуализации с открытым исходным кодом, поддерживаемым Citrix. Когда гостевая сетевая карта маршрутизируется с помощью Open vSwitch, CloudStack создает VIF в управляющем домене XenServer («dom0») и временно подключает его, что создает сетевой мост с базовым PIF (физическим сетевым интерфейсом). Open vSwitch поддерживает свой собственный Vlan, или вы можете выбрать сеть в туннельном стиле для базового сетевого интерфейса.

 protected Network getNetwork(Connection conn, NicTO nic) { ... else if (nicType == BroadcastDomainType.Vswitch) { String nwName = null; if (nic.getBroadcastUri().getAuthority().startsWith("vlan") nwName = "vswitch"; else { nwName = "OVSTunnel" + Long.parseLong(nic.getBroadcastUri().getHost()); nwr.otherConfig = mapPair("ovs-host-setup", ""); } nwr.nameLabel = nwName; network = Network.create(conn, nwr); VM dom0 = null; for (VM vm : Host.getByUuid(conn, _host.uuid) .getResidentVMs(conn)) { if (vm.getIsControlDomain(conn)) { dom0 = vm; break; } } VIF.Record vifr = new VIF.Record(); vifr.VM = dom0; vifr.device = getLowestAvailableVIFDeviceNum(conn, dom0); vifr.otherConfig = mapPair("nameLabel", nwName); vifr.MAC = "FE:FF:FF:FF:FF:FF"; vifr.network = network; VIF dom0vif = VIF.create(conn, vifr); dom0vif.plug(conn); //XenServer creates a bridge dom0vif.unplug(conn); } ... 

Запустите ВМ

Наконец, в строке 24 execute(StartCommand) мы видим вызов метода startVM . Единственная задача startVM — вызвать метод Xen VM.startOnAsync . Это потенциально длительная операция, поэтому она возвращает объект Task Xen, который CloudStack будет отслеживать, ожидая завершения работы виртуальной машины при запуске. startOnAsync также принимает два логических флага, которые в этом случае устанавливаются для запуска виртуальной машины в рабочем состоянии (без приостановки) и для принудительного запуска независимо от того, выглядит ли текущая конфигурация виртуальной машины иначе, чем при ее последнем запуске.

Если вы игнорируете обработку try / catch, процесс запуска ВМ хорош и короток:

 import com.xensource.xenapi.Connection; import com.xensource.xenapi.Host; import com.xensource.xenapi.Task; import com.xensource.xenapi.Types; import com.xensource.xenapi.VM; . public class CitrixResourceBase { . void startVM(Connection conn, Host host, VM vm, String vmName) { boolean startPaused = false; boolean force = true; Task task = vm.startOnAsync(conn, host, startPaused, force); while (task.getStatus(conn) == Types.TaskStatusType.PENDING) { Thread.sleep(1000); } if (task.getStatus(conn) != Types.TaskStatusType.SUCCESS) { task.cancel(conn); throw new Types.BadAsyncResult(); } } . } 

И там у нас это есть! В ходе этой проверки кода мы рассмотрели код Java, в котором CloudStack установил соединение с гипервизором XenServer, вошел в систему, создал гостевую виртуальную машину, выделил ее VBD и VIF и запустил гостевой компьютер. Совершенно новая виртуальная машина запущена и работает в частном облаке.