使用 PHP 实现云计算,第 2 部分: 通过 Zend Framework 使用 Amazon EC2

通过 Zend Framework 使用虚拟机

 

Doug Tidwell, 云计算传播人,Software Group Strategy, IBM

2009 年 11 月 02 日

 

Zend Framework 包含的一些类可以简化基于云的服务的使用。这个 “ 使用 PHP 实现云计算” 系列的第 1 部分介绍如何通过 Amazon 的 S3 云存储服务使用 Zend 类。本文介绍在 Amazon 的 Elastic Compute Cloud (EC2) 中简化虚拟机使用的 Zend 类。

云计算最激动人心的好处之一是可以在云供应商的数据中心中运行虚拟机。Amazon 的 EC2 允许架构师和开发人员在云中创建并运行虚拟机映像。可以创建包含您的组织需要的软件和数据的自定义映像,然后可以根据需要运行任意数量的虚拟机副本。本文介绍 Zend Framework 包含的一些可以简化 EC2 映像的使用的类。

由于 EC2 是一个复杂的产品,所以 EC2 的特性将分为两篇文章介绍。本文介绍关于使用映像、启动和停止映像实例、保护映像实例的密匙对和安全组的基础知识。Elastic IP 地址、Amazon CloudWatch 监控和 Elastic Block Storage 等主题将在另一篇文章中介绍。

入门

在本文中,您将了解如何完成以下几个重要的 EC2 任务:

  • 找到属于您的 Amazon Machine Images (AMIs)
  • 使用密匙对,包括找到属于您的密匙对,创建新密匙对和删除旧密匙对
  • 使用安全组,包括控制对您的虚拟机的 TCP/IP 端口的访问
  • 启动一个 AMI 实例
  • 重新启动或终止一个 AMI 实例

与本系列的其他文章一样,如果您事先安装了 Zend Framework,那么您将能更有效地利用本文提供的示例。如果您还没有安装 Zend Framework,那么请从 zend.com/community/downloads 下载并安装完整的包。该包将在您的机器上安装 Zend Framework、PHP 和 Apache Web 服务器。完成安装后,请访问 http://localhost/ZendServer/。参考 Zend Framework 安装文档获得所有细节。如果可以登录到 ZendServer 控制台,那么您就准备就绪了。

您还需要创建一个 Amazon 帐户(参见 参考资料)。

本文示例使用 使用 PHP 实现云计算,第 1 部分:结合使用 Amazon S3 和 Zend Framework 创建的 PHP Credentials 类,该类管理您的 Amazon 帐户的凭证,这些凭证存储在一个 .ini 文件中。


清单 1. 在一个 PHP .ini 文件中存储凭证

				
; Configuration file to hold secret keys, account numbers and other useful
; strings for Amazon and other cloud accounts.

[amazon]
accessKey=0123456789ABCDEFGHIJ
secretKey=0123456789abcdefghiABCDEFGHI1234567890AB
ownerId=123456789012
            

在本文中,ownerId 值很关键。在 Amazon 有数千个 AMI,多数 AMI 不公开提供。您将使用 ownerId 值过滤您没有访问权限的 AMI。您的 owner ID 是您的 12 位 Amazon Web Services 帐户编号。要找到这个编号,访问 http://aws.amazon.com/,登录您的帐户,然后选择 Your Account 菜单和 Security Credentials 项目。


图 1. 发现您的安全凭证
如何发现您的安全凭证

向下滚动到页面中部,您将看到您的 AWS 帐户 ID。


图 2. 您的 AWS 帐户 ID
向下滚动,您的 AWS 帐户 ID 将出现

将您的帐户 ID 放入 cloud.ini 文件时,不要使用短横线。如果这是您的 ID,您应该将 ownerId=999999999999 添加到该 .ini 文件。

注意:

  • 无论何时您启动一个 AMI 实例,您都需要付费。即使 EC2 的收费很合理(有些实例甚至一小时 10 美分),您也不会希望启动一个 AMI 并让它随意运行。确保使用完实例后立即终止它们。
  • 本文中的示例假定您至少创建了一个 AMI,如果没有,您可以使用这个特殊值 ownerId=amazon。这将返回一个超过 100 个 AMI 的列表,但它将给您一些您可以启动的映像。
  • 如果您使用 ownerId=amazon,请注意某些实例可能相当昂贵。IBM® 有几个 AMI 针对企业级的软件产品。如果您使用其中之一,则只要实例运行您就需要支付许可费用。如果您只是想体验一下 EC2,那么特性较少的 AMI 的价格可能也相对较低。





回页首

关于样例应用程序

样例应用程序包含 6 个 PHP 页面:

  1. amis.php — 列示您的帐户拥有的所有 AMI。只需单击一个按钮,您就可以启动任意 AMI 的一个实例。
  2. manage_instance.php — 处理来自 amis.php 的要求创建、重新启动或终止一个 AMI 实例的请求。
  3. instances.php — 列示您的帐户控制的所有正在运行的实例。只需单击一个按钮就可以重新启动或终止任意正在运行的实例。
  4. keypairs.php — 列示您创建的所有 “公共/私有” 密匙对。检查现有密匙对、创建新的密匙对以及删除现有密匙对。
  5. securitygroups.php — 列示为您的帐户定义的所有安全组。单击一个按钮可以查看一个特定安全组的细节。
  6. securitygroup.php — 列示一个特定安全组的所有权限。添加新权限或删除现有权限。





回页首

使用 AMI

首先,创建一个 Zend_Service_Amazon_Ec2_Image 对象。


清单 2. 创建一个 Zend_Service_Amazon_Ec2_Image 对象

				
require_once 'Credentials.php';

$creds = new Credentials;
$aws_key = $creds->getCredential('amazon', 'accessKey');
$aws_secret = $creds->getCredential('amazon', 'secretKey');

$ec2_img = new Zend_Service_Amazon_Ec2_Image($aws_key, $aws_secret);
            

这个对象允许您使用存储在 Amazon 的 AMI。Zend_Service_Amazon_Ec2_Image->describe() 方法返回关于一个 AMI 的元数据。如果没有使用参数,它返回存储在 Amazon 的每个 AMI 的细节。那将创建大量的数据,多数数据对您没用。遗憾的是,当前的 Zend Framework 版本不支持允许您指定一个 ownerId 参数的 describe() 版本。这意味着第一次列示与该用户相关的 AMI 时,您必须查找整个列表以找到想要的 AMI。要解决这个问题,您可以缓存想要显示的 AMI 的 ID。处理逻辑是这样的:如果存在一个列示相关 AMI 的文件,您只需读取该文件并对每个 ID 调用 describe('ami-id')。如果该文件不存在,那么您必须创建它;这个 PHP 文件还允许用户刷新这个 AMI 列表。刷新需要一些时间,但可以保证列表与 Amazon 上的数据一致。

创建 AMI 列表的代码如清单 3 所示。


清单 3. 缓存 AMI 的 ID

				
if (!file_exists('amis.text') || 
    array_key_exists('refresh', $_POST)) {
  $imageData = $ec2_img->describe();
  $amiFile = fopen('amis.text', 'w');
  $myOwnerId = $creds->getCredential('amazon', 'ownerId');
  foreach ($imageData as $nextImage) {
    if ($nextImage['imageOwnerId'] === $myOwnerId)
      fwrite($amiFile, $nextImage['imageId']."/n");
  }
  fclose($amiFile);
}
            

以上代码检查是否需要列示 Amazon 的目录中的所有 AMI。如果该文件不存在或者 有明确请求要求刷新文件,则代码获取存储在 Amazon 上的所有 AMI 的详细列表。这里的目标是为了创建一个 AMI 表格。


图 3. 一个 AMI 列表
创建一个 AMI 表格

表格中的每一行描述该 AMI 的 ID、位置、状态和平台。另外,还有两个下拉列表允许选择一个密匙对和一个安全组。表格的最后一列是一个按钮,允许您使用当前选中的密匙对和安全组启动该 AMI。要创建这个表格,使用 PHP 方法 file() 将 AMI 列表读入一个数组。对于数组中的每个项目,创建一个包含所有相关信息的表格行。下面是创建这些表格行的代码。


清单 4. 生成 AMI 表格的行

				
$amiList = file('amis.text', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($amiList as $nextAmi) {
  echo "<tr>";

  // Each table row is a form. Clicking the 'Start this image' button submits
  // the data from the current row, which tells instances.php which image to 
  // start and which key pair and security group to use. 
  echo "<form action='manage_instance.php' method='POST' "
;
				  echo " οnsubmit='return confirm(/"Start
image '".$nextAmi."'?/")'>";
				  echo 
"<input type='hidden' name='imagetostart' value='".$nextAmi."'/>";

  echo "<td>".$nextAmi."</td>";

  // Get the description of this AMI
  $imageData = $ec2_img->describe(array('imageId' => $nextAmi));
  echo "<td>".$imageData[0]['imageLocation']."</td>";
  echo "<td>".$imageData[0]['imageState']."</td>";

  // If the platform is blank, assume it's some flavor of Linux
  if (strlen($imageData[0]['platform']))
    echo "<td>".$imageData[0]['platform']."</td>";
  else
    echo "<td>Linux</td>";

  // Insert the lists of key pairs and security groups
  echo "<td>".$keypairsForm."</td>";
  echo "<td>".$secGroupsForm."</td>";

  // If the state of the image is "available," insert a start button.
  if ($imageData[0]['imageState'] == 'available')
    echo "<td><input type='submit' value='Start this image'/></td>";
  else
    echo "<td>Not available</td>";

  echo "</form>";
  echo "</tr>";
}
            

每个表格行必须位于一个单独的 <form> 元素中,这样当单击 Start 按钮时,浏览器才能知道选中了哪个密匙对和安全组。当您迭代整个 AMI ID 数组时,使用一个特定 ID 调用 describe() 方法将返回那个特定 AMI 的描述。表格行的剩余部分包含映像的位置(映像在您的 S3 帐户中的存储位置)、映像的状态、密匙对的下拉列表、安全组的下拉列表和一个 Start this image 按钮。这个 Start 按钮只在映像状态为 available 时显示。针对每个表格行的表单控件如图 4 所示。


图 4. 针对每个表格行的表单控件
针对每个表格行的表单控件

密匙对和安全组的列表在循环外部创建。


清单 5. 生成密匙对和安全组的列表

				
				$ec2_kp = new Zend_Service_Amazon_Ec2_Keypair($aws_key,
 $aws_secret);
				$keypairs = $ec2_kp->describe();
foreach ($keypairs as $key => $row) {
  $keyName[$key]  = $row['keyName'];
  $keyFingerprint[$key] = $row['keyFingerprint'];
}
array_multisort($keyName, $keypairs);

$keypairsList = "<select name='keypair'>";
foreach ($keypairs as $nextKeypair) {
  $keypairsList = $keypairsList."<option value='".$nextKeypair['keyName']."'>";
  $keypairsList = $keypairsList.$nextKeypair['keyName']."</option>";
}
$keypairsList = $keypairsList."</select>";

$secGroupsList = "<select name='secgroup'>";
$ec2_sg = new Zend_Service_Amazon_Ec2_Securitygroups
($aws_key, $aws_secret);
				$securityGroups = $ec2_sg->describe();
foreach ($securityGroups as $key => $row) {
  $groupName[$key]  = $row['groupName'];
  $groupDescription[$key] = $row['groupDescription'];
}
array_multisort($groupName, $securityGroups);

foreach ($securityGroups as $nextGroup) {
  $secGroupsList = $secGroupsList."<option value='".$nextGroup['groupName']."'>";
  $secGroupsList = $secGroupsList.$nextGroup['groupName']."</option>";
}
$secGroupsList = $secGroupsList."</select>";
            

每个列表都是一个字符串,它包含针对密匙对和安全组的 HTML <select><option> 元素。(注意,以上代码使用 PHP array_multisort 对密匙对和安全组的名称排序)。由于启动一个 AMI 实例意味着您的 AWS 帐户中的费用开始增加,因此一个确认对话框将显示。


图 5. 启动一个实例必须经过确认
确认启动一个实例

在这个屏幕截图中,密匙对 id_rsa-gsg-keypair 和安全组 default 被选中。





回页首

使用实例

当您单击 OK 按钮以启动一个实例时,关于这个新实例的信息将被发送到 manage_instance.php 页面。使用实例的第一步是创建一个 Zend_Service_Amazon_Ec2_Instance 对象。


清单 6. 创建一个 Zend_Service_Amazon_Ec2_Instance 对象

				
$creds = new Credentials;
$aws_key = $creds->getCredential('amazon', 'accessKey');
$aws_secret = $creds->getCredential('amazon', 'secretKey');

$ec2_instance = new Zend_Service_Amazon_Ec2_Instance($aws_key, $aws_secret);
			

创建这个对象后,您现在可以启动被请求映像的一个实例。


清单 7. 创建一个新实例

				
if (strlen($imageIdToStart) && strlen($keypairToStart) && strlen($secgroupToStart)) {
$startResults = $ec2_instance->run(array('imageId'
 => $imageIdToStart, 
		'keyName' => $keypairToStart,
		'securityGroup' => $secgroupToStart));
  echo "<p><b>Status of instance ".$startResults['instances'][0]['instanceId'].": ";
  echo $startResults['instances'][0]['instanceState']['name'].".</b></p><hr/>";
}  
            

注意:当您启动一个实例时,可以指定其他一些参数,比如实例数量、实例大小(它有多大的内存和 CPU)以及托管该实例的地理区域。为简便起见,所有启动实例的请求只能启动一个实例,该实例拥有最小的大小(如对 Amazon 的实际请求中的 m1.small 所指定的),并被托管在您的帐户的默认区域中。

以上代码提取 amis.php 页面表单中的 AMI 的 ID 和选中的密匙对以及安全组的名称。该页面然后显示新实例的状态。


图 6. 新创建的实例的状态
显示新创建实例的状态的页面

单击 OK 将把您带到 instances.php 页面。该页面显示当前实例列表。


图 7. 实例列表
显示当前实例列表的页面

注意:映像 ID、密匙名称和安全组的值通过 HTTP POST 传递到 manage_instance.php 页面。使用一个中间页面来创建新实例意味着您可以刷新 instances.php 页面,而不会因疏忽而启动另一个实例。manage_instance.php 页面还处理要求重新启动或终止一个映像的请求。

当一个实例启动时,它的初始状态是 pending,那意味着机器映像正在启动,但它没有马上启动起来并运行。还要注意一点,Amazon 已经将实例 ID i-373c995f 分配给这个实例,此 ID 决定您稍后如何使用该实例。单击 “Refresh list of instances” 按钮更新显示。


图 8. 更新后的实例列表
单击 “Refresh list of instances” 按钮更新显示

在更新后的列表中,实例的状态现在为 running。注意,Amazon 已经将 DNS 名称 ec2-75-101-220-48.compute-1.amazonaws.com 分配给这个实例,那意味着如果您已经启用这个实例以进行 Web 访问且该实例正在端口 80 上运行一个 Web 服务器,那么您就能访问 http://ec2-75-101-220-48.compute-1.amazonaws.com 以查看该实例的主页。(您可以通过在安全组中创建一个规则来启用端口 80,稍后将介绍关于安全组的更多信息)。

要生成实例表格的行,可以使用 Zend_Service_Amazon_Ec2_Instance->describe() 方法。


清单 8. 创建实例表

				
				$instanceDetails = $ec2_instance->describe();
if (count($instanceDetails['instances'])) {
  echo "<table border='1' cellpadding='5'>";
  echo "<tr style='font-weight: bold;'>";
  echo "<td>Status</td><td>Instance ID</td><td>AMI ID</td><td>Public DNS name</td>";
  echo "<td>Security Group</td><td>Key Pair</td><td>&nbsp;</td><td>&nbsp;</td>";
  echo "</tr>";

  foreach ($instanceDetails['instances'] as $nextInstance) {
    echo "<tr>";
    $status = $nextInstance['instanceState']['name'];
    // Make the cell background green if the status is running.
    if (strripos('running', $status) === 0)
      echo "<td style='background-color: green; color: white; font-weight: bold;'>";

    // Make the cell background yellow if the status is shutting-down.
    else if (strripos('shutting-down', $status) === 0)
      echo "<td style='background-color: yellow; font-weight: bold;'>";

    // Make the cell background red if the status is terminated.
    else if (strripos('terminated', $status) === 0)
      echo "<td style='background-color: red; color: white; font-weight: bold;'>";

    // Make the cell background purple for any other status.
    else
      echo "<td style='background-color: purple; color: white; font-weight: bold;'>";
    echo $status."</td>";

    echo "<td>".$nextInstance['instanceId']."</td>";
    echo "<td>".$nextInstance['imageId']."</td>";
    echo "<td>".$nextInstance['dnsName']."</td>";
    echo "<td><a href='securitygroup.php?groupname=".$nextInstance['groupSet'][0];
    echo "&previous=instances'>".$nextInstance['groupSet'][0]."</a></td>";
    echo "<td>".$nextInstance['keyName']."</td>";
    . . .
            

以上代码首先检查是否存在实例。假设至少有一个实例,每个实例在一个表格行中描述,包括实例 ID、AMI ID、公共 DNS 名称、安全组和值对。就像 amis.php 页面一样,每个表格行都是一个表单。


图 9. 管理实例的表单控件
每个表格行都是一个表单

这个表格列示 3 个实例,其中两个实例的状态为 running,因此它们有 RebootTerminate 按钮。对于处于其他状态(pendingshutting-downterminated)的实例,这些按钮不会出现。


清单 9. 为实例表创建表单控件

				
if (strripos('running', $status) === false) {
      echo "<i>[Can't reboot]</i>"; 
    }
    else {
     echo "<form action='manage_instance.php' method='POST' ";
      echo "οnsubmit='return confirm(/"Reboot instance &apos;";
      echo $nextInstance['instanceId']."&apos;?/")'>";
      echo "<input type='hidden' name='imagetoreboot' value='";
      echo $nextInstance['instanceId']."'/>";
      echo "<input type='submit' value='Reboot'/></form>";
    }
    echo "</td>";

    echo "<td style='text-align: center;'>";
    if (strripos('running', $status) === false) {
      echo "<i>[Can't terminate]</i>"; 
    }
    else {
     echo "<form action='manage_instance.php' method='POST' ";
      echo "οnsubmit='return confirm(/"Terminate instance &apos;";
      echo $nextInstance['instanceId']."&apos;?/")'>";
      echo "<input type='hidden' name='imagetoterminate' value='";
      echo $nextInstance['instanceId']."'/>";
      echo "<input type='submit' value='Terminate'/></form>";
    }
    echo "</td>";
            

如果实例正在运行,则以上代码创建重新启动或终止实例的按钮,单击这些按钮将出现确认对话框。


图 10. 终止或重新启动一个实例必须经过确认
代码创建重新启动或终止实例的按钮,单击这些按钮将出现确认对话框。

单击 OK 将再次把您带到 manage_instance.php 页面。


图 11. 一个刚刚终止的实例的状态
单击 OK 将再次把您带到 manage_instance.php 页面

返回实例列表将显示更新后的状态。


图 12. 更新后的实例列表
返回实例列表将显示更新后的状态

在某个点,实例的状态将更改为 terminated


图 13. 一个已终止的实例
实例的状态将更改为 terminated

注意:在实例终止后的某个点,EC2 将删除与该实例关联的所有数据。这通常在 1 小时内发生。关于某个实例的数据删除后,该实例将不再显示在结果中。





回页首

处理密匙对

密匙对 是用于验证一个用户的一对公共和私有密匙。当您启动一个实例时,您将指定一个密匙对,它可以用于在该实例启动并运行时访问实例。Amazon 将公共密匙和该实例关联起来。如果没有私有密匙,您也许不能访问该实例,但这取决于 AMI 是如何配置的。keypairs.php 显示为您的帐户定义的所有密匙对。


图 14. 密匙对列表
keypairs.php 文件显示为您的帐户定义的所有密匙对

对于每个密匙对,表格显示它的名称和指纹(fingerprint)。


清单 10. 创建密匙对表

				
$creds = new Credentials;
$ec2_kp = new Zend_Service_Amazon_Ec2_Keyp
air>
				($creds->getCredential('amazon', 'acce
ssKey'), 
				$creds->getCredential('amazon', 'secr
etKey'));
				$keypairs = $ec2_kp->describe();
if (count($keypairs)) {
  echo "<p>Here are the key pairs defined for your account:</p>";

  echo "<table border='1' cellpadding='5'>";
  echo "<tr style='font-weight: bold;'>";
  echo "<td>Key Pair Name</td>";
  echo "<td>Fingerprint</td>";
  echo "<td>&nbsp;</td>";
  echo "</tr>";

  foreach ($keypairs as $key => $row) {
    $keyName[$key] = $row['keyName'];
    $keyFingerprint[$key] = $row['keyFingerprint'];
  }
  array_multisort($keyName, $keypairs);

  foreach ($keypairs as $nextKeypair) {
    echo "<tr>";
    echo "<td>&nbsp;".$nextKeypair['keyName']."</td>";
    echo "<td>&nbsp;".$nextKeypair['keyFingerprint']."</td>";
    echo "<td>";
    echo "<form action='".$_SERVER['PHP_SELF']."' method='post'";
    echo " οnsubmit='return confirm(/"Do you really want to delete the key pair ";
    echo "&apos;".$nextKeypair['keyName']."&apos;?/");'>";
    echo "<input type='hidden' name='keypairtodelete' ";
    echo "value='".$nextKeypair['keyName']."'/>";
    echo "<input type='submit' value='Delete'/>";
    echo "</form>";
    echo "</td>";
    echo "</tr>";
  }
  echo "</table>";
}
else
  echo "<p>Sorry, your account doesn't have any key pairs defined.</p>";
            

以上代码创建一个 Zend_Service_Amazon_Ec2_Keypair 对象,然后调用它的 describe() 方法获取为您的帐户定义的所有密匙对的细节。对于每个密匙对,AWS 返回它的名称和指纹。每个表格行还包含一个 HTML 表单,表单上有一个 Delete 按钮。该表单要求您确认任何删除请求。


图 15. 删除一个密匙对必须经过确认
表单要求您确认任何删除请求

delete() 方法使得删除密匙对变得很直观。


清单 11. 删除一个密匙对

				
if (strlen($keypairToDelete)) {
  echo "<p><b>";
  try {
    $ec2_kp->delete($keypairToDelete);
    echo "The key pair named ".$keypairToDelete." was deleted successfully.";
  }
  catch (Zend_Service_Amazon_Ec2_Exception $ec2e) {
    echo "The key pair could not be deleted. The error message from Amazon is:";
    echo "<br/><br/><i>".$ec2e->getMessage()."</i>";
  }
  echo "</b></p><hr/><br/>";
}
            

keypairs.php 页面还包含一个表单以创建一个新的密匙对。惟一的参数是新密匙对的名称。


图 16. 创建一个新密匙对的表单
页面还包含一个表单以创建一个新的密匙对

创建密匙对很简单,只需调用 create() 方法。


清单 12. 创建安全组中的规则表

				
foreach ($list[0]['ipPermissions'] as $key => $row) {
  $ipProtocol[$key] = $row['ipProtocol'];
  $startingPort[$key] = $row['fromPort'];
  $toPort[$key] = $row['toPort'];
  $ipRanges[$key] = $row['ipRanges'];
}
array_multisort($startingPort, SORT_ASC, $list[0]['ipPermissions']);

foreach($list[0]['ipPermissions'] as $rule) {
. . .
  echo "<tr>/n";
  echo "<td>&nbsp;".strtoupper($rule['ipProtocol'])."</td>/n";
  echo "<td>&nbsp;".$rule['fromPort']."</td>/n";
  echo "<td>&nbsp;".$rule['toPort']."</td>/n";

  if ((is_array($rule['ipRanges']) && count($rule['ipRanges']) == 0) ||
      (strlen($rule['ipRanges']) == 0)) {
    echo "<td>&nbsp;---</td>/n";
    echo "<td>&nbsp;</td>/n";
  }
  else {
    echo "<td>&nbsp;".$rule['ipRanges']."</td>/n";
    echo "<td style='text-align: center;'>";
    echo "<form action='".$_SERVER['PHP_SELF']."' method='POST'";
    echo " οnsubmit='return confirm(/"Do you really want to revoke access for IP ";
    echo " address range '".$rule['ipRanges']."' on this port?/");'>";
    echo "<input type='hidden' name='groupname' value='".$groupName."'/>";
    echo "<input type='hidden' name='fromport' value='".$rule['fromPort']."'/>";
    echo "<input type='hidden' name='toport' value='".$rule['toPort']."'/>";
    echo "<input type='hidden' name='iptorevoke' value='".$rule['ipRanges']."'/>";
    echo "<input type='submit' value='Revoke'/></form></td>";
  }

来自 Amazon 的响应包含新密匙对的私有密匙,PHP 页面通过向用户发送一条消息来显示该信息。


图 17. 一个新创建的密匙对
PHP 页面通过向用户发送一条消息来显示该信息

用户必须复制并粘贴私有密匙到一个文件中。如果这个信息丢失,则不能被重新创建。(创建一个更好的解决方案以自动将密匙对数据写入一个文件,这将留作读者的练习)。





回页首

使用安全组

允许您创建新实例的表单要求您指定一个机器映像、一个密匙对和一个安全组。安全组允许您定义其他机器访问一个实例的方式。例如,如果您想使用一个运行的实例作为一个 Web 服务器,那么您应该在您的安全组中定义一个规则:任何机器对端口 80 的连接请求都应该被接受。对于每个规则,您要指定一个协议、一个端口范围以及允许通过那个协议访问那些端口的一个 IP 地址范围。一个安全组的显示如图 18 所示。


图 18. 一个安全组
一个安全组的显示

这里的表格列示了安全组 default 的安全策略。在这个屏幕截图的底端,您可以看到该策略允许主机 67.93.2.12 和 76.182.90.9 访问端口 80。端口 3389(通常用于 Windows VMs 的 Remote Desktop 会话)也授权给 IP 地址 76.182.90.9。您可以定义一个端口范围;在这个示例中,端口 21、22 和 23 的规则也可以使用单一规则定义。每个表格行都包含一个表单以删除一个单独的规则。创建这个表格的代码相对直观。


清单 13. 创建安全组中的规则表

				
foreach ($list[0]['ipPermissions'] as $key => $row) {
  $ipProtocol[$key] = $row['ipProtocol'];
  $startingPort[$key] = $row['fromPort'];
  $toPort[$key] = $row['toPort'];
  $ipRanges[$key] = $row['ipRanges'];
}
array_multisort($startingPort, SORT_ASC, $list[0]['ipPermissions']);

foreach($list[0]['ipPermissions'] as $rule) {
. . .
  echo "<tr>/n";
  echo "<td>&nbsp;".strtoupper($rule['ipProtocol'])."</td>/n";
  echo "<td>&nbsp;".$rule['fromPort']."</td>/n";
  echo "<td>&nbsp;".$rule['toPort']."</td>/n";

  if ((is_array($rule['ipRanges']) && count($rule['ipRanges']) == 0) ||
      (strlen($rule['ipRanges']) == 0)) {
    echo "<td>&nbsp;---</td>/n";
    echo "<td>&nbsp;</td>/n";
  }
  else {
    echo "<td>&nbsp;".$rule['ipRanges']."</td>/n";
    if (strripos('0.0.0.0', $rule['ipRanges'][0]) === false) {
      echo "<td style='text-align: center;'>";
      echo "<form action='".$_SERVER['PHP_SELF']."' method='POST'";
      echo " οnsubmit='return confirm(/"Do you really want to revoke access for IP ";
      echo " address range '".$rule['ipRanges']."' on this port?/");'>";
      echo "<input type='hidden' name='groupname' value='".$groupName."'/>";
      echo "<input type='hidden' name='fromport' value='".$rule['fromPort']."'/>";
      echo "<input type='hidden' name='toport' value='".$rule['toPort']."'/>";
      echo "<input type='hidden' name='iptorevoke' value='".$rule['ipRanges']."'/>";
      echo "<input type='submit' value='Revoke'/></form></td>";
    }
    else
    echo "<td>&nbsp;</td>";

在构建表格行之前,以上代码使用 array_multisort 函数根据每个规则的起始端口号对规则数组排序。显示协议名称、起始端口和结束端口非常简单。表格中的最后两列是 IP 地址范围和一个用于撤销权限的按钮。在某些情况下,IP 地址范围返回为一个空数组或一个空字符串。如果出现这种情况,该表格包含一个空单元而不是一个 Revoke 按钮。

最后一个表格行包含一个表单,用于创建一个新规则。


图 19. 创建一个新规则的表单
最后一个表格行包含一个表单,用于创建一个新规则

Amazon 要求 IP 地址以 Classless Inter-Domain Routing (CIDR) 标记指定。CIDR 标记是一个 IP 地址加一个正斜杠再加一个 0 到 32 的数字。正斜杠后面的数字指定前缀中有多少 IP 地址位。例如,CIDR 标记 9.67.0.0/16 匹配以 9.67 开始的任意地址。IP 范围 0.0.0.0/0 匹配任意 IP 地址,而范围 76.182.90.9/32 只匹配 IP 地址 76.182.90.9。如果用户输入的 IP 地址不是 CIDR 标记,则 PHP 页面向该地址添加 /32。(参见 参考资料 了解关于 CIDR 的更多信息)。授权一个 IP 范围的代码很直观。


清单 14. 授权一个新的 IP 地址范围

				
if (strlen($ipToAuthorize) && strripos($ipToAuthorize, '/') === false)
  $ipToAuthorize = $ipToAuthorize."/32";
  . . .
  try {
    $ec2_sg->authorizeIp($groupName, $protocol, $fromPort, $toPort, $ipToAuthorize);
    echo "IP address ".$ipToAuthorize." is now authorized.";
  }
  catch (Zend_Service_Amazon_Ec2_Exception $ec2e) {
     // We get an exception if this IP address is already authorized, that's okay. 
    if ($ec2e->getErrorCode() == 'InvalidPermission.Duplicate')
      echo "IP address range ".$ipToAuthorize." is already authorized.";

    // If the IP address range isn't formatted correctly, tell the user. 
    else if ($ec2e->getErrorCode() == 'InvalidPermission.Malformed')
      echo "The value ".$ipToAuthorize." is not a valid IP address range.";

    //  For all other exceptions, we just dump the exception to the screen.
    else {
      echo "<i>Something really bad happened:</i><br/><pre>";
      var_dump($ec2e);
      echo "</pre>";
    }
  }
      

调用 authorizeIp 方法为安全组创建了一个新规则。以上代码处理两个常见异常:如果您授权一个已经授权的 IP 地址范围,那么 Zend Framework 将抛出一个异常;第二个异常在 IP 地址范围的形式出现错误时发生。对于其他异常,这段代码只是在屏幕上显示错误消息。

注意:当您对一个特定端口授权一个以上 IP 地址范围时,Zend Framework 的一个最新修复的 bug 将返回一组列表。如果您的 Zend Framework 版本没有这个补丁,那么您只能在每个端口上看到一个地址。所有授权都是存储在 Amazon 云中的安全组的一部分,它们不在来自 Zend 的结果中显示。样例代码可以非常优雅地处理这个问题。

最后,单击规则旁边的 Revoke 按钮需要用户进行确认。


图 20. 删除一个安全规则必须经过确认
单击规则旁边的 Revoke 按钮需要来自用户的确认

删除一个安全规则通过 revokeIp() 方法完成。





回页首

结束语

本文呈现一组页面以处理虚拟机映像,映像的运行实例,以及允许您管理实例的密匙对和安全组。重申一次,Zend Framework 能够简化与云的交互。本系列的下一篇文章将向您展示如何通过 Zend Framework 使用更高级的 EC2 特性,比如 Elastic IP 地址、Elastic Block Storage 和 CloudWatch。






回页首

下载

描述名字大小下载方法
样例代码os-php-cloud2-source.zip13KBHTTP
关于下载方法的信息

参考资料

学习

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐