Press "Enter" to skip to content

Using the Opscode aws cookbook to attach an EC2 EBS Volume

This weekend I’m on the steep portion of the Opscode Chef learning curve. Up till now, I was able to do a lot of the basics ok. But now I’m working towards moving our full production environment to be deployable with Chef. The first thing I tried was to create an Amazon EC2 Mysql instance that uses an existing AWS Elastic Block Store (EBS) Volume as the Mysql data store.

There is an aws cookbook in the Opscode Cookbook collection, but not any documentation. Plus this cookbook uses the new Light-weight Resources and Providers (LWRP) mechanism which is also lightly documented and doesn’t have all that many examples (that I can find at least).

I struggled for a while and got close, but I couldn’t figure out how to specify the resource provider (and it turned out I was not specifying the resource itself correctly). Thank goodness for the fantastic support on the Chef IRC channel. In this case, especially Scott M. Likens aka damm and the total Chef Master, Joshua Timberman aka jtimberman .

Using the aws cookbook

I’ll cut to the chase and show how its done. In my example, I’m creating both a role and a cookbook called runtime-db.

“including” the aws cookbook

First of all, you need to “include” the aws cookbook one way or another. You could just put:

depends "aws"

in the metadata.rb of your own cookbook.

Or put it in the recipe declaration in a role file that specifies aws before any recipes that utilize it. Which is what I did. Here’s my runtime-db role file:

name "runtime-db"
description "Use this role along with runa-base and an environment role to install a mysql instance"
recipes "xfs", "aws", "runtime-db", "mysql", "mysql::client", "mysql::server"
override_attributes({ "runa" => { "aws_access_key" => "secret", "aws_secret_access_key" => "secret", "device" => "/dev/sdh", "availability_zone" => "us-east-1b", "volume_id" => "vol-5a6e9633" }, "mysql" => { "server_root_password" => "secret", "datadir" => "/var/local/mysql_data", "ec2_path" => "/mnt/mysql_data", "ebs_vol_dev" => "/dev/sdh", "ebs_vol_size" => 500 } })

Note the recipes line and that aws comes before runtime-db. I also set some attributes that will be used in the recipes that are being used. These could be set elsewhere like in an environment role (staging, development, production, etc)

Using the aws resources in my own cookbook

Here’s how I used it:

aws_ebs_volume "mysql_data_volume" do
  provider "aws_ebs_volume"
  aws_access_key node[:runa][:aws_access_key]
  aws_secret_access_key node[:runa][:aws_secret_access_key]
  volume_id node[:runa][:volume_id]
  availability_zone node[:runa][:availability_zone]
  device node[:runa][:device]
  action :attach

The first line starts with the resource name. I wanted to use the ebs_volume resource. I originally thought it would be just ebs_volume. It turns out that you have to specify the cookbook it is in by prepending the resource with the cookbook. I.E. aws_ebs_volume.

I also had a lot of frustration trying to figure out what the provider attribute should be. Turns out its the same (cookbook prepended to the resource as in aws_ebs_volume).

The attributes that are in the form like node[:runa][::volume_id] map to the attributes set earlier in the role file. (i.e. node[:runa][:volume_id] maps to runa => {:volume_id => "vol-5a6e9633"}

The remaining parameters are pretty self explanatory. The keywords come from the ebs_volume.rb file in cookbooks->aws->resources and they are just as you would expect from the Amazon Ec2 and EBS world.

In this case the action I’m doing is I am attaching a specified volume_id to a specific unix device (node[:runa][:device] resolves to /dev/sdh in this case).

If you wanted to create a volume you would not supply a volume_id but specify a size and optionally a snapshot_id. There are also actions to detach and snapshot volumes.

Mounting the newly attached filesystem

Once you attach the EBS volume, you’ll also want to mount it using the mount resource:

mount "/var/local/" do
  device "/dev/sdh"
  options "rw noatime"
  fstype "xfs"
  action [ :enable, :mount ]
  # Do not execute if its already mounted
  not_if "cat /proc/mounts | grep /var/local"

In my case the filesystem type is xfs yours may be different. One thing I learnt tonight was that you can specify multiple actions in one resource call. Note the line

action [ :enable, :mount ]

This says that this block will both enable, i.e. update the /etc/fstab file with this mount, and mount, i.e. actually mount the filesystem.

I put in the not_if statement to make the mount idempotent. It will not execute the mount if its already mounted. (This will probably only work on systems (like Linux) that have /proc/mounts. It assumes the the /proc/mounts has a list of mounted filesystems).

Finishing up

My runtime-db recipe ends up by chowning the mounted filesystems to mysql:mysql (since the original system that the EBS snapshot came from may have had different UIDs for the mysql user). And then links the mounted mysql data directory to a directory in /mnt. I probably didn’t need this in the end, but it makes it compatible with some other configurations we already have in production.

bash "Changing the owner:group for Mysql data and log folders" do
  code <<-EOC
    user = "root"
    chown -R mysql:mysql /var/local/mysql_data
    chown -R mysql:mysql /var/local/log
link node[:mysql][:ec2_path] do to node[:mysql][:datadir] not_if "test -e #{node[:mysql][:ec2_path]}" end

The only interesting thing there is that the link will not happen if the target point already exists.


Once I’ve seen how its done, it all seems simple. But its the little things that can end up blocking. Once past the blocks, its pretty amazing what can be done with Chef with such little code and so much expressiveness.


  1. Vasco Calais Pedro Vasco Calais Pedro October 19, 2011

    Thanks for a great post. I think the order of :enable, :mount is reversed, doesn’t mount have to come first?

    Also, I am running into a error where the system doesn’t see the volumen as attached by the time it tries to mount it.

  2. Robert J Berger Robert J Berger Post author | October 19, 2011

    I’m not sure if the order matters, but logically :enable needs to come first. That effectively sets things up so the mount can work. (I think it writes to /etc/fstab but not sure on that one)

    In terms of timing between attaching and mounting, from looking at the aws ebs code in the aws cookbook, it says the attach should block until its done. But I also saw a cookbook that has this in it:

    if cluster_ebs_volumes
      cluster_ebs_volumes.each do |conf|
        bash "Wait for ebs volumes to attach" do
          not_if{ File.exists?(conf['device']) }
          code <<EOF
      echo #{conf.to_hash.inspect}:
      while true ; do
        sleep 2
        echo -n "$i "
        test -e "#{conf['device']}" || continue
        echo "`date` #{conf['device']} mounted for #{conf.to_hash.inspect}" >> /tmp/wait_for_attachment_err.log
        ls -l /dev/sd* >>  /tmp/wait_for_attachment_err.log
        mount          >>  /tmp/wait_for_attachment_err.log
        sleep 5

  3. Vasco Calais Pedro Vasco Calais Pedro October 20, 2011

    Ah, I see what happened, linux was converting sdf to xvdf and so mount couldn’t find it. Thanks for your help though

  4. Vasco Calais Pedro Vasco Calais Pedro October 20, 2011

    I am getting a permissions issue when running the mount command, getting an unexpected exit 32. Did you do something to deal with the permissions?

  5. Vasco Calais Pedro Vasco Calais Pedro October 21, 2011

    Hi Robert I gured out what was the issue, I think. The problem was that mount is not running mkfs, so there is no partition on the machine.

    I added the following

    # Make the partition
    bash “format-data-ebs” do
    code “mkfs.xfs /dev/xvdh”

    And it seems to be working

Comments are closed.