Greg Walters
AWS and unfuddle
Setup a t1.micro instance with aws.amazon.com for your blog. Remember, if you don’t have an AWS account yet, this micro instance is free for 12 months! We get ~20KPIs per month so the micro instance is just enough to cover this (with significant tuning).
Sign up with unfuddle.com for 200MB worth of free, private git repositories.
Install & configure puppet standalone
$ apt-get install puppetmaster puppet
$ vi /etc/hosts 127.0.0.1 master.successfulengineering.com ...
$ vi /etc/puppet/puppet.conf ... [agent] server = master.successfulengineering.com
Setup your git repo
unfuddle has a nice help document for this, but the gist is basically to add the base puppet installation directory on your instance to unfuddle’s git repo:
$ cd /etc/puppet $ git remote add unfuddle git@successfulengineering.unfuddle.com:successfulengineering/puppetmaster.git $ git config remote.unfuddle.push refs/heads/master:refs/heads/master $ git add * $ git commit -am "initial commit" $ git push unfuddle master
Now, you can clone this to your local dev env and start creating manifests! After you push your local changes to git, do a git pull
on the remote instance. To apply manifest updates while building up your site, use the --test
option (otherwise puppet will start in daemon mode).
$ puppet agent --test
Congratulations! Now you’ve got a running puppet standalone environment backed with a git repository. Time for the real work.
Base environment (L)
Here’s what our nodes.pp file looks like:
node default { include setenv include ntp include users include mysql include apache include php include blogs }
The setenv
module gets my default packages installed (htop, unzip, wget, git-core, vim, fail2ban), sets up vi as the default editor, and installs a default locale at /etc/default/locale
as:
LANG="en_US.UTF-8" LC_ALL="en_US.UTF8"
NTP should be obvious, and the users
module ensures Matthias and my users’ exist with the appropriate SSH keys and admin group roles.
Base applications (AMP)
Now that our base Linux environment is setup, let’s get to the ‘AMP’ section.
We setup our base MySQL server with the following manifest:
class mysql { package { "mysql-server": ensure => present, } service { "mysql": ensure => running, enable => true, hasstatus => true, require => Package["mysql-server"], } file { "/etc/mysql/my.cnf": ensure => present, content => template("mysql/my.cnf.erb"), notify => Service["mysql"], require => Package["mysql-server"], } exec { "set mysql root password": path => "/usr/bin", unless => "mysql -uroot -p${root_mysql_password}", command => "mysqladmin -u root password ${root_mysql_password}", require => Service['mysql'], } }
The $root_mysql_password
variable is declared in our site.pp
file.
Apache is a bit more complex:
class apache { include apache::install include apache::conf include apache::sites include apache::mods }
For sites and mods, I define a custom method to allow us to easily enable/disable new sites and required modules. Here’s a snippet from the apache::mods
class:
define mods_stats ( $ensure = 'present') { case $ensure { 'present' : { exec { "/usr/sbin/a2enmod $name": unless => "/bin/sh -c '[ -L /etc/apache2/mods-enabled/$name.load ] \ && [ /etc/apache2/mods-enabled/${name}.load -ef /etc/apache2/mods-available/${name}.load ]'", notify => Exec["force-reload-apache"], require => Package["apache"], } } 'absent': { exec { "/usr/sbin/a2dismod $name": onlyif => "/bin/sh -c '[ -L /etc/apache2/mods-enabled/${name}.load ] \ && [ /etc/apache2/mods-enabled/${name}.load -ef /etc/apache2/mods-available/${name}.load ]'", notify => Exec["force-reload-apache"], require => Package["apache"], } } default: { err ( "Unknown ensure value: '$ensure'" ) } } } apache::mods::mods_stats { "rewrite" : ensure => present, notify => Service["apache"], } apache::mods::mods_stats { "deflate" : ensure => present, notify => Service["apache"], }
Although the definition of mods_stats
may seem a bit lengthy, it pays itself back pretty quickly when you can easily enable/disable any mods throughout your manifests.
The PHP module is more of the same, using puppet templates to control the memory allocation for apc.ini
and php.ini
. Email me for more details.
WordPress sites
So we have our default LAMP stack setup and initialized with our passwords, modules, and memory constraints. Let’s get down to business – namely, the blogs
module.
class blogs { include blogs::agileweboperations include blogs::successfulengineering }
Let’s have a look at this blog’s manifest:
class blogs::agileweboperations { # Apache website file { "/etc/apache2/sites-available/agileweboperations.com": ensure => present, owner => root, group => root, mode => 644, source => "puppet:///modules/blogs/etc/apache2/sites-available/agileweboperations.com.conf", require => Package["apache"], notify => Service["apache"], } apache::sites::site { "agileweboperations.com" : ensure => present } exec { "setup awo db": path => "/usr/bin", unless => "mysql -uroot -p${root_mysql_password} ${awo_mysql_db}", command => "mysql -uroot -p${root_mysql_password} -e " CREATE DATABASE ${awo_mysql_db} DEFAULT CHARACTER SET UTF8;"", require => Exec["set mysql root password"], } exec { "setup awo user": path => "/usr/bin", unless => "mysql -u${awo_mysql_user} -p${awo_mysql_pass}", command => "mysql -uroot -p${root_mysql_password} -e " CREATE USER '${awo_mysql_user}'@'localhost' IDENTIFIED BY '${awo_mysql_pass}'; GRANT ALL PRIVILEGES ON ${awo_mysql_db}.* TO '${awo_mysql_user}'@'localhost' WITH GRANT OPTION;"", require => Exec["setup awo db"], } }
This puts all the pieces of the puzzle together: our custom apache configuration, enabling the site with an easy one-liner, and bootstrapping the WordPress database and user (only if they don’t exist).
We don’t do a WordPress install per-se. We’ve been running this site for almost four years, and we have it in a git repository (conveniently hosted next to the puppet repo with unfuddle). All the migrations we’ve performed in the past have shown us the “famous 5-minute installation” can’t beat the ease of a git clone
.
Crafting and finding the above manifests on the Internet took me about 12 hours total work effort, but I have a few years of puppet experience under my belt as well. Hopefully, this guide gives you some ideas and pointers about how to get control of your WordPress environment with puppet. I’ve published an “anonymized” version of the entire configuration over at github – hope this helps you get a jump start on your server setup. Let me know if you have any questions!