[AWS CloudFormation とは何ですか?](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html)
## CloudFormationの動作概要
ポイントは**StackSets**でクロスリージョンやクロスアカウントのリソースを作成できる点。必要であればオリジナルな処理を**カスタムリソース**で定義できる点。クロスアカウントでは管理者とターゲットアカウントの間に信頼関係を設定する。

### CloudFormation テンプレートの操作
動的なパラメータ参照、テンプレートのスニペットなど
* [CloudFormation テンプレートの使用](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-guide.html)
* [CloudFormation テンプレートスニペット](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-snippets.html)
### スタックの作成と管理
* [AWS CloudFormation スタックを使用した単一ユニットとしての AWS リソースの管理](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/stacks.html)
### 組込関数リファレンス
* [組み込み関数リファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html)
```
# 使用例
UserData:
Fn::Base64:
Fn::Sub:
- |
#!/bin/bash
sed -i -e 's/APP_URL=.*/APP_URL=${APP_URL}/' /tmp/.env
sed -i -e 's/DB_HOST=.*/DB_HOST=${DB_HOST}/' /tmp/.env
sed -i -e 's/DB_DATABASE=.*/DB_DATABASE=${DbName}/' /tmp/.env
sed -i -e 's/DB_USERNAME=.*/DB_USERNAME=${UserName}/' /tmp/.env
sed -i -e 's/DB_PASSWORD=.*/DB_PASSWORD=${Password}/' /tmp/.env
sed -i -e 's/AWS_S3_BUCKET=.*/AWS_S3_BUCKET=${S3_BUCKET}/' /tmp/.env
- APP_URL:
Fn::ImportValue:
!Sub ${Prefix}::${Client}::LoadBalancerDNSName
DB_HOST:
Fn::ImportValue:
!Sub ${Prefix}::${Client}::EndpointAddress
S3_BUCKET:
Fn::ImportValue:
!Sub ${Prefix}::${Client}::DataBucket
```
### 疑似パラメータ
* [擬似パラメータ参照](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html)
### AWS リソースとプロパティタイプのリファレンス
* [AWS リソースおよびプロパティタイプのリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html)
### StackSets
信頼関係を構築すれば、Organizationsを利用していなくても利用可能
* [StackSets を使用したアカウントとリージョン全体でのスタックの管理](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/what-is-cfnstacksets.html)
* [AWS CloudFormation StackSets のサンプルテンプレート](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/stacksets-sampletemplates.html)
* [セルフマネージド型のアクセス許可を付与する](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-self-managed.html)
```
# セルフマネージド型のアクセス許可
#
# 管理者アカウント側
#
AWSTemplateFormatVersion: 2010-09-09
Description: Configure the AWSCloudFormationStackSetAdministrationRole to enable use of AWS CloudFormation StackSets.
Parameters:
AdministrationRoleName:
Type: String
Default: AWSCloudFormationStackSetAdministrationRole
Description: "The name of the administration role. Defaults to 'AWSCloudFormationStackSetAdministrationRole'."
ExecutionRoleName:
Type: String
Default: AWSCloudFormationStackSetExecutionRole
Description: "The name of the execution role that can assume this role. Defaults to 'AWSCloudFormationStackSetExecutionRole'."
Resources:
AdministrationRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref AdministrationRoleName
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: cloudformation.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: AssumeRole-AWSCloudFormationStackSetExecutionRole
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Resource:
- !Sub 'arn:*:iam::*:role/${ExecutionRoleName}'
#
# ターゲットアカウント側
#
AWSTemplateFormatVersion: 2010-09-09
Description: Configure the AWSCloudFormationStackSetExecutionRole to enable use of your account as a target account in AWS CloudFormation StackSets.
Parameters:
AdministratorAccountId:
Type: String
Description: AWS Account Id of the administrator account (the account in which StackSets will be created).
MaxLength: 12
MinLength: 12
ExecutionRoleName:
Type: String
Default: AWSCloudFormationStackSetExecutionRole
Description: "The name of the execution role. Defaults to 'AWSCloudFormationStackSetExecutionRole'."
Resources:
ExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref ExecutionRoleName
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
AWS:
- !Ref AdministratorAccountId
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- !Sub arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess
```
### カスタムリソース
* [CloudFormation が提供するリソースタイプの使用によるテンプレートの機能の拡張](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cloudformation-supplied-resource-types.html)
#### 削除イベントの時にS3バケットを掃除するカスタムリソース
```
AWSTemplateFormatVersion: 2010-09-09
Description: If you use S3 bucket, you need to clean up used S3 bucket. so this template is cleanup example
Resources:
#==========================
# S3 Bucket
#==========================
DataBucket:
Type: "AWS::S3::Bucket"
Properties:
BucketName: !Sub 'data-bucket'
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
#==========================
# カスタムリソース
#==========================
# S3バケット掃除のカスタムリソース
CleanoutBucketOnDelete:
Type: Custom::cleanupBucket
Properties:
ServiceToken: !GetAtt CleanoutBucketFunction.Arn
DependsOn:
- CleanoutBucketFunction
# 掃除用ファンクション
CleanoutBucketFunction:
Type: AWS::Lambda::Function
DependsOn:
- DataBucket
- CleanoutBucketRole
Properties:
Description: Clean out Bucket on delete.
Handler: index.handler
Runtime: python3.12
Role: !GetAtt CleanoutBucketRole.Arn
Timeout: 120
Environment:
Variables:
BUCKET_NAME: !Ref DataBucket
Code:
ZipFile: |
import cfnresponse
import logging
import boto3
import time, os
status = cfnresponse.SUCCESS
logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s %(message)s',level=logging.DEBUG)
def handler(event, context):
logger.debug(event)
if event['RequestType'] == 'Delete':
try:
BUCKETNAME = os.environ['BUCKET_NAME']
s3 = boto3.resource('s3')
bucket = s3.Bucket(BUCKETNAME)
bucket_versioning = s3.BucketVersioning(BUCKETNAME)
if bucket_versioning.status == 'Enabled':
bucket.object_versions.delete()
else:
bucket.objects.all().delete()
cfnresponse.send(event, context, status, {}, None)
except Exception as e:
print(e)
cfnresponse.send(event, context, cfnresponse.FAILED, {}, None)
else:
cfnresponse.send(event, context, status, {}, None)
# 掃除用ロール
CleanoutBucketRole:
Type: AWS::IAM::Role
Properties:
PermissionsBoundary: !Sub 'arn:aws:iam::${AWS::AccountId}:policy/DevelopUserBoundary'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: lambda-bucketcleaner
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:DeleteObject
- s3:DeleteObjectVersion
Resource: !Sub 'arn:${AWS::Partition}:s3:::${DataBucket}/*'
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketVersioning
Resource: !Sub 'arn:${AWS::Partition}:s3:::${DataBucket}'
```
#### インスタンスのネットワークインターフェイスを取得するカスタムリソース
```
AWSTemplateFormatVersion: 2010-09-09
Description: Get the network interfaces of an instance.
Resources:
CustomResource:
Type: AWS::CloudFormation::CustomResource
DependsOn:
- Function
Properties:
ServiceToken: !GetAtt Function.Arn
ServiceTimeout: 60
INSTANCEID: i-1234567890abcdef0
Function:
Type: "AWS::Lambda::Function"
Properties:
Handler: index.handler
Runtime: python3.8
Timeout: 60
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: |
import cfnresponse
import boto3
status = cfnresponse.SUCCESS
def handler(event, context):
try:
INSTANCEID = event['ResourceProperties']['INSTANCEID']
ec2 = boto3.client('ec2')
response = ec2.describe_network_interfaces(
Filters=[
{
'Name': 'attachment.instance-id',
'Values': [INSTANCEID]
}
]
)
network_interface_id = response['NetworkInterfaces'][0]['NetworkInterfaceId']
responseData = {}
responseData['Data'] = network_interface_id
cfnresponse.send(event, context, status, responseData, None)
except Exception as e:
print(e)
cfnresponse.send(event, context, cfnresponse.FAILED, {}, None)
LambdaExecutionRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
Action:
- "sts:AssumeRole"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: "LambdaEC2Policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "ec2:DescribeNetworkInterfaces"
Resource: "*"
LogGroup:
DeletionPolicy: Delete
UpdateReplacePolicy: Delete
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '${AWS::StackName}-LogGroup'
RetentionInDays: 1
FlowLogPolicy:
Type: "AWS::IAM::ManagedPolicy"
Properties:
ManagedPolicyName: !Sub '${AWS::StackName}-FlowLogPolicy'
Path: "/"
PolicyDocument: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
],
"Resource": "*"
}
]
}
FlowLogRole:
Type: "AWS::IAM::Role"
DependsOn:
- FlowLogPolicy
Properties:
Path: "/"
RoleName: !Sub '${AWS::StackName}-FlowLogRole'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Principal:
Service:
- vpc-flow-logs.amazonaws.com
MaxSessionDuration: 3600
ManagedPolicyArns:
- !Ref FlowLogPolicy
EC2FlowLog:
Type: "AWS::EC2::FlowLog"
DependsOn:
- CustomResource
Properties:
DeliverLogsPermissionArn: !GetAtt FlowLogRole.Arn
LogGroupName: !Ref LogGroup
ResourceId: !GetAtt CustomResource.Data
TrafficType: "ALL"
LogDestinationType: "cloud-watch-logs"
ResourceType: "NetworkInterface"
LogFormat: "${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status}"
MaxAggregationInterval: 60
```
#### 削除イベントの時にAMIを作成してSSMパラメータにAMIIDを保存するカスタムリソース
```
AWSTemplateFormatVersion: 2010-09-09
Description: Create AMI on stack deletion, update SSM, delete old AMI and snapshots
Resources:
# Description: Create AMI on stack deletion, update SSM, delete old AMI and snapshots
AmiCreatorRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: AmiCreatorPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ec2:CreateImage
- ec2:DeregisterImage
- ec2:DeleteSnapshot
- ec2:DescribeImages
Resource: "*"
- Effect: Allow
Action:
- ssm:GetParameter
- ssm:PutParameter
Resource: "*"
PermissionsBoundary: !Sub 'arn:aws:iam::${AWS::AccountId}:policy/DevelopUserBoundary'
AmiCreatorCustomResource:
Type: Custom::AmiCreator
Properties:
ServiceToken: !GetAtt AmiCreatorFunction.Arn
InstanceId: i-1234567890abcdef0
SsmParameterName: /testweb/latest-ami
AmiCreatorFunction:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.12
Handler: index.handler
Timeout: 300
Role: !GetAtt AmiCreatorRole.Arn
Code:
ZipFile: |
import boto3
import cfnresponse
import datetime
ec2 = boto3.client('ec2')
ssm = boto3.client('ssm')
def handler(event, context):
print("Event:", event)
request_type = event['RequestType']
# Delete のときだけ実行
if request_type != 'Delete':
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
return
instance_id = event['ResourceProperties']['InstanceId']
ssm_param = event['ResourceProperties']['SsmParameterName']
# 既存 AMI の取得(削除は後で行う)
try:
param = ssm.get_parameter(Name=ssm_param)
old_ami = param['Parameter']['Value']
print(f"Existing AMI in SSM: {old_ami}")
except ssm.exceptions.ParameterNotFound:
old_ami = None
print("No existing AMI in SSM")
# 新しい AMI 名
timestamp = datetime.datetime.utcnow().strftime('%Y%m%d-%H%M%S')
ami_name = f"stack-delete-ami-{instance_id}-{timestamp}"
# 新しい AMI を作成
print(f"Creating AMI: {ami_name}")
response = ec2.create_image(
InstanceId=instance_id,
Name=ami_name,
NoReboot=True
)
new_ami = response['ImageId']
print(f"Created AMI ID: {new_ami}")
# SSM パラメータを更新(先に新しい AMI を保存)
ssm.put_parameter(
Name=ssm_param,
Value=new_ami,
Type='String',
Overwrite=True
)
print(f"Updated SSM parameter {ssm_param} with {new_ami}")
# 古い AMI とスナップショット削除
if old_ami:
try:
print(f"Describing old AMI: {old_ami}")
images = ec2.describe_images(ImageIds=[old_ami])['Images']
snapshot_ids = []
for bd in images[0].get('BlockDeviceMappings', []):
if 'Ebs' in bd and 'SnapshotId' in bd['Ebs']:
snapshot_ids.append(bd['Ebs']['SnapshotId'])
# AMI 削除
print(f"Deregistering old AMI: {old_ami}")
ec2.deregister_image(ImageId=old_ami)
# スナップショット削除
for snap in snapshot_ids:
try:
print(f"Deleting snapshot: {snap}")
ec2.delete_snapshot(SnapshotId=snap)
except Exception as e:
print(f"Failed to delete snapshot {snap} (ignored): {e}")
except Exception as e:
print(f"Failed to clean old AMI (ignored): {e}")
cfnresponse.send(event, context, cfnresponse.SUCCESS, {
"AmiId": new_ami
})
```
0 件のコメント:
コメントを投稿