ICL Services corporate blog
System administration
PowerShell
IT Infrastructure
Virtualization
4 October 2018

Как автоматизировать создание виртуальных машин? Рассказываем подробно

Tutorial
Создание новой виртуальной машины — это рутина, отнимающая много времени. И чем больше инфраструктура и организация, тем больше процедур, связанных с этим процессом. Мы автоматизировали это процесс с помощью 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 -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

+7
10.3k 96
Comments 30