В качестве предисловия
Основной задачей моей работы является поддержка парка железных и vm хостов — уже под 200 (а приходил было менше 100, эх, время бежит...) Поддерживаю все железо, а также сеть. Также на мне весь мониторинг (используем Opsview — сделан на ядре nagios), аггрегация логов (я внедрил Logstash, обалденное opensource решение за место ну ооочень дорогого Splunk), configuration management (puppet), бекапы, поддержка баз данных и прочих систем тоже на мне (MongoDB, MySQL, Redis, ElasticSearch, etc). В общем — все самое интересное). Стоит отметить что у нас достаточно тонкая грань между поддержкой и разработкой, и разработчики часто говорят что они хотят, а я уже занимаюсь внедрением. Хочется рассказать обо всем что происходит интересного и какие технологии удается использовать. Какие прижились, а какие по каким-то причинам нет.
В свободное от решения проблем время перевожу инфраструктуру на Infrastructure-as-a-code (IaaC), выбрал puppet для этого из-за неоднородности нашей инфраструктуры. В моей сети зоопарк из Windows Server 2008, Windows Server 2012, CentOS 5.5, CentOS 6.4. Ах, да, пару дедушек на 2003 — пора их на пенсию отправлять скоро…
Я уже писал о том, как я использую Puppet для автоматической настройки мониторинга в Opsvew, а сегодня хочу поговорить о том, как я в очередной раз «боролся» с гетерогенностью моей среды.
Задача
Возникла необходимость автоматизировать конфигурацию WMI на серверах Windows 2008 / 2012. Ключевой необходимостью стало добавление сервисного пользователя (назовем его «domain\service-user») в локальные группы сервера, которые разрешают удаленное использование WMI, а также доступ к Performance Counters, Performance Logs, в общем ко всему что нужно чтобы удаленно мониторить сервер. Сами группы определились достаточно быстро, оставалось найти удобный и быстрый способ это сделать. Также необходимо было дать права пользователю domain\service-user на доступ к корневым неймспейсам WMI. Так же все это должно быть частью общей концепции IaaC, что должно означать как минимум проверку текущего состояния, и пропускать выполнение если пользователь уже добавлен куда нужно в любом варианте присутствия-отсутствия пользователя в группах. Т.е. решение должно быть максимально автоматизированным, а точнее полностью. После небольшого гугления стало ясно что для моего случая нужно, а мне предстояло:
Добавить доменного пользователя domain\service-user в локальные группы (как минимум):
— Certificate Service DCOM Access
— Performance Log Users
— Performance Monitor Users
— Distributed COM Users.
Установить права доступа, как минимум, «read» для пользователя domain\service-user на следующие неймспейсы WMI:
— CIMV2
— MicrosoftIISv2.
Как только все будет установлено, check_wmi_plus (входит в стандартную поставку Opsview Pro) сможет получить необходимые данные об IIS и других интересных параметрах (а это то нам и надо)!
Сложности
Главная сложность которая возникла — готового решения чтобы запустить и быть уверенным в том, что «Сервер будет доступен для мониторинга» нет. В общем-то я и не очень расстроился, потому что очень редко бывают готовые решения под мои задачи, а если и бывают то часто делают что-то не то или не так.
Puppet имеет встроенный ресурс «user», который, по идее, должен был выполнить половину всех задач, не заработал в связке «доменный пользователь — локальная группа». Как оказалось это известный баг и его собираются исправлять (UPD: уже исправили в релизе 3.4), но постоянно отодвигают в следующий релиз puppet. Попытка выполнить workaround в puppet DSL не увенчалась успехом из-за слишком сложной структуры, требующей сложных escape последовательностей, которые не всегда работают.
Еще одна сложность — в windows отсутствует встроенный универсальный способ управления правами доступа к wmi классам, который можно было бы «обернуть» в puppet, если только ковырять реестр и изобретать велосипед.
Реализация
В итоге я принял решение написать свой провайдер и использовать его, до тех пор пока родной провайдер для добавления доменного пользователя в локальную группу на сервере не будет починен. И сделал я это… обернув powershell код!
Puppet::Type.type(:win_user).provide(:win_user) do
@doc = %q{Manage windows users and groups}
desc "Manage windows users and groups"
confine :operatingsystem => :windows
defaultfor :operatingsystem => :windows
commands :powershell =>
if File.exists?("#{ENV['SYSTEMROOT']}\\sysnative\\WindowsPowershell\\v1.0\\powershell.exe")
"#{ENV['SYSTEMROOT']}\\sysnative\\WindowsPowershell\\v1.0\\powershell.exe"
elsif File.exists?("#{ENV['SYSTEMROOT']}\\system32\\WindowsPowershell\\v1.0\\powershell.exe")
"#{ENV['SYSTEMROOT']}\\system32\\WindowsPowershell\\v1.0\\powershell.exe"
else
'powershell.exe'
end
def add_user_to_group
#
end
def exists?
#add transform to array if just a string
groups = resource[:groups]
if groups.kind_of?(String)
groups.to_a
end
found = false
groups.to_a.each do |group|
Puppet.debug("Checking the existence of value: #{self} in #{group}")
result = powershell 'if (([ADSI]"WinNT://$env:computername/'+group+'").IsMember(([ADSI]"WinNT://$env:userdomain/'+ resource[:name]+'").ADsPath) -eq $False){echo 0} else {echo 1}'
if result.chomp == "0" #if not found (ps returned false on search of user in group)
Puppet.debug("User '#{resource[:name]}' is not found in group '#{group}'")
found = false #
break
else
Puppet.debug("User '#{resource[:name]}' is found in a group '#{group}'")
found = true
end
end
found
end
def create
groups = resource[:groups]
if groups.kind_of?(String)
groups.to_a
end
groups.to_a.each do |group|
Puppet.debug("Adding user to a group #{self}")
powershell 'if (([ADSI]"WinNT://$env:computername/'+group+'").IsMember(([ADSI]"WinNT://$env:userdomain/'+ resource[:name]+'").ADsPath) -eq $False){$([ADSI]"WinNT://$env:computername/'+group+'").Add(([ADSI]"WinNT://$env:userdomain/'+resource[:name]+'").ADsPath)} else {echo "User is already in a group"}'
end
end
def destroy
end
end
Для настройки параметров WMI пришлось пользоваться сторонней opensource утилитой wmisecurity.exe. Для ее установки я создал пакет на chocoaltey.org — wmisecurity. Для установки пакета я использовал puppet-провайдер chocolatey, который я использую постоянно.
И вот сам puppet-манифест, который использует раннее написанный модуль, а также содержит powershell хуки для добавления прав доступа к wmi классам для пользователя (возможно перепишу это как отдельный модуль позднее):
class packages::wmi {
$wmiuser = 'service-user'
###Doesn't work on windows right now
#user { $wmiuser:
# groups => ['Certificate Service DCOM Access','Performance Log Users','Performance Monitor Users', 'Distributed COM Users'],
# }
win_user { $wmiuser:
groups => ['Certificate Service DCOM Access','Performance Log Users','Performance Monitor Users', 'Distributed COM Users'],
ensure => present_local,
}
###it is required to add user to those local groups in order monitoring to perform correctly.
exec {"add-to-wmi-cimv2":
command => "wmisecurity.exe /C=\$env:computername /A /N=Root/CIMV2 /M=\$env:userdomain\\$wmiuser:REMOTEACCESS /R",
path => $::path,
#if found user guid - skip
onlyif => "if (WmiSecurity.exe /c=\$env:computername /N=Root/CIMV2 /R | Select-String $($(New-Object System.Security.Principal.NTAccount(
\"\$env:userdomain\", '$wmiuser')).Translate([System.Security.Principal.SecurityIdentifier]).Value)){exit 1} else {exit 0}",
provider => powershell,
require => Package['wmisecurity'],
}
exec {"add-to-wmi-microsoftiisv2":
command => "wmisecurity.exe /C=\$env:computername /A /N=Root/MicrosoftIISv2 /M=\$env:userdomain\\$wmiuser:REMOTEACCESS /R",
path => $::path,
#if found user guid - skip
onlyif => "if (WmiSecurity.exe /c=\$env:computername /N=Root/MicrosoftIISv2 /R | Select-String $($(New-Object System.Security.Principal.NTAccount(
\"\$env:userdomain\", '$wmiuser')).Translate([System.Security.Principal.SecurityIdentifier]).Value)){exit 1} else {exit 0}",
provider => powershell,
require => Package['wmisecurity'],
}
package {'wmisecurity':
provider => 'chocolatey',
install_options => '-pre',
require => Class["packages::chocolatey"]
}
}
Заключение
Конечно, сам модуль далек от идеала, и не хватает много чего, код явно грязный, но он работает и выполняет то, что задумывалось. Рефакторинг планируется в следующей итерации, и думаю это случится когда выйдет 3.4. Вот идеальный вариант манифеста, который я себе представляю (для тех, кто будет ругаться про «грязный код, который работает»):
class packages::wmi {
$wmiuser = "${env:userdomain}\\service-user"
user { $wmiuser:
groups => ['Certificate Service DCOM Access','Performance Log Users','Performance Monitor Users', 'Distributed COM Users'],
ensure => present,
}
wmi_security_user { $wmiuser:
namespaces => ['Root/CIMV2','Root/MicrosoftIISv2'],
ensure => present,
}
}
Теперь при настройке нового сервера мне остается назначить класс packages::wmi на этот сервер (в ручную или через include) и все, puppet сделает свое дело. Лично я чаще всего использую этот класс через класс opsview, который автоматически создает хост для мониторинга в opsview и назначет нужные шаблоны, т.е. если это, скажем, сервер c IIS, итоговый puppet-класс сообщит opsview всю необходимую информацию о том, что это хост с IIS, с такими-то и такими-то хостами которые нужно тоже мониторить определенным образом, а также назначит в opsview шаблон мониторинга через wmi, который зависит от класса который мы описали выше. Вот так, вроде ничего не упустил.
This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.
Комментариев нет:
Отправить комментарий