tl;dr
最新 Amazon Linux AMI の情報を返す Lambda Function を作ったけど死ぬほど遅かったので、その情報を S3 に置くことにして、さらに全リージョン分その処理を行う Function も作った。
ソースはここ。
どうぞご利用ください
$ curl https://s3.amazonaws.com/latest-ami/.json
S3 じゃねーか!
はい。要は S3 に JSON を置いてるだけです。その JSON を自動で更新する仕組みを作ったという話です。理由は後述します。
これを書いてる時点で情報が取得できるリージョンは
です。
$ curl -s https://s3.amazonaws.com/latest-ami/us-west-2.json | jq { "Region": "us-west-2", "RetrievedAt": "2016-06-29T20:01:02.197Z", "LatestImage": { "ImageId": "ami-7172b611", "ImageLocation": "amazon/amzn-ami-hvm-2016.03.3.x86_64-gp2", "State": "available", "OwnerId": "137112412989", "CreationDate": "2016-06-22T09:19:44.000Z", "Public": true, "ProductCodes": [], "Architecture": "x86_64", "ImageType": "machine", "SriovNetSupport": "simple", "EnaSupport": true, "ImageOwnerAlias": "amazon", "Name": "amzn-ami-hvm-2016.03.3.x86_64-gp2", "Description": "Amazon Linux AMI 2016.03.3 x86_64 HVM GP2", "RootDeviceType": "ebs", "RootDeviceName": "/dev/xvda", "BlockDeviceMappings": [ { "DeviceName": "/dev/xvda", "Ebs": { "SnapshotId": "snap-d465048a", "VolumeSize": 8, "DeleteOnTermination": true, "VolumeType": "gp2", "Encrypted": false } } ], "VirtualizationType": "hvm", "Tags": [], "Hypervisor": "xen" } }
こんな感じのデータが返ってきます。
使用例
$ aws ec2 run-instances --region $AWS_REGION --image-id `curl -s https://s3.amazonaws.com/latest-ami/${AWS_REGION}.json | jq -r '.LatestImage.ImageId'`
きっかけから実装まで
動機
EC2 Management Console に入って、 Launch って押すと一番上に Amazon Linux AMI が出てくるじゃないですか。
GUI だとこんなに目立つ位置にあるのに、 SDK や CLI から使おうとすると、最新版 Amazon Linux の AMI ID を簡単に取る方法が意外にないんですよね。
Linux AMI の検索 – AWS CLI を使用した AMI の検索を読んでも
コマンドラインパラメータを使用して、関心のある AMI のタイプのみを表示できます。
お客様または Amazon が所有しているパブリックな AMI を検索できます。
ニーズに合った AMI を見つけたら、その ID を書き留めます。
と、ピンポイントで見つける方法は書かれておらず。
じゃあ作ってみるか、と。
DescribeImages
の遅さ
最初は Lambda で DescribeImages
して、 API Gateway を前段に置けば終わりじゃんwwって思ってたんですよ。
で、 Function を作りました。
しかし、動かしてみると、 DescribeImages
があまりにも遅い。40秒とか50秒とか普通に掛かってしまいます。
星の数ほどある AMI から検索するわけで、仕方ないっちゃ仕方ないんだけれども、これでは遅すぎて API 的に使うことはできません。
そうだ、 S3 に置こう
そこでみんな大好き S3 です。
S3 に置く版 を作りました。変更点の少なさよ。
latest-ami
というバケットを作ってそこに出力することにしました。ヴァージニアだと URL も短くていい感じ!
この Function を定期的に叩く CloudWatch Event を作れば一応それっぽくなるのですが、
- input のリージョン名をそれぞれ変えて、 Event Target をリージョン数だけ登録する必要がある
- 新しいリージョンがリリースされたときに手動で Event Target を追加する必要がある
という点が微妙です。そこで、
全リージョンぶん回し
Lambda Function から別の Lambda Fucntion を起動するときは invoke を使いますが、この時のポイントは InvocationType
プロパティです。
ざっくり言うと Function A から Function B を呼ぶとき、
1.A で B の結果を受け取る必要がある → InvocationType: 'RequestResponse'
2.A で B の結果を受け取る必要がない → InvocationType: 'Event'
を指定します。 InvocationType: 'Event'
を指定した場合、 A は B を起動したあと、 B の終了を待つことなく終了します。
今回の構図に当てはめると、
- A:
DescribeRegions
して B を呼ぶだけ(ただしリージョンの数だけ呼ぶ) - B:
DescribeImages
する必要があるので死ぬほど遅い
ということで 2. がぴったりです。
CloudWatch Event に登録する Event Target はこの Function だけでよくなり、新リージョンのリリース時も対応不要になりました。めでたしめでたし。
現在、この Function を6時間ごとに起動するように設定しています。
雑感
- 当然ながら定期実行による半静半動な API もどきなので、 CloudWatch Event と Lambda が確実に動いているとしても一定時間の遅延はある。しかしながら Amazon Linux という数ヶ月に1回更新される程度のものに限定しているので、これでもそこそこ実用的なのではないか
- 今回は全リージョンぶん回しする Function に呼び出し先の Function を決め打ちしているけど、ここも
event
から参照するようにすると 全リージョンでほげほげする系 で使い回しできそう- 汎用的にリージョンぶん回すやつを作りました。y13i/lambda-iterate-regions
- Lambda Function から Lambda Function を呼ぶパターンはまだまだ遊び甲斐がありそうだ
- なんだかんだ言って S3 は庶民の味方だよなあ