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

в 15:17, , рубрики: cloudformation, puppet, автоконфигурация, администрирование, Блог компании EPAM Systems, системное администрирование, метки: , , ,

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:ProgramDataPuppetLabsfacterfacts.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.WebClientn",
					"$webClient.DownloadFile($MsiUrl, $downloadPath)n",
					"$process = Start-Process -File $downloadPath -arg "/qn /norestart" -PassThru |wait-processn",
					
					"$PublicHostName = Invoke-RestMethod -Uri http://169.254.169.254/latest/meta-data/public-hostname -Method Getn",
					"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-puppetn",
					
					"$MsiUrl = "https://s3-us-west-2.amazonaws.com/mybucket/7zip.msi"n",
					"$downloadPath = "c:\7zip.msi"n",
					"$webClient = New-Object System.Net.WebClientn",
					"$webClient.DownloadFile($MsiUrl, $downloadPath)n",
					"$process = Start-Process -File $downloadPath -arg "/qn " -PassThru |wait-processn",
					
				  "</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.WebClientn",
					"$webClient.DownloadFile($MsiUrl, $downloadPath)n",
					"$process = Start-Process -File $downloadPath -arg "/qn /norestart" -PassThru |wait-processn",
					
					"$PublicHostName = Invoke-RestMethod -Uri http://169.254.169.254/latest/meta-data/public-hostname -Method Getn",
					"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-puppetn",
					
					"$MsiUrl = "https://s3-us-west-2.amazonaws.com/mybucket/7zip.msi"n",
					"$downloadPath = "c:\7zip.msi"n",
					"$webClient = New-Object System.Net.WebClientn",
					"$webClient.DownloadFile($MsiUrl, $downloadPath)n",
					"$process = Start-Process -File $downloadPath -arg "/qn " -PassThru |wait-processn",					
				  "</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').

У меня получилось семь модулей (может дальше их количество вырастет).

  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:\BuildCustomBackendService.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:\configmongodb.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:\MongoDBlogs\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
    exec { 'iis_enable':
    creates => «C:inetpub»,
    command => «ServerManagerCmd -install Web-Server»

    } ->
    exec { 'api_deploy':
    path => $::path,
    command => «powershell.exe Set-ItemProperty 'IIS:SitesDefault Web Site' -Name MySite -Value C:Build»
    }
    }

  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
    }

  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:\configmongodb.zip -oC:\MongoDB\bin',
	} 

Если исполняемый файл c:/MongoDB/bin/mongod.exe отсутствует, то будет выполнена распаковка архива.

Теперь можно для удобства создать задачу в Вашей любимой CI системе, например Jenkins, поместить туда скрипт для запуска Cloud Formation template и девелоперы смогут разворачивать окружение в один клик.

На этом всё. Надеюсь данное руководство будет полезным…
Если среди прочитавших эту статью, будут спецы по использованию Puppet, я с превеликой благодарностью выслушаю Ваше мнение.

Автор: camec

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js