この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
コンニチハ、千葉です。
CloudFormationを利用する上で、かゆいところに手が届く、それがカスタムリソースです。
例えば、EC2で最新のAMIを毎回検索してAMI IDを指定している!そんなことがあると思いますが、Lambda-backedカスタムリソースを使うと、自動で最新のAMIを検索して指定することができるようになります。これは、便利。因みに最新のAMIの取得は公式ドキュメントチュートリアル: Amazon マシンイメージ ID を参照するがありますので、これをやればすぐに対応できます。
では、早速Lambda-backedカスタムリソースを使いこなす上で必要なことを見ていきます。といっても、そんなに難しくないのでご安心を。
Lambda-backedカスタムリソースの仕組み
CloudFormation <-> Lambdaの連携です。
CloudFormationから必要なパラメータをLambdaへ渡し発火させます。そして、Lambdaで処理を行い成功/失敗のステータスをCloudFormationに返します。とてもシンプルです。
利用する上でやることは、2つです。
- CloudFormationでカスタムリソースを定義する
- Lambda関数を作成する
詳しく見てみます。
CloudFormationでカスタムリソースを定義する
---一部抜粋---
"GetServiceName": {
"Type": "Custom::GetServiceName",
"Properties": {
"ServiceToken": "arn:aws:lambda:ap-northeast-1:123456789012:function:getEcsServiceName",
"ServiceArn": "arn"
}
}
Type
:任意のタイプ名を指定します
ServiceToken
:発火させるLambdaのARNを指定します
ServiceArn
:任意のLambdaに渡したいものを指定します。変数みたいなものですね。なので名前はなんでもいいですし、複数定義しても問題無いです。今回は、ServiceArnと指定したのでLambda側でServiceArnとして受け取れます。
Lambda関数の作成
CloudFormationから呼ばれる関数を定義します。CloudFormationから値を受け取って、AWSのAPIを叩くなり、受け取った文字列を編集しCloudFormationに返したり、または最新のAMIを検索してCloudFormationに返したりと、この部分は任意の処理を実行することができます。
---一部抜粋---
exports.handler = function(event, context) {
console.log("REQUEST RECEIVED:\n" + JSON.stringify(event));
var responseStatus = "SUCCESS";
var responseData = {};
responseData["Name"] = returnServiceName(event.ResourceProperties.ServiceArn);
sendResponse(event, context, responseStatus, responseData);
};
event.ResourceProperties.ServiceArn
:CloudFormationで指定したServiceArn
の値を取得して来て処理を行っています。
あとは、sendResponseにて処理をした結果をLambdaに返します。
この例には記載していませんが、event.RequestType
で、Create、Update、Deleteという値(CloudFormationで何が実行されたか)を取得することもできますので、処理にを場合分けすることもできます。
チュートリアル
チュートリアルとして、CloudFormationにより以下を実行してみます。
- ECSのServiceを起動
- 起動されたECS Service名を取得するLambda-backedカスタムリソースを実行
- 起動したECS ServiceのCloudWatchアラームを設定
CloudWatchアラームには、ECSのサービスを名を指定する必要があるのですが、CloudFormationで作成した場合、ECS Service名にはランダムな文字列が指定されるため事前には分かりません。 なので、ECS Servuce名を取得する必要があるのですが、CloudFormationで取得できるのはECS ServiceのARNとなります。そのため、
- ECS ServiceのARNをLambdaへ渡す(arn:aws:ecs:ap-northeast-1:123456789012:service/dev-sampleWebapp-xxxxxxxxxxxx)
- Lambdaで受け取ったARNからサービス名を取得して返す(dev-sampleWebapp-xxxxxxxxxxxx)
- CloudFormation側でサービス名を受け取ってCloudWatchアラームを作成する
という流れになります。
チュートリアルのゴール
ECSサービスの起動
CloudWatchアラームの作成
ディメンションにECS ServiceNameを指定して、アラームが作成されればokです。
Lambdaサンプルコード
このコードを貼り付けてLambda関数を作成します。
var aws = require("aws-sdk");
exports.handler = function(event, context) {
console.log("REQUEST RECEIVED:\n" + JSON.stringify(event));
var responseStatus = "SUCCESS";
var responseData = {};
responseData["Name"] = returnServiceName(event.ResourceProperties.ServiceArn);
sendResponse(event, context, responseStatus, responseData);
};
// return service name from arn
function returnServiceName(ecsArn){
var str1 = ecsArn.split("/");
return str1[1];
}
// Send response to the pre-signed S3 URL
function sendResponse(event, context, responseStatus, responseData) {
var responseBody = JSON.stringify({
Status: responseStatus,
Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName,
PhysicalResourceId: context.logStreamName,
StackId: event.StackId,
RequestId: event.RequestId,
LogicalResourceId: event.LogicalResourceId,
Data: responseData
});
console.log("RESPONSE BODY:\n", responseBody);
var https = require("https");
var url = require("url");
var parsedUrl = url.parse(event.ResponseURL);
var options = {
hostname: parsedUrl.hostname,
port: 443,
path: parsedUrl.path,
method: "PUT",
headers: {
"content-type": "",
"content-length": responseBody.length
}
};
console.log("SENDING RESPONSE...\n");
var request = https.request(options, function(response) {
console.log("STATUS: " + response.statusCode);
console.log("HEADERS: " + JSON.stringify(response.headers));
// Tell AWS Lambda that the function execution is done
context.done();
});
request.on("error", function(error) {
console.log("sendResponse Error:" + error);
// Tell AWS Lambda that the function execution is done
context.done();
});
// write data to request body
request.write(responseBody);
request.end();
}
CloudFormationテンプレート
このサンプルコードでCloudFormationスタックを作成します。
{
"AWSTemplateFormatVersion" : "2010-09-09" ,
"Description" : "CloudFormation create CloudWatch Alarm",
"Resources" : {
"sampleWebapp": {
"Type": "AWS::ECS::Service",
"Properties": {
"Cluster": "default",
"DesiredCount": "1",
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:123456789012:task-definition/console-sample-app-static:1"
}
},
"GetServiceName": {
"Type": "Custom::GetServiceName",
"Properties": {
"ServiceToken": "arn:aws:lambda:ap-northeast-1:123456789012:function:getEcsServiceName",
"ServiceArn": { "Ref": "sampleWebapp" }
}
},
"CPUAlarm" : {
"Type": "AWS::CloudWatch::Alarm",
"Properties": {
"AlarmDescription": "cpu alarm for ECS Service",
"MetricName": "CPUUtilization",
"Namespace": "AWS/ECS",
"Statistic": "Average",
"Period": "300",
"EvaluationPeriods": "1",
"Threshold": "90",
"ComparisonOperator": "GreaterThanThreshold",
"Dimensions": [
{
"Name": "ServiceName",
"Value": { "Fn::GetAtt": [ "GetServiceName", "Name" ]}
},
{
"Name": "ClusterName",
"Value": "default"
}
]
}
}
}
}
最後に
Lambda-backedカスタムリソースは、ここぞというときに柔軟に対応できます。CloudFormationを使っていて、ある処理をなんとか自動化できないか?という、かゆいところに手が届く機能だと思います。こんな機能もあるんだー!と思ってもらえると嬉しいです。