AMIのバックアップをawscliで自動でとる

こんにちは。OPSのほうの小宮です。

どうも社内で需要があるようだったのと、
javaよりpythonのツールが早いということで検索したけどawscliのは見つからなかったのでつくりました。
タグでとるとらない自動判定などはしておりません。bkup_numに世代数を指定すると複数世代とれるように直しました。
引数1にインスタンスID、引数2にホスト名を指定して実行する仕様です。
--no-rebootを付けてあるので稼働中のインスタンスが再起動されることはありません。
データ領域のEBSがあってもamiコマンドが勝手に複数とってくれるようだったので、
osデバイスかdataデバイスか判定してわかりやすいタグつけるようにしてみました。

※※要注意※※
--no-rebootつけてAMIを取得する対象がMyISAMストレージエンジンのmysqlのDBサーバだった場合に、データが壊れて止まるという現象が発生しました。
InnoDBじゃない場合にどうしてもAMI取得したい時はメンテ推奨で止めてやるのがよいかと思われます。
※AWSでは無停止でのAMI取得に関するデータの整合性は保証しておりません。
 参考URL:http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/creating-an-ami-ebs.html
ということはストレージ系のサーバは気をつけたほうがよい(メンテ推奨)ようです。

・awscliが入ってなければインストールする
入れ方参考:(入ってない場合はpip install awscliとする。pipはeasy_installで入れる)
AWS Command Line Tool Python版 | Developers.IO
pythonでeasy_install - ハネ@日記
AWS Command Line Interface(awscli)を使ってみた - 元RX-7乗りの適当な日々

スクリプトから呼ぶAWSのアクセスキーとリージョンの設定ファイルを作っておきます。
[shell]# cat /root/.ec2/aws.config ———— [default] aws_access_key_id=<アクセスキーID> aws_secret_access_key=<シークレットアクセスキーID> region=ap-northeast-1 ————[/shell]
・コマンドの調査(いちおう載せておきます)
[shell]# aws ec2 copy-image help ———— copy-image DESCRIPTION Initiates the copy of an AMI from the specified source region to the region in which the request was made. リージョン間コピーなのでいま要らない。 ———— create-image Creates an Amazon EBS-backed AMI from an Amazon EBS-backed instance that is either running or stopped. create-image [–dry-run | –no-dry-run] –instance-id –name [–description ] [–no-reboot | –reboot] [–block-device-mappings ] Command:

aws ec2 create-image –instance-id i-10a64379 –name “My server” –description “An AMI for my server”

Output:

{ “ImageId”: “ami-5731123e” } ———— describe-images 世代管理するなら–filterオプションでタグ検索して出てきたimageidやホスト名使うとかするとよさそう。 ———— describe-instances これはRootDeviceNameを得るのにつかう感じです。 ———— deregister-image Deregisters the specified AMI. After you deregister an AMI, it can’t be used to launch new instances. SYNOPSIS deregister-image [–dry-run | –no-dry-run] –image-id Command:

aws ec2 deregister-image –image-id ami-4fa54026

Output:

{ “return”: “true” } ———— NAME create-tags - SYNOPSIS create-tags [–dry-run | –no-dry-run] –resources –tags EXAMPLES Command:

aws ec2 create-tags –resources ami-78a54011 –tags Key=Stack,Value=production

Output:

{ “return”: “true” } ———— # aws ec2 delete-snapshot help NAME delete-snapshot - DESCRIPTION Deletes the specified snapshot. SYNOPSIS delete-snapshot [–dry-run | –no-dry-run] –snapshot-id ———— # aws ec2 describe-snapshots help NAME describe-snapshots - SYNOPSIS describe-snapshots [–dry-run | –no-dry-run] [–snapshot-ids ] [–owner-ids ] [–restorable-by-user-ids ] [–filters ] Syntax: –filters (list) o tag :key =*value* - The key/value combination of a tag assigned to the resource. descriptionでamiidをキーにfilter検索し消したいsnapshotidを得るためにつかう ———— メタデータのとりかた

# /usr/bin/curl -s http://169.254.169.254/latest/meta-data/instance-id i-292a442c ————[/shell]

・スクリプトを作る

引数があればそのインスタンス、なければ自分のAMIをcreateし
戻り値のimageidをファイルにだしてそれを用いてタグをつける、
このときRootDeviceNameのsnapshotかどうかでタグがosと判断しデバイス名もタグ名に含める。
戻り値のimageidでホスト名のフィルタで検索してホストにかかわる全てのimageidを得て日時のフィールドで降順にソートして
残したい世代数の行分を削除して消す対象の古いimageidを得て、deregistとsnapshot消去する、という流れです。

世代数はbkup_numという変数に定義します。

[shell]# vi /opt/bin/aws_image_bkup.sh ———— #!/bin/bash # # aws_image_bkup.sh : awscliでAMIバックアップする # 依存関係:awsコマンド # 更新履歴:20140314 - create komiyay # export PATH=$PATH:/usr/local/bin export AWS_CONFIG_FILE=/root/.ec2/aws.config

if [ $# -eq 0 ];then echo “instance-idの指定がない場合local instanceのAMIバックアップを行います” echo “usage: $0 [instance-id] [hostname]” myInstanceID=`/usr/bin/curl -s http://169.254.169.254/latest/meta-data/instance-id` host_name=`uname -n` else myInstanceID=$1 host_name=$2 fi

## default variables base=`dirname “$0”` LOG=/opt/log/bkup.log CurrentImage_LOG=${base}/cimageid_${myInstanceID}.log #OLDimageID=`cat ${CurrentImage_LOG}|grep ImageId|awk ‘{FS=":";print $2}’|sed -e ’s/[ “]//g’` current_result=/opt/log/current_result.log today=`/bin/date +%Y%m%d` amitime=`/bin/date +%Y%m%d_%H%M` datetime=`/bin/date +%Y/%m/%d_%H:%M:%S` mail_to=<宛先メールアドレス> #mail_to=<宛先メールアドレス> bkup_num=1

## check_param. if [ -z ${myInstanceID} ];then echo “error: no instance-id, end script ${datetime}."|tee -a ${LOG} exit 1 fi

## function ### mail_send mail_send(){ echo ${mail_body}|tee -a ${LOG}|mail -s ${mail_title} ${mail_to} }

### create_ami create_ami(){ aws ec2 create-image –instance-id ${myInstanceID} –name “${host_name}_${amitime}” \ –description “${host_name}_${myInstanceID}_${datetime}” –no-reboot |tee ${CurrentImage_LOG} grep ImageId ${CurrentImage_LOG} res_create=$? }

### deregister_ami deregister_ami(){ if [ -z ${OLDimageID} ];then res_deregister=0 else aws ec2 deregister-image –image-id ${OLDimageID}|tee ${current_result} grep true ${current_result} res_deregister=$? if [ ${res_deregister} -ne 0 ];then mail_body=“NG deregister ami for ${myInstanceID}(${host_name}) at ${datetime}” mail_titile=“NG_deregister_ami_${today}” mail_send fi fi }

### delete_snapshot delete_snapshot(){ if [ -z ${OLDimageID} ];then res_delsnap=0 else snapshot_ids=`aws ec2 describe-snapshots –filters Name=description,Values=*${OLDimageID}* \ |grep SnapshotId|awk -F : ‘{print $2}’|sed -e ’s/[ “,]//g’` for i in ${snapshot_ids} do aws ec2 delete-snapshot –snapshot-id ${i}|tee ${current_result} grep true ${current_result} res_delsnap=$? if [ ${res_delsnap} -ne 0 ];then mail_body=“NG delete ami snapshot(${i}) for ${myInstanceID}(${host_name}) at ${datetime}” mail_titile=“NG_delete_ami-snapshot_${today}” mail_send fi done fi }

### create_tag create_tag(){ ### create tag for ami CurImageID=`cat ${CurrentImage_LOG}|grep ImageId|awk ‘{FS=”:";print $2}’|sed -e ’s/[ “]//g’` aws ec2 create-tags –resources ${CurImageID} –tags “Key=Name,Value=${host_name}_${today}” ### create tags for snapshots rootDeviceName=`aws ec2 describe-instances –instance-ids ${myInstanceID} \ |grep RootDeviceName|awk ‘{print $2}’|sed -e ’s/[”,]//g’` snapshot_ids=`aws ec2 describe-snapshots –filters Name=description,Values=*${CurImageID}* \ |grep SnapshotId|awk -F : ‘{print $2}’|sed -e ’s/[ “,]//g’` for i in ${snapshot_ids} do volume_id=`aws ec2 describe-snapshots –snapshot-ids ${i}|grep VolumeId \ |awk ‘{print $2}’|sed -e ’s/[”,]//g’` deviceName=`aws ec2 describe-volumes –volume-ids ${volume_id}|grep Device \ |awk ‘{print $2}’|sed -e ’s/”//g’` if [ “${rootDeviceName}” == “${deviceName}” ];then aws ec2 create-tags –resources ${i} \ –tags “Key=Name,Value=AMI_${host_name}_${today}-os_${deviceName}” else aws ec2 create-tags –resources ${i} \ –tags “Key=Name,Value=AMI_${host_name}_${today}-data_${deviceName}” fi done }

## main exec » ${LOG} exec 2>&1

echo “start ami backup. ${datetime}” create_ami

## rotate_ami delete_images=`aws ec2 describe-images –filters Name=name,Values=*${host_name}* \ |egrep -a ‘ImageId|ImageLocation’|sed -e “N;s/\n//” -e ’s/[”,]//g’|awk ‘{print $2" “$4}’ \ |sort -r -t _ -k 2,3|sed -e “1,${bkup_num}d”|awk ‘{print $1}’` for i in ${delete_images} do OLDimageID=${i} deregister_ami delete_snapshot done

## if get error, then send mail. the other case, creating tags. if [ ${res_create} -ne 0 ];then mail_body=“NG create ami for ${myInstanceID}(${host_name}) at ${datetime}” mail_titile=“NG_create_ami_${today}” mail_send elif [ ${res_create} -eq 0 ];then create_tag fi echo “end ami backup. ${datetime}”

## logrotate DAY=`date +%m%d` if [ $DAY = “0101” ] then OY=`date -d ‘1 year ago’ +%Y` mv $LOG $LOG.$OY find /opt/log -name “$LOG.*” -type f -atime +730 -exec rm -f {} \; fi

exit 0 ———— # chmod +x /opt/bin/aws_image_bkup.sh[/shell]

ためしにとってみる
[shell]# bash -x /opt/bin/aws_image_bkup.sh i-292a442c komiya-test-mysql01 # cat /opt/bin/cimageid_i-292a442c.log # cat /opt/log/bkup.log [/shell]

実行テストして特に問題なく動くようにはなりました。
注意点としては、
AMI_NAMEが被ると両方消えてなくなるようで、そんなことはしないとは思いますが同じ分内に2回実行しないほうがいいです。
分をまたげば普通にAMIがとれますが、
たしか実行回数が増えすぎると利用料が上がる気がしますのでデイリー等の使い方を推奨します。

古いAMIを残したい場合は、bkup_numという変数に世代数を定義してください。

複数インスタンスのAMIをとりたい場合は、以下のようにリストを作っておいてループ処理する感じです。
[shell]vi bkup-hosts.txt —- i-xxxxx,hoge-web01 i-yyyyy,hoge-web02 … —- for i in `cat bkup-hosts.txt` do instance-id=`echo $i|awk -F, ‘{print $1}’` host-name=`echo $i|awk -F, ‘{print $2}’` /opt/bin/aws_image_bkup.sh ${instance-id} ${host-name} done[/shell]

データだけとっとけば復旧はできるしバックアップ量も節約できるしAMIは変えたときだけとればいい、というパターンもあるでしょう。
オートスケーリング等のために最新のAMIとっときたいという場合には、確かに自動AMIバックアップ要りそうな気がします。
バックアップは復旧計画次第というところでしょうか。

今回スクリプト作ってて個人的に勉強になったなと思ったのは、
sed -e “N;s/\n//” 奇数行の改行を削除
のところでしょうか。他はまあ使ったことあったけど、これはキーワード検索してみたらできることを知った感じでした。
まだまだ修行がたらないようで。

・おまけ
awscliのchefレシピを載せます。

クックブックを作る
[shell]knife cookbook create aws -o site-cookbooks[/shell]

awscli.rbというレシピを作る
[shell]# vi site-cookbooks/aws/recipes/awscli.rb —————————————————– if node[“platform_version”] >= 6 package “python-setuptools” do action :install end bash “pip-awscli-install” do not_if “which aws” code «-EOC easy_install pip pip install awscli EOC end elsif node[“platform_version”] >= 5 && node[“platform_version”] < 6 bash “python26-pip-awscli-install” do not_if “which aws” code «-EOC yum -y install python26 python26-devel python26-distribute –enablerepo=epel easy_install-2.6 pip pip2.6 install awscli EOC end end

directory “/root/.ec2” do action :create end

awstest1 = Chef::EncryptedDataBagItem.load(“awskeys”,“awstest1”) awstest1key_id = awstest1[“aws_access_key_id”] awstest1secret_key = awstest1[“aws_secret_access_key”] awstest1region = awstest1[“region”]

bash “set-aws-config” do not_if “grep access_key /root/.ec2/aws.config” code «-EOC echo ‘[default]’ » /root/.ec2/aws.config echo “aws_access_key_id=#{awstest1key_id}” » /root/.ec2/aws.config echo “aws_secret_access_key=#{awstest1secret_key}” » /root/.ec2/aws.config echo “region=#{awstest1region}” » /root/.ec2/aws.config EOC end —————————————————–[/shell] アクセスキーIDとかはdata_bagsで管理すべきと思われる。

[shell]awstest1 = Chef::EncryptedDataBagItem.load(“awskeys”,“awstest1”) awstest1key_id = awstest1[“aws_access_key_id”] awstest1secret_key = awstest1[“aws_secret_access_key”] awstest1region = awstest1[“region”][/shell]

data_bagsにawsアクセスキー等を登録する

[shell]knife solo data bag create awskeys awstest1 –secret-file ~/.chef/encrypted_data_bag_secret { “id”: “awstest1”, “aws_access_key_id”: “AKIAID4CBY76********”, “aws_secret_access_key”: “4+saCCqOQL8+CKnzdHOpc2CKg2oWmKRO********”, “region”: “ap-northeast-1” }

# knife solo data bag show awskeys awstest1 –secret-file data_bag_key WARNING: The encrypted_data_bag_secret option defined in knife.rb was overriden by the command line. aws_access_key_id: AKIAID4CBY76******** aws_secret_access_key: 4+saCCqOQL8+CKnzdHOpc2CKg2oWmKR******** id: awstest1 region: ap-northeast-1[/shell]

シンタックステスト
[shell]# knife cookbook test aws[/shell]

以上、読んでいただいてありがとうございました。