share facebook facebook twitter menu hatena pocket slack

2012.07.05 THU

タグを利用したEC2のバックアップ(AMI取得)と世代管理

鈴木 宏康

WRITTEN BY 鈴木 宏康

今回は、Cloud Design Pattern(CDP)の記事になります。
対象は「Cloud DIパターン」です。

このパターンの「利点」に下記の記載があります。

EC2インスタンスの構築だけでなく、AMIやスナップショットの自動取得を行う仕組みを作る場合にも
利用できる。

そこで今回は、EC2のタグ情報を利用してスナップショット(AMI)の取得や世代管理を行なう
PHPスクリプトを作成しました。

このPHPスクリプトを定期的に実行することで、AWSマネジメントコンソールにてEC2のタグを編集し、
スナップショット(AMI)の取得対象にするか、世代をどれだけ残すかということを容易に管理することができます。

PHPスクリプトは長いので最後に掲載することとし、先に仕様をまとめておきます。

  • 指定のアカウント・リージョンのすべての稼働しているEC2に対して実施
     ○稼働はinstance-state-nameがrunningの状態
  • バックアップ対象EC2はBackup-Generationタグがついているもの
     ○Backup-Generationの値は0以上の数字(0のときはバックアップしない)
  • バックアップはcreate_imageつまりAMIの作成で実施
     ○NoRebootオプションをつけてEC2のリブートは抑制
  • 作成したAMIにはタグを付与
     ○NameタグはEC2と同じものを付与
     ○自動バックアップとわかるようにBackup-Typeタグをautoとしても付与
     ○関連するスナップショットにもBackup-Typeタグをautoとして付与
     ○スナップショットのNameタグの値はAMI名とデバイスの値からの文字列
     ○ただしタグの付与は作成後すぐだとエラーの可能性があるので最後に実施
  • AMIは最新からEC2のBackup-Generationタグの値だけ維持
     ○AMIのNameタグがEC2と同様のものが対象
     ○残りの古いAMIは削除
     ○Backup-Typeタグがautoになっていないものは対象外
  • 削除したAMIに関連するスナップショットも削除
     ○Backup-Typeタグがautoのものも対象(注意!)
  • AMIやスナップショットへのタグ付けは作成後に実施
     ○作成後すぐに行うとエラーになる可能性

実際の挙動は下記のようになります。

まずはバックアップ対象のEC2です。
バックアップ(AMI)を2世代管理するようにBackup-Generationを付け、値を2としています。

この状態で最後に掲載するバックアップスクリプト(PHP)を実行すると、
Backup-Generationタグが付いているEC2に対してAMIを作成します。

Backup-Generationの値は2なので、バックアップは2世代残るようになっており、
タグもNameはEC2と同じもの、そして自動バックアップがわかるように、
Backup-Typeもautoで付いています。

また、AMIの作成と同時にスナップショットも取得しています。

こちらのタグも自動バックアップがわかるように、Backup-Typeがautoとして付き、
NameはAMI名にアタッチしているデバイス名を付与したものが付いています。

上記を図にすると、下記のようになります。

最後にPHPスクリプトですが、下記のようになっています。

#!/usr/bin/php
// 初期設定
require_once("/opt/aws/php/default/sdk.class.php");
date_default_timezone_set("Asia/Tokyo");
$ec2 = new AmazonEC2(array(
"key" => "ACCESS KEY",
"secret" => "SECRET KEY"
));
$ec2->set_region(AmazonEC2::REGION_APAC_NE1);
error_log(date("Y/m/d H:i:s") . " [Info] Begin create images.");

// 対象EC2(runnning)の取得
$response = $ec2->describe_instances(array(
"Filter" => array(
array("Name" => "instance-state-name", "Value" => "running")
)
));
if(!$response->isOK()) {
error_log(date("Y/m/d H:i:s") . " [" . $response->body->Errors->Error->Code . "] " . $response->body->Errors->Error->Message);
}

// 対象EC2に対するバックアップ処理
if(isset($response->body->reservationSet->item)) {
foreach($response->body->reservationSet->item as $reservation) {
foreach($reservation->instancesSet->item as $instance) {
error_log(date("Y/m/d H:i:s") . " [Info] Begin execute instance(" . $instance->instanceId . ").");
$is_backup = false;
$instance_id = $instance->instanceId;
$image_tag = $instance_id;

// バックアップ条件の確認
if(isset($instance->tagSet->item)) {
foreach($instance->tagSet->item as $tag) {
if($tag->key == "Name" && $tag->value != null && trim($tag->value) != "") {
$image_tag = $tag->value;
}
if($tag->key == "Backup-Generation" && is_numeric($tag->value->to_string()) && intval($tag->value) > 0) {
$is_backup = true;
$generation = intval($tag->value);
}
}
}

// バックアップと世代管理の実施
if($is_backup) {
// AMI名の作成
$image_name = $image_tag . "-" . date("YmdHis");
// AMIの作成
$image_id = create_image($ec2, $image_name, $instance_id);
// 削除対象AMIの取得
$images = find_delete_images($ec2, $image_tag, $generation);
// AMIとスナップショットの削除
delete_images($ec2, $images);
// AMIにタグ付け
tag_image($ec2, $image_id, $image_tag);
// スナップショットにタグ付け
tag_snapshots($ec2, $image_id);
} else {
error_log(date("Y/m/d H:i:s") . " [Info] Skip create image from " . $instance->instanceId . ".");
}
error_log(date("Y/m/d H:i:s") . " [Info] End execute instance(" . $instance->instanceId . ").");
}
}
}

error_log(date("Y/m/d H:i:s") . " [Info] End create images.");
exit(0);

// AMIの作成
function create_image($ec2, $image_name, $instance_id) {
error_log(date("Y/m/d H:i:s") . " [Info] Begin create image(" . $image_name . ") from " . $instance_id . ".");
$response = $ec2->create_image(
$instance_id,
$image_name,
array(
"Description" => "Create from " . $instance_id . ".",
"NoReboot" => true
)
);
if(!$response->isOK()) {
error_log(date("Y/m/d H:i:s") . " [" . $response->body->Errors->Error->Code . "] " . $response->body->Errors->Error->Message);
}
error_log(date("Y/m/d H:i:s") . " [Info] End create image(" . $image_name . ") from " . $instance_id . ".");
return $response->body->imageId;
}

// 削除対象AMIの取得
function find_delete_images($ec2, $image_tag, $generation) {
$response = $ec2->describe_images(array("Filter" => array(
array("Name" => "tag:Name" , "Value" => $image_tag),
array("Name" => "tag:Backup-Type", "Value" => "auto")
)));
if(!$response->isOK()) {
error_log(date("Y/m/d H:i:s") . " [" . $response->body->Errors->Error->Code . "] " . $response->body->Errors->Error->Message);
}
$images = array();
foreach($response->body->imagesSet->item as $image) {
$images["$image->name"] = array(
"id" => $image->imageId,
"snapshots" => $image->blockDeviceMapping
);
}
krsort($images);
return array_slice($images, $generation - 1);
}

// AMIとスナップショットの削除
function delete_images($ec2, $images) {
foreach($images as $image_name => $image) {
error_log(date("Y/m/d H:i:s") . " [Info] Begin delete image(" . $image_name . ").");
$image_id = $image["id"];
$response = $ec2->deregister_image($image_id);
if(!$response->isOK()) {
error_log(date("Y/m/d H:i:s") . " [" . $response->body->Errors->Error->Code . "] " . $response->body->Errors->Error->Message);
}
error_log(date("Y/m/d H:i:s") . " [Info] End delete image(" . $image_name . ").");
foreach($image["snapshots"]->item as $snapshot) {
$snapshot_id = $snapshot->ebs->snapshotId;
error_log(date("Y/m/d H:i:s") . " [Info] Begin delete snapshot(" . $snapshot_id . ").");
$response = $ec2->delete_snapshot($snapshot_id);
if(!$response->isOK()) {
error_log(date("Y/m/d H:i:s") . " [" . $response->body->Errors->Error->Code . "] " . $response->body->Errors->Error->Message);
}
error_log(date("Y/m/d H:i:s") . " [Info] End delete snapshot(" . $snapshot_id . ").");
}
}
}

// AMIにタグ付け
function tag_image($ec2, $image_id, $image_tag) {
error_log(date("Y/m/d H:i:s") . " [Info] Begin tag(" . $image_tag . ") image to " . $image_id . ".");
$response = $ec2->create_tags($image_id, array(
array("Key" => "Name" , "Value" => $image_tag),
array("Key" => "Backup-Type", "Value" => "auto"),
));
if(!$response->isOK()) {
error_log(date("Y/m/d H:i:s") . " [" . $response->body->Errors->Error->Code . "] " . $response->body->Errors->Error->Message);
}
error_log(date("Y/m/d H:i:s") . " [Info] End tag(" . $image_tag . ") image to " . $image_id . ".");
}

// スナップショットにタグ付け
function tag_snapshots($ec2, $image_id) {
$response = $ec2->describe_images(array("ImageId" => $image_id));
foreach($response->body->imagesSet->item as $image) {
foreach($image->blockDeviceMapping->item as $snapshot) {
$snapshot_id = $snapshot->ebs->snapshotId;
$snapshot_tag = $image->name . "-" . basename($snapshot->deviceName);
error_log(date("Y/m/d H:i:s") . " [Info] Begin tag(" . $snapshot_tag . ") snapshot to " . $snapshot_id . ".");
$response = $ec2->create_tags($snapshot_id, array(
array("Key" => "Name" , "Value" => $snapshot_tag),
array("Key" => "Backup-Type", "Value" => "auto")
));
if(!$response->isOK()) {
error_log(date("Y/m/d H:i:s") . " [" . $response->body->Errors->Error->Code . "] " . $response->body->Errors->Error->Message);
}
error_log(date("Y/m/d H:i:s") . " [Info] End tag(" . $snapshot_tag . ") snapshot to " . $snapshot_id . ".");
}
}
}
?>

こちらの記事はなかの人(suz-lab)監修のもと掲載しています。
元記事は、こちら

鈴木 宏康

鈴木 宏康

愛知県生まれ。東京工業大学大学院修士課程修了。在学時より、ベンチャー企業でインターネットに関する業務に携わり、現在はクラウド(主にAmazon Web Services)上での開発・運用を軸とした事業の、業務の中心として活躍。