Using the Opscode aws cookbook to attach an EC2 EBS Volume

By Robert J Berger on November 29, 2009

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.

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:

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)

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:

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 <
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.

Conclusion

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.