Статья будет полезна разработчикам, которые работают с Windows-инфраструктурой и выбирают способы управления, и тем, кто уже внедрил Powershell DSC или Ansible.
10 марта 2021
Стоит ли переходить с Powershell DSC на Ansible и как это сделать
Об IaC под Windows пишут мало, потому что DevOps/SRE ассоциируется в основном c Linux и Kubernetes. Мы решили исправить эту ситуацию и сравнить инструменты, которыми можно управлять IaC на базе Windows. Статья будет полезна разработчикам, которые работают с Windows-инфраструктурой и выбирают способы управления, и тем, кто уже внедрил Powershell DSC или Ansible, но сомневается в своем решении. Ниже поделимся опытом и расскажем:
- как устроен Powershell DSC и чем он отличается от Ansible при управлении инфраструктурой на Windows;
- почему мы перешли на Ansible;
- с какими проблемами столкнулись и как их решали;
- как соотносятся ожидания и реальность после перехода на Ansible;
- кому стоит выбрать Powershell DSC, а кому — Ansible.
Почему изначально выбрали PowerShell DSC
В Mindbox развита культура DevOps/SRE, несмотря на преимущественно Windows-инфраструктуру: Hyper-V, IIS, MS SQL Server. И хотя компания постепенно переходит на Linux и Open Source, Windows пока превалирует.
Чтобы управлять этой инфраструктурой, планировали использовать инфраструктурный код: писать его, сохранять в репозиторий, а затем с помощью какого-то инструмента превращать код в реальную инфраструктуру. В то время как Ansible — самый популярный инструмент управления инфраструктурой через код, он все-таки традиционно ассоциируется с Linux-миром. Нам хотелось что-то нативное и заточенное под Windows, поэтому выбрали PowerShell DSC.
Как работает Powershell DSC
PowerShell Desired State Configuration (DSC) — это сервис, который уже есть в Windows из коробки и помогает управлять инфраструктурой через конфигурационные файлы. На вход он принимает инфраструктурный код на PowerShell, а внутри преобразует его в команды, которые конфигурируют систему. Кроме тривиальных операций, например установки Windows-компонентов, изменения ключей реестра, создания файлов или конфигурирования служб, он умеет многое из того, что обычно выполняется PowerShell-скриптами. Например, полный цикл настройки DNS или высокодоступный инстанс MS SQL Server.
Устройство DSC
Полезные ссылки к схеме:
Чем DSC отличается от Ansible
Критерий
DSC
Ansible
Архитектура
Служба на каждом управляемом хосте. В случае pull-модели, отдельный управляющий хост и база данных, пререквизиты в виде .NET Framework 4.0 и WMF 5.1
Несколько исполняемых файлов, например ansible, ansible-playbook и ansible-inventory. Запускается с любого Linux-хоста, пререквизит у управляемых хостов один — python
Хранение состояния хостов
Можно хранить в базе данных
Нет
Кроссплатформенность
Да
Да, включая управление сетевыми устройствами
Pull/push-режимы
Pull и push
Только push
Устранение дрифта конфигурации
Есть в pull-режиме
Нет
Декларативность
Декларативно-процедурный: важна последовательность выполнения тасков, можно писать недекларативные конструкции в любом месте, а значит, больше шансов написать запутанный код
Декларативно-процедурный: важна последовательность выполнения тасков, при этом невозможно написать скриптовый код, не обернув в таск
Аудитория
~1300 ресурсов в Gallery
~20000 ролей в Ansible Galaxy
Используемый язык
PowerShell
YAML
Инвентаризация
Да, по удобству проигрывает Ansible
Да
Единица распространения
Ресурс (модуль)
Роль
Проблемы, которые возникли с DSC
Ожидания от DSC оправдались не во всём. Кроме этого, во время работы возникли новые потребности, которые не могли удовлетворить с помощью DSC.
Разработчики не могут использовать инструмент самостоятельно без помощи SRE. Хотя почти в каждой команде есть SRE, инструмент IaC должен быть достаточно простым, чтобы разработчик мог им пользоваться и тратить на это не больше получаса. DSC позволяет использовать не только декларативный код, но и любые конструкции на Powershell. Это значит, что высок шанс сделать код, который будет сложно сопровождать или который приведет к инфраструктурной аварии. Например, развертывание приложения с некорректными параметрами не в той среде.
Невозможно пропустить конфигурацию в режиме dry run перед прокаткой, чтобы увидеть, какие именно изменения будут применены, а какие — нет.
Для DSC трудно организовать синтаксические проверки и проверки стиля кода. Инструментов для проверки мало, и они не обновляются. Для Ansible мы это уже сделали.
В push-режиме DSC нет удобного способа отслеживать состояние тасков. В случае если конфигурация применилась с ошибкой, для диагностики следует совершать дополнительные действия: выполнять команды, чтобы получить статус применения конфигурации, смотреть журналы событий. Если ошибка произошла на нескольких серверах, то это отнимает много времени.
Pull-режим так и не стал преимуществом. В нем конфигурация применяется асинхронно — узнать, когда точно закончено применение новой конфигурации, невозможно без обвязок и костылей.
Избыточное использование двух отличных друг от друга инструментов IaC, которые конфигурируют серверы. Ansible может делать то же, что и DSC, а ведь мы уже используем Ansible для конфигурирования Linux-хостов и сетевого оборудования.
Как планировали перейти с DSC на Ansible
Сначала задача казалась простой, приблизительно на месяц. Мы выделили три этапа работ:
- научиться подключаться к Windows-хостам с помощью Ansible;
- переписать конфигурации DSC с помощью Ansible-модулей;
- удалить DSC pull server, его базу данных и прочие артефакты.
Вот какой рабочий процесс был на DSC, и как планировали организовать в Ansible:
Как было на DSC
Что хотели получить на Ansible
Весь код DSC хранился в одном из наших закрытых репозиториев на Github вместе с ad-hoc скриптами для администрирования. Серверы были разделены на типы, например: Hyper-V, IIS, Influx. Для каждого типа — отдельная конфигурация DSC. Также существовала общая для всех типов конфигурация Standard.
Отдельный репозиторий, где хранятся:
— плейбуки, которые содержат специфические пре- или пост-таски, вызовы ролей с фиксированной версией роли;
— файлы инвентаризации, которые содержат управляемые хосты, иерархия их групп, а также переменные для групп или хостов.
Для каждой роли — отдельный репозиторий, где есть проверка синтаксиса, линтинг и тесты. Выполнение происходит через GitHub Actions.
— плейбуки, которые содержат специфические пре- или пост-таски, вызовы ролей с фиксированной версией роли;
— файлы инвентаризации, которые содержат управляемые хосты, иерархия их групп, а также переменные для групп или хостов.
Для каждой роли — отдельный репозиторий, где есть проверка синтаксиса, линтинг и тесты. Выполнение происходит через GitHub Actions.
Пайплайн, который отправлял конфигурации на pull server. Проводить тестирование было сложно, поэтому от него отказались. Конфигурация стягивалась с pull server управляемыми серверами в течение неопределенного времени, до 30 минут.
Пайплайн, который производит линтинг и проверку синтаксиса, а также публикацию релиза в нашу CD-систему (Octopus Deploy).
Отдельная CD-система нужна, потому что код храним в GitHub, а работать он должен внутри периметра нашей сети. Для этого через Github Actions публикуем релиз в Octopus Deploy и через него запускаем раскатку командой ansible-playbook.
Отдельная CD-система нужна, потому что код храним в GitHub, а работать он должен внутри периметра нашей сети. Для этого через Github Actions публикуем релиз в Octopus Deploy и через него запускаем раскатку командой ansible-playbook.
Мы сами написали несколько модулей DSC. Они билдились CI-системой и публиковались в NuGet feed.
Переиспользовать самописные DSC-модули в коде Ansible.
При появлении нового сервера добавляли его в файл с данными о нодах, прописывали ему среду и площадку. От среды и площадки зависели некоторые параметры конфигурации — так мы обеспечивали гибкость настроек. Хранились они в виде многоуровневой хэш-карты в отдельном файле. При этом часть переменных объявлялась прямо «на ходу» перед вызовом ресурса.
Использовать инвентаризацию Ansible.
Обращение с данными DSC очень вольное: переменные там можно определять, задавать, ссылаться на них в любом месте. Это обязывает самостоятельно задавать логику обращения с переменными и следить за её единообразием во всём коде, а это сложно.
В Ansible заложена своя строгая логика обращения с переменными. Мы всегда знаем, где можно объявить переменную и что будет, если одну и ту же переменную определить в двух местах по-разному.
Хотя был настроен pull server, мы не хотели ждать неопределенное время, пока pull-конфигурация начнет применяться — поэтому сами пушили конфигурацию на сервер.
Всегда сами запускаем деплоймент Ansible-кода из CD-системы. В этой системе есть история запусков и изменений, а также возможность увидеть будущий результат перед применением.
Репозиторий DSC
Репозиторий Ansible
Configurations — конфигурации DSC для каждого типа серверов.
ConfigFiles — конфигурации ПО, которое разворачивается с помощью DSC.
BuildSteps — шаги для билда кастомных DSC-модулей.
Modules — код кастомных DSC-модулей.
ConfigFiles — конфигурации ПО, которое разворачивается с помощью DSC.
BuildSteps — шаги для билда кастомных DSC-модулей.
Modules — код кастомных DSC-модулей.
Inventories — файлы с перечнями серверов, их групп и переменных.
Playbooks — плейбуки, которые вызывают роли, и файлы requirements.yml со списками требуемых ролей и их версии. Каждая роль вынесена в отдельный репозиторий. Структура ролей стандартная.
Playbooks — плейбуки, которые вызывают роли, и файлы requirements.yml со списками требуемых ролей и их версии. Каждая роль вынесена в отдельный репозиторий. Структура ролей стандартная.
На Ansible мы планировали отделить сложный код, который что-то конфигурирует и устанавливает, в код ролей и разнести роли по отдельным репозиториям. В главном репозитории Ansible должны были остаться только вызовы ролей, переопределения параметров ролей и списки серверов по группам. Так не только SRE, но и любой разработчик мог бы развернуть роль на нужные серверы или подправить параметр, не углубляясь в логику инфраструктурного кода. Исправить же код роли разработчик сможет только после ревью SRE.
С какими сложностями столкнулись при переходе на Ansible и как их решали
Когда работа началась, мы поняли, что ошиблись: задача оказалась непростой. Проблем не возникло только с репозиторием, а в других вопросах пришлось много исследовать и улучшать наработки.
WinRM или SSH
Первый сюрприз состоял в выборе типа подключения. В случае Windows их два — WinRM и SSH. Оказалось, что Ansible медленно работает через WinRM. При этом Ansible не рекомендует использовать OpenSSH из коробки для Windows Server 2019. И мы нашли новое решение:
- Форкнули и переделали под себя роль из Galaxy.
- Написали плейбук, в котором есть только вызов этой роли. Это единственный плейбук, при котором идет подключение к хостам по WinRM.
- Стандартными средствами Prometheus Blackbox Exporter сделали мониторинг порта 22/tcp и версии OpenSSH.
- alert: SSHPortDown
expr: probe_success{job=~".*-servers-ssh",instance!~".*domain..ru"} == 0
for: 1d
annotations:
summary: "Cannot reach {{`{{ $labels.instance }}`}} with SSH"
- Выбрали и настроили LDAP-плагин для инвентаризации, чтобы не вписывать вручную все Windows-серверы из домена в статическую инвентаризацию.
plugin: ldap_inventory
domain: 'ldaps://domain:636'
search_ou: "DC=domain,DC=ru"
ldap_filter: "(&(objectCategory=computer)(operatingSystem=*server*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"
validate_certs: False
exclude_hosts:
- db-
account_age: 15
fqdn_format: True
- Развернули везде OpenSSH с нужными ключами и убедились, что ни одного алерта о недоступности Windows-серверов по SSH больше нет.
- Чуть позже интегрировали установку OpenSSH в стандартный образ. Наши образы готовятся с помощью Packer, который также умеет вызывать Ansible.
"type": "shell-local",
"tempfile_extension": ".ps1",
"execute_command": ["powershell.exe", "{{.Vars}} {{.Script}}"],
"env_var_format": "$env:%s=\"%s\"; ",
"environment_vars": [
"packer_directory={{ pwd }}",
"ldap_machine_name={{user `ldap_machine_name`}}",
"ldap_username={{user `ldap_username`}}",
"ldap_password={{user `ldap_password`}}",
"ansible_playbooks={{user `ansible_playbooks`}}",
"github_token={{user `github_token`}}"
],
"script": "./scripts/run-ansiblewithdocker.ps1"
Рефакторинг
Когда мы переписывали код под Ansible, то периодически натыкались на дублирование кода. Например, почти все конфигурации DSC содержали установку windows_exporter. Единственное, что отличалось — это коллекторы, которые экспортер должен был использовать.
Чтобы избавиться от дублированного кода, вынесли windows_exporter в отдельную Ansible-роль, а параметры этой установки — в переменные групп хостов.
Second hop authentication
Наверное, second hop authentication — самая распространенная проблема, с которой сталкиваются те, кто начал использовать Ansible под Windows.
- name: Custom modules loaded into module directory
win_copy:
src: '\\share\dsc\modules'
dest: 'C:\Program Files\WindowsPowerShell\Modules'
remote_src: yes
Такая конструкция вызывает ошибку Access Denied из-за того, что по умолчанию делегировать учетные данные для авторизации на удаленном ресурсе невозможно без дополнительных настроек. Обойти ошибку помогает, например, new_credentials. Но мы предпочли воспользоваться тем, что Ansible умеет вызывать ресурсы DSC через модуль win_dsc. Вызываем DSC-ресурс File, который по умолчанию выполняется под учетной записью компьютера. Делегация Kerberos в этом случае не нужна:
- name: Custom modules loaded into module directory
win_dsc:
resource_name: File
SourcePath: '\\share\dsc\modules'
DestinationPath: 'C:\Program Files\WindowsPowerShell\Modules'
Type: Directory
Recurse: true
Force: true
MatchSource: true
При этом нет противоречия в том, чтобы отказываться от DSC, но использовать его ресурсы, если они лучше решают задачу, чем модуль Ansible. Главная цель — прекратить использовать DSC-конфигурации, потому что нас не устраивала именно экосистема DSC, а не сами ресурсы. Например, если нужно создать виртуальный свитч Hyper-V, то придется использовать ресурс DSC — в Ansible пока нет средств по управлению конфигурацией Hyper-V.
Сетевой дисконнект
Некоторые задачи вызывают отключение сети (дисконнект) на конфигурируемых серверах. Например, создание виртуального свитча Hyper-V из примера выше:
- name: External switch present
win_dsc:
resource_name: xVMSwitch
Ensure: 'Present'
Name: 'Virtual Network'
Type: 'External'
NetAdapterName: 'TEAM_LAN'
AllowManagementOS: True
Проблема в том, что в DSC такой вызов работает, а в Ansible завершается с ошибкой, так как управляемый хост дисконнектнул. Это происходит потому, что Windows всегда дисконнектит при создании виртуального экстернал-свитча. Решение — добавить к таску аргумент async.
async: 10
Так Ansible отправляет таск на хост, ждет заданное время и только потом запрашивает состояние.
Дрифт инфраструктуры
Когда мы стали переносить код, обнаружили дрифт конфигураций. Это фактические различия между тем, что описано в коде, и реальной конфигурацией сервера или ПО. Причина в том, что в некоторых случаях DSC выполнял только часть работы, а остальное делали либо скриптами, либо вручную по инструкции.
Чтобы облегчить работу с IaC, мы собрали все скрипты и документы и сделали единые однозначные инструкции. Кроме этого, организовали процесс так, чтобы никто не внес случайные изменения в Ansible. Мы храним весь инфраструктурный код в GitHub, а задачи инженерам ставим через GitHub Projects, поэтому у нас есть возможность связывать изменения инфраструктурного кода (pull requests) с задачами. Так мы можем посмотреть изменения по каждой выполненной задаче. Если у задачи не будет изменений, то её не примут и вернут на доработку.
Баги сбора фактов
В отличие от DSC, Ansible при запуске собирает факты об управляемых хостах, чтобы у разработчика была возможность определить поведение тасков в зависимости от состояния хоста. При сборе фактов с Windows-хостов Ansible может выдавать ошибку, из-за некорректного кода модуля. Чтобы её исправить, нужно подключить коллекцию ansible.windows.
[WARNING]: Error when collecting bios facts: New-Object : Exception calling ".ctor" with "0" argument(s): "Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index" At line:2
char:21 + ... $bios = New-Object -TypeName
Пайплайн для Ansible перед запуском каждого плейбука проверяет наличие файлов requirements.yml со списком необходимых ролей и коллекций, а затем устанавливает их. Туда мы и добавили коллекцию ansible.windows.
Коллекции — это новый концепт развития Ansible. Если раньше в Galaxy распространялись только роли, то теперь там можно найти подборки различных плагинов и модулей, плейбуков и ролей.
Тесты
Прежде чем передать IaC-инструментарий разработчикам, мы хотели быть уверенными, что Ansible-код будет надежным и ничего не сломает. В случае с DSC никаких специальных тестов не было, хотя существует специальный фреймворк для этой задачи. Конфигурации обычно валидировались на стейджинг-серверах, поломка которых не приводила к дефектам.
Для тестирования Ansible обычно используют инструмент molecule как обертку для запуска тестов. Это удобный инструмент для Linux-ролей, но в случае с Windows есть проблема. Раньше molecule умела поднимать инфраструктуру сама, но сейчас разработчики убрали такую возможность. Теперь инфраструктура поднимается либо с помощью molecule в Docker, либо вне molecule. Протестировать Windows-роли в Docker чаще всего невозможно: Hyper-V и большинство других Windows-фич в Docker-контейнере не установятся. Придется разворачивать инфраструктуру под тесты вне molecule и использовать delegated driver в molecule.
Эту задачу мы еще не решили, но нашли инструменты, которые обнаружат самые очевидные ошибки:
Проверка
Функционал
Комментарий
Проверяет синтаксис и возможность запуска кода
Используем синтаксическую проверку и линтинг локально и в репозитории. Например, встраиваем в pre-commit check и настраиваем GitHub Action, который будет запускаться при каждом pull request
Позволяет до запуска плейбука узнать, что он сделает
Используем в пайплайне раскатки кода: запускаем ansible-playbook с флагами check и diff, затем оцениваем изменения и подтверждаем раскатку. Когда пишем роли, учитываем, что для некоторых тасков необходимо явно указывать, что именно они должны поменять. Например, win_command и win_shell
Как устроена работа с Ansible
После того как мы внедрили Ansible и преодолели все сложности, сформировался процесс действий инженеров и автоматических запусков:
- Инженер пишет код роли и тесты к ней, если это роль для Linux-серверов. Как только инженер решит, что роль готова, он делает pull request в отдельный бранч в GitHub-репозитории, созданном специально для роли.
- При создании pull request автоматически запускается воркфлоу GitHub Actions, который выполняет синтаксическую проверку и линтинг роли. Если это Linux-роль, то запускаются еще и тесты. Инженер проверяет, что всё хорошо, и при необходимости исправляет.
- Другой инженер делает ревью кода из pull request. После того как автор роли исправляет все замечания, код вливается в мастер-бранч, а версия роли автоматически повышается.
- Теперь нужно развернуть новую версию роли. Версии перечислены в специальных файлах requirements.yml, которые лежат в GitHub-репозитории с плейбуками. Для каждого плейбука — отдельный такой файл. Автор роли изменяет версию в таком файле. Если нужно развернуть роль на серверы, которых нет в инвентаризации Ansible, автор дополняет инвентаризацию. Потом автор снова создает pull request, но уже в репозиторий с плейбуками.
- После подтверждения pull request снова запускается GitHub Actions, который создает новый релиз в Octopus Deploy. Роль готова к развертыванию.
- Инженер заходит в Octopus Deploy и запускает развертывание. Процесс развертывания позволяет инженеру ограничить теги и хосты, а также переопределить переменные аналогично опциям команды ansible-playbook: --tags, --limit и --extra-vars.
- Процесс развертывания сначала запускает режим проверки, который показывает, какие изменения будут сделаны. Инженер оценивает результат проверки и либо подтверждает развертывание кода на целевую инфраструктуру, либо сначала устраняет обнаруженные недостатки.
Организация работы с Ansible
Что выбрать: DSC или Ansible
Перейти с DSC на Ansible
Если важно:
— отслеживать состояние тасков;
— иметь возможность пропустить конфигурацию в режиме dry run перед прокаткой;
— модифицировать инфраструктурный код;
— делать синтаксические и логические проверки.
Если Linux-хосты или сетевое оборудование уже управляются с помощью Ansible.
Если не боитесь работать с Linux, потому что Ansible нужно централизованно запускать на Linux, будь то агент CI/CD системы или Docker-контейнер.
— отслеживать состояние тасков;
— иметь возможность пропустить конфигурацию в режиме dry run перед прокаткой;
— модифицировать инфраструктурный код;
— делать синтаксические и логические проверки.
Если Linux-хосты или сетевое оборудование уже управляются с помощью Ansible.
Если не боитесь работать с Linux, потому что Ansible нужно централизованно запускать на Linux, будь то агент CI/CD системы или Docker-контейнер.
Внедрить с нуля или остаться на DSC
Если инфраструктура только на Windows и вы не хотите работать с Linux.
Если готовы дописывать свои ресурсы для DSC.
Нужно хранить состояние инфраструктуры, а также исправлять её дрифт.
Если готовы дописывать свои ресурсы для DSC.
Нужно хранить состояние инфраструктуры, а также исправлять её дрифт.
Внедрить с нуля Ansible
Если управляете смешанной Windows/Linux средой и хотите переделать существующие скрипты в инфраструктурный код и разворачивать его с помощью CI/CD систем.