Deploy WordPress to Amazon EC2 Micro Instance with Opscode Chef

Updates

September 9, 2011

Included the latest Chef Knife ec2 server create argument that sets the EBS Volume to not be deleted on the termination of the EC2 Instance

Intro

Up until recently a friend lent me a Virtual Machine in he Cloud for my Blog. I didn’t have to do anything to manage it. But his company is no longer supporting those machines so I had to move my blog.
Right around that time Amazon announced their Micro Instances at a very low price. I also wanted to try out the new Opscode Chef knife commands that bootstrap an EC2 instance from scratch as well as their Chef Server SaaS. So this was a good reason to combine all these to create my new Blog Instance. And now Amazon even offers the ability to have a single micro instance free for a year! (You still have to pay for I/O charges but they are really cheap compared to the instance charges, unless you have a blog that is too popular, but then you’ll need a bigger server anyway)
Spoiler Alert: It was way too easy and no problem at all! (Though I did end up having to write a few support cookbooks like vsftpd, but now you don’t have to)

Some Assumptions for this post

  • You are using a *nix platform for your local development (ie your laptop is a Mac, Linux, *BSD or equivalent) and that your target server you want to deploy to is a relatively recent Ubuntu Linux.
  • You have or will install git client on your local development box
  • You followed the directions or have done the equivalent of the instructions in the Opscode How to Get Started pages as noted below

Set up an Account on Amazon Web Services

If you don’t already have an Amazon EC2 Account, go to the Amazon Web Services page and click on the Sign Up Now button. Create all your user info and then Sign Up for Amazon EC2. You’ll need to put in  credit card info at this point since you’ll need to pay for the EC2 instance you’ll be using shortly. After you complete your signup, you’ll need to get your credentials at the AWS Security Credentials page.  Copy down your Access Key ID and click on Show under the Secret Access Key and get that as well. You will need these values to put into your knife.rb file that you will get to in the following steps.

Get an Opscode Platform Account

Its free and easy. Just go to the Opscode Platform Signup page. Fill in your information and submit. There is no cost for up to 5 client nodes. Once you set up and confirm your account you can go thru the How to Get Started pages which includes how to set up your client development machine (installing Chef Client, Knife and various dependencies) as well as downloading your private key, organization key and your Knife Configuration File. You should go thru all 5 steps of the Getting Started section. And please do follow their examples of using git. The rest of this post assumes you have git installed and will use it for your own repository even if you don’t push it to an upstream git repository.

Once you have completed that you will be ready to use the remaining steps of this blog post. The remaining steps will assume you put your chef-repo in the same location as the Opscode instructions suggested (~/chef-repo). If you put it somewhere else, just adjust your path to your chef-repo as appropriate.

It also assumes you got your private user key (your_user_name.pem) and organization validator key (your_organization-validator.pem) and knife.rb in Section 3 of How to Get Started: Setting Up a Chef Client. In that section you ran the command knife configure client ./client-config inside your ~/chef-repo/ directory. That will have created ~/chef-repo/.chef and put the keys and knife.rb in that directory.

For the use of this blog post, we will use the username: rberger_test and organization name: install_wordpress. So the private user key name for this example will be: rberger_test.pem and the organization validator key will be called install_wordpress-validator.pem. You should copy your keys someplace that you will not loose outside of ~/chef-repo. There are ways to create new ones, but its always easier not to have to. Bottom line, is its expected that your keys and the knife.rb will be in your ~/chef-repo/.chef directory at this point.

Set up your Development Environment

Your development environment is your home or work computer/laptop. Its the machine that is local to you. It is on this machine that you put together your Cookbooks. From here you push your cookbooks to the Opscode Chef Server, issue the commands to configure AWS and launch your AWS instances.

Tweak up your chef-repo

I like to keep the “standard” chef recipes that get downloaded from git or from cookbook.opscode.com in their own directory (called cookbooks) and all the cookbooks I create or highly modify in another directory (site-cookbooks). In Step 2 of the How to Get Started: Setting Up Your User Environment, they had you create a ~/chef-repo directory and populate it from git or from a tar ball. You should add the site-cookbooks directory to your ~/chef-repo. We’re also going to add an empty README.md to the site-cookbooks directory so when we create our own git repository that directory will be there (an empty directory will not be added to a git repository)

cd ~/chef-repo
mkdir site-cookbooks
echo "Directory for customized cookbooks" > site-cookbooks/README.md

You will probably also not want to include your .chef directory with all your keys in what gets uploaded to any outside chef repository. If you are just keeping things local, you can skip this step. Edit ~/chef-repo/.gitignore and add .chef to the file on its own line. You might also want to add client-config to .gitignore as well as any temporary or backup file suffixes you might have. For instance if you use Emacs, you would add ~* (emacs backup files suffix), the .DS_Store which is something left by the Mac filesystem,  .rake_test_cache which is left around by Rake and metadata.json which is a file generated by chef. My .gitignore looks like:

.chef
client-config
*~
.DS_Store
.rake_test_cache
metadata.json

If you created the ~chef-repo from the git clone of the Opscode repository, you’ll want to get rid of the git configuration and history from the cloning of the Opscode chef-repo and create your own git repository:

rm -rf .git
git init
git add -A
git commit -a -m "Created my own basic chef-repo"

The above commands will have removed the old git config that came when you did the git clone http://github.com/opscode/chef-repo.git command as part of the Opscode How to Get Started pages. The git init, add and commit will create a new local git repository for your own use not connected to the opscode repository. You can then add a remote repository if you want to be able to push your repository and future changes to another git repository such as github.com.

Updating your knife.rb file with Amazon Credentials

Add the following lines to the end of your ~/chef-repo/.chef/knife.rb file. You should have gotten your AWS Access Key and Secret Access key when you signed up to Amazon Web Services, but you can always go back and get it at AWS Security Credentials page. Your final knife.rb should look something like this, except the various items that are customized to your setup. In the example below rberger_test would be replaced by your Opscode User name and install_wordpress would be replaced by your Opscode Organization name that was used when you went thru the Section 3 of the Opscode How to Get Started: Setting Up a Chef Client.

current_dir = File.dirname(__FILE__)
log_level                :info
log_location             STDOUT
node_name                "rberger_test"
client_key               "#{current_dir}/rberger_test.pem"
validation_client_name   "install_wordpress-validator"
validation_key           "#{current_dir}/install_wordpress-validator.pem"
chef_server_url          "https://api.opscode.com/organizations/install_wordpress"
cache_type               'BasicFile'
cache_options( :path => "#{ENV['HOME']}/.chef/checksums" )
cookbook_path            ["#{current_dir}/../cookbooks", "#{current_dir}/../site-cookbooks"]
knife[:aws_access_key_id]     = "Your Access Key"
knife[:aws_secret_access_key] = "Your Secret Access Key"

You can test that your knife.rb is setup enough to access AWS by issuing the command

knife ec2 server list

And you should see something like this (just the heading and no instances unless you’ve launched some EC2 instances earlier:

Instance ID      Public IP        Private IP       Flavor        Image        Security Groups  State

Get the Appropriate Cookbooks

We’ll get cookbooks using the knife command and the cookbooks.opscode.com web service. We’ll be using the following cookbooks:

  • chef
  • apache2
  • mysql
  • openssl
  • php
  • postfix
  • sudo
  • users
  • vsftpd
  • wordpress

Use the knife command on your local development machine to pull down the cookbooks you need. The command we’re using (knife cookbook site vendor COOKBOOK) will automatically download the cookbooks and install them in the ~/chef-repo/cookbooks directory. It will also check them into your git repository as a vendor branch (Stay on the master branch at least until you have installed all the cookbooks).

cd ~/chef-repo
knife cookbook site vendor chef -d
knife cookbook site vendor apache2 -d
knife cookbook site vendor mysql -d
knife cookbook site vendor openssl -d
knife cookbook site vendor php -d
knife cookbook site vendor postfix -d
knife cookbook site vendor sudo -d
knife cookbook site vendor users -d
knife cookbook site vendor vsftpd -d
knife cookbook site vendor wordpress -d

Those commands will download all the cookbooks and any other cookbook dependencies they may have into your ~/chef-repo/cookbooks directory and check each one in as a git branch in your repo. If you do an ls on your ~/chef-repo/cookbooks directory you should see:

README.md       bluepill        couchdb         java            php             runit           users           xml
apache2         build-essential daemontools     mysql           postfix         sudo            vsftpd          zlib
apt             chef            erlang          openssl         rabbitmq_chef   ucspi-tcp       wordpress

And if you do a git branch you should see your master branch as the current and a chef-vendor- for each of the cookbooks you installed:

  chef-vendor-apache2
  chef-vendor-apt
  chef-vendor-bluepill
  chef-vendor-build-essential
  chef-vendor-chef
  chef-vendor-couchdb
  chef-vendor-daemontools
  chef-vendor-erlang
  chef-vendor-java
  chef-vendor-mysql
  chef-vendor-openssl
  chef-vendor-php
  chef-vendor-postfix
  chef-vendor-rabbitmq_chef
  chef-vendor-runit
  chef-vendor-sudo
  chef-vendor-ucspi-tcp
  chef-vendor-users
  chef-vendor-vsftpd
  chef-vendor-wordpress
  chef-vendor-xml
  chef-vendor-zlib
* master

If you ever want to update these standard cookbooks,  you can just redo the knife cookbook site vendor Cookbook command.

Create site-cookbooks to extend standard cookbooks

It is standard practice to put the official cookbooks in the ~chef-repo/cookbooks directory, as we just did in the previous step. Any cookbook overrides, extensions or custom cookbooks go into the ~chef-repo/site-cookbooks directory. If you create a cookbook directory in ~chef-repo/site-cookbooks with the same name as a cookbook in the ~chef-repo/cookbooks directory, the files, templates and/or recipes in the ~chef-repo/site-cookbook directory will override the matching files, templates and/or recipes in the cookbook of the same name in the ~chef-repo/cookbooks directory. We will now extend two of the cookbooks; users and wordpress.

Extend the Sudo cookbook so its suitable for EC2

The standard sudo cookbook creates a sudoers file that requires passwords to activate sudo. Most EC2 environments do not allow passwords for logins and require that you login only with ssh keys. So we need to modify the Sudo cookbook to create the sudoers file with the NOPASSWORD flag set for all the users we want to have sudo powers. We just need to override the template file used in the standard sudo cookbook.

First have to make a directory for the new template in your site-cookbooks directory:

mkdir -p site-cookbooks/sudo/templates/default

Copy the following into site-cookbooks/sudo/templates/default/sudoers.erb:

#
# /etc/sudoers
#
# Generated by Chef for
#

Defaults !lecture,tty_tickets,!fqdn

# User privilege specification
root  ALL=(ALL) ALL


 ALL=(ALL) NOPASSWD:ALL


# Members of the sysadmin group may gain root privileges
%sysadmin ALL=(ALL) NOPASSWD:ALL


# Members of the group '' may gain root privileges
% ALL=(ALL) NOPASSWD:ALL

Fix a bug in the latest version of the Standard Mysql Cookbook

As I was writing this post, Opscode came out with a new version of the Mysql Cookbook that seems to have a bug with the Chef Client version 0.9.12. It may be fixed by the time you read this. If you are running Chef 0.9.12, check for line 59 of cookbooks/mysql/recipes/client.rb. Change

if platform_version.to_f >= 5.0

to:

if node.platform_version.to_f >= 5.0

Extend the WordPress cookbook to do some custom actions

We need to do a few custom actions after we install wordpress. The main one being to change the onwnership of the wordpress directory and most of the files to the user blog.

We need to add a user named blog that has its home directory the same as the wordpress directory. We will use this blog user to do automatic updates to wordpress. It will use vsftpd for secure ftp and will have only access to the wordpress directory.

We also need to add a swap file to the server. We could create a new cookbook to hold this as its not really wordpress related, but because this is such a simple system, we will just add a new recipe to wordpress to handle these miscellaneous actions.

Create a recipe to add the blog user and change ownership of the wordpress directory

First make the directories in site-cookbooks for extending the wordpress cookbook:

mkdir -p site-cookbooks/wordpress/recipes
mkdir -p site-cookbooks/wordpress/attributes
mkdir -p site-cookbooks/wordpress/templates/default

Create and edit the file site-cookbooks/wordpress/attributes/wordpress.rb and put the following in it (note, this must have a different name than the one used in the standard wordpress cookbook templates directory):

default[:wordpress][:blog_updater][:username] = "blog"

::Chef::Node.send(:include, Opscode::OpenSSL::Password)

default[:wordpress][:blog_updater][:password] = secure_password
# hash set by recipe or manually using makepasswd
default[:wordpress][:blog_updater][:hash] = nil

# For creating the swap partition. Swap_size is in GB
default[:wordpress][:gb_swap_size] = 2
default[:wordpress][:swap_file] = "/swap_file"

This will set the [:wordpress][:blog_updater] to be the name “blog”. This is the default for the username that will have the ability to use vsftpd to update wordpress and its plugins. We actually override this in the wordpress.rb role file. But we put a default here as well for good practice (ie. the cookbook will work even if someone doesn’t override the value in a role).

The ::Chef::Node.send(:include, Opscode::OpenSSL::Password) line is there so we can use the Chef mechanism to create an auto-generated password (secure_password). We then use that mechanism to set the default password for the blog_updater.

Create and edit site-cookbooks/wordpress/recipes/blog_user.rb. put the following as the contents:

# Get the password cryptographic hash for node[:wordpress][:blog_updater][:password
package "makepasswd"
package "libshadow-ruby1.8"
if node[:wordpress][:blog_updater][:hash].nil? || node[:wordpress][:blog_updater][:hash].empty?
  cmd = "echo #{node[:wordpress][:blog_updater][:password]} | /usr/bin/makepasswd --clearfrom=- --crypt-md5 |awk '{ print $2 }'"
  ruby_block "create_blog_updater_pw" do
    block do
      node.set[:wordpress][:blog_updater][:hash] = `#{cmd}`.chomp
    end
    action :create
  end
end

# Create the blog_updater user with their home directory being the wordpress directory and the group as the same group as the Apache runtime group
user "#{node[:wordpress][:blog_updater][:username]}" do
  home "#{node[:wordpress][:dir]}"
  gid "#{node[:apache][:user]}"
  shell "/bin/bash"
  supports :manage_home => true
  unless node[:wordpress][:blog_updater][:hash].nil? || node[:wordpress][:blog_updater][:hash].empty?
    password "#{node[:wordpress][:blog_updater][:hash]}"
  end
end

# Change the ownership of the wordpress directory so that the blog user can update
execute "chown wordpress home for blog user" do
  cwd "#{node[:wordpress][:dir]}"
  user "root"
  command "chown -R #{node[:wordpress][:blog_updater][:username]}:#{node[:apache][:user]} #{node[:wordpress][:dir]}"
  not_if { node[:wordpress][:dir].nil? || node[:wordpress][:dir].empty? || (not File.exists?(node[:wordpress][:dir])) }
end

The above code will create the blog_user as a Linux user on the target system and set its home directory to be the wordpress directory. This is to make it work with vsftpd.

Create a template to override the default wordpress apache config

The standard WordPress cookbook sets the Apache Server Name the FQDN of the EC2 Public DNS and sets the Server Aliases to the EC2 FQDN Private DNS name. This is pretty useless. We would like to have the cookbook set the Server Alias to the FQDN’s based on our own DNS names. To do this without overriding the whole standard WordPress cookbook, we can override one template and name it: site-cookbooks/wordpress/templates/default/wordpress.conf.erb.


  ServerName
  ServerAlias
  DocumentRoot

  >
    Options FollowSymLinks
    AllowOverride FileInfo
    Order allow,deny
    Allow from all



    Options FollowSymLinks
    AllowOverride None


  LogLevel info
  ErrorLog /-error.log
  CustomLog /-access.log combined

  RewriteEngine On
  RewriteLog /-rewrite.log
  RewriteLogLevel 0


The key changes are the ServerAlias line where we now add the @node[:wordpress][:server_aliases] will add any aliases specified by this attribute which we set in the wordpress.rb role file. We also change the AllowOverride to FileInfo for the docroot

Create a recipe to add a swap file to the server

The t1.micro instance only has 612MB of RAM. You can easily run out of that with a WordPress blog. So we have a recipe to add a swap file system utilizing some space the EBS  disk Volume. This recipe creates a 2GB file called /swap_file  using dd and then uses the mkswap and swapon commands to make that file into a swap partition. The recipe also updates the /etc/fstab file so that the swap file will be mounted again if the instance reboots.

Create and edit the file site-cookbooks/wordpress/recipes/add_swap.rb with the following content:

mb_block_size = 100
count = (node[:wordpress][:gb_swap_size] * 1024) / mb_block_size
bash "add_swap" do
  user "root"
  code < "#{node[:wordpress][:swap_file]}"
  )
end

Create and edit the file site-cookbooks/wordpress/templates/default/fstab.erb and put the following content:

# /etc/fstab: static file system information.
#                             
proc                   /proc           proc   nodev,noexec,nosuid     0       0
      none            swap   sw                      0       0
/dev/sda1              /               ext3   defaults                0       0
/dev/sda2              /mnt            auto   defaults,nobootwait,comment=cloudconfig 0       0

Create WordPress Role

This example will use a single role named wordpress. Use your favorite editor to create a file in your repo with the path roles/wordpress.rb with the following contents (Substitute your domain for ibd.com and change the hostnames such as test and wordpress-test to names appropriate for your blog. Replace rberger_test with the userid you want to use to log into your server via ssh):

name "wordpress"
description "Blog using wordpress"
recipes "apt", "build-essential", "chef::client_service", "users::sysadmins",
        "sudo", "postfix", "mysql::server", "wordpress", "wordpress::blog_user",
        "wordpress::add_swap", "vsftpd"

override_attributes(
  "postfix" => {"myhostname" => "test.ibd.com", "mydomain" => "ibd.com"},
  "authorization" => {
    "sudo" => {
      "groups" => [],
      "users" => ["rberger_test", "ubuntu"]
    }
  },
  "wordpress" => {
     "server_aliases" => %w(test.ibd.com wordpress-test.ibd.com),
     "version" => "3.0.4",
     "checksum" => "c68588ca831b76ac8342d783b7e3128c9f4f75aad39c43a7f2b33351634b74de",
     "blog_updater" => {
       "username" => "blog",
       "password" => "big-secret"
     }
   },
   "vsftpd" => {"chroot_users" => %w(blog)}
)

The recipes line will be used to determine which cookbook/recipes (order is important) should be loaded by Chef when the chef-client is run on your new server.

  • apt: Configures various APT components on Debian-like systems.
  • build-essential: Installs C compiler / build tools
  • chef::client_service: Sets up a Chef client daemon to run periodically
  • users::sysadmins: Creates users with ssh authorized keys. Requires a databag to be configured with users info
  • sudo: Installs sudo and configures the /etc/sudoers file
  • postfix: Installs and configures postfix for outgoing email
  • mysql::server: Installs & configures packages required for mysql servers
  • wordpress: Installs and configures WordPress according to the instructions at http://codex.wordpress.org/Installing_WordPress
  • wordpress::blog_user: Custom add-on recipe to add a user named “blog” to use with vsftpd for automatic wordpress and plugin updates
  • wordpress::add_swap: Custom add-on recipe to add a swap partition to the instance
  • vsftpd: Very Basic installation and configuration of vsftpd to support Secure (SSL) SFTP

The override_attributes are used to configure various cookbooks.

  • postfix – Parameters for the postfix cookbook. Mainly sets the host and domain name to be meaningful
  • authorization – Configures the sudo cookbook. Tells which users and groups should have sudo capability
  • wordpress Some of these override values in the base cookbook and others for the site-cookbook version
    • server-aliases – Sets aliases for the blog name. Will be used as serveralias names in the apache config.
    • version – The version of wordpress to download.
    • checksum – The checksum of the tar image of the wordpress download.
    • blog_updater- Info needed to create a user that will do auto updates to wordpress via vsftps
      • username – The username of the user
      • password – The password to create for the user
  • vsftpd – Sets what user should be allowed to access via ftp and have their home directory chroot’d (should be the same as wordpress-blog_updater).

Upload the cookbooks and roles to Opscode Chef Platform

Run the following commands while you are in ~/chef-repo. This will upload the wordpress role and all the cookbooks in your chef-repo to your account on the Opscode Chef Platform:

knife role from file roles/wordpress.rb
knife cookbook upload -a

Create the Users databag

The users cookbook will take info from Opscode Chef Server Data Bag names users. There can be an item for each user that you want to create a login for. The standard Opscode users cookbook expects the users set up in the data bags to be in the group sysadmin and have the ability to sudo and gain root powers.

We’ll need to create an item for each user you would like to have on your system. I suggest you make at least one for yourself. Here is the data bag I used for my setup. I don’t show the ssh key. You’ll have to substitute your own public ssh key for <your public ssh key> that you will use to ssh to the server. Its a requirement that you have an ssh key as described in the next section on the sudo cookbook.

Here is the JSON representation of my user data bag item. Create a directory users in ~/chef-repo/data_bags/users and put the following JSON in the file ~/chef-repo/users/.json (where is the username you want to have on the target system. The id will be the name of the item in the data bag and what will become your username  (in this case rberger_test) You will also need to include the public ssh key you want associated with this user. You will need to have created a ssh keypair (private and public) locally using something like ssh-keygen. You don’t really need the openid. You should be able to set that to an empty string (“”):

{
  "id": "rberger_test",
  "comment": "Robert J. Berger",
  "uid": 2001,
  "groups": "sysadmin",
  "shell": "/bin/bash",
  "openid": "rberger_test.myopenid.com",
  "ssh_keys": ""
}

You will need to create the users databag and then upload your version of the user JSON (rberger_test.json in the example) to the Chef server with the following commands:

knife data bag create users
knife data bag from file users data_bags/users/rberger_test.json

With Amazon EC2 instances its best to only allow access without passwords using ssh keys. Since the login is protected by ssh keys, and you don’t have passwords associated with the users, you need to make sure sudo is set up to allow invoking sudo for specific users (sysadmins) without a password. The users cookbook creates such a user based on the users data bag. But the sudo cookbook does not set up sudoers to support not having password. We will modify the sudoers.erb template later. Make sure you don’t deploy without this modification as the default sudo cookbook will make it impossible to sudo on an EC2 instance after its run.

Configure AWS

You can do most of the following by using the a GUI web app such as Amazon’s AWS console, the Firefox plugin ElasticFox other such GUI  tools or the command line ec2-api-tools. For now, we’ll show how to do this with the Amazon AWS Console.

Set up Security Group

Add a WordPress group, that enables  ssh, http and https. You should open at least http and https to all IP addresses (represented by Source IP: 0.0.0.0/0) You can decide to open up ssh to every IP or just to your own development network or host. In this example we’ll open it up to the world. Note: by default ping (ICMP) is not enabled so you can not ping your instance. You can enable ping by adding a line where it doesn’t matter what is in Connection Method, Protocol is ICMP, From Port and To Port is set to -1 and Source IP is 0.0.0.0/0.

Navigate to Security Groups and Click on Create Security Group

Enter the name and description of the Security Group

Set the Ports that are to be enabled (Select the Connection Method, enter the Source IP, and click Save)

Generate an SSH Key Pair for accessing your instance[s]

You need to use the Amazon Key Pair generator to generate a key that will be used to make initial ssh connections to your new instances after they are created. You can also do this on the AWS Management Console’s EC2 Key Pairs page:

Navigate to the Key Pairs page and click on Create Key Pair

You can name the key pair anything, but you may want to use this key pair to access this and future instances, so you might want to name it something general like aws-east. Here we’re going to name it something more specific: aws-wordpress just for this example.

Enter the name for the key

After the key pair is created, make sure to save the private key that is downloaded automatically

At this point a file named asw-wordpress.pem will have been downloaded by your browser. Make sure not to loose it! Put it into your ~/.ssh directory and chmod it to 0600:

chmod 0600 ~/.ssh/aws-wordpress.pem

The final Key Pairs page on the AWS Management Console should look something like:

Final Key Pair Page

Create the Instance and Bootstrap Chef on the Instance

The Chef Knife command has the ability to launch EC2 (and other cloud) instances. This process automatically installs chef and all its dependencies after the instance is created. If all goes well, it then loads and executes your roles and cookbooks on the instance creating your server.

You can see what options are available to this command:

# knife ec2 server create --help
knife ec2 server create (options)
    -Z, --availability-zone ZONE     The Availability Zone
    -A, --aws-access-key-id KEY      Your AWS Access Key ID
    -K SECRET,                       Your AWS API Secret Access Key
        --aws-secret-access-key
        --user-data USER_DATA_FILE   The EC2 User Data file to provision the instance with
        --bootstrap-version VERSION  The version of Chef to install
    -N, --node-name NAME             The Chef node name for your new node
        --server-url URL             Chef Server URL
    -k, --key KEY                    API Client Key
        --color                      Use colored output
    -c, --config CONFIG              The configuration file to use
        --defaults                   Accept default values for all questions
    -d, --distro DISTRO              Bootstrap a distro using a template
        --ebs-no-delete-on-term      Do not delete EBS volumn on instance termination
        --ebs-size SIZE              The size of the EBS volume in GB, for EBS-backed instances
    -e, --editor EDITOR              Set the editor to use for interactive commands
    -E, --environment ENVIRONMENT    Set the Chef environment
    -f, --flavor FLAVOR              The flavor of server (m1.small, m1.medium, etc)
    -F, --format FORMAT              Which format to use for output
    -i IDENTITY_FILE,                The SSH identity file used for authentication
        --identity-file
    -I, --image IMAGE                The AMI for the server
        --no-color                   Don't use colors in the output
    -n, --no-editor                  Do not open EDITOR, just accept the data as is
        --no-host-key-verify         Disable host key verification
    -u, --user USER                  API Client Username
        --prerelease                 Install the pre-release chef gems
        --print-after                Show the data after a destructive operation
        --region REGION              Your AWS region
    -r, --run-list RUN_LIST          Comma separated list of roles/recipes to apply
    -G, --groups X,Y,Z               The security groups for this server
    -S, --ssh-key KEY                The AWS SSH key id
    -P, --ssh-password PASSWORD      The ssh password
    -x, --ssh-user USERNAME          The ssh username
    -s, --subnet SUBNET-ID           create node in this Virtual Private Cloud Subnet ID (implies VPC mode)
        --template-file TEMPLATE     Full path to location of template to use
    -V, --verbose                    More verbose output. Use twice for max verbosity
    -v, --version                    Show chef version
    -y, --yes                        Say yes to all prompts for confirmation
    -h, --help                       Show this message

The actual command we’ll use is:

knife ec2 server create --run-list 'role[wordpress]' --node-name test-wordpress --flavor t1.micro \
--identity-file ~/.ssh/aws-wordpress.pem --image ami-a2f405cb --groups wordpress \
--ssh-key aws-wordpress --ssh-user ubuntu --ebs-no-delete-on-term

Details of knife command to launch instance

role[wordpress]: The role[s] given to this instance. More than one can be specified by an orderd space separated list of strings: ‘role[role0]‘ ‘role[role1]‘ …

–node-name test-wordpress: The name of the instance. Used by Chef to name the Node and Client

–flavor t1.micro: The EC2 Instance Type. Here we are using the smallest type. This is the only one that is “free”

–identity-file ~/.ssh/aws-wordpress.pem: The path to the ssh private key that was downloaded earlier from the AWS Management Console. You could potentially not include this if you added the key to your ssh-agent.

–image ami-a2f405cb: The Amazon Machine Image assigned to this instance. It is the image of the root file system for the instance and thus determines what OS and software is booted when the instance is started. In this case it is the Canonical Ubuntu 10.4 32 bit AMI. You can find the latest Ubuntu AMIs for each region at the top of the home page of Eric Hammond’s super helpful site.

–groups wordpress: The Security Group[s] to be assigned to this instance. In this case its “wordpress” Multiple Groups can be assigned as a comma separated list

–ssh-key aws-wordpress: The name of the SSH Key Pair that was downloaded from the AWS Management Console

–ssh-user ubuntu: The user name for ssh access. This AMI uses “ubuntu”. The AMI’s usually are configured to allow only a single user to ssh by default. Different AMI’s use different names such as root or ec2-user.

–ebs-no-delete-on-term: By default, the EBS Volume is deleted when the EC2 instance is terminated. By adding this flag it will instead make it so the EBS volume will continue to exist after the EC2 instance has been terminated. You want this for your final deployed site so that if something goes wrong with the EC2 instance you will still have your EBS volume and can use it to create a new EC2 instance without loosing your data. (That is the topic of another tutorial though!)

Successful launch results

After you fire off the knife ec2 server create command, you’ll see something like:

[WARN] Fog::AWS::EC2#new is deprecated, use Fog::AWS::Compute#new instead (/Library/Ruby/Gems/1.8/gems/chef-0.9.12/lib/chef/knife/ec2_server_create.rb:145:in `run')
Instance ID: i-d10ae5bd
Flavor: t1.micro
Image: ami-a2f405cb
Availability Zone: us-east-1b
Security Groups: wordpress
SSH Key: aws-wordpress

Waiting for server..............
Public DNS Name: ec2-184-73-44-17.compute-1.amazonaws.com
Public IP Address: 184.73.44.17
Private DNS Name: domU-12-31-39-10-60-17.compute-1.internal
Private IP Address: 10.198.99.229

Waiting for sshd...done
INFO: Bootstrapping Chef on ec2-184-73-44-17.compute-1.amazonaws.com

That will be followed by loads of debugging info as the knife command bootstraps chef and its related packages and gems. This can go on for 10 to 20 minutes. Eventually you’ll see something along the lines of:

Instance ID: i-d10ae5bd
Flavor: t1.micro
Image: ami-a2f405cb
Availability Zone: us-east-1b
Security Groups: wordpress
SSH Key: aws-wordpress
Public DNS Name: ec2-184-73-44-17.compute-1.amazonaws.com
Public IP Address: 184.73.44.17
Private DNS Name: domU-12-31-39-10-60-17.compute-1.internal
Private IP Address: 10.198.99.229
Run List: role[wordpress]

You can look just before this block and see if chef finished the running of the wordpress related cookbooks ok. If within a page above the last block you don’t see any errors then all is ok. The last few lines should be something like:

[Mon, 03 Jan 2011 07:23:34 +0000] INFO: Chef Run complete in 10.945359 seconds
[Mon, 03 Jan 2011 07:23:34 +0000] INFO: cleaning the checksum cache
[Mon, 03 Jan 2011 07:23:34 +0000] INFO: Running report handlers
[Mon, 03 Jan 2011 07:23:34 +0000] INFO: Report handlers complete

If there are errors, you’ll have to debug your cookbooks which is beyond the scope of this post.

Now you should be able to log into your instance ether as the ubuntu default user or the user you created in the wordpress role and the Users Databag (rberger_test in this example):

# Using the ubuntu user and an explicit ssh key
ssh -i ~/.ssh/aws-wordpress.pem ubuntu@ec2-184-73-44-17.compute-1.amazonaws.com

# Using the user created by the cookbook and a key that is already on you ssh-agent
ssh rberger_test@ec2-184-73-44-17.compute-1.amazonaws.com

Configure DNS to have preferred FQDNs point to your instance

You can access your site using the Amazon Public DNS name, but that would not be good in general. You probably want to access it via a URL like http://www.myydomain.com. To do this you must configure your DNS to add a CNAME to map your FQDN to the Amazon Public DNS name. How this is done is very specific to your DNS service provider. Bottom line is that you want to do a CNAME not an A record.  (I.E. an alias of your FQDN for the Amazon Public DNS name, not an A record that uses the Amazon IP address). There are some issues of using an A record with Amazon. You probably won’t see them for a simple situation such as hosting a single instance. But once you have many instances that need to talk to each other, using the CNAME will make life easier.

Installing your WordPress Blog

At this point you should be able to access your new instance via http. The initial screen will be the WordPress setup dialog. You should be able to access it via http using the Amazon Public DNS name or any CNAME aliases  you created and also added in the wordpress.rb role file override attribute for (wordpress =>server_aliases. You should see something like:

Wordpress startup installation page

It is possible to move an existing WordPress Blog to this new instance but that is beyond the scope of this post.

Happily Ever After

By default, the chef client runs every 1/2 hour on the instance. If you change any of the cookbooks and push them up to the Opscode Chef Server, those changes will be propogated to the instance the next time the chef-client runs again on the instance.

This is the way to maintain the server. By updating or adding cookbooks, you define the state of the server and the server will converge to that state when the chef-client runs. The inverse is true. If you change something on the server directly and the service you changed is managed by Chef, your direct changes could be reverted by the chef-client the next time it runs.

You shouldn’t need to but you can disable the chef-client from running automatically by running the following command while ssh’d to the instance:

sudo /etc/init.d/chef-client stop

That will be reset (ie automatic chef-client runs will be re-enabled) if you reboot. You can permanently disable the automatic running of chef-client by running the following commands while ssh’d into the instance:

cd /etc/init.d
sudo update-rc.d -f chef-client remove

Using the WordPress Automatic Upgrade Mechanism

At this point you should be able to use your wordpress blog as normal. You should be able to use the automatic update feature of WordPress to update WordPress itself and the plugins. When you are asked to supply the Connection Information, put in:

  • Hostname: The Public FQDN of the host (ether the EC2 Public DNS Name or one of the DNS CNAMEs you set up)
  • FTP Username: “blog” (or whatever you set node[:wordpress][:blog_updater][:username] in the wordpress.rb role file)
  • FTP Password: “big-secret” (or whatever you SHOULD have set node[:wordpress][:blog_updater][:password] in the wordpress.rb role
  • Connection Type: FTPS (SSL)

For instance for the Plugin Update Page:

Upgrade Plugins Connection Information

That should work and be secure using the vsftpd server that we installed automatically.

Hopefully all will work well for you. I will try to answer questions but can’t guarantee quick response here. A great resource is the Opscode Chef IRC channel irc.freenode.net #chef. And of course the Opscode Chef Wiki and the Opscode Support Site.

Source Code at Github

You can get all the source for this at https://github.com/rberger/ibd-wordpress-repo

Share and Enjoy:
  • Print
  • Digg
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Yahoo! Buzz
  • Twitter
  • Google Bookmarks
  • LinkedIn
  • Slashdot
  • Suggest to Techmeme via Twitter

28 comments to Deploy WordPress to Amazon EC2 Micro Instance with Opscode Chef

  • BOK

    Great walk-through, thanks!
    However I’m stuck at “knife ec2 server list”, the infamous “read server certificate B: certificate verify failed” error…
    Even a clean install of chef and all other ruby gems didn’t work out.
    FYI I’m trying to connect to an EU-based AWS-instance.

    Any thoughts on this?

  • Great post!

    You should probably highlight that during the edition of knife.rb you need to add site-cookbooks to the cookbook_path array. That’s the only problem I found when I tried to replicate the steps, I didn’t notice that edition since is not highlighted.

    Again, what a fantastic post! Thanks!

  • Bok: Try using the standard us-east-1 you can always terminate it down after you test it, Just to see if its an issue of using the EU zone. I found that many libraries just assume the us region.

    If you do continue to try the EU region, make sure you are adding the –availability-zone eu-west-1a –region eu-west-1 to the knife ec2 server create command.

  • Sebastian: Hi, Glad you liked it. Didn’t I specify the need to add site-cookbooks to the cookbook_path in the section: Updating your knife.rb file with Amazon Credentials?

  • BOK

    Been hacking all evening. Got things running by now, sort of.
    Now I’m stuck with this one:
    “Cannot find a recipe matching blog_user in cookbook wordpress (ArgumentError)”

    It’s good puzzling however! ;-)

  • Fry

    “It was way too easy and no problem at all!”
    um, then why is that sentence followed by
    100K of nit picking tech details on how to do it? I guess one person’s “way too easy”
    is another persons “gawdawful endless magic spells”.

  • Fry: Are you Chris Fry?
    It was easy and no problem at all for me. I did the actual implementation for my own blog in one evening. It took me many nites of work to explain it in a way that someone who is not expert in several domains to be able to do it. And if you follow those instructions it will hopefully be pretty easy an no problem for you too.

    And after you go thru this, you will be in a position to create many other servers and services using Chef and EC2.

  • Bok: The blog_user (and the add_swap.rb) recipes are not part of the Standard WordPress Cookbook but an add on recipe that I describe in the article. See the section where it says

    Create and edit site-cookbooks/wordpress/recipes/blog_user.rb. put the following as the contents:

  • i have no words. this is just awesome.

    thank you.

  • BOK

    Robert, don’t get me wrong: I’m grateful for your efforts.
    But with this walk-through, in the end I’ll end up with a “half baked cake”…
    Well, maybe it pushes me deeper into Chef, but Puppet is the next thing to try.

  • Peter

    Nice writeup.

    I think I’m going to try using the built-in vendor branching instead of keeping a site-cookbooks directory.

    http://lists.opscode.com/sympa/arc/chef/2010-04/msg00083.html

  • Peter: I believe that is what I described in the article. I used the vendor branches of all the standard cookbooks that came from opscode. It described in the section “Get the Appropriate Cookbooks”. I use the

    knife cookbook site vendor

    command to pull in the standard cookbooks as vendor branches in the cookbooks directory. I then put all the custom cookbooks that I created for this application in site-cookbooks. That makes it easier to see and keep track of the cookbooks that are custom…

  • Peter

    Yea, I understood that you were using the vendor branch syntax to get the cookbook the first time, but instead of modifying in place, I thought you were always copying it to the site-cookbooks dir. But sounds like just for overrides. Good plan.

    Chef is a great tool it seems. The main thing it seems to suffer from is the large number of different conventions being used in the docs and examples. So it’s good too see the whole process here in one page instead of spread out all over the place.

  • Stefan

    This is awesome! Everything worked like a charm. Thanks!

  • rob

    hi Robert – i’m stuck at generating the users. when i run the command I get this error:


    robert@robert-virtual-machine:~/chef-repo$ knife data bag from file users data_bags/users/mnbf9rca.json
    ERROR: JSON::ParserError: 705: unexpected token at ‘{
    “id”: “rob”,
    “comment”: “rob”,
    “uid”: 2001,
    “groups”: “sysadmin”,
    “shell”: “/bin/bash”,
    “openid”: “”,
    “ssh_keys”: “ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCcfv6+nWm/twL6pYtGqv2X2BfQNqJtu/GMOl8J2W7aGqdKgL85wsBipQEO0me2qAvBZprVJKqdbNQmd4xWhzxadY/kYyReUy0nOpfplE1rKPq2vqgw37Z9QTu0tJvKEn26QaJgKsA6FOCGyobg5sYojT+i6HWUgjxEfGNcoYxe5sBYkEbjSApSTnCcFpjjE+oFotCwh/YnPioBdCiq5zuxag7vHdMZfTWm5K35BNNQ/hLIqSen2Ld43MpbGiNAIWsORuUhojTbOwOez0SqBVAX9obdYyT5HLi6+w7bVAtnuaI2R9vZI5XF7YK1/xet6POQX2qCxCZqzf8F2Ui26Y1V”,
    }


    my JSON looks like this:

    {
    "id": "rob",
    "comment": "rob",
    "uid": 2001,
    "groups": "sysadmin",
    "shell": "/bin/bash",
    "openid": "",
    "ssh_keys": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCcfv6+nWm/twL6pYtGqv2X2BfQNqJtu/GMOl8J2W7aGqdKgL85wsBipQEO0me2qAvBZprVJKqdbNQmd4xWhzxadY/kYyReUy0nOpfplE1rKPq2vqgw37Z9QTu0tJvKEn26QaJgKsA6FOCGyobg5sYojT+i6HWUgjxEfGNcoYxe5sBYkEbjSApSTnCcFpjjE+oFotCwh/YnPioBdCiq5zuxag7vHdMZfTWm5K35BNNQ/hLIqSen2Ld43MpbGiNAIWsORuUhojTbOwOez0SqBVAX9obdYyT5HLi6+w7bVAtnuaI2R9vZI5XF7YK1/xet6POQX2qCxCZqzf8F2Ui26Y1V",
    }

    any ideas?

  • rob

    ahh – i found it – i’d added a comma after the closing quote for the SSH key! i spent hours staring at that!

    thanks for the great guide!

  • rob

    Robert – what happens if there is an outage (as we’ve seen 2 of in the last 6 months) and my instance is terminated? i mean – do i lose all my wordpress data as i thought EBS-backed AMIs lose the EBS volumes at termination?

    rob

  • You bring up a good point. By default, if the EC2 instance is terminated, the attached EBS volumes will also be terminated. But you can change that behvior when you create the instance or after you create the instance. The latest versions of the knife ec2 server create command now have an argument to set things so that the ebs volume will NOT be deleted automatically. I am changing the document to show that.

    You can also change the behavior at any time after the instance has been created. But there is no way I know of to do it with a GUI. The AWS Console, Ylastic and ElasticFox don’t expose that feature. So the only way I know of to set the existing EBS volume to disable the default “Delete on Termination” is to use the ec2-api-tools Command Line tools. A great article on this is Three Ways to Protect EC2 Instances from Accidental Termination and Loss of Data by the ever educational Eric Hammond His site, Alestic, is great resource for Ubuntu on EC2.

    The important bit in this article is:
    ec2-modify-instance-attribute --block-device-mapping /dev/sda1=::false i-d10ae5bd where i-d10ae5bd is the instance id of your AWS EC2 instance. You would have gotten that from the output of the original knife ec2 server create command or at any time using the AWS Console for EC2.

    Instructions for installing and basic usage of AWS ec2-api-tools:
    AWS EC2 API Tools Home
    Ubuntu
    General Linux
    Macintosh
    I don’t do Windows. You’ll have to Google or I supose Bing it

  • rob

    thanks! i’ve never used Chef before but i think it’s great. i’m going to see if i can work out how to get it to use AWS RDS rather than MySQL as that sounds like an interesting way to learn Knife.

    BTW – did you also see this warning at the beginning?
    WARNING: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    WARNING: The cookbooks: sudo, wordpress exist in multiple places in your cookbook_path.
    A composite version of these cookbooks has been compiled for uploading.

    IMPORTANT: In a future version of Chef, this behavior will be removed and you will no longer
    be able to have the same version of a cookbook in multiple places in your cookbook_path.
    WARNING: The affected cookbooks are located:
    sudo:
    /home/robert/chef-repo/cookbooks/sudo
    /home/robert/chef-repo/site-cookbooks/sudo
    wordpress:
    /home/robert/chef-repo/cookbooks/wordpress
    /home/robert/chef-repo/site-cookbooks/wordpress

  • chef_newb

    Hi.

    Thanks for the tutorial. Really helped… everything seems to work except for the sudo cookbook part. When I follow the instructions for that and include it in my role all users seem to ask for passwords, including the ubuntu user.

    Any ideas ?

    Thanks for your help.

  • Don’t know if you figured it out or not. I don’t know specificly why, but you can see if the /etc/sudoers got configured as expected. There is a slight danger that if the recipe screws up, it could lock you out of sudo access of the instance. (I.e. if it removes the entry for the Ubuntu user or removes the setting for NOPASSWORD for Ubuntu.) The only way I know to recover from that would be to shutdown the instance and mount the ebs root volume on another instance, and fix it. That only works if the original instace was an EBS backed instance and not a SPOT instance.

    Otherwise, assuming you were just going thru the tutorial, start over…

    But it would be good to look at the /etc/sudoers and see that it has all your users and it has them set for NOPASSWORD.

  • Yeah, this is a new thing. They are depreciating the pattern that I described where you have a chef_repo/cookbooks and chef_repo/site-cookbooks and that you can over-ride / customize cookbooks that are in chef_repo/cookbooks with files in the same directory structure as a cookbook in chef_repo/site-cookbooks.

    Opsocde is promoting the use of the knife cookbook site install to implement the git vendor branch pattern as the way to create local changes to standard cookbooks…

    That’s a whole ‘nother blog post someday…

  • chef_newb

    Hi.

    Thanks for getting back on this.. I will try to look into the ‘knife cookbook site install…’ approach.

    Cheers.

  • jobicoppola

    Part of the fun of ‘mkdir -p’ is you can do things like:

    mkdir -p site-cookbooks/wordpress/{recipes,attributes,templates/default}

    …although I guess that is no longer considered best practice, as you stated. :)

  • I don’t know, that’s one of my favorite tricks. Most people don’t know about {foo,bar,baz} in shell scripts and command line shortcuts. And mkdir -p is generally awesome.

  • jobicoppola

    Ah sorry, to clarify, I was just referring to creating the site-cookbooks subdir as no longer being best practice, since using the vendor branch pattern allows for your local changes to a cookbook to be merged in when you update it to get the latest changes upstream.

    And yes agreed, ‘mkdir -p’ is super handy. :)

  • Oh yes. I was kind of bummed when after I was told way back that the site-cookbooks for customized cookbooks and cookbooks for standard cookbooks was best practice. And now its considered depreciated…

    I guess its for the best, but I think that the difficulty of sharing individual cookbooks as git-repos is a major short comming of Chef right now.

    I like the vendor branch pattern pretty much.. The plugin knife-github-cookbooks is helpful

Leave a Reply

  

  

  

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>