С выходом System Center Virtual Machine Manager 2012 у нас появилась возможность создавать так называемые сервисы: т.е. штамповать виртуальные машины не по одиночке и даже не из шаблонов, а комбинировать разные профили, шаблоны, машины, а после — настраивать взаимосвязь между виртуальным оборудованием. Насмотревшись выступлений на TechEd'е, начитавшись статей и книжек, и, конечно, получив от начальства волшебное задание на одном из проектов, я решил, что имеющихся в SCVMM средств достаточно для создания сервиса полноценной песочницы, т.е. набора виртуальных машин, изолированных от остальной инфраструктуры предприятия, но при этом складно взаимодействующих друг с дружкой.
Но все оказалось не так гладко и красиво, как у ashapo на демонстрациях, поэтому милости прошу под кат за дозой обработки напильником.
Начнем с того, что сервис-ориентированная облачная идеология Microsoft практически идеально ложится на задачу создания песочницы: вот, пожалуйста, тебе пул ресурсов, вот, пожалуйста, тебе песочница, песочница запускается в облаке, люди, например разработчики, сами через портал самообслуживания эту песочницу создают, удаляют, при необходимости масштабируют, получают отчетность по потреблению ресурсов, их не волнует, на каком оборудовании песочница запущена, не волнует, как она резервируется или кем гипервизор управляется.
Но когда дело доходит до практики, до, собственно, автоматизации развертывания сервиса, то возникают ограничения. Чтобы их пояснить понагляднее, я обращусь к знаменитому и избитому одновременно, учебному приложению от Microsoft — Pet Shop .Net, между прочим уже 4-й версии.
Pet Shop — обычное трехзвеньевое клиент-серверное приложение: есть сервер СУБД, есть сервер приложения, и есть веб-морда, которая позволяет потенциальным покупателям заходить и выбирать попугайчиков через браузер. Поэтому, когда мы развертываем это приложение как облачный сервис, мы:
- Развертываем три виртуальных машины, пару пустых, одну с SQL-сервером, они впоследствии станут нашими звеньями;
- На SQL-сервер устанавливем базу данных приложения, прописываем пользователей;
- На сервер приложения раскатываем App-V пакет с приложением магазина, конфигурируем строки подключения к SQL-серверу;
- На веб-морду устанавливаем веб-странички, конфигурируем веб-приложение для подключения к серверу приложения.
Как вообще происходит развертывание? Первым делом при развертывании Virtual Machine Manager создает виртуальную дискетку, а на дискетку записывает файл ответов и при развертывании виртуальной машины, она читает этот файл ответов, присваивает себе имя, устанавливает параметры сетевого интерфейса и даже включает себя в домен Active Directory. На второй стадии SCVMM устанавливает на виртуальные машины своего агента и службы интеграции, а на третьей уже создает ISO-образ c приложениями, базами данных, другими конфигурационными скриптами, монтирует этот ISO-образ в виртуальный привод и запускает с него установку приложений. Как видите, взаимодействие с виртуальной машиной происходит только с помощью примитивных вещей: виртуальной дискетки и виртуального DVD-диска, ибо установка агента, равно как и установка служб интеграции также является установкой с соответствующего ISO-образа в приводе.
Все так? Так, да не так. Дело в том, что для того, чтобы собрать строку подключения к SQL-серверу, необходимо имя этого SQL-сервера как минимум знать. Для того, чтобы собрать строку подключения к приложению, также нужно знать адрес сервера этого приложения. Между тем, в System Center Virtual Machine Manager 2012 есть три способа задать имя сервера:
- Задать его просто текстом, например «SQLServer»;
- Задать его шаблоном с циферками, например «SQLServer##», в этом случае когда будет создаваться первый сервер, SCVMM даст ему имя SQLServer01, второй — SQLServer02 и т.п.;
- Задать его переменной, например "@SQLServer@", в этом случае администратору перед развертыванием в диалоговое окошко придется вбить «SQLServerForPetShop» или что-то вроде того, что и станет впоследствии именем сервера.
Первый вариант отметаем сразу, потому что мы исходим из того, что сервисов у нас много, а значит много машин с одним и тем же именем, а машины с одним и тем же именем иметь плохо. Второй вариант хорош, но появление цифр в конце исключает детерминированность сервера. Проще говоря, на звене 2 мы не знаем, какую именно цифру SCVMM присвоил звену 1. В самом деле, мы знаем, что там в начале SQLServer, а дальше 01? 05? А может 25? Третий вариант хороший, спору нет, но в этом случае администратору самому, лично приходится заботиться об во-первых, уникальности названий серверов, и о во-вторых, соответствии имен серверов конвенции именования, принятой в организации. В общем, выходит, что все три варианта есть, и все три плохие. Как Microsoft рекомендует выбираться из такого рода ситуации? А никак.
Вот эта картинка с 245 страницы книги Айдана Финна Microsoft Private Cloud Computing издательства Sybex наглядно показывает, что чтобы развернуть сервис, Petshop'а, необходимо посмотреть глазками в верхнюю часть экрана, увидеть надпись MidSvr01 и ручками написать эти слова вниз, в поле lobComputerName.
Увеличить
Причем такое вот переписывание буковок с экрана на этот же экран, видимо, официальная позиция Microsoft, которая повторяется, например, здесь, в блогах на Technet.
То есть еще раз: они предлагают увидеть на экране строчку и вбить ручками эту строчку на этом же экране.
Приемлемо ли это для нас? Ну конечно же нет! Информация возникает лишь в одном месте, необоснованное дублирование информации чревато ошибками, и использовать такой подход мы будем чуть реже, чем никогда в жизни.
Вернемся к нашей песочнице. Предположим, что у нас есть контроллер домена в лесу Active Directory и сервер — член этого домена. Теоретически, этого достаточно при условии возможности масштабирования сервиса, т.е. дублирования этого сервера-члена домена еще, еще, и еще.
Формулируя техническое задание к системе песочницы, мы можем сказать, что система должна содержать контроллер домена Active Directory, не связанного с другими лесами какими-либо, в том числе и доверительными, отношениями, а также система должна содержать в себе сервер, включенный в домен Active Directory, который бы без лишних усилий мог бы быть тиражирован. В этом случае при желании мы можем накатить на сервер Exchange, SQL Server, и многое, многое другое.
Создать контроллер домена Active Directory из SCVMM непросто: контроллер домена не поддается виртуализации приложений App-V, поэтому контроллер домена мы будем устанавливать из командной строки. Есть такая утилитка dcpromo, которая создает хоть домен, хоть лес, хоть даже вселенную, и, скажу я вам, вопреки разнообразным статьям из разряда «Наступил конец dcpromo, да здравствует PowerShell!» dcpromo сохранен в Windows Server 2012, но лишь как средство командной строки. Об этом можно почитать, например, здесь. Обратите внимание на строку:
If you run dcpromo /unattend from a command prompt, you can still perform unattended installations that use Dcpromo.exe
То есть, unattend-инсталляцией можно прекрасно пользоваться и в Windows Server 2012, что, собственно, нам и нужно. Хотя, конечно, PowerShell мы все любим неувядающей любовью, и касабельно новшеств именно в 12 сервере у меня даже есть пара статей. Но совместимости ради мы используем файл ответов, потому что он работает как в 2008R2, так и в 2012. Итак, чтобы поднять контроллер домена Active Directory, мы запустим этот cmd-файл, привязанный к профилю приложений в сервисе песочницы:
- %systemroot%\System32\WindowsPowerShell\v1.0\powershell.exe Set-ExecutionPolicy Unrestricted
- %systemroot%\System32\WindowsPowerShell\v1.0\powershell.exe .\CreateAnswerFile.ps1 -Domain %1
- dcpromo /unattend:%systemroot%\Temp\dcpromo.ini
- shutdown -r -t 30
Обратите внимание, с самого начала запускается
- Set-ExecutionPolicy Unrestricted
По той простой причине, что далее у меня неподписанные скрипты PowerShell. Скрипты PowerShell можно и нужно подписывать: от этого зависит ваша безопасность, безопасность вашей организации. О том, как подписывать, уже вроде бы пролетали тут пара топиков, ну а если нет, я с удовольствием опишу этот забавный, но познавательный процесс.
Теперь смотрите, вызывается некий скрипт CreateAnswerFile.ps1, которому как входной параметр дается домен. Вот этот скрипт:
- Param ([string]$Domain)
- [Reflection.Assembly]::LoadWithPartialName(“System.Web”)
- Enable-PSRemoting -force
- import-module servermanager
- add-windowsfeature DNS
- $RandPassLength = [int] 30
- $DSRMPass = [System.Web.Security.Membership]::GeneratePassword($RandPassLength,2)
- $SafeModePWD = $DSRMPass
- $NetBIOSName=($Domain.Split(".")[0]).ToUpper()
- $DCPromoFile = @"
- [DCINSTALL]
- ReplicaOrNewDomain=Domain
- NewDomain=Forest
- NewDomainDNSName=$Domain
- ForestLevel=4
- DomainNetBIOSName=$NetBIOSName
- DomainLevel=4
- InstallDNS=Yes
- ConfirmGc=Yes
- CreateDNSDelegation=No
- DatabasePath=`"C:\Windows\NTDS`"
- LogPath=`"C:\Windows\NTDS`"
- SYSVOLPath=`"C:\Windows\SYSVOL`"
- RebootOnCompletion=No
- SafeModeAdminPassword=$SafeModePWD
- "@
- $DCPromoFile | out-file C:\windows\temp\dcpromo.ini -force
Только опять же, обратите внимание, у меня тут %windir% как C:\windows, вряд ли у вас будет иначе, но всё же я обязан предупредить.
Окей, контроллер домена у нас поднят, это нам по нраву. Но для того, чтобы некий сервер включить в члены этого домена, он должен во-первых, получить адрес этого контроллера домена, во-вторых, получить настройку на своем сетевом интерфейсе: ему в качестве DNS сервера должен быть установлен этот контроллер домена, ну а в-третьих, получить команду на включение в домен. А как нам получить ip адрес DNS-сервера? Ведь по условию задачи, мы находимся в изолированной отовсюду сети и не знаем ничего, даже имени контроллера домена, который разворачивается параллельно с нами.
Есть способ. Помните, что когда-то был развернут SCVMM — гостевой агент? Так вот, во время развертывания этого гостевого агента, внутри него совершенно магическим образом возникает файл настроек с конфигурацией сервиса. Этот файл содержит в себе информацию, во-первых, жутко обрезанную, а во-вторых, доступную лишь учетной записи системы (SYSTEM). Но это, как вы понимаете, файл не спасет, ибо если информация проявилась, то ее уже не уничтожить. Из этого файла, который очень похож на XML, за исключением первых 16 бит, мы извлекаем имя контроллера домена. Да, да, то самое, которое было динамически сгенерировано во время развертывания сервиса. Имея имя, мы можем это имя разрешить в ip адрес, правильно?
Нет, не правильно, потому что у нас нет DNS-сервера, который бы нам ответил, мы попросту не знаем, к кому обращаться. Поэтому мы на помощь призываем старый добрый протокол NetBIOS, который понимает, что такое широковещательный запрос разрешения имени. Иными словами мы, зная имя сервера, кричим громким криком в сеть: «А ну, такой-то и такой-то, отзовись!» И он отвечает: «Вот он я, только не бейте меня пожалуйста!». Мы берем его адрес в качестве DNS-сервера, и далее включаем себя в домен. Mission Accomplished.
Учитывая то, что этот скрипт выполняется каждый раз масштабировании сервиса, мы можем выполнять его бесконечно и таким образом наша песочница превратится в «контроллер домена + (пустой_сервер x N)».
Ну а теперь, пожалуйста, сам скрипт:
- Start-Sleep 90
- $scvmmGuestAgentProcess = Get-Process ScvmmGuestService
- $guestDirectory = $scvmmGuestAgentProcess.Path
- $settingsDirectory = (Split-Path $guestDirectory -Parent)+"\Settings"
- $uname = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
- takeown /F $settingsDirectory /A /R /D Y
- $goalStateFile = $settingsDirectory + "\DesiredGoalState.dat"
- $rule = $uname+":(R)"
- icacls $goalStateFile /grant $rule
- $goalStateText = [string](Get-Content $goalStateFile)
- $goalState = [xml]($goalStateText.Substring($goalStateText.IndexOf("<")))
- $computerNames=($goalState.GuestGoalState.ServiceSettings.ServiceSetting | Where-Object {$_.name -eq "ServiceVMComputerNames"}).value
- $namesArray = $computerNames.Split("[]")
- $dCName = $namesArray[[array]::IndexOf($namesArray,"Domain controller")+1]
- $dCIP = (([System.Net.Dns]::gethostentry($dCName)).AddressList | Where-Object {$_.AddressFamily -eq "InterNetwork"}).IPAddressToString
- $wmi = Get-WmiObject win32_networkadapterconfiguration -filter "ipenabled = 'true'"
- $wmi.SetDNSServerSearchOrder($dCIP)
- $domain = ($goalState.GuestGoalState.ServiceSettings.ServiceSetting | Where-Object {$_.name -eq "Domain"}).value
- $domainUserName = $domain+"\administrator"
- $domainPassword = "Pa`$`$w0rd"
- $credentials = new-object -typename System.Management.Automation.PSCredential -ArgumentList $domainUserName,($domainPassword | ConvertTo-SecureString -AsPlainText -Force)
- Add-Computer -Credential $credentials -DomainName $domain
- Restart-Computer
Обратите внимание, тут у меня логин/пароль указаны явно. Совершенно очевидно, что вам безопасности ради придется их спрятать.
Итак, включив этот скрипт в профиль приложения, и выполнив его при развертывании на N серверах, мы получаем нашу песочницу.
Теперь мы можем нашу песочницу модифицировать как угодно, писать туда программы, удалять оттуда программы, создавать пользователей, удалять, модифицировать схему, атрибуты, делать что нашей душе угодно. Ну а если вдруг ее испортим, то песочницу удалить и в полчаса создать по новой! Или даже две песочницы создать нам не составит труда. Это же просто рай для разработчика и тестировщика!