はじめに
AWS認定試験ではCloudFormationに関する問題が多く出題されますが、これまでCloudFormationに触れたことがない方にとっては、「そもそもCloudFormationとは何か?」や「どのように実行・管理するのか?」といった具体的なイメージが湧かないかもしれません。
本ハンズオンでは、CloudFormationを使ってリソースの作成を実際に行いながら、その仕組みを理解することを目指します。
CloudFormationとは?
※CloudFormationについて概要を知っている方は読み飛ばしてもらっていいです。
簡単にいうとAWSのリソースの設定値をテンプレート化してリソースを配備・管理が可能なサービスです。
テンプレートすることで以下のメリットがあります。
- 一貫性のある環境を迅速に構築できる
- インフラストラクチャをコード(Infrastructure as Code, IaC)として管理できる
- AMS内のリソースの作成、更新、削除といった運用作業を自動化し、効率敵に管理できる
ハンズオン概要
このハンズオンの構成は以下です。
このハンズオンでは、CloudFormationを使用して、以下のS3バケットを構築します。
- 特定のIAMロールを持つLambda関数のみが、バケットに対してオブジェクトの取得、登録、更新、削除が可能(バケットポリシーの設定)
- アップロードされたファイルは1日後に自動削除(ライフサイクルポリシーの設定)
1. 前提条件
事前に以下を準備してください。
- AWSアカウント
- IAMロールとLambda関数の事前作成
2. CloudFormationテンプレートの作成
以下がS3バケットとIAMポリシーを作成するCloudFormationテンプレートの例です。
Parameters:
S3BucketName:
Type: String
Description: "Name of the S3 bucket."
IAMRoleArn:
Type: String
Description: "ARN of the IAM role that will have access to the S3 bucket."
ExpirationInDays:
Type: Number
Default: 1
Description: "Number of days after which objects expire in the S3 bucket."
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref S3BucketName
LifecycleConfiguration:
Rules:
- Id: "ExpireFilesAfterSpecifiedDays"
Status: "Enabled"
ExpirationInDays: !Ref ExpirationInDays
S3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3Bucket
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: !Ref IAMRoleArn
Action:
- s3:GetObject
- s3:ListBucket
- s3:PutObject
- s3:DeleteObject
Resource:
- !Sub arn:aws:s3:::${S3BucketName}/*
- !Sub arn:aws:s3:::${S3BucketName}
3. CloudFormationスタックのデプロイ
- AWS管理コンソールのCloudFormationの画面を開きます。「スタックの作成」を選択してください。
- テンプレートの準備画面が表示されます。アップロードをします。以下指定の上、「次へ」を指定してください。
テンプレートの準備:既存のテンプレートを選択
テンプレートの指定:テンプレートファイルのアップロード
ファイルの選択:2. CloudFormationテンプレートの作成のyamlファイルを選択
- スタックの詳細を選択画面が表示されます。以下を指定の上、「次へ」を選択してください。スタック名:任意の文字列を入力してください。パラメータ
パラメータ名 | 設定値 | 説明・注意事項 |
---|---|---|
S3BucketName | 【作成するS3バケット名】 | S3の規約にあった文字列をしてしてください。 規約外の場合スタックの作成が失敗します。 |
IAMRoleArn | 【Lambda関数に紐づけするロールのARN】 | Lambda関数からのアクセスを許可します。 |
ExpirationInDays | 1 | オブジェクトの有効期限(日) 指定した日付が過ぎるとバケットから削除されます。 今回はハンズオンなので1日に設定します。 |
- スタックオプションの設定画面が表示されます。デフォルトのまま「次へ」を選択してください。
- 確認して作成画面が表示されます。設定内容確認の上、「送信」を選択してください。
- スタックが作成されます。しばらくすると「CREATE_IN_PROGRESS」から「CREATE_COMPLETE」に変わります。
4. バケットの確認
※ユーザの権限によっては閲覧ができない可能性があります。
- AWS管理コンソールのS3の画面を開きます。指定したバケットが作成されています。バケットを選択して詳細を確認します。
- アクセス許可を選択して設定したバケットポリシーが正しく設定されているか確認してみます。
- バケットポリシーも正しく設定されています。
- 管理を選択して、ライフサイクルポリシーが正しく設定されているか確認してみます。
- ライフサイクルポリシーも正しく設定されています。
5. Lambda関数の設定
Lambda関数が指定のIAMロールを持つことを確認し、S3バケットへのアクセスを実行します。
lambda関数の例は以下です。
ランタイム:python 3.12
環境変数
環境変数名 | 設定値 |
---|---|
AWS_REGION | S3バケットの配備先リージョン(「ap-northeast-1」など |
BUCKET_NAME | 作成したS3バケット名 |
コード
import boto3
import datetime
import json
import os
region = os.getenv('AWS_REGION', 'ap-northeast-1')
s3_client = boto3.client('s3', region_name=region)
bucket_name = os.getenv('BUCKET_NAME', '')
def lambda_handler(event, context):
command = event.get('command')
bucket_name = event.get('bucket_name')
file_name = event.get('file_name')
if not command or not bucket_name:
return {
'statusCode': 400,
'body': json.dumps('Error: command and bucket_name are required parameters.')
}
try:
if command == 'upload_file':
return upload_file(bucket_name)
elif command == 'get_file':
if not file_name:
return {
'statusCode': 400,
'body': json.dumps('Error: file_name is required for get_file command.')
}
return get_file(bucket_name, file_name)
elif command == 'list_files':
return list_files(bucket_name)
elif command == 'delete_file':
if not file_name:
return {
'statusCode': 400,
'body': json.dumps('Error: file_name is required for delete_file command.')
}
return delete_file(bucket_name, file_name)
else:
return {
'statusCode': 400,
'body': json.dumps('Error: Invalid command.')
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps(f'Error: {str(e)}')
}
def upload_file():
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
file_content = f"Current time: {current_time}"
file_name = f"current_time_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
try:
s3_client.put_object(
Bucket=bucket_name,
Key=file_name,
Body=file_content
)
return {
'statusCode': 200,
'body': json.dumps(f'File {file_name} uploaded to {bucket_name}.')
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps(f'Error uploading file: {str(e)}')
}
def get_file(file_name):
try:
response = s3_client.get_object(
Bucket=bucket_name,
Key=file_name
)
file_content = response['Body'].read().decode('utf-8')
return {
'statusCode': 200,
'body': json.dumps(f'File content: {file_content}')
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps(f'Error retrieving file: {str(e)}')
}
def list_files():
try:
response = s3_client.list_objects_v2(
Bucket=bucket_name
)
files = [obj['Key'] for obj in response.get('Contents', [])]
return {
'statusCode': 200,
'body': json.dumps(f'Files in bucket {bucket_name}: {files}')
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps(f'Error listing files: {str(e)}')
}
def delete_file(file_name):
try:
s3_client.delete_object(
Bucket=bucket_name,
Key=file_name
)
return {
'statusCode': 200,
'body': json.dumps(f'File {file_name} deleted from {bucket_name}.')
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps(f'Error deleting file: {str(e)}')
}
テストイベント:
①ファイル登録
{
"command": "upload_file"
}
②ファイル取得
{
"command": "get_file",
"file_name": "current_time_********_******.txt" // ファイル名を指定
}
③ファイル一覧取得
{
"command": "list_files"
}
④ファイル削除
{
"command": "delete_file",
"file_name": "current_time_********_******.txt" // ファイル名を指定
}
6. Lambdaでアクセス検証
指定のIAMロールがLambdaに紐づいていない場合(バケット取得時の例)
{
"statusCode": 500,
"body": "\"Error retrieving file: An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:sts::************:assumed-role/***************/handson-s3bucketaccess is not authorized to perform: s3:ListBucket on resource: \\\"arn:aws:s3:::20241020-handsonbucket\\\" because no identity-based policy allows the s3:ListBucket action\""
}
指定のIAMロールが紐づいている場合
①ファイル登録
{
"statusCode": 200,
"body": "\"File current_time_20241020_070609.txt uploaded to 20241020-handsonbucket.\""
}
S3バケットを確認するとファイルが登録されています。
②ファイル取得
{
"statusCode": 200,
"body": "\"File content: Current time: 2024-10-20 07:06:09\""
}
③ファイル一覧取得
S3バケットに以下が登録されている状態で実行してみます。
{
"statusCode": 200,
"body": "\"Files in bucket 20241020-handsonbucket: ['current_time_20241020_070609.txt', 'current_time_20241020_071216.txt', 'current_time_20241020_071229.txt']\""
}
④ファイル削除
S3バケット登録されている「current_time_20241020_070609.txt」を削除してみます。
{
"statusCode": 200,
"body": "\"File current_time_20241020_070609.txt deleted from 20241020-handsonbucket.\""
}
S3バケットを確認するとファイルが削除されています。
7. ライフサイクルポリシーの検証
こちらのハンズオンではライフサイクルポリシーの検証はハンズオンの時間内でできないため、省略します。
まとめ
このハンズオンでは、CloudFormationを使ってS3バケットの作成とIAM制限、ライフサイクルポリシーを実装する方法を紹介しました。
CloudFormationに初めて触れる方でも、実際の利用イメージが掴めたのではないでしょうか。
ぜひこのハンズオンを参考に、他のリソースの配備オプションの設定や、別のサービスリソースの配備にも挑戦してみてください。