Создание новой виртуальной машины — это рутина, отнимающая много времени. И чем больше инфраструктура и организация, тем больше процедур, связанных с этим процессом. Мы автоматизировали это процесс с помощью PowerShell.
Добро пожаловать под кат, если вам это интересно.
Программисты не любят делать двойную работу, сисадмины тоже.
Ниже пример автоматизации одного из наших заказчиков.
Мы хотели сделать так, чтобы любой инженер или project-менеджер смог создать новую виртуальную машину с минимальными усилиями и за минимальный срок. У нашего заказчика есть ITSM-система, в данном примере это ServiceNow, мы создали соответствующую web-форму в сервисном каталоге. Для «заказа» новой машины менеджеру необходимо заполнить поля и подтвердить «заказ», после этого запускается цепочка процессов, и на выходе получаем готовую к использованию машину.
Итак, давайте рассмотрим, что нужно определить менеджеру, чтобы создать новую виртуальную машину:
VM Description: описание виртуальной машины
Тут нужны некоторые пояснения. В нашем решении активно используется PowerShell 5.1, поэтому пока Windows-only, в будущем мы постараемся добавить поддержку Unix-машин и перейдем на PowerShell Core.
OS, операционная система. Никаких особых препятствий использовать Windows 2008 (R2) нет, но мы используем 2012R2 или 2016.
VM Size, размер виртуальной машины. У каждого это может быть определено по-своему, в данном примере Small 1CPU-4Gb Ram, Medium 2CPU-8Gb, Large 4-16.
VM Storage, Disk 0 (C:\) имеет фиксированный размер, который вы не можете изменить, доступен только селектор Fast/Slow storage. «Fast» — это может быть Storage Tier с SSD, а «Slow» — это storage на «обычных» HDD (конечно — SAN). Disk1 (Disk2 и далее) также имеют селектор выбора типа Storage, а также поля для ввода желаемого размера в гигабайтах, Letter для раздела и размер кластера (что важно для SQL Server).
Trust, определяем, что машина должна быть Domain-joined или нет, с доступом из Public Network или нет.
Type, тип машины. Почти каждую машину можно определить, как front-end или back-end приложения или же other во всех оставшихся случаях. На основе выбранного типа мы сможем в дальнейшем определить наиболее подходящую подсеть для машины.
Environment, в инфраструктуре заказчика есть два дата центра: Primary (Production) и Secondary (Dev/test), DC связаны между собой быстром каналом связи и обеспечивают отказоустойчивость. По договоренности все виртуальные машины в Primary DC имеют IP-адрес, начинающийся на 10.230, а в Secondary DC — на 10.231.
(SLA) Service Level Agreement, этот параметр влияет на качество обслуживания данной машины.
Приложения. Мы добавили возможность установки и настройки SQL Server. Необходимо выбрать издание, instance name и collation. Также возможно настроить и Web Server роль и многое другое.
Теперь нам нужно определить, как хранить выбранные значения. Мы решили, что наиболее удобный формат — JSON-файл. Как я говорил ранее, в среде заказчика используется ITSM ServiceNow; менеджер, после того как выбрал все необходимы значения, нажимает кнопку «order» и после этого ServiceNow передает все параметры нашему PowerShell-скрипту (на back-end ServiceNow), который и создаст JSON-файл. Выглядит это примерно так:
В теле CreateConfiguration .ps1 скрипта:
В конце экспортируем наш объект в JSON-файл:
Примерный образец конфигурации:
Вы могли заметить, что в веб-форме отсутствовало имя виртуальной машины и IP-адрес. Мы получаем эти значения автоматически следующим образом:
Имя машины, в ITSM ServiceNow есть специальный раздел: CMDB (Configuration Management Data Base), в этой базе хранятся все записи о существующих виртуальных машинах, их статус, команда поддержки и прочее. Мы создали порядка 200 резервных записей со статусом Allocated. Чтобы получить имя для виртуальной машины мы делаем REST-запрос к CMDB и получаем первую «свободную» запись и меняем её статус с Allocated на Pending install.
IP адрес и VLAN, мы развернули IPAM в нашей сети — это встроенная feature в Windows Server 2016, которая позволяет управлять IP-адресами в вашей сети. Вовсе не обязательно использовать все возможности IPAM (DHCP, DNS, AD), а использовать её только как базу данных IP-адресов с потенциальным расширением функционала. Скрипт, который создает JSON файл, делает запрос к IPAM на предмет первого свободного IP адреса в подсети. А подсеть VLAN (х/24 подсеть) определяется на основе выбранных значений SLA, Environment, Trust и Type.
Файл-конфигурация готов, все поля на месте, можно создавать машину. Встает вопрос «как хранить учетные данные для всех наших скриптов?». Мы используем пакет CredentialManager. Этот пакет работает со встроенным Windows Credential Manager API для хранения паролей. Пример создания пароля:
Пароль будет доступен для чтения в пределах данной машины и учетной записи.
У нас есть сервер, на котором хранятся все конфигурации c GIT, теперь мы можем надежно отслеживать все изменения в конфигурациях: кто, что, где и когда.
На этом сервере настроен scheduled task: проверять папку c конфигурациями и писать в Windows Event Log обо всех изменениях.
Через 15 минут scheduled task напишет в Windows EventLog, что обнаружен новый файл-конфигурация.
Пришло время проверить эту конфигурацию. В первую очередь нам нужно убедиться, что файл имеет корректное форматирование:
Если все хорошо, пора приступать к созданию машины и запусить BuildVM.ps1 скрипт.
В BuildVM.ps1 мы проверяем, что файл-конфигурация имеет описание всех характеристик виртуальной машины: size, env, sla, type, storage, ram, network.
Обязательно проверим, есть ли в инфраструктуре машина с таким же именем (CheckVM.ps1).
Подключаемся через VMWare PowerShell CLI к нашей vSphere:
Проверяем, есть ли машина с таким же именем в инфраструктуре
И отключаемся:
Убедимся, что машина также не доступна по WinRM
Если в $VM и $ping пусто, то можно создавать новую машину. (Мы обрабатываем ситуации, когда машина уже создана в ESXi вручную или же эта машина в другом дата-центре.)
Создание виртуальной машины
Найдем соответствующий SLR-кластер:
Проверим, что у нас достаточно места на Datastore:
И достаточно памяти:
Берем наш темплейт
И создаем новую виртуальную машину
Важно подключить сетевой интерфейс к подсети с включенным DHCP.
Запускаем виртуальную машину
И сохраняем описание машины, чтобы потом можно было определить машину на уровне VMWare.
Машина запустилась и теперь мы можем узнать полученный MAC-адрес:
Сохраним это значение в наш JSON-файл
Здесь самое время сделать commit в наш Git, что машина создана и имеет свой уникальный MAC.
Машина начинает инициализироваться (после sysprep), настраивать оборудование и начальную конфигурацию.
Давайте дождемся, когда будет доступна наша машина по WinRM c скриптом EstablishConnection.ps1.
Сначала узнаем какой IP машина получила от DHCP:
А теперь дождемся, когда машина будет доступна по WinRM:
Машина готова к управлению.
Desired State Configuration
Для настройки желаемой конфигурации мы используем часть PowerShell — DSC (Desired State Configuration). В сети есть настроенный DSC Pull Server: dscpull.testdomain.eu.
Ниже конфигурация нашего DSC Pull Server. Хорошая статья по настройке DSC Pull.
Он доступен по адресу: https://dscpull.testdomain.eu:8080
Его Endpoint: https://dscpull.testdomain.eu:8080/PSDSCPullserver.svc
На всех клиентах pull сервера должен быть установлен PowerShell 5.1
Если установлен не PowerShell 5.1:
установить PowerShell 5.1:
В нашей сети также развернут PKI-сервер. Это условие для безопасного шифрование учетных данных сохранённых в DSC mof файлах (Mof файлы — это «язык» на котором общаются Pull Server и его клиенты). Когда клиент пытается зарегистрироваться на Pull Server, необходимо указать Thumbprint сертификата и в дальнейшем Pull Server будет использовать этот сертификат для шифрования паролей. Ниже мы рассмотрим, как это работает.
Импортируем Root CA нашей новой машине:
Для дальнейшей работы нам нужна пара RSA-ключей. Сгенерируем самоподписанный сертификат и временно будем работать с ним.
Теперь мы можем зарегистрироваться на Pull Server:
Отправим первую конфигурацию нашей машине
Сервер автоматически переименуется и перезагрузится. Теперь мы можем выполнить Join Domain.
Вот как выглядит наш mof-файл:
DSC зашифровал учетные данные от сервисной учетки с правами Domain Admin: testdomain.eu\\service_DomainJoin_001 самоподписанным сертификатом. DSC Client своим Private Key расшифровывает учетные данные и применяет все модули конфигурации c указанными доменным учетными данными. В данном случае выполняет Domain Join в указанную organization unit.
Этот модуль добавляет dscstaging в локальные администраторы для дальнейшей настройки.
После перезагрузки, мы сможем зайти на машину с доменными учетными данными.
Ждем, когда сервер получит сертификат от нашего PKI (у нас настроен auto enrollment) и в дальнейшем будем работать с сертификатом, выпущенным нашим PKI.
Теперь снова зарегистрируемся на Pull Server с обновленным thumbprint.
Всё, машина domain-joined, и мы можем использовать её так, как нам удобно.
Установка SQL Server
В JSON- файле описаны требования по MS SQL Server, для установки и настройки SQL Server мы также используем DSC. Вот как выглядит конфигурация:
Где $MSSQL определен:
$MSSQL.InstanceName – всё это указано в нашем Json файле. Применение данной конфигурации выполнит установку MS SQL Server cо всеми обновлениями в папке Updates и перезагрузит сервер, если это необходимо.
Машина готова.
В Service-Now доступно несколько API. Мы используем Rest API.
Для получение списка машин со статусом Allocated используется запрос вида:
instance.service-now.com/cmdb_ci_server_list.do?sysparm_query=install_status=16^u_subtype=^ORDERBYname
В PowerShell это выглядит так:
Первый объект массива и есть нужный нам hostname.
Если машина готова, то можно изменить статус машины в Service-Now, для этого скрипт UpdateCI.ps1:
Для получения таблицы и записей используются REST API GET запросы, для изменения записи PUT/POST запросы, в теле которого поля которые необходимо изменить.
Мы создали удобный инструмент с графическим инструментом подобный Azure Portal, который позволяет управлять on-premises инфраструктурой максимально удобно нам и нашему заказчику.
P.S. 24.12.2018. Кажется это всё устарело? Пора использовать Azure DevOps. В следующей статье я расскажу как всё это сделать с помощью пайплайнов Azure DevOps
Добро пожаловать под кат, если вам это интересно.
Программисты не любят делать двойную работу, сисадмины тоже.
Ниже пример автоматизации одного из наших заказчиков.
Мы хотели сделать так, чтобы любой инженер или project-менеджер смог создать новую виртуальную машину с минимальными усилиями и за минимальный срок. У нашего заказчика есть ITSM-система, в данном примере это ServiceNow, мы создали соответствующую web-форму в сервисном каталоге. Для «заказа» новой машины менеджеру необходимо заполнить поля и подтвердить «заказ», после этого запускается цепочка процессов, и на выходе получаем готовую к использованию машину.
Итак, давайте рассмотрим, что нужно определить менеджеру, чтобы создать новую виртуальную машину:
VM Description: описание виртуальной машины
Тут нужны некоторые пояснения. В нашем решении активно используется PowerShell 5.1, поэтому пока Windows-only, в будущем мы постараемся добавить поддержку Unix-машин и перейдем на PowerShell Core.
OS, операционная система. Никаких особых препятствий использовать Windows 2008 (R2) нет, но мы используем 2012R2 или 2016.
VM Size, размер виртуальной машины. У каждого это может быть определено по-своему, в данном примере Small 1CPU-4Gb Ram, Medium 2CPU-8Gb, Large 4-16.
VM Storage, Disk 0 (C:\) имеет фиксированный размер, который вы не можете изменить, доступен только селектор Fast/Slow storage. «Fast» — это может быть Storage Tier с SSD, а «Slow» — это storage на «обычных» HDD (конечно — SAN). Disk1 (Disk2 и далее) также имеют селектор выбора типа Storage, а также поля для ввода желаемого размера в гигабайтах, Letter для раздела и размер кластера (что важно для SQL Server).
Trust, определяем, что машина должна быть Domain-joined или нет, с доступом из Public Network или нет.
Type, тип машины. Почти каждую машину можно определить, как front-end или back-end приложения или же other во всех оставшихся случаях. На основе выбранного типа мы сможем в дальнейшем определить наиболее подходящую подсеть для машины.
Environment, в инфраструктуре заказчика есть два дата центра: Primary (Production) и Secondary (Dev/test), DC связаны между собой быстром каналом связи и обеспечивают отказоустойчивость. По договоренности все виртуальные машины в Primary DC имеют IP-адрес, начинающийся на 10.230, а в Secondary DC — на 10.231.
(SLA) Service Level Agreement, этот параметр влияет на качество обслуживания данной машины.
Приложения. Мы добавили возможность установки и настройки SQL Server. Необходимо выбрать издание, instance name и collation. Также возможно настроить и Web Server роль и многое другое.
Теперь нам нужно определить, как хранить выбранные значения. Мы решили, что наиболее удобный формат — JSON-файл. Как я говорил ранее, в среде заказчика используется ITSM ServiceNow; менеджер, после того как выбрал все необходимы значения, нажимает кнопку «order» и после этого ServiceNow передает все параметры нашему PowerShell-скрипту (на back-end ServiceNow), который и создаст JSON-файл. Выглядит это примерно так:
.\CreateConfiguration.ps1 -SecurityZone trusted -VMDescription "VM for CRM System" -Requestor "evgeniy.vpro" -OSVersion 2k16 -OSEdition Standard -BuildNewVM -VMEnvironment Prod -VMServiceLevel GOLD -VMSize Medium -Disk0Tier Fast -Disk1Size 50 -Disk1Tier Eco -Disk1Letter D -MSSQLServer -MSSQLInstanceName "Instance1" -SQLCollation Latin1_General_CI_AS -SQLEdition Standard -Disk2Size 35 -Disk3Size 65
В теле CreateConfiguration .ps1 скрипта:
#создаем PowerShell-объект
$config = [ordered]@{}
#И заполняем его входными параметрами.
$config.SecurityZone=$SecurityZone
В конце экспортируем наш объект в JSON-файл:
$ServerConfig = New-Object –TypeName PSObject $config
ConvertTo-Json -InputObject $ServerConfig -Depth 100 | Out-File "C:\Configs\TargetNodes\Build\$($Hostname.ToLower()).json" -Force
Примерный образец конфигурации:
{
"Hostname": "dsctest552",
"SecurityZone": "trusted",
"Domain": "testdomain",
"Requestor": "evgeniy.vpro",
"VM": {
"Size": "Medium",
"Environment": "Prod",
"SLA": "GOLD",
"DbEngine": "MSSQL",
"RAM": 8,
"Storage": [
{
"Id": 0,
"Tier": "Fast",
"Size": "100",
"Allocation": 4,
"Letter": "C"
},
{
"Id": 1,
"Tier": "Eco",
"Size": 50,
"Label": "Data",
"Allocation": 64,
"Letter": "D"
},
{
"Id": 2,
"Tier": "Fast",
"Size": 35,
"Label": "Data",
"Allocation": 64,
"Letter": "E"
},
{
"Id": 3,
"Tier": "Fast",
"Size": 65,
"Label": "Data",
"Allocation": 64,
"Letter": "F"
}
]
},
"Network": {
"MAC": "",
"IP": "10.230.168.50",
"Gateway": "10.230.168.1",
"VLAN": “VLAN168”
},
"OS": {
"Version": "2k16",
"Edition": "Standard",
"Administrators": [
"LocaAdmin",
"testdomain\\ Security-LocalAdmins"
]
},
"OU": "OU=Servers,OU=Staging,DC=testdomain",
"Applications": [
{
"Application": "Microsoft SQL Server 2016",
"InstanceName": "vd",
"Collation": "Latin1_General_CI_AS",
"Edition": "Standard",
"Features": "SQLENGINE",
"Folders": {
"DataRoot": "E:\\MSSQL",
"UserDB": "E:\\MSSQL\\MSSQL11.vd\\MSSQL\\Data",
"UserLog": "E:\\MSSQL\\MSSQL11.vd\\MSSQL\\Log",
"TempDB": "D:\\MSSQL\\MSSQL11.vd\\MSSQL\\TempDB",
"TempDBLog": "D:\\MSSQL\\MSSQL11.vd\\MSSQL\\TempDB",
"Backup": "E:\\MSSQL\\MSSQL11.vd\\MSSQL\\Backup"
},
"MaxMemory": 2147483647
}
],
"Description": "VM for CRM",
"Certificate": {
"File": null,
"Thumbprint": null
},
"Version": 0
}
Вы могли заметить, что в веб-форме отсутствовало имя виртуальной машины и IP-адрес. Мы получаем эти значения автоматически следующим образом:
Имя машины, в ITSM ServiceNow есть специальный раздел: CMDB (Configuration Management Data Base), в этой базе хранятся все записи о существующих виртуальных машинах, их статус, команда поддержки и прочее. Мы создали порядка 200 резервных записей со статусом Allocated. Чтобы получить имя для виртуальной машины мы делаем REST-запрос к CMDB и получаем первую «свободную» запись и меняем её статус с Allocated на Pending install.
IP адрес и VLAN, мы развернули IPAM в нашей сети — это встроенная feature в Windows Server 2016, которая позволяет управлять IP-адресами в вашей сети. Вовсе не обязательно использовать все возможности IPAM (DHCP, DNS, AD), а использовать её только как базу данных IP-адресов с потенциальным расширением функционала. Скрипт, который создает JSON файл, делает запрос к IPAM на предмет первого свободного IP адреса в подсети. А подсеть VLAN (х/24 подсеть) определяется на основе выбранных значений SLA, Environment, Trust и Type.
Файл-конфигурация готов, все поля на месте, можно создавать машину. Встает вопрос «как хранить учетные данные для всех наших скриптов?». Мы используем пакет CredentialManager. Этот пакет работает со встроенным Windows Credential Manager API для хранения паролей. Пример создания пароля:
New-StoredCredential -Target "ESXi" -UserName "testdomain.eu\vmwareadm" -Password "veryultraP@ssw00rd." -Type Generic -Persist LocalMachine
Пароль будет доступен для чтения в пределах данной машины и учетной записи.
$ESXiAdmin = Get-StoredCredential -Type Generic -Target ESXi
У нас есть сервер, на котором хранятся все конфигурации c GIT, теперь мы можем надежно отслеживать все изменения в конфигурациях: кто, что, где и когда.
На этом сервере настроен scheduled task: проверять папку c конфигурациями и писать в Windows Event Log обо всех изменениях.
Через 15 минут scheduled task напишет в Windows EventLog, что обнаружен новый файл-конфигурация.
Пришло время проверить эту конфигурацию. В первую очередь нам нужно убедиться, что файл имеет корректное форматирование:
$Configuration=(Get-Content -Raw $File | Out-String | ConvertFrom-Json)
Если все хорошо, пора приступать к созданию машины и запусить BuildVM.ps1 скрипт.
В BuildVM.ps1 мы проверяем, что файл-конфигурация имеет описание всех характеристик виртуальной машины: size, env, sla, type, storage, ram, network.
Обязательно проверим, есть ли в инфраструктуре машина с таким же именем (CheckVM.ps1).
Подключаемся через VMWare PowerShell CLI к нашей vSphere:
$VmWareAdmin = Get-StoredCredential -Type Generic -Target ESXi
Connect-VIServer -Server "vSphereSrv" -Credential $VmWareAdmin | Out-Null
Проверяем, есть ли машина с таким же именем в инфраструктуре
$VM=Get-VM $server -ErrorAction SilentlyContinue
И отключаемся:
Disconnect-VIServer * -Force -Confirm:$false
Убедимся, что машина также не доступна по WinRM
$ping=Test-NetConnection -ComputerName $Configuration.Hostname -CommonTCPPort WINRM -InformationLevel Quiet -ErrorAction SilentlyContinue
Если в $VM и $ping пусто, то можно создавать новую машину. (Мы обрабатываем ситуации, когда машина уже создана в ESXi вручную или же эта машина в другом дата-центре.)
Пару слов о машине. Это подготовленный образ виртуальный машины, который был финализирован sysprep и сконвертирован в template в нашем vSphere. В образе сохранен локальный администратор с известным нам паролем, эта учетная запись «не слетает» после sysprep, что позволит нам получить доступ к каждой машине из этого темплейта, а позже мы сможем заменить этот пароль в целях безопасности.
Создание виртуальной машины
Найдем соответствующий SLR-кластер:
$Cluster=Get-Cluster -Name $Configuration.VM.SLA
Проверим, что у нас достаточно места на Datastore:
$DatastoreCluster = Get-DatastoreCluster |Where-Object {$_.Name -like $Datastore1Name}
$Datastore1 = Get-Datastore -Location $DatastoreCluster |sort -Property "FreeSpaceGB" |select -Last 1
IF ($Datastore1.FreeSpaceGB -le "200"){
Write-Host -foreground red "STOP: Not enough datastore capacity for DISK" $vdisk.Id
Break
}
И достаточно памяти:
$VMHost = Get-VMHost -Location $Cluster |sort -Property "MemoryUsageGB" |select -First 1
IF ($VMHost.MemoryUsageGB -le "20"){
Write-Host -foreground red "STOP: No enough ESXi host capacity"
Break
}
Берем наш темплейт
$VMTemplate = Get-Template -Name 'Win2016_Std_x64_Template'
И создаем новую виртуальную машину
New-VM -Name $Configuration.Hostname.ToUpper() -VMHost $VMHost -ResourcePool $ResourcePool -Datastore $Datastore -Template $VMTemplate -Location "AutoDeployed VMs"
Важно подключить сетевой интерфейс к подсети с включенным DHCP.
Запускаем виртуальную машину
Start-VM $VM
И сохраняем описание машины, чтобы потом можно было определить машину на уровне VMWare.
Set-Annotation -Entity $VM -CustomAttribute "Change request" -Value $Configuration.Request -Confirm:$false
Set-VM $VM -Notes $Configuration.Description -Confirm:$false
Машина запустилась и теперь мы можем узнать полученный MAC-адрес:
$vMAC = (($VM | Get-NetworkAdapter | Select-Object -Property "MacAddress").MacAddress).Replace(':','')
Сохраним это значение в наш JSON-файл
$Configuration.Network.MAC=$VMAC
ConvertTo-Json -InputObject $Configuration -Depth 100 | Out-File "C:\Configs\TargetNodes\Build\$Hostname.json" -Force
Здесь самое время сделать commit в наш Git, что машина создана и имеет свой уникальный MAC.
Машина начинает инициализироваться (после sysprep), настраивать оборудование и начальную конфигурацию.
Давайте дождемся, когда будет доступна наша машина по WinRM c скриптом EstablishConnection.ps1.
Сначала узнаем какой IP машина получила от DHCP:
#Здесь $MAC = $vMAC
while($isOnline -ne $true){
if((Get-DhcpServerv4Lease -ClientId $MAC -ScopeId $StagingDHCPScope -ComputerName $DHCPServer -ErrorAction Ignore).IPAddress.IPAddressToString){
$tempIP=(Get-DhcpServerv4Lease -ClientId $MAC -ScopeId $StagingDHCPScope -ComputerName $DHCPServer).IPAddress.IPAddressToString
break
}
else{
if($isOnline -ne $true){
Write-Host "`r$i`t" -NoNewline
$i++
}
}
}
А теперь дождемся, когда машина будет доступна по WinRM:
$LocalAdmin = Get-StoredCredential -Type Generic -Target LocalAdmin
$i=0
$isOnline=$false
while($isOnline -ne $true){
if(Invoke-Command -ComputerName $tempIP -ScriptBlock{ Get-ItemProperty -Path "Registry::\HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing" } -Credential $LocalAdmin -ErrorAction SilentlyContinue){
$isOnline=$true
break
}
else{
if($isOnline -ne $true){
Write-Host "`r$i" -NoNewline
$i++
Start-Sleep -Seconds 1
}
}
}
Машина готова к управлению.
Desired State Configuration
Для настройки желаемой конфигурации мы используем часть PowerShell — DSC (Desired State Configuration). В сети есть настроенный DSC Pull Server: dscpull.testdomain.eu.
Ниже конфигурация нашего DSC Pull Server. Хорошая статья по настройке DSC Pull.
Node $NodeName
{
WindowsFeature DSCServiceFeature
{
Ensure = "Present"
Name = "DSC-Service"
}
xDscWebService PSDSCPullServer
{
Ensure = "Present"
EndpointName = "PSDSCPullServer"
Port = 8080
PhysicalPath = "$env:SystemDrive\inetpub\PSDSCPullServer"
CertificateThumbPrint = $certificateThumbPrint
ModulePath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules"
ConfigurationPath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Configuration"
State = "Started"
DependsOn = "[WindowsFeature]DSCServiceFeature"
RegistrationKeyPath = "$env:PROGRAMFILES\WindowsPowerShell\DscService"
AcceptSelfSignedCertificates = $true
UseSecurityBestPractices = $true
}
File RegistrationKeyFile
{
Ensure = 'Present'
Type = 'File'
DestinationPath = "$env:ProgramFiles\WindowsPowerShell\DscService\RegistrationKeys.txt"
Contents = $RegistrationKey
}
}
Он доступен по адресу: https://dscpull.testdomain.eu:8080
Его Endpoint: https://dscpull.testdomain.eu:8080/PSDSCPullserver.svc
На всех клиентах pull сервера должен быть установлен PowerShell 5.1
Если установлен не PowerShell 5.1:
$PSVersionTable.PSVersion.Major –lt 5
установить PowerShell 5.1:
Write-Host "Download PowerShell 5.1"
Invoke-Command -ComputerName $Node -ScriptBlock { [System.Net.ServicePointManager]::SecurityProtocol=[System.Net.SecurityProtocolType]::Tls12;Invoke-WebRequest -Uri "https://dscpull.testdomain.eu:8080/Files/Updates/WMF.msu" -OutFile C:\TEMP\WMF.MSU }
Write-Host "Extract PowerShell 5.1"
Invoke-Command -ComputerName $Node -ScriptBlock {Start-Process -FilePath 'wusa.exe' -ArgumentList "C:\temp\WMF.msu /extract:C:\temp\" -Wait -PassThru }
Write-Host "Apply PowerShell 5.1"
Invoke-Command -ComputerName $Node -ScriptBlock {Start-Process -FilePath 'dism.exe' -ArgumentList "/online /add-package /PackagePath:C:\temp\WindowsBlue-KB3191564-x64.cab /Quiet" -Wait -PassThru }
Write-Host "PowerShell 5.1 has been installed"
В нашей сети также развернут PKI-сервер. Это условие для безопасного шифрование учетных данных сохранённых в DSC mof файлах (Mof файлы — это «язык» на котором общаются Pull Server и его клиенты). Когда клиент пытается зарегистрироваться на Pull Server, необходимо указать Thumbprint сертификата и в дальнейшем Pull Server будет использовать этот сертификат для шифрования паролей. Ниже мы рассмотрим, как это работает.
Импортируем Root CA нашей новой машине:
Invoke-Command -ComputerName $server -ScriptBlock{
$PKI="-----BEGIN CERTIFICATE-----
MIIF2TCCA8GgAwIBAgIQSPIjcff9rotNdxbg3+ygqDANBgkqhkiG9w0BAQUFADAe
****************************************************************
znafMvVx0B4tGEz2PFss/FviGdC3RohBHG0rF5jO50J4nS/3cGGm+HGdn1w/tZd0
a0FWpn9VCOSmXM2It+tSW1f4nZVt6T2kr1ZlTxkDhT7HMSGsrX/XJswzCkDGe3dE
qrVVjNUkhVTaeeBWdujB5J6mcx7YkNsAUhODiS9Cf7FnYnxLFA72M0pijI48P5F0
ShM9HWAAUIrLkv13ug==
-----END CERTIFICATE-----"
$PKI | Out-File RootCA.cer
Import-Certificate RootCA.cer -CertStoreLocation Cert:\LocalMachine\Root | select Thumbprint | Out-Null
} -Credential $LocalAdmin | Out-Null
Для дальнейшей работы нам нужна пара RSA-ключей. Сгенерируем самоподписанный сертификат и временно будем работать с ним.
Теперь мы можем зарегистрироваться на Pull Server:
$DscHostFQDN = [System.Net.Dns]::GetHostEntry([string]$env:computername).HostName
$DscPullServerURL = "https://$($DscHostFQDN):8080/PSDSCPullserver.svc"
$DscWebConfigChildPath = '\inetpub\psdscpullserver\web.config'
$DscWebConfigPath = Join-Path -Path $env:SystemDrive -ChildPath $DscWebConfigChildPath
$DscWebConfigXML = [xml](Get-Content $DscWebConfigPath)
$DscRegKeyName = 'RegistrationKeys.txt'
$DscRegKeyXMLNode = "//appSettings/add[@key = 'RegistrationKeyPath']"
$DscRegKeyParentPath = ($DscWebConfigXML.SelectNodes($DscRegKeyXMLNode)).value
$DscRegKeyPath = Join-Path -Path $DscRegKeyParentPath -ChildPath $DscRegKeyName
$DscRegKey = Get-Content $DscRegKeyPath
[DSCLocalConfigurationManager()]
configuration RegisterOnPull
{
Node $Node
{
Settings
{
ConfigurationModeFrequencyMins = 1440
CertificateID = $Thumbprint
RefreshMode ='Pull'
RefreshFrequencyMins = 1440
RebootNodeIfNeeded = $true
ConfigurationMode ='ApplyAndAutoCorrect'
AllowModuleOverwrite = $true
DebugMode = 'None'
StatusRetentionTimeInDays = 1
}
ConfigurationRepositoryWeb $([string]$env:computername)
{
ServerURL = $DscPullServerURL
RegistrationKey = $DscRegKey
CertificateID = $Thumbprint
ConfigurationNames = @("$hostx")
}
}
}
RegisterOnPull -OutputPath $MetaConfigsStorage
Set-DscLocalConfigurationManager -ComputerName $Node -Path $MetaConfigsStorage -Verbose -Force -Credential $LocalAdmin
Отправим первую конфигурацию нашей машине
Configuration Rename
{
param
(
[Parameter()]
[System.String[]]
$Node,
$hostname
)
Import-DscResource -ModuleName xComputerManagement
Import-DscResource –ModuleName PSDesiredStateConfiguration
Node $Node
{
xComputer JoinDomain
{
Name = $hostname
}
}
}
Rename -Node $Node -OutputPath $DscConfigPath -hostname $hostname
New-DscChecksum $DscConfigPath -Force
Invoke-Command -ComputerName $Node -ScriptBlock{Update-DscConfiguration -Verbose -Wait } -Credential $LocalAdmin -Verbose
Сервер автоматически переименуется и перезагрузится. Теперь мы можем выполнить Join Domain.
Configuration JoinAD
{
param
(
[Parameter()]
[System.String[]]
$Node,
[Parameter(Mandatory = $true)]
[ValidateNotNullorEmpty()]
[System.Management.Automation.PSCredential]
$DomainAdmin,
$hostname,
$domain
)
Import-DscResource -ModuleName xComputerManagement
Import-DscResource –ModuleName PSDesiredStateConfiguration
Node $Node
{
xComputer JoinDomain
{
Name = $hostname
DomainName = $domain
Credential = $DomainAdmin
JoinOU = "OU=Servers,OU=Staging,DC=testdomain,DC=eu"
}
GroupSet LocalAdmins
{
GroupName = @( 'Administrators')
Ensure = 'Present'
MembersToInclude = @( 'testdomain-eu\dscstaging' )
}
}
}
$cd = @{
AllNodes = @(
@{
NodeName = $Node
PSDscAllowPlainTextPassword = $false
PSDscAllowDomainUser=$true
Certificatefile = $CertFile
Thumbprint = $Certificate.ToString()
}
)
}
JoinAD -Node $Node -OutputPath $DscConfigPath -DomainAdmin $DomainAdmin -hostname $hostname -ConfigurationData $cd -domain $domain
New-DscChecksum $DscConfigPath -Force
Invoke-Command -ComputerName $Node -ScriptBlock{Update-DscConfiguration -Verbose -Wait } -Credential $LocalAdmin -Verbose
Вот как выглядит наш mof-файл:
instance of MSFT_Credential as $MSFT_Credential1ref
{
Password = "-----BEGIN CMS-----\nMIIBsgYJKoZIhvcNAQcDoIIBozCCAZ8CAQAxggFKMIIBRgIBADAuMBoxGDAWBgNVBAMMD1dJTi1H\nNFFKTFFQME4xNQIQOQN77pxew75HU6l7GPn99TANBgkqhkiG9w0BAQcwAASCAQAlhFf7Zs2gJbJEnc1DEK2yWbKcO+BEyD2cr6vKHdn\nQ9TrjvbysEOvYjT15o6MccwkMEwGCSqGSIb3DQEHATAdBglghkgBZQMEASoEEEdKJT+GX4IkPezR\nwYncyQiAIAFKxwJocH4ufRsq9L2Ipkp+VQCx2ljlwif6ac4X/PqG\n-----END CMS-----";
UserName = "testdomain.eu\\service_DomainJoin_001";
};
instance of MSFT_xComputer as $MSFT_xComputer1ref
{
ResourceID = "[xComputer]JoinDomain";
Credential = $MSFT_Credential1ref;
DomainName = "testdomain.eu";
SourceInfo = "C:\\Program Files\\WindowsPowerShell\\Scripts\\JoinAD.ps1::34::9::xComputer";
Name = "dsctest51";
JoinOU = "OU=Servers,OU=Staging,DC=testdomain,DC=eu";
ModuleName = "xComputerManagement";
ModuleVersion = "4.1.0.0";
ConfigurationName = "JoinAD";
};
DSC зашифровал учетные данные от сервисной учетки с правами Domain Admin: testdomain.eu\\service_DomainJoin_001 самоподписанным сертификатом. DSC Client своим Private Key расшифровывает учетные данные и применяет все модули конфигурации c указанными доменным учетными данными. В данном случае выполняет Domain Join в указанную organization unit.
GroupSet LocalAdmins
{
GroupName = @( 'Administrators')
Ensure = 'Present'
MembersToInclude = @( testdomain-eu\dscstaging' )
}
Этот модуль добавляет dscstaging в локальные администраторы для дальнейшей настройки.
После перезагрузки, мы сможем зайти на машину с доменными учетными данными.
Ждем, когда сервер получит сертификат от нашего PKI (у нас настроен auto enrollment) и в дальнейшем будем работать с сертификатом, выпущенным нашим PKI.
$vmcert=Invoke-Command -ComputerName $server -ScriptBlock{ return Get-ChildItem -Path cert:\LocalMachine\My | where {$_.EnhancedKeyUsageList.FriendlyName -eq "Document Encryption"-and $_.Issuer -eq "CN=TestDomain Issuing CA, DC=testdomain, DC=eu"} } -ErrorAction Ignore
Теперь снова зарегистрируемся на Pull Server с обновленным thumbprint.
Всё, машина domain-joined, и мы можем использовать её так, как нам удобно.
Установка SQL Server
В JSON- файле описаны требования по MS SQL Server, для установки и настройки SQL Server мы также используем DSC. Вот как выглядит конфигурация:
Configuration $Node{
WindowsFeature "NetFramework35"{
Name = "NET-Framework-Core"
Ensure = "Present"
Source = "\\$DscHostFQDN\Files\Updates"
}
WindowsFeature "NetFramework45"{
Name = "NET-Framework-45-Core"
Ensure= "Present"
}
SqlSetup "MSSQL2012NamedInstance"{
InstanceName = $MSSQL.InstanceName
Features = $MSSQL.Features
ProductKey = $ProductKey
SQLCollation = $MSSQL.Collation
SQLSysAdminAccounts = @('testdomain-EU\SQLAdmins',' testdomain-EU\Backup')
InstallSharedDir = "C:\Program Files\Microsoft SQL Server"
InstallSharedWOWDir = "C:\Program Files (x86)\Microsoft SQL Server"
InstallSQLDataDir = $MSSQL.DataRoot
SQLUserDBDir = $MSSQL.UserDBDir
SQLUserDBLogDir = $MSSQL.UserLogDir
SQLTempDBDir = $MSSQL.TempDBDir
SQLTempDBLogDir = $MSSQL.TempDBLogDir
SQLBackupDir = $MSSQL.BackupDir
SourcePath = $SQLSource
SAPwd = $SA
SecurityMode = 'SQL'
UpdateSource = ".\Updates"
Action = "Install"
ForceReboot = $True
SQLSvcAccount = $SqlServiceCredential
AgtSvcAccount = $SqlServiceCredential
ISSvcAccount = $SqlServiceCredential
BrowserSvcStartupType = "Automatic"
DependsOn = '[WindowsFeature]NetFramework35', '[WindowsFeature]NetFramework45'
}
Где $MSSQL определен:
$MSSQL=$Configuration.Applications | where {$_.Application -eq "Microsoft SQL Server 2012"}
$MSSQL.InstanceName – всё это указано в нашем Json файле. Применение данной конфигурации выполнит установку MS SQL Server cо всеми обновлениями в папке Updates и перезагрузит сервер, если это необходимо.
Машина готова.
Service-Now
В Service-Now доступно несколько API. Мы используем Rest API.
Для получение списка машин со статусом Allocated используется запрос вида:
instance.service-now.com/cmdb_ci_server_list.do?sysparm_query=install_status=16^u_subtype=^ORDERBYname
В PowerShell это выглядит так:
$url="https://instance.service-now.com/api/now/table/cmdb_ci_server?sysparm_query=install_status=16^u_subtype=^ORDERBYname"
$uri= new-object System.Uri("https://instance.service-now.com/")
#берем учетные записи нашей сервисной учетной записи
$credentials = (Get-StoredCredential -Type Generic -Target DSC).GetNetworkCredential()
$credentials = new-object System.Net.NetworkCredential $credentials.UserName, $credentials.SecurePassword
Add-Type -AssemblyName System.Net.Http
$handler = New-Object System.Net.Http.HttpClientHandler
$handler.CookieContainer = New-Object System.Net.CookieContainer
$handler.UseCookies=$true
$handler.Credentials=$credentials
$HttpClient = New-Object System.Net.Http.HttpClient($handler)
$HttpClient.BaseAddress= $uri
$Header = New-Object System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")
$HttpClient.DefaultRequestHeaders.Accept.Clear()
$HttpClient.DefaultRequestHeaders.Accept.Add($Header);
$response=$HttpClient.GetAsync($url)
$respStream=$response.Result.Content.ReadAsStringAsync()
$Servers = $respStream.Result | ConvertFrom-Json
#получаем объекты нашего Configuration Items каталога
$ServersCI=$Servers.result
Первый объект массива и есть нужный нам hostname.
Если машина готова, то можно изменить статус машины в Service-Now, для этого скрипт UpdateCI.ps1:
param(
$CI,
[ValidateSet("Allocated","In use","Pending install")]
$NewStatus='In use'
)
$url="https://instance.service-now.com/api/now/table/cmdb_ci_server?sysparm_query=name=$CI"
$uri= new-object System.Uri("https://instance.service-now.com/")
$credentials = (Get-StoredCredential -Type Generic -Target DSC).GetNetworkCredential()
$credentials = new-object System.Net.NetworkCredential $credentials.UserName, $credentials.SecurePassword
Add-Type -AssemblyName System.Net.Http
$handler = New-Object System.Net.Http.HttpClientHandler
$handler.CookieContainer = New-Object System.Net.CookieContainer
$handler.UseCookies=$true
$handler.Credentials=$credentials
$HttpClient = New-Object System.Net.Http.HttpClient($handler)
$HttpClient.BaseAddress= $uri
$Header = New-Object System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")
$HttpClient.DefaultRequestHeaders.Accept.Clear()
$HttpClient.DefaultRequestHeaders.Accept.Add($Header);
$response=$HttpClient.GetAsync($url)
$respStream=$response.Result.Content.ReadAsStringAsync()
$Servers = $respStream.Result | ConvertFrom-Json
$ServerCI=$Servers.result[0]
$update=@{}
if($NewStatus -eq "In use"){
$update.install_status=1
}
if($NewStatus -eq "Pending install"){
$update.install_status=4
}
$stringcontent = New-Object System.Net.Http.StringContent((ConvertTo-Json -InputObject $update -Depth 100),[System.Text.Encoding]::UTF8, "application/json");
$result=$HttpClient.PutAsync("https://instance.service-now.com/api/now/table/cmdb_ci_server/$($ServerCI.sys_id)", $stringcontent)
Для получения таблицы и записей используются REST API GET запросы, для изменения записи PUT/POST запросы, в теле которого поля которые необходимо изменить.
Мы создали удобный инструмент с графическим инструментом подобный Azure Portal, который позволяет управлять on-premises инфраструктурой максимально удобно нам и нашему заказчику.
P.S. 24.12.2018. Кажется это всё устарело? Пора использовать Azure DevOps. В следующей статье я расскажу как всё это сделать с помощью пайплайнов Azure DevOps