はじめに
AWS CloudFormationを使用するとAWSリソースの作成、更新、削除を自動化することができますが、どのサービスをCloudFormationで管理するかどうかなどの運用の仕方はプロジェクトやチームなどによって異なります。すべてのリソースをCloudFormation管理で行っているチームもあれば、試作用や1回限りしか反映しないリソースだったりテンプレートの作成の手間を省くためにあえてCloudFormationで管理しないチームやリソースがあったりします。
どのような運用が最も良いものかはプロジェクトの特性によるところがあるので一概に言えませんが、運用ルールを守るための仕組みはあるべきだと思います。
この記事ではCloudFormationのドリフト検出と呼ばれる仕組みを利用することでCloudFormationで管理されているリソースが他の方法で更新されることを防ぐ仕組みについて記載しました。
この記事で説明する内容
①ドリフト検出とは
②ドリフト検出実施方法
③ドリフト検出の定期実行とモニタリング
④ドリフト検出内容の自動修正方法
ドリフト検出とは
AWS CloudFormationのドリフト検出(Drift Detection)は、CloudFormationスタックに対して実際のAWSリソースの設定とテンプレートに記述された期待される設定との間に生じた差異を検出する機能です。この機能を使用すると、スタック内のリソースが手動で変更されたり、直接AWSコンソールやCLIで変更されたりした場合に、それがCloudFormationテンプレートと一致しなくなる(ドリフトする)かどうかを検知できます。
ドリフトは、スタックの変更セットを作成する前にテンプレートと実際のリソースの設定を比較することで検出されます。これにより、CloudFormationスタックがテンプレートに記述された形式と一致しなくなった場合、開発者や管理者はその変更を把握し、適切な措置を講じることができます。
ドリフト検出がないことで発生する懸念事項は?
ドリフト検出がされないということはCloudFrmatnion以外で行われた更新が検出しにくい状態になるということになると思います。そのため、何らかの重要な更新をCloudFormation外で変更が行われていた場合、次回のCloudFormationテンプレートからのスタックの作成でその変更が失われたり、その変更をテンプレートに取り込む作業が変更に気が付いたタイミングで発生してしまい、余計な工数の増加や反映ミスによる
不具合につながってしまう可能性があります。
プログラムの内容をCodeCommitで管理していてCloudFormationにコードを記載しないようにしていたり、運用方法はチームやプロジェクトごとに異なるので、ドリフト検出以外での差分比較を行うこともあります。
ドリフト検出の方法
・ドリフト検出は任意のタイミングで実行することができます。CloudFormationでのスタック作成作業を行う前には1度ドリフト検出を行って以前実行したテンプレートの内容と現在の実際のリソースの状態を比較して差分がないことを確認したほうが認識していない変更を消してしまうことを防ぐ意味ではよいと思います
方法1:AWSコンソールを使用したドリフト検出実行方法
テンプレートを選択してドリフト検出を開始する
テンプレートを選択してドリフト結果を表示する
ドリフトがない場合
ドリフトがある場合
・ドリフトがある場合「DRIFTED」と表示されます
・ドリフトの詳細を表示を選択すると別画面でドリフトの詳細が表示されます
※今回はLambdaコンソールから手動でタイムアウト設定を更新したのでタイムアウト値の違いが表示されています
方法2:AWS CLIを使用したドリフト検出実行方法
AWS CLIを使用して、ドリフト検出をスタートします。次のコマンドを使用します。
aws cloudformation detect-stack-drift --stack-name YourStackName
ドリフト検出のジョブが完了すると、以下のコマンドを使用してドリフトの結果を確認します。
aws cloudformation describe-stack-resource-drift --stack-name YourStackName --logical-resource-id YourLogicalResourceId
ドリフト検出の定期実行とモニタリング
・CloudFormationを使ったスタック作成を行うリリース作業の最中にドリフト検出してドリフトを発見し、ドリフトの変更をテンプレートに取り込むかどうかを検討していてはリリース作業が遅れてしまいますし、ドリフトの判断と取り込みという予定になかった作業が入ることになります。
・そのため定期的にドリフト検出を行い、差異の検知とその扱いを検討し対応できるようにするのはリリース作業を円滑に行うためにも有効な構造になります。
アーキテクチャ図(通知)
定期実行の実現方法(AWS Configを使う)
①AWS Configの有効化
AWS Configを有効化します。AWS Configを有効にすることで、AWSアカウント内で発生したリソースの変更に関する詳細な情報がAWS Configに収集されます。
②AWS Config Rulesの作成
AWS Configルールを使用して、ドリフト検出の結果を評価および監視するためのルールを作成します。これにより、異常な変更に対するアラートや通知を受け取ることができます
・評価するリソースと評価頻度を設定します
→CloudFormationのスタックのドリフト検出のため、リソースは「AWS CloudFormationStack」を選択します
★ロール設定
・ロールの信頼設定と許可設定は以下の内容が設定されているロールを指定してください
(信頼関係)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": [
"cloudformation.amazonaws.com",
"config.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
(許可設定)
・AWSConfigRole
・CloudFormation関連許可
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"cloudformation:DetectStackResourceDrift",
"cloudformation:DetectStackDrift",
"cloudformation:BatchDescribeTypeConfigurations"
],
"Resource": "*"
}
]
}
④ドリフトの確認
・Configの評価が行われ、ドリフトを検知されたことはConfigのルールから確認できます
⑤ドリフト検知結果の通知
・ドリフト検知した際に通知を行うためにはEventBridgeを設定してSNSやChatBot経由でSlackやメールで担当者に通知します
EventBridgeルール設定
・Configの結果が非標準を検知("complianceType"が["NON_COMPLIANT"])の場合にイベントを発火してSNSを呼び出すように設定します
(設定内容)
イベントパターン
ターゲット(SNSを指定)
SNSの設定
・SNSのサブスクリプションでChatBotに通知するように設定します
Slack通知確認
ChatBotの設定
★注意★
上記の設定ではConfigの評価結果が「非準拠状態になったとき」に通知を行います。
つまり、非標準になってから次の評価で再度非標準になったときは通知がされません。
標準になるまで通知が不要な場合は適していますが、もし毎回の通知が欲しい場合は
Configの状態でEventBrdgeを設定するのではなく、CloudFormationのドリフト検知の動きに対してEventBridgeが動くようにしてください。
ここまでの内容でAWS Configが定期的にドリフトを検出し、設定との一致しない変更がある場合、通知が生成されます。通知を受信することで、異常な変更に対処できます。
ドリフト検出結果の自動修正
・もし仮に開発チームで「AWSリソースの変更はCloudFormation経由のものしか認めない」というルールがある場合、ドリフトを検出した時点でドリフトがない状態に自動的にリソースを更新してあげるのもそのチームの運用では有効かもしれません。
・その場合、あるべき姿は前回実行したCloudFormationテンプレートの状態になるわけですからドリフト検出したタイミングで前回のテンプレートを実行して修復する構造を作成してみます
自動修正の実現方法(AWS Lambdaを使う)
・アーキテクチャ図ではSNSからChatBot経由でSlackにメッセージを送っていますが、SNSの通知先にLambdaを設定して以下のような関数を実行すればドリフトの自動修正が可能になります。
アーキテクチャ図(自動修復)
(コード例)
import boto3
import json
def lambda_handler(event, context):
client = boto3.client('cloudformation')
for record in event['Records']:
stack_name = record['Sns']['Message']
drift_status = client.detect_stack_drift(StackName=stack_name)['StackDriftDetectionId']
if drift_status == 'DRIFTED':
# ドリフトが検出された場合、適切なアクションを実行
# 例えば、変更セットを再適用するなど
client.create_change_set(StackName=stack_name, UsePreviousTemplate=True)
client.execute_change_set(StackName=stack_name, ChangeSetName='your-change-set-name')