#はじめに
CloudFormationで作成したスタック内のリソースを直接設定変更すると、テンプレートで定義された設定内容と実際のリソースの設定内容に差分(以下、「ドリフト」と記述)が生じます。
ドリフトが発生することにより、スタックの更新や削除の際に問題が生じる可能性があります。
今回は「ドリフト検出」の機能を使用して、発生したドリフトの内容の確認やドリフトの解消を実施してみたいと思います。
また、定期的なドリフト検出とSlackへの通知も併せて構成していきます。
#構成図
定期的なドリフト検出とSlackへの通知のためにCloudFormation以外に以下のサービスを使用します。
※ドリフト検出を実行するのみであれば不要です。
- AWS Config
- Amazon EventBridge
- Amazon SNS
- AWS Chatbot
#CloudFormation スタック作成
####スタック構成図
以下のような構成でCloudFormationスタックを作成します。
####テンプレート
テンプレートの内容は以下となります。
AWSTemplateFormatVersion: 2010-09-09
Description: CloudFormation drift detection test
Parameters:
VpcName:
Type: String
Description: VPC name
Default: "testVPC"
VpcCidrBlock:
Type: String
Description: VPC CIDR block
Default: "10.0.0.0/16"
IgwName:
Type: String
Description: Internet gateway name
Default: "testIGW"
SubnetName:
Type: String
Description: Subnet name
Default: "testSubnet"
SubnetCidrBlock:
Type: String
Description: Subnet CIDR block
Default: "10.0.0.0/24"
PublicRouteTableName:
Type: String
Description: Public route table name
Default: "public-rt"
SecurityGroupName:
Type: String
Description: Security group name
Default: "testSG"
SSHAccessIp:
Type: String
Description: Source IP address of SSH access
Default: "0.0.0.0/0"
InstanceName:
Type: String
Description: EC2 instance name
Default: "testEC2"
KeyName:
Type: String
Description: Key pair name
Default: "cloudtech-keypair"
ImageId:
Type: String
Description: Image ID of EC2 instance
Default: "ami-0404778e217f54308"
BucketName:
Type: String
Description: S3 bucket name
Default: "drift-detection-test01"
Resources:
testVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidrBlock
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Ref VpcName
testIGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Ref IgwName
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref testVPC
InternetGatewayId: !Ref testIGW
testSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Sub ${AWS::Region}a
VpcId: !Ref testVPC
CidrBlock: !Ref SubnetCidrBlock
Tags:
- Key: Name
Value: !Ref SubnetName
publicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref testVPC
Tags:
- Key: Name
Value: !Ref PublicRouteTableName
publicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref publicRouteTable
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref testIGW
publicRouteTableAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref testSubnet
RouteTableId: !Ref publicRouteTable
testSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Ref SecurityGroupName
GroupDescription: !Ref SecurityGroupName
VpcId: !Ref testVPC
SecurityGroupIngress:
-
IpProtocol: "tcp"
FromPort: 22
ToPort: 22
CidrIp: !Ref SSHAccessIp
Tags:
- Key: Name
Value: !Ref SecurityGroupName
testEC2:
Type: AWS::EC2::Instance
Properties:
KeyName: !Ref KeyName
ImageId: !Ref ImageId
InstanceType: "t2.micro"
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeviceIndex: "0"
SubnetId: !Ref testSubnet
GroupSet:
- !Ref testSG
Tags:
- Key: Name
Value: !Ref InstanceName
s3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
VersioningConfiguration:
Status: "Suspended"
Tags:
- Key: Name
Value: !Ref BucketName
#ドリフト検出
まずはリソースの設定変更を実施する前に現状ドリフトが発生していないことを確認します。
CloudFormationのスタック一覧画面で対象スタックを選択し、[スタックアクション]-[ドリフトの検出]をクリックします。
ドリフトの検出実行後、再度対象スタックを選択し、[スタックアクション]-[ドリフト結果を表示]をクリックします。
ドリフトステータス
が「IN_SYNC」になっていればスタック内のリソースにドリフトが発生していない状態となります。
#スタック内のリソース設定変更
作成したスタック内のEC2インスタンス, S3バケットに対して以下の変更を加えます。
- EC2インスタンス
- インスタンスタイプを「t2.micro」から「t2.small」に変更
- S3バケット
- バケットを削除
上記設定変更後、再度ドリフト検出を実施してみます。
ドリフトステータス
が「DRIFTED」となり、スタック内のリソースにドリフトが発生している状態となります。
また、リソースのドリフトステータス
でリソースごとのドリフト発生状況を確認すると、EC2インスタンスは「MODIFIED」、S3バケットは「DELETED」とステータスが変更されていることが確認できます。
リソースごとにどのようなドリフトが発生しているのか確認してみます。
EC2インスタンスを選択し、[ドリフトの詳細を表示]をクリックします。
違い
内にドリフトが発生している項目の一覧、詳細
内にスタックを作成・更新した際に使用したテンプレートの内容(予定)と現在の実際の設定(現在)が表示されます。
違い
内のドリフト発生項目にチェックを入れると、詳細
内でどのようなドリフトが発生しているかが色付けされ確認することができます。
今回の場合はインスタンスタイプが「t2.micro」から「t2.small」に変更されたことが確認できます。
#ドリフト解消
ドリフトを解消するためには以下の3つの方法があります。
- 直接変更したリソースの設定を元に戻す
- 直接変更したリソースの設定をテンプレートに反映させ、スタックを更新する
- リソースをスタックから取り除き、インポートし直す
####方法1:直接変更したリソースの設定を元に戻す
直接変更した設定を元の設定に戻します。
EC2インスタンスは停止後にインスタンスタイプを「t2.micro」に戻し、S3バケットは同じ設定で再作成します。
上記作業実施後にドリフト検出を実行してみると、ドリフトが解消されていることを確認できます。
S3バケットについてはCloudFormationで付与されたタグが付いていない状態ではありますが、ドラフトがない状態として認識されています。
####方法2:直接変更したリソースの設定をテンプレートに反映させ、スタックを更新する
直接変更した設定内容をテンプレートに反映させます。
ただし、S3バケットに関しては削除されているだけで設定内容自体に変更はありません。
今回はEC2インスタンスの設定内容に変更があるため発生しない想定ですが、テンプレートの内容を変更しないままスタックを更新すると以下のエラーが発生します。
そのため、EC2インスタンスについてはインスタンスタイプの設定値を修正し、S3バケットの記述についてはいったんコメントアウトをします。
テンプレートの修正ができたら、スタックを更新します。
testEC2:
Type: AWS::EC2::Instance
Properties:
KeyName: !Ref KeyName
ImageId: !Ref ImageId
InstanceType: "t2.small"
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeviceIndex: "0"
SubnetId: !Ref testSubnet
GroupSet:
- !Ref testSG
Tags:
- Key: Name
Value: !Ref InstanceName
# s3Bucket:
# Type: AWS::S3::Bucket
# Properties:
# BucketName: !Ref BucketName
# AccessControl: Private
# PublicAccessBlockConfiguration:
# BlockPublicAcls: true
# BlockPublicPolicy: true
# IgnorePublicAcls: true
# RestrictPublicBuckets: true
# VersioningConfiguration:
# Status: "Suspended"
# Tags:
# - Key: Name
# Value: !Ref BucketName
スタック更新時にEC2インスタンスが再起動されます。
インスタンスタイプはすでに「t2.small」となっていますが、本来インスタンスタイプの変更にはインスタンスの停止が必要となるため再起動が発生するものと思われます。
スタック更新が完了すると、EC2インスタンスについてのドリフトが解消されていることが確認できます。
S3バケットについてはテンプレートでコメントアウトされているので、そもそもスタックに存在しないリソースという扱いとなります。
S3バケットを再度スタック内に作成するためには、テンプレートでのコメントアウトを解除し、スタックを更新します。
s3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
VersioningConfiguration:
Status: "Suspended"
Tags:
- Key: Name
Value: !Ref BucketName
これでEC2インスタンス、S3バケットともにドリフトが解消された状態となります。
EC2インスタンスの設定は新しい設定となり、S3バケットは同名ではありますが別バケットとなります。
####方法3:リソースをスタックから取り除き、インポートし直す
スタックを更新する際にリソースの再作成が発生する場合には方法3を用いて、既に作成されているリソースを保持したままドリフトを解消することができます。
方法2でEC2インスタンスのドラフトを解消する際には再起動が発生してしまいましたが、方法3で対応することで再起動を発生させずドラフトを解消することができます。
S3バケットについては方法2を使用して再作成を行っていきます。
テンプレート内のEC2インスタンスの記述にDeletionPolicy属性を追加し、スタックを更新します。
DeletionPolicy属性を「Retain」とすることでスタック削除時にリソースが保持されるようになります。
S3バケットはコメントアウトします。
testEC2:
Type: AWS::EC2::Instance
DeletionPolicy: Retain
Properties:
KeyName: !Ref KeyName
ImageId: !Ref ImageId
InstanceType: "t2.micro"
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeviceIndex: "0"
SubnetId: !Ref testSubnet
GroupSet:
- !Ref testSG
Tags:
- Key: Name
Value: !Ref InstanceName
# s3Bucket:
# Type: AWS::S3::Bucket
# Properties:
# BucketName: !Ref BucketName
# AccessControl: Private
# PublicAccessBlockConfiguration:
# BlockPublicAcls: true
# BlockPublicPolicy: true
# IgnorePublicAcls: true
# RestrictPublicBuckets: true
# VersioningConfiguration:
# Status: "Suspended"
# Tags:
# - Key: Name
# Value: !Ref BucketName
これでEC2インスタンスについては削除時にリソースが保持される状態となりました。
次にテンプレート内のEC2インスタンスをコメントアウトし、S3バケットのコメントアウトを解除した状態でスタックを更新します。
# testEC2:
# Type: AWS::EC2::Instance
# DeletionPolicy: Retain
# Properties:
# KeyName: !Ref KeyName
# ImageId: !Ref ImageId
# InstanceType: "t2.micro"
# NetworkInterfaces:
# - AssociatePublicIpAddress: true
# DeviceIndex: "0"
# SubnetId: !Ref testSubnet
# GroupSet:
# - !Ref testSG
# Tags:
# - Key: Name
# Value: !Ref InstanceName
s3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
VersioningConfiguration:
Status: "Suspended"
Tags:
- Key: Name
Value: !Ref BucketName
コメントアウトしたEC2インスタンスはスタックには存在しない状態となりますが、更新前に作成されていたEC2インスタンスは保持されます。
S3バケットについては新規作成されます。
スタック詳細画面で[リソース]タブを選択し、「AWS::EC2::Instance」「AWS::S3::Bucket」でそれぞれ検索し、EC2インスタンスはスタックに存在しないこと、S3バケットは新規作成されていることを確認します。
スタックからEC2インスタンスが除外されたことを確認後、テンプレート内のコメントアウトを解除し、設定値を現在のEC2インスタンスの設定値に変更します。
testEC2:
Type: AWS::EC2::Instance
DeletionPolicy: Retain
Properties:
KeyName: !Ref KeyName
ImageId: !Ref ImageId
InstanceType: "t2.small"
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeviceIndex: "0"
SubnetId: !Ref testSubnet
GroupSet:
- !Ref testSG
Tags:
- Key: Name
Value: !Ref InstanceName
上記テンプレート修正が完了したら、リソースのインポートを実施します。
スタック一覧画面で対象スタックを選択し、[スタックアクション]-[スタックへのリソースのインポート]をクリックします。
識別子の値
にインポートするリソースについての値を入力します。
EC2インスタンスの場合はインスタンスIDを入力します。
入力できたら、[次へ]をクリックします。
設定の概要を確認し、[リソースをインポート]をクリックします。
リソースのインポートが完了したら、ドリフト検出を実行し、ドリフトステータスが「IN_SYNC」になっていることを確認します。
EC2インスタンスの設定は新しい設定となり、S3バケットは同名ではありますが別バケットとなります。
#ドリフト検出の定期実行
AWS Configにてドリフトを定期的に検出し、ドリフトが発生してるかどうかを判断するためのルールを作成します。
まずはルール作成時に必要となるIAMロールを先に作成しておきます。
####IAMロールの作成
CloudFormationスタックのドリフト検出を実行できる権限を持つIAMロールを作成します。
信頼関係やポリシーについては以下の記事を参考にして作成しました。
Configルールを作成する際にはIAMロールのロールARNを指定することになるので、ロールARNを控えておきます。
####Configルールの作成
Config管理画面でルール一覧を開き、[ルールを追加]をクリックします。
AWS マネージド型ルール
の検索窓に「cloudformation」と入力し検索をかけます。
[cloudformation-stack-drift-detection-check]を選択し、[次へ]をクリックします。
パラメータ
内のキー[cloudformationRoleArn]の値として、事前に作成したIAMロールのARNを入力し、[次へ]をクリックします。
確認画面で設定を確認し、[ルールを追加]をクリックします。
ルール一覧画面で作成されたルール名をクリックし、ルール詳細画面を開きます。
対象範囲内のリソース
で「すべて」を選択し、CloudFormationスタックのドリフトステータス評価状況を確認します。
コンプライアンス
が「準拠」となっていれば、ドリフトは発生していない状態となります。
#通知設定
####SNSトピックの作成
通知を構成するためにまずはSNSトピックを作成していきます。
SNS管理画面で[トピック]を選択し、[トピックを作成]をクリックします。
設定値を入力し、[トピックの作成]をクリックします。
今回は以下の設定値を使用します。
※以下に記載のない設定値はデフォルトとします。
項目名 | 設定値 |
---|---|
タイプ | スタンダード |
名前 | drift-detection-topic |
####通知用Slackチャンネルの作成
Slackで通知を受け取るチャンネルをあらかじめ作成しておきます。
チャンネルはパブリック、プライベートのいずれでもOKですが、今回はプライベートチャンネルを作成して通知を構成します。
プライベートの場合、あらかじめAWS Chatbotの招待するためのコマンドをSlackで実行しておく必要があります。
If you configure a private Slack channel, run the /invite @AWS command in Slack to invite the AWS Chatbot to the chat room.
作成したプライベートチャンネル内で/invite @AWS
と入力し、実行します。
また、Chatbotチャネル作成時にプライベートチャンネルのIDが必要になりますので控えておきます。
####Chatbot用IAMロール・ポリシーの作成
Chatbotチャネルを作成するにあたり、Chatbotチャネルに割り当てるIAMロールとチャネルのメンバーが実行可能なアクションを制御する「Channelガードレール」に使用するポリシーを指定する必要があります。
Chatbotチャネル作成ウィザード内で新たにIAMロール・ポリシーを作成することもできますが、今回はChatbotチャネルに割り当てるIAMロールにアタッチするポリシーをChannelガードレールにも設定したいと思いますのでウィザードのデフォルト設定で作成されるIAMロール・ポリシーの設定値に合わせて事前に作成します。
ポリシーは以下の設定で作成します。
項目名 | 設定値 |
---|---|
名前 | AWS-Chatbot-NotificationsOnly-Policy |
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"cloudwatch:Describe*",
"cloudwatch:Get*",
"cloudwatch:List*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
また、IAMロールは以下の設定で作成します。
項目名 | 設定値 |
---|---|
名前 | AWSChatbot-role |
ポリシー | AWS-Chatbot-NotificationsOnly-Policy |
信頼されたエンティティ | chatbot.amazonaws.com |
####Chatbotチャネルの作成
作成したSlackチャンネルへの通知を構成するためにChatbotチャネルを作成します。
Chatbot管理画面でチャットクライアント
で[Slack]を選択し、[クライアントを設定]をクリックします。
Slackワークスペースへのサインイン画面にリダイレクトされます。
ワークスペースのURLを入力し、[続行する]をクリックします。
AWS ChatbotからSlackワークスペースへのアクセス権限リクエストが表示されるので、[許可する]をクリックします。
Management Console画面に戻ったら、[新しいチャネルを設定]をクリックします。
設定値を入力し、[設定]をクリックします。
今回は以下の設定値を使用します。
※以下に記載のない設定値はデフォルトとします。
項目名 | 設定値 |
---|---|
設定名 | drift-detection-channel |
チャネルタイプ | プライベート |
チャネル ID | 通知に使用するチャネルのID |
ロール設定 | チャネル IAM ロール |
チャネル IAM ロール | 既存の IAM ロールを使用する |
ロール名 | AWSChatbot-role |
Channel ガードレール | AWS-Chatbot-NotificationsOnly-Policy |
トピック | drift-detection-topic |
Slack通知のテストを行います。
作成されたチャネルを選択し、[テストメッセージを送信]をクリックします。
Slackの通知用チャンネルでテストメッセージを受信していることを確認します。
また、事前に作成していたSNSトピックにサブスクリプション設定が自動的に追加されていることを確認します。
####EventBridgeルールの作成
ConfigによるCloudFormationスタックのドリフトの定期検出、SNS・ChatbotによるSlackへの通知をそれぞれ構成してきました。
その2つをEventBridgeを使用して紐づけることで、ドリフト検出時のSlack通知を構成していきます。
以下を参考に設定していきます。
EventBridge管理画面で[ルール]を選択し、[ルールを作成]をクリックします。
名前
に任意のルール名を入力します。
パターンを定義
ブロックで[イベントパターン]を選択肢、イベント一致パターン
で[カスタムパターン]を選択します。
イベントパターン
に以下を入力し、[保存]をクリック
{
"source": [
"aws.config"
],
"detail-type": [
"Config Rules Compliance Change"
],
"detail": {
"messageType": [
"ComplianceChangeNotification"
],
"configRuleName": [
"cloudformation-stack-drift-detection-check"
],
"resourceType": [
"AWS::CloudFormation::Stack"
],
"newEvaluationResult": {
"complianceType": [
"NON_COMPLIANT"
]
}
}
}
ターゲットを選択
ブロックでターゲット
に[SNS トピック]を選択し、トピック
で[drift-detection-topic]を選択後、[作成]をクリックします。
#動作確認
Slack通知の動作確認をするためにドリフトを発生させてみましょう。
EC2インスタンスのインスタンスタイプを変更します。
今回Configルールを設定するにあたって、頻度
を「1時間」としましたのでConfigルールが最後に評価を実行した時刻から1時間後に以下の流れでSlackに通知されます。
※すぐに動作確認したい場合は評価を手動で実行することも可能です。
- Configルールによる評価が実行される
- CloudFormationスタックのコンプライアンスが「非準拠」に変更される
- EventBridgeルールによってCloudFormationスタックのコンプライアンスが「非準拠」になったイベントをきっかけにSNSトピックにメッセージが発行される
- SNSトピックのサブスクリプションであるChatbotがメッセージを受信
- ChatbotがSlackチャンネルへのメッセージ送信を実行
無事1時間後にスタックのコンプライアンスが非準拠であることがSlackに通知されました。
#あとがき
この記事は CloudTech の課題として作成しました。
動画やハンズオン等で学習を進めることができるので、AWS初学者にはおすすめです。