Для начала постановка задачи довольно тривиальная — организовать для девелоперов возможность быстро и просто разворачивать окружение. Обязательное требование — для автоконфигурации использовать Puppet Enterprise
Итак, более подробно об необходимом окружении. Оно будет состоять из двух компонентов, первый — FrontEnd, функции которого выполняет IIS сервер, второй — BackEnd, который будет содержать некий собственно разработаный Worker service и базу MongoDB. Оба компонента, как уже понятно, будут реализованы на Windows Server. Исходники для контента FrontEnd и Worker service будут браться из AWS S3, куда их уже исправно складывает каждую ночь Jenkins.
Создание Cloud Fromation Template
Реализовать Cloud Formation template, который будет стартовать два Windows сервера абсолютно не сложно. Куда интереснее придумать, каким образом сообщить Puppet, какую конфигурацию применять к этим серверам.
В официальной документации Puppet предлагается применять регулярные выражения к хостнейму клиента, что в нашем случае не удобно использовать, так как хостнейм на AWS Amazon выдаётся автоматом и может меняться после стоп-старта инстанса, то есть я был бы вынужден выдумывать пост-старт скрипт, который должен менять хостнейм машины и только потом стартовать puppet agent.
Покопавшись еще в документации, я нашёл то, что надо — Custom External Facts. Для тех, кто работает c Chef Server, facts — это аналог attributes.
Чтобы добавить свои факты для виндоус машины, необходимо создать bat или ps1 файл примерно следующего содержания и положить его в "C:\ProgramData\PuppetLabs\facter\facts.d\".
@echo off echo node_role=frontend echo app_version=Build1.2.0
Где serverRole — это, как понятно из названия, роль, которая будет назначена серверу, а buildNumber — это версия приложения, которая будет скачана с S3 AWS.
Создавать этот файл будет Cloud Formation template.
{ "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Developers Stack", "Parameters" : { "KeyName" : { "Description" : "Key-pair name", "Type" : "String" }, "SuffixName" : { "Description" : "Suffix for all created resources", "Type" : "String" }, "FrontEndInstanceType" : { "Type" : "String", "Default" : "m1.small", "AllowedValues" : [ "m1.small", "m1.medium", "m1.large", "m1.xlarge"], "Description" : "EC2 instance type" }, "BackEndInstanceType" : { "Type" : "String", "Default" : "m1.small", "AllowedValues" : [ "m1.small", "m1.medium", "m1.large", "m1.xlarge"], "Description" : "EC2 instance type" }, "PuppetServer": { "Description" : "Puppet Server URL", "Type" : "String", "Default" : "ec2-231-231-123-123.us-west-2.compute.amazonaws.com" }, "Zone" : { "Type" : "CommaDelimitedList", "Description" : "The Availability Zone ", "Default" : "us-west-2c" }, "BuildVersion" : { "Type" : "String", "Description" : "Version of application build" }, "RoleName" : { "Type" : "String", "Description" : "Instance IAM role", "Default" : "WebInstance" }, "SecurityGroup" : { "Type" : "String", "Description" : "Default security group for stack", "Default" : "taws-security-group" } }, "Mappings" : { "WindowsInstanceType" : { "t1.micro" : { "Arch" : "64" }, "m1.small" : { "Arch" : "64" }, "m1.medium" : { "Arch" : "64" }, "m1.large" : { "Arch" : "64" }, "m1.xlarge" : { "Arch" : "64" } }, "WindowsRegionMap" : { "us-east-1" : { "AMI" : "ami-e55a7e8c" }, "us-west-2" : { "AMI" : "ami-1e53c82e" }, "us-west-1" : { "AMI" : "ami-b687b1f3" }, "eu-west-1" : { "AMI" : "ami-5f3ad728" }, "ap-southeast-1" : { "AMI" : "ami-96cd98c4" }, "ap-southeast-2" : { "AMI" : "ami-ab4a2daa" }, "ap-northeast-1" : { "AMI" : "ami-133fa329" }, "sa-east-1" : { "AMI" : "ami-bd3d9ba0" } } }, "Resources" : { "FrontEnd" : { "Type" : "AWS::EC2::Instance", "Properties" : { "KeyName" : { "Ref" : "KeyName" }, "ImageId" : { "Fn::FindInMap" : [ "WindowsRegionMap", { "Ref" : "AWS::Region" }, "AMI" ]}, "InstanceType" : { "Ref" : "FrontEndInstanceType" }, "IamInstanceProfile" : { "Ref" : "RoleName" }, "SecurityGroups" : [{ "Ref" : "SecurityGroup" }], "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : ["",[{"Ref" : "SuffixName"},"-DEV-FrontEnd"]]}} ], "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ "<powershell>\n", "$MsiUrl = \"https://s3-us-west-2.amazonaws.com/mybucket/puppet.msi\"\n", "$downloadPath = \"c:\\puppet.msi\"\n", "$webClient = New-Object System.Net.WebClient\n", "$webClient.DownloadFile($MsiUrl, $downloadPath)\n", "$process = Start-Process -File $downloadPath -arg \"/qn /norestart\" -PassThru |wait-process\n", "$PublicHostName = Invoke-RestMethod -Uri http://169.254.169.254/latest/meta-data/public-hostname -Method Get\n", "Clear-Content 'C:\\ProgramData\\PuppetLabs\\puppet\\etc\\puppet.conf'\n", "Add-Content 'C:\\ProgramData\\PuppetLabs\\puppet\\etc\\puppet.conf' \"[main]\", \"runinterval=300\", \"certname=$PublicHostName\", \"server=",{ "Ref" : "PuppetServer" },"\", \"environment=",{ "Ref" : "PuppetEnvironment" },"\"\n", "Add-Content 'C:\\ProgramData\\PuppetLabs\\facter\\facts.d\\facts.bat' \"@echo off\", \"echo node_role=frontend\", \"echo app_version=",{ "Ref" : "BuildVersion" },"\"\n", "Restart-Service pe-puppet\n", "$MsiUrl = \"https://s3-us-west-2.amazonaws.com/mybucket/7zip.msi\"\n", "$downloadPath = \"c:\\7zip.msi\"\n", "$webClient = New-Object System.Net.WebClient\n", "$webClient.DownloadFile($MsiUrl, $downloadPath)\n", "$process = Start-Process -File $downloadPath -arg \"/qn \" -PassThru |wait-process\n", "</powershell>\n" ]]}} } }, "BackEnd" : { "Type" : "AWS::EC2::Instance", "Properties" : { "KeyName" : { "Ref" : "KeyName" }, "ImageId" : { "Fn::FindInMap" : [ "WindowsRegionMap", { "Ref" : "AWS::Region" }, "AMI" ]}, "InstanceType" : { "Ref" : "BackEndInstanceType" }, "IamInstanceProfile" : { "Ref" : "RoleName" }, "SecurityGroups" : [{ "Ref" : "SecurityGroup" }], "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : ["",[{"Ref" : "SuffixName"},"-DEV-BackEnd"]]}} ], "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ "<powershell>\n", "$MsiUrl = \"https://s3-us-west-2.amazonaws.com/mybucket/puppet.msi\"\n", "$downloadPath = \"c:\\puppet.msi\"\n", "$webClient = New-Object System.Net.WebClient\n", "$webClient.DownloadFile($MsiUrl, $downloadPath)\n", "$process = Start-Process -File $downloadPath -arg \"/qn /norestart\" -PassThru |wait-process\n", "$PublicHostName = Invoke-RestMethod -Uri http://169.254.169.254/latest/meta-data/public-hostname -Method Get\n", "Clear-Content 'C:\\ProgramData\\PuppetLabs\\puppet\\etc\\puppet.conf'\n", "Add-Content 'C:\\ProgramData\\PuppetLabs\\puppet\\etc\\puppet.conf' \"[main]\", \"runinterval=300\", \"certname=$PublicHostName\", \"server=",{ "Ref" : "PuppetServer" },"\", \"environment=",{ "Ref" : "PuppetEnvironment" },"\"\n", "Add-Content 'C:\\ProgramData\\PuppetLabs\\facter\\facts.d\\facts.bat' \"@echo off\", \"echo node_role=backend\", \"echo app_version=",{ "Ref" : "BuildVersion" },"\"\n", "Restart-Service pe-puppet\n", "$MsiUrl = \"https://s3-us-west-2.amazonaws.com/mybucket/7zip.msi\"\n", "$downloadPath = \"c:\\7zip.msi\"\n", "$webClient = New-Object System.Net.WebClient\n", "$webClient.DownloadFile($MsiUrl, $downloadPath)\n", "$process = Start-Process -File $downloadPath -arg \"/qn \" -PassThru |wait-process\n", "</powershell>\n" ]]}} } } }, "Outputs" : { "FrontEndPublicDnsName" : { "Description" : "Public IP address of FrontEnd", "Value" : { "Fn::Join" : ["",[{ "Fn::GetAtt" : [ "FrontEnd", "PublicDnsName" ] }]]} }, "BackEndPublicDnsName" : { "Description" : "Public IP address of BackEnd", "Value" : { "Fn::Join" : ["",[{ "Fn::GetAtt" : [ "BackEnd", "PublicDnsName" ]}]]} } } }
Параметры, которые используются в темплейте:
- KeyName — Имя ключа для доступа
- SuffixName — Некий суффикс, который будет добавлен а тэг Name (это могут быть инициалы девелопера)
- FrontEndInstanceType — Тип шейпа для FrontEnd
- BackEndInstanceType — Тип шейпа для BackEnd
- PuppetServer — Url вашего Puppet сервера
- Zone — Зона, в которой будут созданы сервера
- BuildVersion — Версия приложения, которая будет взята с S3
- RoleName — заранее создання IAM Role с правами «S3 Read-Only»
- SecurityGroup — Также заранее созданная секьюрити группа
IAM Role и Security Group могут создаваться этим же темплейтом, это будет даже правильнее. В моей примере это не делается с целью упрощения понимания.
В разделе UserData выполняется скачивание и установка puppet agent, 7zip и формируются puppet.conf и facts.bat.
С Cloud Formation закончили, пора переходить к настройке Puppet.
Настройка Puppet Server Enterprise
Чтобы установить Puppet Server Enterprise, необходимо только скачать архив установщика, распаковать и запустить puppet-server-installer. Чтобы включить автоматическую регистрацию клиентов на сервере, нужно создать файл /etc/puppetlabs/puppet/autosign.conf следующего содержания:
*
Создадим необходимые модули. Модули, это что-то вроде кукбуков в Chef. Размещаются они в папке /etc/puppetlabs/puppet/modules.
Упрощённая структура модуля:
my_module/
— Название директории будет названием модуля.
manifests/
— Содержит манифесты модуля.
init.pp
— Содержит один классmy_module
. Название класса должно быть таким же как название модуля.other_class.pp
— Содержит еще один класс модуляmy_module::other_class
.
files/
— Содержит файлы, которые будут скачаны клиентомlib/
— Содержит плагины, кастомные фактыtemplates/
— Содержит темплейты, которые могут быть использованы в модуле
component.erb
— Этот манифест будет доступен в модуле какtemplate('my_module/component.erb')
.
У меня получилось семь модулей (может дальше их количество вырастет).
- nodes — модуль, который будет, исходя из значения node_role, подключать следующий необходимый модуль
/etc/puppetlabs/puppet/modules/nodes/manifests/init.ppclass nodes {
if "${node_role}" == «backend» {
include backend
}
if "${node_role}" == «frontend» {
include frontend
}
} - getbuild — этот модуль нужен для скачивания и распаковки архива приложения из AWS S3.
/etc/puppetlabs/puppet/modules/getbuild/manifests/init.ppclass getbuild {
file { ‘c:\config’:
ensure => ‘directory’
} ->
file { ‘c:\Build’:
ensure => ‘directory’
} ->
exec { ‘download_build’:
creates => «c:\\config\\${app_version}»,
path => $::path,
command => «powershell.exe -executionpolicy unrestricted start-bitstransfer -source s3-us-west-2.amazonaws.com/mybucket/${app_version} -Destination ‘c:\\config\\’»,
} ->
exec { ‘app_install’:
creates => «c:\\Build\CustomBackendService.exe.config»,
command => "\«c:\\Program Files\\7-Zip\\7z.exe\» x c:\\config\\${app_version} -oC:\\Build ",
}}
- mongodb — модуль для установки MongoDB
/etc/puppetlabs/puppet/modules/mongodb/manifests/init.ppclass mongodb {
file { ‘c:/config’:
ensure => directory,
} ->
file { ‘c:/config/mongodb.zip’:
ensure => file,
mode => ‘0777’,
source => ‘puppet:///modules/mongodb/mongodb-win32-x86_64-v2.4-latest.zip’,
} ->
file { ‘c:/MongoDB’:
ensure => directory,
} ->
file { ‘c:/MongoDB/bin’:
ensure => directory,
} ->
file { ‘c:/MongoDB/Data’:
ensure => directory,
} ->
file { ‘c:/MongoDB/logs’:
ensure => directory,
} ->
exec { ‘mongodb-unzip’:
creates => ‘c:/MongoDB/bin/mongod.exe’,
command => ‘«c:\\Program Files\\7-Zip\\7z.exe» e c:\\config\mongodb.zip -oC:\\MongoDB\\bin’,
} ->
exec { ‘mongodb-install’:
creates => ‘c:/MongoDB/logs/mongodb.log’,
command => ‘«c:\\MongoDB\\mongod.exe» —dbpath=c:\\MongoDB\\Data —port 27017 —logpath=c:\\MongoDB\logs\\mongodb.log —install —serviceName mongodb —serviceDisplayName «MongoDB Server» —serviceDescription «MongoDB Server»’,
} ->
exec { ‘mongodb-run’:
path => $::path,
command => ‘powershell.exe start-service mongodb’
}
} - api — модуль для установки приложения на FrontEnd
/etc/puppetlabs/puppet/modules/api/manifests/init.ppclass api {
include getbuild
exec { ‘iis_enable’:
creates => «C:\inetpub»,
command => «ServerManagerCmd -install Web-Server»} ->
exec { ‘api_deploy’:
path => $::path,
command => «powershell.exe Set-ItemProperty ‘IIS:\Sites\Default Web Site’ -Name MySite -Value C:\Build»
}
} - worker — модуль для установки приложения на BackEnd
/etc/puppetlabs/puppet/modules/worker/manifests/init.ppclass worker {
include getbuild
exec { ‘service_install’:
creates => «c:\\Build\\Custom.AWS.BackendService.InstallLog»,
command => «c:\\Build\\Custom.AWS.BackendService.exe -install»,
} ->
exec { ‘service-run’:
path => $::path,
command => ‘powershell.exe start-service Custom.AWS.Backend’
}
} - frontend — модуль, который подключает все необходимые модули для работы FrontEnd
/etc/puppetlabs/puppet/modules/frontend/manifests/init.ppclass frontend {
include api
} - backend — модуль, который подключает все необходимые модули для работы BackEnd
/etc/puppetlabs/puppet/modules/backend/manifests/init.ppclass backend {
include mongodb
include worker
}
В своих манифестах я практически везде использовал ресурс exec. При правильно подобранном параметре creates этот ресурс работает безотказно.
Более детально на одном из примеров:
exec { 'mongodb-unzip': creates => 'c:/MongoDB/bin/mongod.exe', command => '"c:\\Program Files\\7-Zip\\7z.exe" e c:\\config\mongodb.zip -oC:\\MongoDB\\bin', }
Если исполняемый файл c:/MongoDB/bin/mongod.exe отсутствует, то будет выполнена распаковка архива.
Теперь можно для удобства создать задачу в Вашей любимой CI системе, например Jenkins, поместить туда скрипт для запуска Cloud Formation template и девелоперы смогут разворачивать окружение в один клик.
На этом всё. Надеюсь данное руководство будет полезным…
Если среди прочитавших эту статью, будут спецы по использованию Puppet, я с превеликой благодарностью выслушаю Ваше мнение.
ссылка на оригинал статьи http://habrahabr.ru/company/epam_systems/blog/208710/
Добавить комментарий