はじめに
みなさんは CloudFormation はお好きでしょうか?私は以前のエントリで以下のように書いていました。
CloudFormation、便利ですよね。インフラのコード化はインフラの状態の変化を可視化できるというたいへん素晴らしいメリットがあります。くわえてCloudFormationはスタック単位で作成・削除ができるため、WEBコンソールから手動で作成・削除するときに起こりうる「不要なリソースが残ったまま」や「必要なリソースを削除してしまう」というリスクが減ることで心理的・金銭的なコストが軽減される点がわたしは気に入っています。
cfn-initはUbuntuのどの起動システムにも対応しているのか?(其の一) | QUARTETCOM TECH BLOG
この思いに嘘偽りないのですが、実はだからといって CloudFormation が好きなわけではありません。むしろ「めんどくさいなー」と思っています。 いろいろ好きになれない理由はあるのですが、一番は「目的のリソースを構築しようとするときにどう書いていいかわかりづらい」という「根本的なところやないかい」というものです。
新しく触れるリソースを作るのにいきなり CloudFormation に手を出す方はいないと思います(いらっしゃったらごめんなさい)。私はまずはWebコンソールからゴニョゴニョしてリソースの設定やリソース間の関係性などをなんとなく把握してから CloudFormation のテンプレートに立ち向かっていました。この際ドキュメントを読むのですが「こんな名前の(設定|リソース)さっきは出てこなかったんですけどー」と思うことが多々あります。
これがつらい、私は。
特に日本語ドキュメントがこなれていないこともあり、四六時中 CloudFormation を触っているわけではない人間からすると脳みそ汗かきまくりなんですよね。
で、先日久しぶりに CloudFormation 使って作業することがあって、また動悸が始まったのですが、自分なりに工夫して「少し好きになれるかも」となったのでその方法を書いてみたいと思います。
要件
例として以下のような要件の環境を作ります。
作成する主なリソース
- ECS
- SQS
- CloudWatch Alarms
- CloudWatch Logs
動作
- SQS キューにメッセージがエンキューされる
- CloudWatch Alarm がアラーム状態になる
- アラームの状態によって ECS のサービスタスク数が増減する
- ECS のサービスは現在時刻を標準出力に echo するだけ。これを CloudWatch Logs に出力する
AWS Organizations でメンバーアカウントを作成
AWS Organizations は 公式サイト にあるように 複数の AWS アカウントへの一括請求が行えたり、ポリシーを集中管理できたりと何かと便利です。
各種リソースを仮に作る場合はまっさらなアカウントで試したいものです。そのために新たなアカウントを作るたびに請求情報を登録するのも面倒なのでこちらを利用します。
ここでは以下のメンバーアカウントを作っておきます。
naoyes001
: Webコンソールでリソースが作成されるメンバーアカウントnaoyes002
: CloudFormation でリソースが作成されるメンバーアカウント
Webコンソールでリソース作成
まずは メンバーアカウント naoyes001
にログインしてWebコンソールでリソースを作ります。
ECSからクラスターを選んで…という細かい手順は今回は割愛します。なお、名前をつけられるリソースについては基本的に test-***
という接頭辞をつけるようにしました。
たとえば以下は作成した CloudWatch Alarm test-alarm
を利用して、 ECS Service に Auto Scaling の設定するところです。こんな感じでポチポチやっていきます。
各種リソースを作成した後、Webコンソールから SQS のキューにメッセージを追加すると、やがて ECS のサービス必要タスク数が増えて所望のタスクが稼働しました。 リソースは問題なく作られたようです。
CloudFormer
手本となるべきリソースは完成しました。これを基に CloudFormation の定義を構築していきます。
まずは CloudFormer を利用したいと思います。CloudFormer とは、以下の引用にあるとおりのツールです。
CloudFormer は、アカウントに既に存在する AWS リソースから AWS CloudFormation テンプレートを作成するテンプレート作成用のベータツールです。
CloudFormer (ベータ) を使用して既存の AWS リソースから AWS CloudFormation テンプレートを作成する - AWS CloudFormation
引用元に記載のあるとおり、すべてのリソースがサポートされているわけではありません。また利用に際してはツールが動くためのEC2インスタンスが起動しますので料金が発生することも念頭に置いておく必要があります。
さて、ここでは naoyes001
におけるリソースを CloudFormer で解析していきます。
まずは CloudFormer ツールの構築です。
Webアクセス時のユーザネームとパスワードを忘れないようにします。なお、このツールリソース自体が CloudFormation で作成されています。
構築が完了したらWebアクセスするための URL が発行されますのでブラウザでアクセスします。テンプレート作成のウィザードが表示されますのでこれに従って操作を進めます。
リージョンを選んで Create Template
をクリック
テンプレートに記載される Description を記入して、CloudFormer が提示してくれるリソースをフィルタするための文字列を任意で入力します。今回は test
を入力しています。
DNS 関係のリソースはありませんので提示されません。
SQS のキューが提示されました。チェックを入れておきます。
CloudWatch のアラームも提示されていますのでチェックを入れておきます。
チェックを入れたふたつのリソースについて、テンプレート定義を吐き出してくれました。S3 の指定のバケットに登録しておくこともできるようですが、とりあえず私は手元のテンプレートファイルにコピペしておきました。
---
AWSTemplateFormatVersion: '2010-09-09'
Resources:
queuetestqueue:
Type: AWS::SQS::Queue
Properties:
DelaySeconds: '0'
MaximumMessageSize: '262144'
MessageRetentionPeriod: '345600'
ReceiveMessageWaitTimeSeconds: '0'
VisibilityTimeout: '30'
alarmtestalarm:
Type: AWS::CloudWatch::Alarm
Properties:
ActionsEnabled: 'true'
ComparisonOperator: GreaterThanOrEqualToThreshold
EvaluationPeriods: '1'
MetricName: ApproximateNumberOfMessagesVisible
Namespace: AWS/SQS
Period: '300'
Statistic: Average
Threshold: '0.0'
AlarmActions:
- arn:aws:autoscaling:ap-northeast-1:********:scalingPolicy:**********:resource/ecs/service/test-cluster/test-service:policyName/ScalePolicy
Dimensions:
- Name: QueueName
Value: test-queue
Description: test-ecs-app
aws-cli
次に、 CloudFormer で足りない部分を補っていきます。
ここでは先ほど CloudFormer が吐いてくれた AWS::CloudWatch::Alarm
が参照している AlarmActions
にあたる部分の定義を作成していきます。(なお、この際に必要な AWS::ECS::Cluster
および AWS::ECS::Service
は既に記述済みの前提で進めていきます。)
このアラームが ALARM 以外の状態から ALARM 状態に遷移するときに実行されるアクションのリスト。各アクションを Amazon リソースネーム (ARN) として指定します。アラームの作成および指定可能なアクションの詳細については、Amazon CloudWatch API リファレンス の「PutMetricAlarm」および Amazon CloudWatch ユーザーガイド の「Amazon CloudWatch アラームの作成」を参照してください。
ドキュメントを読むと上記のような記載がありました。これだけだと正直私はぜんぜんピンときません。しかし、 CloudFormer の吐いてくれた定義上に arn:aws:autoscaling:ap-northeast-1:********:scalingPolicy:**********:resource/ecs/service/test-cluster/test-service:policyName/ScalePolicy
とあるので見当をつけて試行錯誤してみます。
ここで、AWS リソースおよびプロパティタイプのリファレンス - AWS CloudFormation を見てそれっぽいリソースをあたってみます。
そのものズバリ Auto Scaling
というリソースがあるようです。 aws-cli で確認してみましょう。
$ aws autoscaling describe-policies --profile naoyes001
{
"ScalingPolicies": []
}
空配列が返ってきました。これは見当が外れたようです。
もう一度リソースの一覧を眺めてみると、似たようなもので Application Auto Scaling
というリソースがあるようです。これも aws-cli で確認してみましょう。
$ aws application-autoscaling describe-scaling-policies --service-namespace ecs --profile naoyes001
{
"ScalingPolicies": [
{
"PolicyName": "ScalePolicy",
"ScalableDimension": "ecs:service:DesiredCount",
"ResourceId": "service/test-cluster/test-service",
"CreationTime": 1564132644.053,
"StepScalingPolicyConfiguration": {
"MetricAggregationType": "Average",
"Cooldown": 300,
"StepAdjustments": [
{
"ScalingAdjustment": 0,
"MetricIntervalLowerBound": 0.0,
"MetricIntervalUpperBound": 1.0
},
{
"ScalingAdjustment": 1,
"MetricIntervalLowerBound": 1.0,
"MetricIntervalUpperBound": 2.0
},
{
"ScalingAdjustment": 2,
"MetricIntervalLowerBound": 2.0
}
],
"AdjustmentType": "ExactCapacity"
},
"PolicyARN": "arn:aws:autoscaling:ap-northeast-1:********:scalingPolicy:**********:resource/ecs/service/test-cluster/test-service:policyName/ScalePolicy",
"PolicyType": "StepScaling",
"Alarms": [
{
"AlarmName": "test-alarm",
"AlarmARN": "arn:aws:cloudwatch:ap-northeast-1:********:alarm:test-alarm"
}
],
"ServiceNamespace": "ecs"
}
]
}
手作業でWebコンソールにて設定した内容らしいものが返ってきました。
どうやら AWS::ApplicationAutoScaling::ScalingPolicy というリソースが必要そうだということがわかりました。
このドキュメントと先ほどの aws-cli の戻り値を照らし合わせながら記述していきます。 CloudFormation で定義すべきプロパティと aws-cli の戻り値がけっこう整合しているので値を埋めていくだけです。簡単ですね。
ResourceId : Required: Conditional.ScalingTargetId プロパティまたは ResourceId、ScalableDimension、そして ServiceNamespace プロパティのいずれかを指定する必要があります。ResourceId、ScalableDimension、そして ServiceNamespace プロパティを指定した場合、ScalingTargetId プロパティを指定しないでください。 :
AWS::ApplicationAutoScaling::ScalingPolicy - AWS CloudFormation
ドキュメントには上記のように記載されていて、ResourceId
, ScalableDimension
, ServiceNamespace
がすべて記述されていれば ScalingTargetId
は不要とのことです。さきほどの aws-cli の戻り値を見ても、ScalingTargetId
という値はありませんでしたしこれで良さそうですね。
TestAppAutoscalingScalingPolicy:
Type : AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: 'ScalePolicy'
PolicyType: StepScaling
ResourceId:
!Join
- '/'
- - 'service'
- !Ref TestCluster
- !GetAtt TestService.Name
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
StepScalingPolicyConfiguration:
AdjustmentType: ExactCapacity
Cooldown: 300
MetricAggregationType: Average
StepAdjustments:
- ScalingAdjustment: 0
MetricIntervalLowerBound: 0
MetricIntervalUpperBound: 1
- ScalingAdjustment: 1
MetricIntervalLowerBound: 1
MetricIntervalUpperBound: 2
- ScalingAdjustment: 2
MetricIntervalLowerBound: 2
さっそく適用してみます。ふたつ作ったメンバーアカウントのうちこの時点でまっさらな naoyes002
に適用してみます。
$ aws cloudformation create-stack --stack-name test-ecs-app --template-body file://$(pwd)/template.yaml --capabilities CAPABILITY_NAMED_IAM --profile naoyes002
メンバーアカウント naoyes002
のWebコンソールなどで CloudFormation のスタック作成の進捗を確認すると、以下のようなエラーメッセージと共に異常終了していることがわかりました。
No scalable target registered for service namespace: ecs, resource ID: service/test-ecs-app-TestCluster-***********/test-ecs-app-TestService-*********, scalable dimension: ecs:service:DesiredCount (Service: AWSApplicationAutoScaling; Status Code: 400; Error Code: ObjectNotFoundException; Request ID: ****-****-****-****)
訳がわかりませんね。No scalable target registered
とあるので、文面からするとscalable target
なるリソースが足りないようです。
application-autoscaling
コマンドには describe-scalable-targets
というサブコマンドがあるので、
$ aws application-autoscaling help
:
:
o describe-scalable-targets
:
これを使って naoyes001
のリソースを確認してみましょう。
$ aws application-autoscaling describe-scalable-targets --service-namespace ecs --profile naoyes001
{
"ScalableTargets": [
{
"ScalableDimension": "ecs:service:DesiredCount",
"ResourceId": "service/test-cluster/test-service",
"RoleARN": "arn:aws:iam::********:role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService",
"CreationTime": 1564129660.911,
"MinCapacity": 0,
"ServiceNamespace": "ecs",
"MaxCapacity": 2
}
]
}
さきほどの AWS::ApplicationAutoScaling::ScalingPolicy
における
ResourceId:
!Join
- '/'
- - 'service'
- !Ref TestCluster
- !GetAtt TestService.Name
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
の箇所と似ていますね。
ResourceId
,ScalableDimension
,ServiceNamespace
がすべて記述されていればScalingTargetId
は不要とのことです。さきほどの aws-cli の戻り値を見ても、ScalingTargetId
という値はありませんでしたしこれで良さそうですね。
と先述しましたが、 No scalable target registered
と言われてしまっているので、ここは ScalingTargetId
を定義する方向に方針転換してみます。
公式ドキュメントを見てみましょう。AWS::ApplicationAutoScaling::ScalableTarget - AWS CloudFormation
aws application-autoscaling describe-scalable-targets --service-namespace ecs --profile naoyes001
での戻り値を参考に埋められそうです。なお IAMロール IamEcsAppAutoscalingRole
の定義についてはここでは割愛します。
TestAppAutoscalingScalableTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MaxCapacity: 2
MinCapacity: 0
ResourceId:
!Join
- '/'
- - 'service'
- !Ref TestCluster
- !GetAtt TestService.Name
RoleARN: !GetAtt IamEcsAppAutoscalingRole.Arn
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
AWS::ApplicationAutoScaling::ScalableTarget
を定義し、
TestAppAutoscalingScalingPolicy:
Type : AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: 'ScalePolicy'
PolicyType: StepScaling
- ResourceId:
- !Join
- - '/'
- - - 'service'
- - !Ref TestCluster
- - !GetAtt TestService.Name
- ScalableDimension: ecs:service:DesiredCount
- ServiceNamespace: ecs
+ ScalingTargetId: !Ref TestAppAutoscalingScalableTarget
StepScalingPolicyConfiguration:
AdjustmentType: ExactCapacity
Cooldown: 300
MetricAggregationType: Average
StepAdjustments:
- ScalingAdjustment: 0
MetricIntervalLowerBound: 0
MetricIntervalUpperBound: 1
- ScalingAdjustment: 1
MetricIntervalLowerBound: 1
MetricIntervalUpperBound: 2
- ScalingAdjustment: 2
MetricIntervalLowerBound: 2
AWS::ApplicationAutoScaling::ScalingPolicy
から参照するように変更しました。
以上で完成です。さきほどの異常終了したスタックを削除して再度スタックを作成すると無事に完了しました。
おわりに
Webコンソールは偉大です。直感的ですし面倒なリソース作成は裏でやってくれます。なのでじゃんじゃん使うべきだと思います。
ただし持続可能な再現性を担保するのは難しい。そうしたときに CloudFormation は非常にありがたい存在です。
今回提示した手法が少しでも参考になればと思います。
成果物は https://github.com/naoyes/201908-qtb-cfn/blob/master/template.yaml として上げておきました。こちらも併せて参考にしていただければ。