目的
EC2と一緒に作成されるEBSにはタグ付けが自動で行えないので基本何もタグがつかないEBSが大量にできることになる。するとボリュームの一覧を見た時に何処にアタッチされてるボリュームなのかわからないくて不便。
何より個人的に嫌なのは、Cost Allocation Tagによる請求レポート分析から漏れて案件毎の請求額の把握が面倒なことになるのが嫌(プロジェクト単位でアカウント分けるてのもありだけどConsolidatedBillingて増やせるとはいえ20アカウント上限があるしAWSクレジットが適用されると請求書がゼロとかにされて実態把握が面倒だし結局BillingReportの詳細CSVから自分の好みに応じて集計するのが痒いところに手が届いて気持ち良いのよねー)なので。
だから、タグは可能な限り漏れ無く付けておきたい→自動化したい。
要件
以下の理由により各EC2インスタンス自身で付けさせるのはやりたくない。
- 絶対設定漏れがでる。(userdataで必ずこれ実行すること!とかルール決めてもどうせ忘れる)
- インスタンス起動後に別途EBSをアタッチした場合にそいつに付けるタイミングがないのでやっぱ漏れる。
- EC2には第三者(発注元や外注先など)もログインorコードデプロイ可能なので、与えるIAM権限は最小にしたい。(特定インスタンス(リソースIDじゃなく自身のインスタンスに対してとかファジーな感じ)や特定タグに対してのみに絞って権限を与えるということが出来ればいいが、そこまで細かい制御をIAMでは出来ないので)
実装
要件を踏まえて、管理用のインスタンスのcron実行により、タグ無し(or EC2と不一致)EBSを見つけて勝手にタグを付け直す形で実装してみました。
IAM設定
まず管理用インスタンスのIAMRole(or IAMUser)に最低限以下の様なポリシーを追加しておます。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:CreateTags",
"ec2:DescribeInstances",
"ec2:DescribeRegions",
"ec2:DescribeVolumes"
],
"Resource": "*"
}
]
}
スクリプト
で、管理用インスタンス上で以下のスクリプトをcronで実行しとけば大体よい。
# !/bin/bash
volumeNamesByInstances() {
region="$1"
aws ec2 describe-instances --region "$region" --no-paginate --filters Name=tag-key,Values=Name |
jq -r '.Reservations[].Instances[]|"\(.BlockDeviceMappings[].Ebs.VolumeId) \(.Tags[]|select(.Key=="Name").Value)"' |
sort
}
volumeNamesByVolumes() {
region="$1"
aws ec2 describe-volumes --region "$region" --no-paginate |
jq -r '.Volumes[]|"\(.VolumeId) \(.Tags[]?|select(.Key=="Name").Value)"' |
sort
}
regions() {
az=$(curl -sL http://169.254.169.254/latest/meta-data/placement/availability-zone)
region="${az%[a-z]}"
aws ec2 describe-regions --region "$region" | jq -r '.Regions[].RegionName'
}
for region in $(regions); do
# EC2とEBSでタグの値に差異のある部分だけを更新する
diff <(volumeNamesByVolumes "$region") <(volumeNamesByInstances "$region") | grep '^> ' |
while read _ volumeId name; do
[[ $name =~ ^- ]] && continue # skip dangerous
aws ec2 create-tags --region "$region" --resources "$volumeId" --tags "Key=Name,Value=$name"
done
done
実行間隔について
cronの実行間隔は、AWSの課金が1時間単位なのでその前にはタグを付けておきたいがそんなに急がなくてもよい、逆に短すぎてもAWSのAPIサーバに負荷かけると悪いかなーって気持ちと、そもそも現実問題としてdescribe系のAPIの結果が実は数分くらいキャッシュされてるらしくそれが原因でEC2側のタグを変えてもdescribeのレスポンスは暫く変わらないこともよくあるので頻繁にチェックする意味が無いので、僕は15分間間隔くらいで実行させている。
おまけ
僕の要件には合わないけど、EC2自身にタグ付けさせたい人にはクラスメソッドさんのこの記事が参考になると思う。>EC2インスタンスのEBSルートボリュームに自動でNameタグを付与する | Developers.IO