Using Fabric to Create a LAMP server on HP Cloud
Fabric is a network-aware build tool. Think of it like
pake, etc.) for remote servers. With Fabric, it is just as easy to script a remote environment as it is to script a local one. In fact, with Fabric, you can do both.
Fabric can actually be used to automate installing a server in the cloud. In this article, I show how to use Fabric with the hpcloud UNIX client to create an HP Cloud server instance and then configure it as a LAMP server.
While I show this with HP Cloud, the technique could readily be adapted to other cloud providers like OpenStack or AWS.
Updated: Fixed indenting that was breaking Python. Thanks to aeronotix. <!--break-->
From the outset, I assume a few things:
- You have the
hpcloudUNIX command line client installed and configured. (There is no reason why you can't substitute some other cloud CLI and tweak for another platform.)
- You have already set up your cloud provider's account and created an SSH key for your cloud account. My key is called
- You have installed Fabric.
- You have security groups set up for Compute. (A security group is like a firewall for cloud instances.) Mine is called
Later on in this article the fabric script puts a file called
index.php onto the server. This is what that file looks like:
<?php print "Hello World"; ?>
Getting Started with Fabric
make reads a
fabric reads a
fabfile.py. A fabfile doesn't have its own format; it's just a Python script. Here's a basic fabfile to get started with:
from fabric.api import * @task def hello(): puts("Hello")
The first line (
from fabric.api import *) imports the fabric system. Most Fabric scripts start with this line.
Next, we define a Fabric task. We use the
@task decorator to tell the Fabric system that this is a task that can be run from the command line. Our example
hello task prints "Hello" to the console.
At this point, we have a working fabfile. Fabric will tell us all of the commands that it knows about by running
fab --list in the directory with the
$ fab --list Available commands: hello
We can execute our command with
$ fab hello Hello Done.
And there we have it. From here we can do something much more interesting.
A Task for HP Cloud
Fabric can run commands locally on the host operating system. Since we have the
hpcloud client installed locally, we can run it programmatically with fabric. For example, let's write a quick command that displays a list of all of the servers in our current availability zone:
@task def server_details(name = ''): """Get the server details.""" local("hpcloud servers %s" % name)
The task above runs the local command
hpcloud servers. If
name is specified, it runs
hpcloud servers NAME. But if name is empty, it will print a list of all of the servers.
fab server_details should print out a list of all of your servers. Running
fab server_details:foo will print the details of the server whose name is
foo (assuming you have one). We'll use that form of the command a little later.
Really, all Fabric is doing here is running the command
hpcloud servers for us.
Create a New Server
Let's build a more complex command that uses
local() to create a server:
@task def create_server(name): """Create a new HP Cloud server.""" image = 81078 # Ubuntu 12.04 in AZ-1 flavor = 100 # extra-small security_group = 'Web' key = 'hpcompute' # This is the local command we'll run. template = "hpcloud servers:add %s %s -i %s -k %s -s %s" cmd = template % (name, flavor, image, key, security_group) local(cmd)
This command is a little wordier, but all it really does is run
hpcloud servers:add with a bunch of pre-configured settings. Notice that since the function
create_server() takes a parameter
name, and since
name does not have a value, it will be a required parameter. To run it, we can do this:
$ fab create_server:dev [localhost] local: hpcloud servers:add dev 100 -i 81078 -k hpcompute -s Web Created server 'dev' with id '1512143'. Done.
fab create_server gets one parameter (
name), which we set to
dev. Fabric does use the rather non-conventional method of passing parameters to tasks using the colon syntax above. But as odd as it looks at first, it's easy to use.
Now we can use the
server_details task we created earlier to see the server's status:
$ fab server_details:dev [localhost] local: hpcloud servers dev +---------+------+--------+-------+----------------+--------------+-----------+-----------------+----------------------+--------+ | id | name | flavor | image | public_ip | private_ip | keyname | security_groups | created | state | +---------+------+--------+-------+----------------+--------------+-----------+-----------------+----------------------+--------+ | 1512143 | dev | 100 | 81078 | 22.214.171.124 | 10.5.250.104 | hpcompute | Web | 2013-05-29T02:52:47Z | ACTIVE | +---------+------+--------+-------+----------------+--------------+-----------+-----------------+----------------------+--------+ Done.
This tells us that our server has been created, and is now ACTIVE and ready for work.
Installing a LAMP Stack
Now that we have a server available, we can create a new task to install the LAMP stack on this server. While the other tasks have been running local commands, this one will use SSH and SCP (in the background) to interact with the server.
Here's our new task:
@task def install_lamp(): """Install a LAMP stack on the server.""" # Install the packages sudo("apt-get -q update") packages = "apache2 mysql-server libapache2-mod-php5 php5-mysql" sudo("apt-get install -q %s" % packages) # Push the index.php file to the server and get rid of # the old index.html file. put("index.php", "/var/www/index.php", use_sudo=True) sudo("rm /var/www/index.html")
install_lamp task does the following:
- It runs
apt-get updateto get the latest package repository.
- It then uses
apt-get installto install the packages we need.
- It copies a file called
index.php(in the local directory) to
- And then it removes the old
There are two important commands used above:
sudo: This performs a
sudocommand on the remote server. If you don't need root permissions, use
put: Copies a file to the remote server.
These are built into fabric, which also has many other useful functions like this.
To run this task, we do have to provide an additional piece of input: We need to tell Fabric which server to run the command on. Often times, we could store this information inside of the
fabfile.py or in a config file, but here I'll pass it in on the command line using the
-H flag. Note that it follows SSH, specifying
$ fab -H email@example.com install_lamp [firstname.lastname@example.org] Executing task 'install_lamp' [email@example.com] sudo: apt-get -q update Lots and lots of lines… [firstname.lastname@example.org] put: index.php -> /var/www/index.php [email@example.com] sudo: rm /var/www/index.html Done. Disconnecting from firstname.lastname@example.org... done.
When you run this on your own, you will notice something that I didn't capture above: If the remote server prompts for input, Fabric passes that prompt on to you. So you can actually do interactive work as you go (like settings passwords).
At the end of the installation process, we can do a simple browser-based test. By pointing a browser toward the public IP address of the server (
http://126.96.36.199) we can see whether our new
index.php gets executed correctly.
fabfile.py script that we covered here is very basic, and requires multiple steps to configure the server. But we could easily build more involved Fabric scripts that, for example, do the entire setup by calling one task. In the past, I've used Fabric to set up block storage and other cloud services, too.
Better still, a single Fabric file can become a tool for not just installing servers, but maintaining them. Fabric can be used to automate application deployment, remote backups, and other standard maintenance tasks. And since they can be used like Makefiles, it's not at all uncommon to see a
fabfile.py at the root of a project.
The entire script that I used (including a
destroy_server task I did not discuss here) is available in this Gist.