Autoconfiguration using Puppet and AWS Cloud Formation

by Oleksandr Molchanov

The day has come for us to put aside our cookbooks, recipes and Chef's knives, and try on the role of puppet master!

Our initial goal is pretty trivial ? give developers a quick and easy way to deploy an environment. One exclusive requirement ? we must use Puppet Enterprise for the auto-configuration.

A few words about the environment we're going to deploy. It contains two components - FrontEnd (its functions are performed by an IIS server) and BackEnd (it contains a MongoDB database and a Worker service created by developers). Both components, as you can already tell, are implemented on a Windows Server. The source code for the FrontEnd and Worker service content are taken from AWS S3, where it is diligently stored by Jenkins on a nightly basis.

Creating a Cloud Formation template

Implementing a Cloud Formation template that will launch two Windows servers is very simple. A much more interesting challenge is figuring out how to inform Puppet about which configuration to apply to these servers.

Puppet's official documentation suggests applying regular expressions to the client hostname, but this does not really work for us, because on AWS Amazon the hostname is assigned automatically and can change after stop-starting the instance, so I would have to create a post-start script to change the hostname and only after that, run the puppet agent.

After digging around in the documentation a bit more, I found just the right thing - Custom External Facts. For those who have worked with Chef Server, facts are the equivalent to attributes. To add our own facts for the Windows machine, we need to create a bat or ps1 file with approximately the following content and put it here: "C:\ProgramData\PuppetLabs\facter\facts.d\"

@echo off
echo node_role=frontend
echo app_version=Build1.2.0

serverRole is, as the name implies, the role that is assigned to the server, and buildNumber is the version of that application that will be downloaded from S3 AWS.A Cloud Formation template will create this file (find the template text in the PDF attached to this article).

Parameters used in the template:

  • KeyName - the name of the access key
  • SuffixName - a suffix that is added to the Name tag (for example, it can be the developer's initials)
  • FrontEndInstanceType - shape type for the FrontEnd
  • BackEndInstanceType - shape type for the BackEnd
  • PuppetServer - Puppet server URL
  • Zone - the zone where the servers are created
  • BuildVersion - the version of the application that is downloaded from ? S3
  • RoleName - IAM Role created in advance with ?S3 Read-Only? permissions
  • SecurityGroup - a Security Group also created in advance

The IAM Role and Security Group can be created with the same template, and this would be the best option. I am not using this in my example for the sake of simplifying.

In the UserData section, Puppet agent and 7zip are downloaded, and puppet.conf and facts.bat are generated. We are done with Cloud Formation, now on to Puppet configuration.

Configuring Puppet Server Enterprise

To install Puppet Server Enterprise, we must simply download the installer archive and unpack and launch puppet-server-installer. To enable automatic client registration on the server, we create a /etc/puppetlabs/puppet/autosign.conf file with the following content:

*

On to creating the necessary modules. Modules are similar to the cookbooks in Chef. They are stored in the /etc/puppetlabs/puppet/modules folder. The simplified structure of a module:

my_module/ - the directory title will be the module's name

  • manifests/ - contains the module's manifests.

- init.pp - contains one my_module class. The name of the class must match the module's class

- other_class.pp - contains the other class of the module: my_module::other_class.

  • files/ - contains the files that will be downloaded by the client
  • lib/ - contains the plugin and custom facts
  • templates/ - contains the templates that can be used in the module

- component.erb - a manifest that will be available in the module as template('my_module/component.erb').

First, we add the necessary modules from PuppetLabs for installing IIS and for management.

puppet module install dism
puppet module install 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}"
} }

I got seven modules (their number may increase over time).

1. nodes - the module that will connect the next necessary module according to the node_role value

/etc/puppetlabs/puppet/modules/nodes/manifests/init.pp
class nodes {
if "${node_role}" == «backend» {
include backend
}
if "${node_role}" == «frontend» {
include frontend
}
}

2. getbuild - the module for downloading and unpacking the application archive from 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 -sources3-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 - the module for installing 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 - the module for installing the application on the 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 - the module for installing the application on the 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 - the module that connects all modules necessary for the FrontEnd to work

/etc/puppetlabs/puppet/modules/frontend/manifests/init.pp class frontend { include api include iis }

7. backend - the module that connects all modules necessary for the BackEnd to work

/etc/puppetlabs/puppet/modules/backend/manifests/init.pp
class backend {
include mongodb
include worker
}

In my manifests, I used the exec resource almost everywhere. With the correctly set creates parameter, this is a fail-proof solution.

More details in the following example:

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',
}

If there is no a c:/MongoDB/bin/mongod.exe executable file, then the archive will be unpacked.

Now, for the sake of convenience, we can create a task in our favorite CI system, for example, Jenkins, insert the script for running the Cloud Formation template into it, and the developers will be able to deploy the environment in one click.

That's all, folks. I hope this tutorial proves useful.

If any readers out there happen to be Puppet experts, I would be extremely grateful for your feedback.

Documentation

Below is a list of documents related to this section. You can find the full list of our documents in the Documentation Storage.

Please select a required document:

DevEnd Template

A template complementing the "Autoconfiguration Using Puppet and AWS" blog post.