A Basic Example using PHP in AWS (Amazon Web Services)

By: Clay Loveless, Chief Architect, Mashery  

To illustrate the use of AWS services, we'll build a simple video database with the following characteristics:

  • Hosted on EC2
  • Uses Amazon S3 as the primary data store.
  • Uses SQS and a separate EC2 server to manage batch encoding of video for Flash display.
  • Uses SimpleDB to store metadata about the videos.

While there are a number of ways to get all these services running, including very convenient management interfaces in Firefox (such as Elasticfox), plus web-based management tools from RightScale and Scalr, we'll do it all with PHP and the API, since that's what this article is about.

To keep the examples as focused as possible, from here on they will make use of an inclusion calledexample_setup.php, which includes and parses your aws.conf file, configures PHP 5's autoload functionality, and a few other things to keep example code concise and accurate. The example_setup.php file looks like this:

File: example_setup.php

<?php
// keep ourselves honest
error_reporting(E_ALL);
ini_set('display_errors', 'on');
ini_set('log_errors', 'on');
ini_set('error_log', '/tmp/php.log');

// use the config that we set up previously
$creds = parse_ini_file('/etc/aws.conf');
if (! isset($creds['tutorial_file_path'])) {
    $creds['tutorial_file_path'] = sys_get_temp_dir();
}

// set include_path for article's bundled AWS libraries
ini_set('include_path', dirname(__FILE__) 
        . DIRECTORY_SEPARATOR
        . 'AWSforPHP'
        . PATH_SEPARATOR
        . ini_get('include_path'));

// make loading simple
function __autoload($class_name) 
{
    if (class_exists($class_name, false) || 
        interface_exists($class_name, false)) {
        return;
    }
    // AWS and Killersoft classes follow PEAR
    // file naming conventions
    $file = str_replace('_', 
                DIRECTORY_SEPARATOR,
                $class_name
            ) . '.php';
    @include_once $file;
}

Finally, we'll be generating an SSH keypair specific to this tutorial, so please add a path to your config file that specifies where those files should be stored once generated. Like so:

; Path to writable directory where we can save files
tutorial_file_path = "/tmp"

Start Your Server

We need a place to host the scripts associated with this video library, so let's start by getting a server running with the latest Amazon-provided Fedora Core Linux image. At the time of this writing, that's Fedora Core 8 and has an AMI ID of ami-2b5fba42.

Before launching an instance, let's briefly explore how to set up EC2 Security Groups and generate EC2 KeyPairs. In a nutshell, EC2 Security Groups are firewall rules that control what ports and serivces may be accessed on any server running under the specified group. The full details are outside the scope of this article, but there is an excellent exploration of difference Security Group scenarios and use cases in the EC2 Developer Guide.

We'll also set up an EC2 KeyPair, which is simply an SSH keypair that you'll need in order to access the EC2 server once the instance is launched. The public key will be stored on the instance when you launch it, and you will then be able to login via SSH using the private key.

The following block of code will:

  1. Make sure that the 'awstutorial' KeyPair exists, and will create it if it does not exist. The private key will be stored as 'id_rsa-awstutorial' in the 'tutorial_file_path' directory you specified in your aws.conf file above.
  2. Make sure that the 'awstutorial' Security Group exists, and will create it if it does not exist.
  3. Make sure that ports 80 and 22 are open in the 'awstutorial' Security Group.

Note: Careful tuning of EC2 Security Group settings is beyond the scope of this article. Please readthe documentation on that subject carefully.

File: ec2-prelaunch.php

<?php
/**
 * Script to check port 80 on the 'awstutorial' security group,
 * and open it if it is not open. This will use the 'awstutorial' 
 * keypair as well, creating it if it does not exist.
 */
require_once 'example_setup.php';

// load the service
$ec2 = new Amazon_EC2_Client(
    $creds['access_key'], 
    $creds['secret_key']
);

/**
 * 
 * Use 'awstutorial' keypair
 * 
 */
$req = new Amazon_EC2_Model_DescribeKeyPairsRequest();
$kpmatch = false;

// run the request
$resp = $ec2->describeKeyPairs($req);

$pairs = $resp->getDescribeKeyPairsResult()->getKeyPair();
foreach ($pairs as $pair) {
    if ($pair->getKeyName() == 'awstutorial') {
        echo "KeyPair awstutorial exists!\n";
        $kpmatch = true;
        break;
    }
}

// create the keypair if we need to
$keypath = $creds['tutorial_file_path']
         . DIRECTORY_SEPARATOR
         . 'id_rsa-awstutorial';

if (! $kpmatch) {
    echo "Creating awstutorial keypair...\n";
    $req = new Amazon_EC2_Model_CreateKeyPairRequest();
    $req->withKeyName('awstutorial');
    
    $resp = $ec2->createKeyPair($req);
    
    if ($resp->isSetCreateKeyPairResult()) {
        $res = $resp->getCreateKeyPairResult();
        $kp = $res->getKeyPair();
        
        echo "generated fingerprint\n";
        echo $kp->getKeyFingerprint() . "\n";
        
        echo "generated material\n";
        echo $kp->getKeyMaterial() . "\n";
                
        // save private key for future use      
        @file_put_contents(
            $keypath,
            $kp->getKeyMaterial()
        );
        
        // set permissions appropriately for key
        chmod($keypath, 0600);
    }
} else {
    // emit a notice if key file isn't where we expect it
    if (! is_readable($keypath)) {
        echo "!! Couldn't read id_rsa-awstutorial file "
           . "at $keypath\n";
    }
}

/**
 * 
 * Use 'awstutorial' Security Group. Create it if
 * necessary.
 * 
 */
$req = new Amazon_EC2_Model_DescribeSecurityGroupsRequest();
$req->withGroupName('awstutorial');

// run the request
try {
    $resp = $ec2->describeSecurityGroups($req);
    echo "Security Group awstutorial exists!\n";
} catch (Amazon_EC2_Exception $e) {
    // doesn't exist, create it
    echo "Creating awstutorial security group ...";
    $new = new Amazon_EC2_Model_CreateSecurityGroupRequest();
    $new->withGroupName('awstutorial')
        ->withGroupDescription(
            'AWS for PHP Developers Tutorial Group'
        );
    $ec2->createSecurityGroup($new);
    echo "done.\n";
    
    // now fe-fetch response
    $resp = $ec2->describeSecurityGroups($req);
}

/**
 * 
 * Make sure 'awstutorial' group has ports 80 and 22 open.
 * 
 */
if ($resp->isSetDescribeSecurityGroupsResult()) {
    $list = $resp->getDescribeSecurityGroupsResult()
                 ->getSecurityGroup();

    // look for wide-open port 80
    $port80open = false;
    $port22open = false;
    foreach ($list as $securityGroup) {
        
        if (! $securityGroup->isSetIpPermission()) {
            continue;
        }
        
        $perms = $securityGroup->getIpPermission();
        foreach ($perms as $perm) {
            if ($perm->isSetIpProtocol() 
                && $perm->getIpProtocol() == 'tcp'
                && $perm->isSetFromPort()
                && $perm->getFromPort() == '80'
                && $perm->isSetToPort()
                && $perm->getToPort() == '80'
            ) {
                $port80open = true;
                continue;
            }
            if ($perm->isSetIpProtocol() 
                && $perm->getIpProtocol() == 'tcp'
                && $perm->isSetFromPort()
                && $perm->getFromPort() == '22'
                && $perm->isSetToPort()
                && $perm->getToPort() == '22'
            ) {
                $port22open = true;
                continue;
            }
        }
    }
}

if ($port80open) {
    echo "Security Group awstutorial port 80 is open!\n";
} else {
    // open port 80
    echo "Opening awstutorial port 80 ...";
    $req = new Amazon_EC2_Model_AuthorizeSecurityGroupIngressRequest();
    $req->withGroupName('awstutorial')
        ->withIpProtocol('tcp')
        ->withFromPort(80)
        ->withToPort(80)
        ->withCidrIp('0.0.0.0/0');
    $ec2->authorizeSecurityGroupIngress($req);
    echo "done.\n";
}

if ($port22open) {
     echo "Security Group awstutorial port 22 is open!\n";
} else {
    // open port 22
    echo "Opening awstutorial port 22 ...";
    $req = new Amazon_EC2_Model_AuthorizeSecurityGroupIngressRequest();
    $req->withGroupName('awstutorial')
        ->withIpProtocol('tcp')
        ->withFromPort(22)
        ->withToPort(22)
        ->withCidrIp('0.0.0.0/0');
    $ec2->authorizeSecurityGroupIngress($req);
    echo "done.\n";
}

From your workstation:

PROMPT> cd ~/awsfiles
php ec2-prelaunch.php

Now that credentials are created and a known firewall setting is in place, it's time to launch the server. In the next block of code, we set up the Amazon_EC2_Client object provided by the Amazon EC2 PHP library, launch a small Fedora Core instance, and wait for it to complete the initial boot process so that we can retrieve its public hostname.

File: ec2-launch.php

<?php
/**
 * Script to launch a small EC2 instance with Amazon's
 * Fedora Core 8 AMI.
 * 
 * The script will loop during the launch, waiting 
 * for the instance to obtain a public DNS name.
 */
require_once 'example_setup.php';

// load the service
$ec2 = new Amazon_EC2_Client(
    $creds['access_key'], 
    $creds['secret_key']
);

// set up the request with nice fluent interface
$req = new Amazon_EC2_Model_RunInstancesRequest();

// ami-2b5fba42 is Amazon's Fedora Core 8 image
$req->setImageId('ami-2b5fba42')
    ->withMinCount(1)
    ->withMaxCount(1)
    ->withKeyName('awstutorial')
    ->withSecurityGroup('awstutorial');

// run the request
echo "Launching awstutorial instance.\n";
$response = $ec2->runInstances($req);

// this can take a few minutes
$start = microtime(true);
set_time_limit(0);

// get the pending instance id
$reservation = $response->getRunInstancesResult()
                        ->getReservation();
$instances = $reservation->getRunningInstance();
$runningInstance = $instances[0];

// output instance id and state
$id = $runningInstance->getInstanceId();
echo $id
    . ' is ' 
    . $runningInstance->getInstanceState()
                        ->getName()."\n";
// save example instance id
@file_put_contents(
    $creds['tutorial_file_path']
    . DIRECTORY_SEPARATOR
    . 'awstutorial-instance.txt',
    $id
);

// set up DescribeInstances request to fetch status on new
// instance in a loop.
$desc = new Amazon_EC2_Model_DescribeInstancesRequest();
$desc->setInstanceId($id);

echo 'Waiting for public hostname';
while (1) {
    // output progress
    echo '.';
    
    $response = $ec2->describeInstances($desc);
    $res = $response->getDescribeInstancesResult()
                    ->getReservation();
                   
    $ri = $res[0]->getRunningInstance();
    $runningInstance = $ri[0];
    
    if ($runningInstance->isSetPublicDnsName()) {
        $end = microtime(true);
        break;
    }
    
    sleep(2);
}
echo "\n";

// now we're ready to set up our server
echo $id
    . ' is ' 
    . $runningInstance->getInstanceState()->getName()
    . ' at '
    . $runningInstance->getPublicDnsName()
    . "\n";
    
// save hostname for later
@file_put_contents(
    $creds['tutorial_file_path']
    . DIRECTORY_SEPARATOR
    . 'awstutorial-hostname.txt',
    $runningInstance->getPublicDnsName()
);

$span = $end - $start;
echo "Instance booted in {$span} seconds.\n";
exit;

From your workstation:

PROMPT> cd ~/awsfiles
php ec2-launch.php

Note: Establishing a public DNS name does not necessarily mean that the server is ready to accept SSH logins. It may take a minute or two after running the launch example before you can log in to your instance. Do not panic, this is normal.

As you can see from these examples, the Amazon_EC2_Model class and its descendants fully encapsulate all the details of making the requests and parsing the responses from the API.

This concludes the majority of this article's use of the EC2 API. The Amazon-provided EC2 library is robust and well-documented. Be sure to check out its reference documentation.

Setting Up the Server

Now that we have an EC2 instance available, it's time to set up PHP and Apache so we can serve the video library interface.

Start by logging in to the instance, using SSH key, instance id and hostname that we acquired in the previous section of the tutorial. If you need introductory assistance on how to log in to an instance using SSH, please refer to the EC2 Getting Started Guide.

If you want to use the data we saved in the previous section to login, set these shell variables (using the appropriate path, of course).

From your workstation:

# assumes current working directory - change path as needed
PROMPT> export AWSTUTORIAL_HOST=`cat \`pwd\`/awstutorial-hostname.txt`
PROMPT> export AWSTUTORIAL_KEY=`pwd`/id_rsa-awstutorial

Now, make sure you'll be able to run tutorial examples from the server by copying the aws.conf file we've been working with to the new instance.

From your workstation:

PROMPT> scp -i $AWSTUTORIAL_KEY /etc/aws.conf [email protected]$AWSTUTORIAL_HOST:/etc/aws.conf
PROMPT> ssh -i $AWSTUTORIAL_KEY [email protected]$AWSTUTORIAL_HOST \
    'curl -O http://s3.killersoft.com/AWSforPHP/awsfiles.zip; \
    unzip awsfiles.zip -d /var/www/'
PROMPT> scp -i $AWSTUTORIAL_KEY awstutorial-*.txt [email protected]$AWSTUTORIAL_HOST:/tmp/

With that, you're ready to login:

From your workstation:

PROMPT> ssh -2 -i $AWSTUTORIAL_KEY [email protected]$AWSTUTORIAL_HOST

Once you're in, we need to set up PHP and Apache. In case you're inclined to try out the SOAP examples mentioned above, we'll install the packages necessary for those as well as the basic PHP 5 and Apache requirements.

From your instance:

PROMPT> yum -y install php php-mcrypt.i386 php-soap.i386 \
    php-xml.i386 php-cli.i386 httpd.i386

The server should be ready to go. To confirm that PHP is properly configured, and Apache is running, do the following:

From your instance:

PROMPT> echo "<?php phpinfo(); ?>" > /var/www/html/index.php
PROMPT> service httpd start

Now load the hostname of your instance in your browser. You should see the standard phpinfo() output page. If you don't, please review the steps above again -- it's possible that you missed a step, or that something's gone wrong. However, if all's right with the world, you'll see phpinfo() output and be ready to move on to the next step.




Archived Comments


Most Viewed Articles (in PHP )

Installing PHP with nginx-server under windows

PHP ./configure RESULTING IN [email protected]_2_2_3_... AND UNRESOLVED REFERENCES WITH ORACLE OCI8

PHP 5.1.4 INSTALLATION on Solaris 9 (Sparc)

Building PHP 5.x with Apache2 on SuSE Professional 9.1/9.2

Installing PHP 5.x with Apache 2.x on HP UX 11i and configuring PHP 5.x with Oracle 9i

Cannot load /usr/local/apache/libexec/libphp4.so into server: ld.so.1:......

Setting up PHP in Windows 2003 Server IIS7, and WinXP 64

error: "Service Unavailable" after installing PHP to a Windows XP x64 Pro

Running different websites on different versions of PHP in Windows 2003 & IIS6 platform

Function to convert strings to strict booleans in PHP

Convert IP address to integer and back to IP address in PHP

Function to return number of digits of an integer in PHP

Function to force strict boolean values in PHP

Function to sort array by elements and count of element in PHP

PHP pages does not display in IIS 6 with Windows 2003

Latest Articles (in PHP)