Pull to refresh
EPAM
Компания для карьерного и профессионального роста

Автоконфигурация с помощью Puppet и AWS Cloud Formation

Reading time 9 min
Views 8.8K
imageВот и настал тот день, когда пришлось отложить в сторону кукбуки, рецепты, нож шеф-повара и немного позаниматься кукловодством.
Для начала постановка задачи довольно тривиальная — организовать для девелоперов возможность быстро и просто разворачивать окружение. Обязательное требование — для автоконфигурации использовать 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.
DevEnv.tmpl
{
    "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').

Сначала добавим необходимые модули из PuppetLabs для установки IIS и менеджмента.
puppet module install dism
puppet module install opentable-iis

Теперь нужно немного подправить манифест для opentable-iis
/etc/puppetlabs/puppet/modules/nodes/manifests/init.pp
class iis {
iis::manage_app_pool {"${fqdn}":
enable_32_bit => true,
managed_runtime_version => 'v4.0',
} ->

iis::manage_site {"${fqdn}":
site_path => 'C:\MyAppPath',
port => '80',
ip_address => '*',
host_header => "${fqdn}",
app_pool => "${fqdn}"
}

}

У меня получилось семь модулей (может дальше их количество вырастет).
  1. nodes — модуль, который будет, исходя из значения node_role, подключать следующий необходимый модуль
    /etc/puppetlabs/puppet/modules/nodes/manifests/init.pp
    class nodes {
    if "${node_role}" == «backend» {
    include backend
    }
    if "${node_role}" == «frontend» {
    include frontend
    }
    }

  2. getbuild — этот модуль нужен для скачивания и распаковки архива приложения из AWS S3.
    /etc/puppetlabs/puppet/modules/getbuild/manifests/init.pp
    class 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 ",
    }

    }

  3. mongodb — модуль для установки MongoDB
    /etc/puppetlabs/puppet/modules/mongodb/manifests/init.pp
    class 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'
    }
    }

  4. api — модуль для установки приложения на FrontEnd
    /etc/puppetlabs/puppet/modules/api/manifests/init.pp
    class api {

    include getbuild

    dism { 'IIS-WebServerRole':
    ensure => present,
    } ->

    dism { 'IIS-WebServer':
    ensure => present,
    require => Dism['IIS-WebServerRole'],
    }

    }

  5. worker — модуль для установки приложения на BackEnd
    /etc/puppetlabs/puppet/modules/worker/manifests/init.pp
    class 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'
    }
    }

  6. frontend — модуль, который подключает все необходимые модули для работы FrontEnd
    /etc/puppetlabs/puppet/modules/frontend/manifests/init.pp
    class frontend {
    include api
    include iis
    }

  7. backend — модуль, который подключает все необходимые модули для работы BackEnd
    /etc/puppetlabs/puppet/modules/backend/manifests/init.pp
    class 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, я с превеликой благодарностью выслушаю Ваше мнение.
Tags:
Hubs:
+5
Comments 10
Comments Comments 10

Articles

Information

Website
www.epam.com
Registered
Founded
1993
Employees
over 10,000 employees
Location
США
Representative
vesyolkinaolga