AWS CloudFormationのSNS-backedカスタムリソースを利用する

カスタムリソース

CloudFormationカスタムリソースは、テンプレートの中だけでは記述できないリソースタイプなどを外出しして記述する方法です。ユーザーがスタックを作成、更新、削除するたびに AWS CloudFormation がそれを実行します。

Untitled (2)

今回は、Amazon SNSを使って外部のリソースと連携してみたいと思います。

Amazon SNS-backedカスタムリソース

まず始めにCloudFormationテンプレートを作成したいと思います。カスタムリソース指定の部分に注目してください。このテンプレートは、Custom::HelloWorld型のリソースであるMyCustomLogicを定義しています。このカスタムリソースは、SNSに通知をしますので、このテンプレート内でSNSを作成しています。そして、後からポーリングして取り出せるように、SNSのサブスクライバとしてSQSを指定しています。

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Resources" : {
    "MyCustomLogic" : {
      "Type": "Custom::HelloWorld",
      "Version" : "1.0",
      "Properties" : {
        "ServiceToken": {"Ref" : "MySNSTopic"}
      }
    },
	  "MySNSTopic":{
	    "Type":"AWS::SNS::Topic",
	    "Properties":{
	      "Subscription":[{
	            "Endpoint":{"Fn::GetAtt":["MyQueue","Arn"]},
	            "Protocol":"sqs"
	      }] 
	    }
	  },
		"MyQueue":{
		  "Type":"AWS::SQS::Queue"
		},
    "MyQueuePolicy":{
      "Type":"AWS::SQS::QueuePolicy",
      "Properties":{        
        "PolicyDocument":{
          "Version":"2012-10-17",
          "Id":"MyQueuePolicy",
          "Statement":[
            {
              "Sid":"Allow-SendMessage-To-Queues-From-SNS-Topic",
	      "Effect":"Allow",           
	      "Principal":"*",
 	      "Action":["sqs:SendMessage"],
	      "Resource":"*",
              "Condition":{
                "ArnEquals":{
                  "aws:SourceArn":{"Ref":"MySNSTopic"}
                }
              }
            }
          ]
        },
        "Queues":[{"Ref":"MyQueue"}]
      }
    }
  }
}

リソースプロバイダーが受け取るメッセージ

CloudFormation内のテンプレートデベロッパーから送られてきたメッセージは、カスタムリソースプロバイダーで処理されて応答メッセージを返すことになります。ここでは、通知メッセージのボディを見てみたいと思います。Messageブロックには、応答に必要な各種ID情報などが含まれています。

{
  "Type" : "Notification",
  "MessageId" : "cd211b4f-1dc7-557e-99a1-5553926141df",
  "TopicArn" : "arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:CustomHello6-MySNSTopic-1W0F4LGK7AVL2",
  "Subject" : "AWS CloudFormation custom resource request",
  "Message" : "{
    \"RequestType\":\"Create\",
    \"ServiceToken\":\"arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:CustomHello6-MySNSTopic-1W0F4LGK7AVL2\",
    \"ResponseURL\":\"https://cloudformation-custom-resource-response-apnortheast1.s3-ap-northeast-1.amazonaws.com/arn%3Aaws%3Acloudformation%3Aap-northeast-1%XXXXXXXXXXXX%3Astack/CustomHello6/0b8ba5b0-28ae-11e5-8e5c-50fa59279ce0%7CMyCustomLogic%7C27bb6dbe-557a-40f9-8793-85a25a908d71
    \"StackId\":\"arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/CustomHello6/0b8ba5b0-28ae-11e5-8e5c-50fa59279ce0\",
    \"RequestId\":\"27bb6dbe-557a-40f9-8793-85a25a908d71\",
    \"LogicalResourceId\":\"MyCustomLogic\",
    \"ResourceType\":\"Custom::HelloWorld\",
    \"ResourceProperties\":{\"ServiceToken\":\"arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:CustomHello6-MySNSTopic-1W0F4LGK7AVL2\"}}",
  "Timestamp" : "2015-07-12T15:53:10.020Z",
  "SignatureVersion" : "1",
  "Signature" : "Lgtvyeg+sDWbTxHGJrWUNeB1UikS7eMOy2ml+pUhGBAYbyLkpDf7p2mn/06oapDsyV2qIX9y66CI6UuWfsAOTW/+BEjVpJpdx1kux+wuu9165p0MTq0o6c4o5qTaWSOX/IKn0DSLukNKd+5WDSNWwKH3hMs4W0aU7rKh9y5PLep2FtK3wXxFyHWBh4Rsy8Fk6UCDz+HK+K0AORxaRamVL4d4j/vZDXplAw6HbhSaasUnkakzRdllnISiZRd3iiwaaBny3c6M012t8MCVDfq8zUS/JoXRzFXoGmAr9PcExvHUuq+Dy1h/aCOlWHEZu8TmDF2zYyjf7oSkzR0gC2MA1A==",
  "SigningCertURL" : "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-d6d679a1d18e95c2f9ffcf11f4f9e198.pem",
  "UnsubscribeURL" : "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:CustomHello6-MySNSTopic-1W0F4LGK7AVL2:d6dec38a-a5f7-4cd8-a419-aa3cad7b1cd1"
}

処理成功の応答をする。

カスタムリソースプロバイダー側の処理が成功したと仮定して、応答を返したいと思います。本来であれば、プログラミング等を結果を返すことになりますが、今回はわかりやすさを優先して、curlコマンドでメッセージを返します。以下は、応答メッセージのボディです。これをresult.jsonという名前で保存しました。

{
  "Status" : "SUCCESS",
	"PhysicalResourceId" : "MyCustomLogic",
	"StackId" : "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/CustomHello6/0b8ba5b0-28ae-11e5-8e5c-50fa59279ce0",
	"RequestId" : "27bb6dbe-557a-40f9-8793-85a25a908d71",
	"LogicalResourceId" : "MyCustomLogic"
}

次に、curlコマンドでS3にPUTします。ここの記述方法でかなりハマりました。HTTPステータスコードが200番になっていることを確認してください。

$ curl -v -X PUT -H 'User-Agent: ' -H 'Content-Type: ' -T body.json "https://cloudformation-custom-resource-response-apnortheast1.s3-ap-northeast-1.amazonaws.com/arn%3Aaws%3Acloudformation%3Aap-northeast-1%3AXXXXXXXXXXXX%3Astack/CustomHello6/0b8ba5b0-28ae-11e5-8e5c-50fa59279ce0%7CMyCustomLogic%7C27bb6dbe-557a-40f9-8793-85a25a908d71

> PUT /arn%3Aaws%3Acloudformation%3Aap-northeast-1%3AXXXXXXXXXXXX%3Astack/CustomHello6/0b8ba5b0-28ae-11e5-8e5c-50fa59279ce0%7CMyCustomLogic%7C27bb6dbe-557a-40f9-8793-85a25a908d71 HTTP/1.1
> Host: cloudformation-custom-resource-response-apnortheast1.s3-ap-northeast-1.amazonaws.com
> Accept: */*
> Content-Length: 285
> Expect: 100-continue
> 
< HTTP/1.1 100 Continue
* We are completely uploaded and fine
< HTTP/1.1 200 OK
< x-amz-id-2: IcIFg/AdjYZJ89C/f8YrHjPDTXDvKxS1K3oHtIi4w6lPHkmCed8pVdKoIuSAZ/x7F8oQ6PYmONo=
< x-amz-request-id: 3CCDF82395162625
< Date: Sun, 12 Jul 2015 15:54:41 GMT
< ETag: "60b76cabf261d140b4dbc32a5873973b"
< Content-Length: 0
< Server: AmazonS3
< 
* Connection #0 to host cloudformation-custom-resource-response-apnortheast1.s3-ap-northeast-1.amazonaws.com left intact

スタック完了を確認

最後に、CloudFormationが完了したか確認を行います。Custom::HelloWorldのステータスがCREATE_COMPLETEになっていればOKです!

screenshot 2015-07-13 0.55.13

まとめ

今回は、CloudFormationの作成/更新/削除といったスタック操作に合わせて実行する、カスタムリソースについて学びました。環境構築に合わせたテストの実施や、あらかじめCloudFormationで用意されていない処理の実行など、応用範囲はかなり広いと思いました。当初、SNS連携しか用意されていませんでしたが、最近、Lambda対応したことにより、カスタムリソースを使いやすくなりましたので、今後活用が進むと思います。さらに、Service CatalogというCloudFormationを管理するサービスも出てきましたので、CloudFormationから目が離せませんね!

参考資料

Amazon Simple Notification Service-backed カスタムリソース

ws/aws-cfn-resource-bridge

awslabs/aws-cfn-custom-resource-examples

TheNeatCompany/cfn-bridge

【新機能】AWS CloudFormationのLambda-Backed Custom Resourcesを使ってBlue-Green Deploymentをより簡単に実現する