Desired State Configuration (DSC) — инструмент управления конфигурацией сервера. С его помощью можно настроить сервер (внести изменения в реестр, копировать файлы, установить и удалить компоненты), проконтролировать текущее состояние настроек и быстро откатиться до базовых настроек.

DSC интересен тем, кто придерживается DevOps-подхода. Этот инструмент хорошо укладывается в парадигму Infrastructure as a Code: разработчики могут вносить свои требования в конфигурацию и включать ее в систему управления версиями, а команды — развертывать код без использования «ручных» процессов.

Совместно со Станиславом Булдаковым из Райффайзенбанка мы объединили наш опыт работы с движком DSC и разделили его на 2 статьи. В первой мы разберем основные принципы работы и познакомимся с особенностями использования на практических примерах:

  • «распакуем коробку» с движком DSC, посмотрим, какие ресурсы есть по умолчанию, и покажем, где взять дополнительные ресурсы;
  • разберем, как описывать  конфигурацию в DSC; 
  • узнаем, как встроенный агент Local Configuration Manager применяет конфигурации на сервере, покажем, как он настраивается с помощью метаконфигураций;
  • перейдем к более сложным случаям настройки: частичным конфигурациям и конфигурациям-заглушкам.

А про настройку и особенности работы в режимах Push и Pull можно узнать во второй статье.


DSC — легковесный и быстрый движок. Например, c его помощью можно намного быстрее установить  .NET Framework 3.5 на виртуальные машины.  Вот что помогает ему ускорять процесс настройки сервисов:

  • Доступность «из коробки» на всех Windows с предустановленным PowerShell. 
    DSC входит в привычный нам PowerShell и базируется на штатных механизмах Windows Management Framework. С Linux чуть сложнее. Как минимум, нужно будет доустановить сам PowerShell. 
  • Декларативный, а не императивный способ управления целевыми хостами. 
    Мы описываем, какие настройки должны быть выполнены, но не описываем, как это должно быть сделано. Движок самостоятельно разберется с этим с помощью ресурсов DSC. 
  • Идемпотентность, то есть способность выдавать один и тот же результат при многократных вызовах.
    DSC удобно использовать для аудита конфигурации и приведения настроек к тем, которые заранее определены для сервера. Эта особенность гарантирует неизменность настроек и возможность быстро откатиться к ним.

Конфигурации всегда выполняются последовательно, без условий и ветвлений. Поэтому кратко алгоритм работы DSC выглядит так:

  1. Конфигурируем Local Configuration Manager (LCM). Это встроенный агент, который отвечает за применение конфигураций на сервере.  Мы передаем ему, как заявленные конфигурации должны работать и в каком порядке. 
  2. Если необходимы дополнительные ресурсы, заранее их устанавливаем и подключаем.
  3. В декларативной форме пишем порядок конфигурации. 
  4. Транслируем конфигурацию в формат MOF-файла.
  5. Отправляем конфигурацию на целевой свежеразвернутый или уже действующий сервер.
  6. LCM получает конфигурацию (MOF-файл) плюс инструкции по настройке самого LCM.
  7. Конфигурация тут же применяется по нашей команде.


Упрощенная схема архитектуры DSC.

Знакомство с движком начнем с изучения предустановленных ресурсов. Далее попробуем написать конфигурации.

Ресурсы DSC 


Ресурсы DSC — своеобразный аналог модулей PowerShell. Любой сервер Windows уже имеет предустановленный набор ресурсов DSC, они лежат в том же каталоге, что и модули PowerShell. Список можно получить через командлет Get-DscResourse. Вот как этот список выглядит на Windows 10 1809:
 
PS C:\windows\system32> Get-DscResource | Sort-Object -Property Name | ft ImplementedAs, Name -a
 
ImplementedAs Name
------------- ----
   PowerShell Archive
   PowerShell Environment
       Binary File
   PowerShell Group
    Composite GroupSet
       Binary Log
   PowerShell Package
   PowerShell PackageManagement
   PowerShell PackageManagementSource
    Composite ProcessSet
   PowerShell Registry
   PowerShell Script
   PowerShell Service
    Composite ServiceSet
       Binary SignatureValidation
   PowerShell User
   PowerShell WaitForAll
   PowerShell WaitForAny
   PowerShell WaitForSome
   PowerShell WindowsFeature
    Composite WindowsFeatureSet
   PowerShell WindowsOptionalFeature
    Composite WindowsOptionalFeatureSet
   PowerShell WindowsPackageCab
   PowerShell WindowsProcess

Названия ресурсов дают понимание, с чем они работают: 

  • Archive — с упаковкой и распаковкой архивов;
  • Environment — с переменными окружения;
  • File — с файлами;
  • Group — с локальными группами пользователей;
  • Log — с логами;
  • Package —- с пакетами ПО;
  • Registry —- с ключами реестра и их состоянием;
  • Service — с сервисами и их состоянием;
  • User — с локальными учетными записями пользователей;
  • WindowsFeature — с компонентами Windows Server;
  • WindowsProcess — с процессами Windows;
  • Script —  вставляет полноценный PowerShell-скрипт в тело конфигурации. Имеет 3 отдельных блока: SetScript — сам скрипт-блок, TestScript — условие, при нарушении которого будет запущен скрипт, GetScript — возвращает текущее состояние объекта.

Чаще всего ресурсов DSC  «из коробки» недостаточно. В этом случае можно написать свои скрипты для всего, что выходит за пределы штатного модуля. Чтобы не изобретать велосипед, можно использовать ресурсы DSC, написанные другими разработчиками. Например, отсюда https://github.com/PowerShell/DscResources или из PSGallery
 
Как установить дополнительные ресурсы. Используем командлет Install-Module. Установка ресурсов — это обычное копирование файлов ресурса по одному из путей в переменной окружения $env:PSModulePath.  Установленные ресурсы автоматически не подключаются по ходу компиляции, поэтому позже мы дополнительно подключим их в самой конфигурации.

Для использования ресурса нужно установить его локально и на целевом сервере. В On-Premise инфраструктурах политика безопасности обычно запрещает доступ в интернет для серверов. В этом случае сервер DSC не сможет подгрузить дополнительные ресурсы из внешних источников. Для публикации архивов с модулями мы разворачиваем локальный репозиторий NuGet или обычный веб-сервер. Для веб-сервера установить дополнительные ресурсы можно с помощью распаковки модуля в каталог C:\Program Files\WindowsPowerShell\Modules\
Это как раз и делает командлет Install-Module.

Во второй статье мы подробнее рассмотрим, чем отличается установка для режимов Push и Pull. 

Разбор простой конфигурации


Конфигурация — это простое последовательное описание того, что должно быть сделано на сервере. Так выглядит простая конфигурация DSC: 

Configuration EnvironmentVariable_Path
{
param ()
Import-DscResource -ModuleName 'PSDscResources'
Node localhost
    {
        Environment CreatePathEnvironmentVariable
        {
            Name = 'TestPathEnvironmentVariable'
            Value = 'TestValue'
            Ensure = 'Present'
            Path = $true
            Target = @('Process', 'Machine')
        }
    }
}
EnvironmentVariable_Path -OutputPath:"C:\EnvironmentVariable_Path"

На ее примере посмотрим, из чего состоит конфигурация.

Блок Configuration — специальный тип функции PowerShell с описанием того, что мы хотим получить. 

Внутри блока содержится: 

  • блок param с параметрами, которые могут использоваться внутри; 
  • блок с дополнительными вызовами PowerShell. Здесь в начале конфигурации мы всегда запускаем Import-Resource, чтобы подключить дополнительные ресурсы;
  • блоки с настройками для конкретных серверов Node $servername. 

Внутри блока Node мы указываем, какие ресурсы на конкретном сервере мы будем настраивать. В примере выше это создание переменной окружения с использованием штатного ресурса Environment.

Заглянем чуть глубже и посмотрим синтаксис конкретного ресурса через команду Get-DscResource -Name Environment -Syntax:
 

PS C:\windows\system32> Get-DscResource -Name Environment -Syntax
Environment [String] #ResourceName
{
    Name = [string]
    [DependsOn = [string[]]]
    [Ensure = [string]{ Absent | Present }]
    [Path = [bool]]
    [PsDscRunAsCredential = [PSCredential]]
    [Value = [string]]
}

В этом примере: 

  • Name — имя переменной окружения.
  • В DependsOn указываем зависимость от других ресурсов. Важно помнить, что операция не будет выполнена, пока не будет выполнен указанный здесь ресурс. 
  • В Ensure мы указываем условия выполнения конфигурации. Если такая переменная отсутствует, то мы ее создадим; если она будет присутствовать, то этот ресурс настраиваться не будет.
  • В Path указывается, будет ли переменная окружения содержать пути. 
  • В PsDscRunAsCredential указываются учетные данные.
  • Value — значение переменной окружения. 
  • В Target может указываться, в отношении кого будет применена конфигурация.

Как запустить компиляцию. Просто вызываем конфигурацию по ее имени с необходимыми параметрами. Результатом станет mof-файл, который в дальнейшем используется движком DSC для настройки конкретного сервера:
 
PS C:\windows\system32> EnvironmentVariable_Path -OutputPath:"C:\EnvironmentVariable_Path"
 
    Directory: C:\EnvironmentVariable_Path
 
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       25.02.2020     14:05           2172 localhost.mof

Настройка Local Configuration Manager


Local configuration manager отвечает за применение конфигураций, которые мы скомпилировали в mof-файлы. Именно он следит за сохранением состояния, определенного в конфигурации. Если система выходит из этого состояния, LCM вызывает код в ресурсах для восстановления заданного состояния. 

Посмотрим настройки Local configuration manager через Get-DscLocalConfigurationManager:
 
PS C:\windows\system32> Get-DscLocalConfigurationManager
 
ActionAfterReboot              : ContinueConfiguration
AgentId                        : 1FB3A2EE-57C9-11EA-A204-58A023EF3A48
AllowModuleOverWrite           : False
CertificateID                  :
ConfigurationDownloadManagers  : {}
ConfigurationID                :
ConfigurationMode              : ApplyAndMonitor
ConfigurationModeFrequencyMins : 15
Credential                     :
DebugMode                      : {NONE}
DownloadManagerCustomData      :
DownloadManagerName            :
LCMCompatibleVersions          : {1.0, 2.0}
LCMState                       : Idle
LCMStateDetail                 :
LCMVersion                     : 2.0
StatusRetentionTimeInDays      : 10
SignatureValidationPolicy      : NONE
SignatureValidations           : {}
MaximumDownloadSizeMB          : 500
PartialConfigurations          :
RebootNodeIfNeeded             : False
RefreshFrequencyMins           : 30
RefreshMode                    : PUSH
ReportManagers                 : {}
ResourceModuleManagers         : {}
PSComputerName                 :

  • RefreshMode содержит режим работы LCM — Push или Pull. О режимах мы поговорим подробнее во второй статье.
  • ConfigurationMode показывает текущий режим применения конфигурации. В нашем случае это ApplyAndMonitor — применить и отслеживать изменения. Также доступны режимы ApplyOnly (LCM не будет отслеживать изменения конфигурации) и ApplyAndAutocorrect (LCM будет не только отслеживать изменения, но и откатывать их к базовой конфигурации).
  • RebootNodeIfNeeded — может перезагружать сервер после завершения конфигурации, если это необходимо для применения настроек. 
  • ConfigurationModeFrequencyMins — фиксирует, как часто LCM будет проверять изменения конфигурации.

Настройки LCM меняем в метаконфигурации. Вот ее пример:
 

Configuration LCMConfiguration
{
   Node Localhost
   {
       LocalConfigurationManager
       {
           RebootNodeIfNeeeded = $True
       }
   }
}
LCMConfiguration

То же самое для последней версии WMF с комментариями:


 
[DSCLocalConfigurationManager()]
Configuration LCMConfiguration
{
   param
   (
       [string[]]$Server = "localhost"
   )

 

# Настраиваем LCM:
# Перезагружать сервер, если нужно
# Режим: PUSH
# Обновлять состояние каждые 30 минут
   Node $Server
   {
       Settings
       {
           RebootNodeIfNeeded = $True
           RefreshMode        = 'Push'
           RefreshFrequencyMins = 30
       }
   }
}


# Старт компиляции метаконфигурации
LCMConfiguration -Server "localhost" -OutputPath "C:\DSC\MetaConfigurations\EnvironmentVariable_Path\"

Особенности метаконфигурации. При написании метаконфигурации мы используем те же блоки, что и для обычной конфигурации DSC.  Исключение — внутренний блок  LocalConfigurationManager (v4) или атрибут DSCLocalConfigurationManager (v5) для конкретного сервера. В них описываются все необходимые настройки. 

Метаконфигурация тоже компилируется в mof-файл, но для ее применения используется командлет Set-DSCLocalConfigurationManager, а не Start-DSCConfiguration.
 
PS C:\windows\system32> LCMConfiguration -OutputPath C:\EnvironmentVariable_Path 
    Directory: C:\EnvironmentVariable_Path
 
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       26.02.2020     20:05           1016 Localhost.meta.mof

 

PS C:\windows\system32> Set-DscLocalConfigurationManager -Path C:\EnvironmentVariable_PathPS C:\windows\system32> Get-DscLocalConfigurationManager
 
…
RebootNodeIfNeeded             : True
…

Теоретически ничто нам не мешает совмещать настройку LCM и обычных ресурсов в рамках одной конфигурации. Но для простоты рекомендуется написание и применение конфигурации и метаконфигурации разделять.

Частичные конфигурации 


Частичные конфигурации (Partial Configurations) — несколько конфигураций, которые исполняются последовательно друг за другом. Они полезны в том случае, когда над сервисом работает несколько команд. Каждая из команд указывает настройки для своей части сервиса, а частичная конфигурация затем применяет все настройки последовательно.  В настройках LCM мы указываем частичные конфигурации в PartialConfigurations

В частичных конфигурациях мы должны учесть прямолинейную логику DSC. Движок не допускает ветвлений, так что для выполнения разных условий нужны дополнительные ресурсы. Разберем это на нескольких примерах. 

Допустим, мы хотим гарантированно доставить конфигурацию на сервер и действуем по следующему алгоритму:

  1. Сначала проверяем, что нужные модули установлены на сервер.
  2. Выполняем конфигурацию, которая приведет сервер в нужное состояние.

Вот как выглядит метаконфигурация с несколькими последовательными конфигурациями внутри:


# Начало функции метаконфигурации
[DSCLocalConfigurationManager()]
configuration MetaPushConfig
{
param
   (
       [ValidateNotNullOrEmpty()]
       [string] $NodeName = 'localhost'
   )

 

   Node $NodeName
   {
 	
 # Описание первой конфигурации с указанием LCM, что конфигураций будет несколько
       PartialConfiguration ModulesDownloadConfig
       {
               Description = 'Download and install modules'
               RefreshMode = 'Push'
       }

 

       # Описание следующей конфигурации с указанием зависимости от первой
       PartialConfiguration ServerOSConfig
       {
               DependsOn = "[PartialConfiguration]ModulesDownloadConfig"
               Description = 'Configuration'
               RefreshMode = 'Push'
       }

 

       # Глобальная настройка LCM
       Settings
       {
               RefreshMode        = 'Push'
               RefreshFrequencyMins = 30
               RebootNodeIfNeeded = $true
       }
   }
}

 
 

# Компиляция метаконфигурации
MetaPushConfig -NodeName "NewServer.contoso.com" -OutputPath c:\DSC\MetaConfigurations

 

# Учетные данные для обращения к серверу
$cred = (Get-Credential -UserName Administrator -Message "Enter admin credentials")

 

# Конфигурирование LCM на целевом сервере
Set-DscLocalConfigurationManager -ComputerName "NewServer.contoso.com" -Credential $cred -Path "c:\DSC\MetaConfigurations" -Verbose -Force

 

# Публикация конфигураций
Publish-DscConfiguration c:\DSC\Configurations\ModulesDownloadConfig -ComputerName "NewServer.contoso.com" -Credential $cred -Force
Publish-DscConfiguration c:\DSC\Configurations\ServerOSConfig -ComputerName "NewServer.contoso.com" -Credential $cred -Force

 

# Запуск конфигураций на исполнение
Start-DscConfiguration -UseExisting -ComputerName "NewServer.contoso.com" -Credential $cred -Force -Verbose

В первой части конфигурации указано Download and install modules: скачай и установи необходимые ресурсы. Вторая часть ServerOSConfig приводит сервер к нужному состоянию. Какие здесь могут быть нюансы с прямолинейностью DSC: 

  1. Если первая часть конфигурации изначально вернула значение FALSE и выполнилась полностью, то LCM не перейдет ко второй конфигурации.  По логике DSC, сначала нужно привести  сервер к первому описанному состоянию. 
    Как лечим: запускаем конфигурирование дважды или автоматизируем весь процесс в скрипте.
  2. Если сервер после установки какого-либо компонента требует перезагрузки, то  конфигурация дальше не пойдет, пока мы сами не перезагрузим сервер. Даже если мы укажем LCM, что RebootNodeIfNeeeded = $True, то во время конфигурации агент  будет ожидать нашего решения.
    Как лечим: на помощь приходит ресурс xPendingReboot, который контролирует ключ реестра по перезагрузке. Этот ресурс перезагружает сервер за нас.

Приведем пример конфигурации для скачивания и установки ресурсов в сценарии «кровавый энтерпрайз», когда сервер не имеет выхода в интернет. Здесь предполагается наличие веб-сервера, где заранее скачанные ресурсы доступны всем по протоколу http.


Configuration ModulesDownloadConfig
{
   param
   (
       [string[]]$Server
   )

 

   # импорт базового ресурса
   Import-DscResource -ModuleName "PSDesiredStateConfiguration"

 

   # Конфигурация сервера
   Node $Server
   {
       # Отключаем IE Security
       Registry DisableIEESC-Admin {
           Key = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}"
           ValueName = "IsInstalled"
           Ensure = "Present"
           ValueData = 0
           ValueType = "DWORD"
       }

 

       Registry DisableIEESC-User {
           Key = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}"
           ValueName = "IsInstalled"
           Ensure = "Present"
           ValueData = "0"
           ValueType = "DWORD"
       }

 

	
 # Создаем папку для ресурсов, если ее не существует
       File CreateDistribDir {
           Ensure          = "present"
           DestinationPath = "C:\Install\PSModules"
           Type            = "Directory"
       }

 

	
 # Скачиваем ресурс NetworkingDsc (<a href="https://www.powershellgallery.com/packages/NetworkingDsc/8.0.0-preview0004">https://www.powershellgallery.com/packages/NetworkingDsc/8.0.0-preview0004</a>), если его нет в локальном каталоге
       Script NetworkingDscDownLoad {
           SetScript = { Invoke-WebRequest -Uri "http://repo.contoso.com/repo/modules/NetworkingDsc.zip" -OutFile "C:\Install\PSModules\NetworkingDsc.zip" }
           GetScript = { return @{ Result = Test-Path "C:\Install\PSModules\NetworkingDsc.zip"
               GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
               }
           }
           TestScript = { Test-Path "C:\Program Files\WindowsPowerShell\Modules\NetworkingDsc" }
       }

 

 # Распаковываем ресурс NetworkingDsc в C:\Program Files\WindowsPowerShell\Modules
       Archive UnpackNetworkingDsc {
           Ensure = "Present"
           DependsOn = "[Script]NetworkingDscDownLoad"
           Path = "C:\Install\PSModules\NetworkingDsc.zip"
           Destination = "C:\Program Files\WindowsPowerShell\Modules\"
       }

 

 # Скачиваем ресурс ComputerManagementDsc (<a href="https://www.powershellgallery.com/packages/ComputerManagementDsc/8.2.1-preview0001">https://www.powershellgallery.com/packages/ComputerManagementDsc/8.2.1-preview0001</a>), если его нет в локальном каталоге
       Script ComputerManagementDscDownLoad {
           SetScript = { Invoke-WebRequest -Uri "http://repo.contoso.com/repo/modules/ComputerManagementDsc.zip" -OutFile "C:\Install\PSModules\ComputerManagementDsc.zip" }
           GetScript = { return @{ Result = Test-Path "C:\Install\PSModules\ComputerManagementDsc.zip"
               GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
               }
           }
           TestScript = { Test-Path "C:\Program Files\WindowsPowerShell\Modules\ComputerManagementDsc" }
       }

 

	
 # Распаковываем ресурс ComputerManagementDsc в C:\Program Files\WindowsPowerShell\Modules
       Archive UnpackComputerManagementDsc {
           Ensure = "Present"
           DependsOn = "[Script]ComputerManagementDscDownLoad"
           Path = "C:\Install\PSModules\ComputerManagementDsc.zip"
           Destination = "C:\Program Files\WindowsPowerShell\Modules\"
       }

 

	
 # Скачиваем ресурс xPendingReboot (<a href="https://www.powershellgallery.com/packages/xPendingReboot/0.4.0.0">https://www.powershellgallery.com/packages/xPendingReboot/0.4.0.0</a>), если его нет в локальном каталоге
       Script xPendingRebootDownLoad {
           SetScript = { Invoke-WebRequest -Uri "http://repo.contoso.com/repo/modules/xPendingReboot.zip" -OutFile "C:\Install\PSModules\xPendingReboot.zip" }
           GetScript = { return @{ Result = Test-Path "C:\Install\PSModules\xPendingReboot.zip"
               GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
               }
           }
           TestScript = { Test-Path "C:\Program Files\WindowsPowerShell\Modules\xPendingReboot" }
       }

 

	
 # Распаковываем ресурс xPendingReboot в C:\Program Files\WindowsPowerShell\Modules
       Archive UnpackxPendingReboot {
           Ensure = "Present"
           DependsOn = "[Script]xPendingRebootDownLoad"
           Path = "C:\Install\PSModules\xPendingReboot.zip"
           Destination = "C:\Program Files\WindowsPowerShell\Modules\"
       }
   }
}

Про безопасность


Жирным минусом DSC на «земле» является отсутствие Run As Accounts. Этот механизм безопасно хранит учетные записи в виде логин+«соленый хэш пароля» и подсовывает их сервису, который отвечает за аутентификацию. Без него  DSC не может управлять учетными записями от имени другого сервиса. А если нам нужно аутентифицироваться под учетной записью с особыми привилегиями, процесс автоматизации сильно осложняется. Например, так будет при вводе сервера в домен.

Все, что есть в нашем распоряжении:

  • учетные данные хранятся в Plain Text,
  • шифрованные учетные данные работают только на том ПК, где они были сгенерированы.

На практике есть несколько вариантов решения. 

  • Использовать Azure Automation Run As Accounts и завязать наши конфигурации на движок Azure Automation State Configuration. В этом сценарии мы сохраним безопасность при передаче учетных данных и простоту применения конфигураций к целевым виртуальным машинам за счет «черной магии» сервисов Azure. Однако, использование Run As аккаунтов в On-Premise сильно усложнит инфраструктуру решения. Поэтому в условиях «кровавого энтерпрайза» данный подход малоприменим, хоть и возможен.
  • После применения конфигурации DSC сделать конфигурацию-заглушку, которая не содержит хэш пароля и исключает компрометирование учетных данных. 

«Заглушки» хороши, когда должна быть обеспечена предсказуемость поведения сервера.
Мы делаем «заглушку» после конфигурации сервера в нескольких случаях, если мы использовали:

  • перезагрузку (ресурс xPendingReboot), 
  • передачу учетных данных, 
  • другие ресурсы, которые могут повлиять на работу сервера в части незапланированной перезагрузки или безопасности.

Для этого создадим и переопубликуем конфигурации БЕЗ блоков, содержащих ресурс xPendingReboot и конфигурации с учетными данными.

Подобный подход применим только к сценарию Push, поскольку Pull в достаточной степени прямолинеен и не подразумевает замену конфигурации на лету. В следующей статье от buldakov мы в деталях рассмотрели настройку и особенности работы в режимах Push и Pull.

А еще приходите обсудить нюансы технологии DSC и границы ее применения 28 мая в 18.00 на первом онлайн-митапе сообщества DGTL Communications Райффайзенбанка. Также на встрече поговорим, как подружить ELK и Exchange и что может Microsoft Endpoint Manager в управлении устройствами. Вот тут регистрация на митап.