殿堂入り記事

AWSで構築した環境にありがちなシェルスクリプトたち まとめ

2015.03.10

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

AWSでサーバを運用する際にはEC2からAWS CLIを使って他のAWSのサービスと連携したりすることがあると思いますが、AWS環境ならではのシェルスクリプトを集めてみました。AWS CLIのバージョンは1.7.13、Pythonのバージョンは2.6.9を使っています。私はAmazon Linuxで動作を確認しています。

目次

準備する

AWS CLIのインストール

Amazon Linux以外のOSの場合はAWS CLIをインストールする必要があります。AWS CLIはPythonで実装されているので、まずはPythonをインストールします。Pythonのインストール方法はOSによって違うと思いますので割愛させていただきます。Pythonをインストールしたら以下のコマンドでインストールできます。

$ sudo easy_install pip
$ sudo pip install awscli

Macの場合は以下の記事が参考になると思います。

【初心者向け】MacユーザがAWS CLIを最速で試す方法

bashを使っている場合はaws_completerを使うとサブコマンドのオートコンプリートされるようになります。Amazon Linuxの場合はこのコマンドを実行しなくてもオートコンプリートが有効になっているようです。

$ complete -C aws_completer aws

AWS CLIのアップデート

AWS CLIをアップデートしたい場合は以下のコマンドを実行します。

$ sudo pip install -U awscli

aws configureでセットアップする

インストールができたらaws configureでアクセスキー、シークレットアクセスキー、リージョン、出力形式を対話形式で設定します。 アクセスキーとシークレットアクセスキーはIAM Roleを選択する場合は入力する必要はありません。リージョンは東京リージョン(ap-northeast-1)にしています。ここでセットアップすればコマンドのオプションで指定する必要はありません。出力形式(output format)はtext、json、table 3種類あります。ここで紹介しているシェルを実行するにはtextを選択する必要があります。 このコマンドを実行すると、~/.aws/ にconfigという名前のファイルが出力されます。

$ aws configure
AWS Access Key ID [None]: XXXX
AWS Secret Access Key [None]: XXXXXXXX
Default region name [None]: ap-northeast-1
Default output format [None]: text

IAM roles for EC2 instancesに関して

EC2上でAWS CLIを実行する場合はEC2にIAM Roleを付与すればアクセスキー、シークレットアクセスキーを設定する必要がなくなります。

IAM roles for EC2 instancesを使ってみる

監視系

CloudWatchでカスタムメトリクスを設定する

CloudWatchにLoad Average、Stealなどの項目を追加したい場合はカスタムメトリクスを使います。
メモリ使用率やディスク使用量はAWSのサイトにスクリプトがありますので、そちらをご利用ください。
Amazon CloudWatch Monitoring Scripts for Linux

#!/bin/bash
 
# instance id の取得
INSTANCE_ID=`wget -q -O - http://169.254.169.254/latest/meta-data/instance-id`
 
LOAD_AVERAGE1=`uptime | tr -s ' ' | cut -d ' ' -f 9 | cut -d ',' -f 1`
#LOAD_AVERAGE5=`uptime | tr -s ' ' | cut -d ' ' -f 9 | cut -d ',' -f 2`
#LOAD_AVERAGE15=`uptime | tr -s ' ' | cut -d ' ' -f 9 | cut -d ',' -f 3`
STEAL=`vmstat | tail -1 | tr -s ' ' | cut -d ' ' -f 18`
 
aws cloudwatch put-metric-data --metric-name LoadAverage --namespace System/Linux --value ${LOAD_AVERAGE1} --unit Count  --dimensions "InstanceId=${INSTANCE_ID}"
aws cloudwatch put-metric-data --metric-name Steal --namespace System/Linux --value ${STEAL} --unit Percent  --dimensions "InstanceId=${INSTANCE_ID}"

当ブログにはカスタムメトリクスの記事が他にもいくつかありますのでこちらもご覧ください。

CloudWatchのカスタムメトリクスでJavaVMのGC関連情報を取得
CloudWatchのカスタムメトリクスでFreeMemoryMBytes、UsedMemoryPercent、LoadAverage、Stealを取得
Tomcatの使用メモリ量をCloudWatchで表示する

ZabbixからCloudWatchの値を取得する

システムを監視するためにZabbixを利用することはあると思いますが、ZabbixにはCloudWatchの値を見る機能がありません。 EC2はエージェントを使って値をとることはできるのですが、RDSやELBなどフルマネージドなリソースを監視する場合は以下のようにCloudWatchの値を取得するスクリプトを書く必要があります。 こちらのスクリプトは5分間隔で実行される前提です。

cloudwatch.sh

#!/bin/sh

while getopts n:r:d:m:s: OPT
do
  case ${OPT} in
    n) NAMESPACE=${OPTARG} ;;
    d) DIMENSIONS=${OPTARG} ;;
    m) METRIC=${OPTARG} ;;
    s) STATISTICS=${OPTARG} ;;
    *) exit 1 ;;
  esac
done

aws cloudwatch get-metric-statistics \
  --namespace ${NAMESPACE} \
  --dimensions ${DIMENSIONS} \
  --metric-name ${METRIC} \
  --statistics ${STATISTICS} \
  --start-time `date -u -d '5 minutes ago' +%Y-%m-%dT%TZ` \
  --end-time `date -u +%Y-%m-%dT%TZ` \
  --period 300 | sort -k 3,3 | tail -n 1 | awk '{print $2}'

実装したシェルはZabbixの外部チェックで呼び出すのですが、何を監視するかは引数で渡します。2つほど例を示します。値が標準出力に表示されればOKです。

RDSのCPU使用率の平均値を見たい場合

  • 名前空間:AWS/RDS
  • メトリクス名:CPUUtilization
  • ディメンジョン名と値:Name=DBInstanceIdentifier,Value=[インスタンス名]
  • 値の算出方法:Average
$ ./cloudwatch.sh -n AWS/RDS -d Name=DBInstanceIdentifier,Value=mydbinstance -m CPUUtilization -s Average

ELBのリクエスト数の合計を見たい場合

  • 名前空間:AWS/ELB
  • メトリクス名:RequestCount
  • ディメンジョン名と値:Name=LoadBalancerName,Value=[ロードバランサー名]
  • 値の算出方法:Sum
$ ./cloudwatch.sh -n AWS/ELB -d Name=LoadBalancerName,Value=[ロードバランサー名] -m RequestCount -s Sum

実装したスクリプトをZabbixに設定する方法などは以下のページが参考になると思います。
ZabbixでAWS/CloudWatchの値を取得してみた

名前空間と取得できるメトリクスの一覧などは以下をご覧ください。
Amazon CloudWatch の名前空間、ディメンション、メトリックスのリファレンス

プロセス監視する

CloudWatchではカスタムメトリクスを使うことにより、プロセス監視することも可能です。Apacheのプロセスを監視するスクリプトが以下になります。プロセスがない場合はProcessMonitoring が0になるので0の場合はアラームを送信するように設定します。

#!/bin/sh

if ! (ps ax | grep -v grep | grep -q httpd); then
  VALUE=0
  sudo service httpd start
else
  VALUE=1
fi

aws cloudwatch put-metric-data --metric-name ProcessMonitoring --namespace Processes --value ${VALUE} --dimensions "Processes=httpd"

プロセス監視の間隔を1分ごとにしたい場合、カスタムメトリクスを使うと有料になってしまいます。
Amazon CloudWatch 料金

コストの面でカスタムメトリクスを使いたくない場合はSNSを使って通知する方法もあります。

#!/bin/sh
ACCOUNT="[アカウント]"
TOPIC="[トピック]"
MESSAGE="[メッセージ]"

if ! (ps ax | grep -v grep | grep -q httpd); then
  sudo service httpd start
  aws sns publish --topic-arn arn:aws:sns:ap-northeast-1:${ACCOUNT}:${TOPIC} --message ${MESSAGE}
fi

バックアップ系

次はバックアップ系になります。バックアップ処理の流れとしては以下のようになります。バックアップ対象の判定やバックアップの削除判定にはタグを利用しています。バックアップしたいEBSボリュームやEC2インスタンスにBackupという名前のタグを追加して値をtrueにして下さい。

1.バックアップの作成
1-1.バックアップを作成したいリソース一覧を取得する
1-2.タグを見てバックアップ対象かどうかチェックする
1-3.バックアップを作成する
1-4.検索に使うタグを作成する
2.古いバックアップの削除
2-1.タグを条件にして削除可能なバックアップを検索する
2-2.タグを見て削除対象か判定を行い、対象であればバックアップを削除する

AMIとEBSのバックアップを作成する

AMIのEBSとバックアップを作成するスクリプトです。バックアップを取得する目的としてはそれぞれ以下のような目的になります。

AMIのバックアップ
インスタンスの障害時やAZ障害時に別AZでEC2を起動したい場合に使います。複数EBSの構成情報も含まれます
EBSのスナップショット
データバックアップが目的の場合に使います。

AMIのバックアップの取得は以下になります。create-imageコマンドではno-rebootオプションをつけて再起動しないようにしていますが、no-rebootの場合は整合性は保証されませんので注意して下さい。

AMIのバックアップの取得

#!/bin/sh
DATE_CURRENT=`date +%Y-%m-%d`
TIME_CURRENT=`date +%Y%m%d%H%M%S`
PURGE_AFTER_DAYS=10
PURGE_AFTER=`date -d +${PURGE_AFTER_DAYS}days -u +%Y-%m-%d`

# 1-1.バックアップを作成したいリソース一覧を取得する
INSTANCES=`aws ec2 describe-tags --filters "Name=resource-type,Values=instance" "Name=key,Values=Backup" | awk '{print $3}'`

for INSTANCE in ${INSTANCES}; do

  BACKUP=`aws ec2 describe-tags --filters "Name=resource-type,Values=instance" "Name=resource-id,Values=${INSTANCE}" "Name=key,Values=Backup" | awk '{print $5}'`

  # 1-2.タグを見てバックアップ対象かどうかチェックする
  if [ "${BACKUP}" == "true" ]; then

    # 1-3.バックアップを作成する
    AMI_ID=`aws ec2 create-image --instance-id ${INSTANCE} --name "${INSTANCE}_${TIME_CURRENT}" --no-reboot`

    # 1-4.検索に使うタグを作成する
    aws ec2 create-tags --resources ${AMI_ID} --tags Key=PurgeAllow,Value=true Key=PurgeAfter,Value=$PURGE_AFTER
  fi
done

# 2-1.タグを条件にして削除可能なバックアップを検索する
AMI_PURGE_ALLOWED=`aws ec2 describe-tags --filters "Name=resource-type,Values=image" "Name=key,Values=PurgeAllow" | awk '{print $3}'`

for AMI_ID in ${AMI_PURGE_ALLOWED}; do
  PURGE_AFTER_DATE=`aws ec2 describe-tags --filters "Name=resource-type,Values=image" "Name=resource-id,Values=${AMI_ID}" "Name=key,Values=PurgeAfter"  | awk '{print $5}'`

  if [ -n ${PURGE_AFTER_DATE} ]; then
    DATE_CURRENT_EPOCH=`date -d ${DATE_CURRENT} +%s`
    PURGE_AFTER_DATE_EPOCH=`date -d ${PURGE_AFTER_DATE} +%s`

    if [[ ${PURGE_AFTER_DATE_EPOCH} < ${DATE_CURRENT_EPOCH} ]]; then
      # 2-2.タグを見て削除対象か判定を行い、対象であればバックアップを削除する
      aws ec2 deregister-image --image-id ${AMI_ID}
      
      SNAPSHOT_ID=`aws ec2 describe-images --image-ids ${AMI_ID} | grep EBS | awk '{print $4}'`
      aws ec2 delete-snapshot --snapshot-id ${SNAPSHOT_ID}
    fi
  fi
done
[/shell]

<h4>EBSのスナップショット</h4>


<h3 id="rds-snapshot">RDSのスナップショットを作成する</h3>
<p>
続いてRDSのスナップショットを取得するスクリプトです。RDSの場合は設定したバックアップ保持期間内であれば自動で取得してくれています。設定できる保持期限は最大で35日前のものになります。<br/>
</p>



<h3 id="s3-cleanup">S3のフォルダをクリーンアップする</h3>
<p>
S3のフォルダをクリーンアップするシェルスクリプトです。YYYY-MM-DDなど日付がフォルダ名になっている場合に使えます。
</p>

#!/bin/sh
BUCKET='[バケット名]'
PURGE_AFTER_DAYS=10
PURGE_AFTER=`date +'%Y-%m-%d' --date "${PURGE_AFTER_DAYS} days ago"`

TARGET="s3://${BUCKET}/${PURGE_AFTER}/"
aws s3 rm ${TARGET} --recursive

この例では10日間でフォルダを削除していますが、一定の期間更新されなかったファイルを削除したり、Glacierに移動するだけでしたらS3のLifecycle設定でできますのでそちらをお勧めします。

Amazon S3でオブジェクトの有効期限を設定できるようになりました
Amazon S3のオブジェクト移行・削除タイミングを調べてみた
Amazon S3のGlacierアーカイブ機能を活用する

便利スクリプト系

Route53の自動登録

開発環境のEC2を利用していない時間に停止すると、起動した際にIPアドレスが変わってしまいます。その都度、IPアドレスを調べるのが面倒な場合は起動時にIPアドレスをRoute53に自動登録するスクリプトを実行すると常に同じドメイン名でアクセスすることができるようになります。

/etc/init.d/route53-register.sh

#!/bin/bash
DOMAIN_NAME=“[ドメイン名]”
HOST_NAME="[ホスト名]"
IP_ADDRESS=`curl -s http://169.254.169.254/latest/meta-data/public-ipv4`

HOSTED_ZONE_ID=`aws route53 list-hosted-zones | grep ${DOMAIN_NAME}  | awk '{print $3}' | sed -e 's/\/hostedzone\///'`
BATCH_JSON='{
  "Changes": [
    { "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "'${HOST_NAME}'.'${DOMAIN_NAME}'",
        "Type": "A",
        "TTL" : 60,
        "ResourceRecords": [
          { "Value": "${IP_ADDRESS}" }
        ]
      }
    }
  ]
}'

aws route53 change-resource-record-sets --hosted-zone-id ${HOSTED_ZONE_ID}  --change-batch "${BATCH_JSON}"

実装できたら起動するスクリプトに登録します。

$ sudo chkconfig --add route53-register
$ sudo chkconfig route53-register on

以下の記事を参考にしました。
Jenkinsを導入したEC2インスタンスに常に同じ名前でアクセスする

その他

EC2インスタンスを起動、停止する

バッチ処理などに利用されるEC2インスタンスを特定の曜日、時間帯のみ起動させたい、ということがあると思います。 起動と停止だけであればcrontabコマンドで以下のように指定すれば可能です。

0 7 * * 1-5 aws ec2 start-instances --instance-ids=i-xxxxxxxx
0 22 * * 1-5 aws ec2 stop-instances --instance-ids=i-xxxxxxxx

実装したらやること

作成したシェルスクリプトを cron で実行します。まずは実装したシェルに実行権限を付与します。

$ chmod +x test.sh

次にcrontabコマンドで登録します。

$ crontab -e
0 12 * * * /usr/local/bin/ebs-backup.sh
0 15 * * * /usr/local/bin/custom-metrics.sh

最後に

自分はシェルスクリプトがあまり得意ではないので文法から調べながら記事を書いていたので、書き方とかあまりうまくないところがあるかもしれませんが勉強してリファクタリングする予定です。私はまだAWSの全部のサービスを使ったことがないので、まだ”あるある”なスクリプトがあると思います。見つけ次第どんどん追加していきたいです!

リンク集

シェルスクリプトではないのですが、便利そうなRubyスクリプトがあったのでリンクをまとめておきます。

5秒でAmazon Linuxを起動するRubyスクリプト書いた
5秒でAmazon Linuxを起動して1日で殺すRubyスクリプトを書いた
新規アカウントでもこれ一発!CloudTrailを全リージョンで有効化するスクリプトを書いた