Introduction
- AWS CloudFormation Custom Resources là một tính năng nâng cao cho phép mở rộng các khả năng vốn có của CloudFormation thông qua việc tích hợp các logic tùy chỉnh khi các resource types mặc định không đáp ứng được yêu cầu.
Cơ chế hoạt động:
Khi Create/Update/Delete stack:
- CloudFormation gặp Custom Resource trong template và dừng stack operation
- Custom Resource sẽ gọi tới Lambda/SNS với Request Type tương ứng (Create/Update/Delete) để thực hiện xử lý như mong muốn
- Lambda/SNS xử lý xong sẽ trả về response (SUCCESS/FAILED) cho CloudFormation
- CloudFormation nhận response và tiếp tục stack operation
Use cases:
- Tích hợp với non-AWS services:
- Third-party services
- Internal APIs của doanh nghiệp
- External systems
- Thực thi các operations đặc thù, phức tạp mà CloudFormation native không support:
- Xử lý vòng lặp phức tạp
- Custom DNS configurations
- Xóa non-empty S3 bucket
Lưu ý quan trọng khi sử dụng Custom Resource:
- Để đảm bảo Custom Resource hoạt động, Lambda function hoặc SNS topic phải được tạo trong cùng region với CloudFormation stack
- CloudFormation sẽ đợi response từ Lambda/SNS với Default timeout 1 giờ, nếu quá 1 giờ không nhận được response, CloudFormation sẽ coi như FAILED và rollback stack
Lab Introduction
- AWS experience: Intermediate
- Time to complete: 30+ minutes
- AWS Region: US East (N. Virginia) us-east-1
- Services used: CloudFormation, S3, Lambda, IAM
Architecture
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/8acb71c1-0010-4edb-8a25-e924b1b678af.png)
Trong bài lab này, chúng ta sẽ implement một use case phổ biến Lambda-backed Custom Resource: tự động xóa các objects trong S3 bucket trước khi xóa bucket đó. Khi CloudFormation thực hiện DELETE stack, flow xử lý sẽ diễn ra như sau: CloudFormation phát hiện Custom Resource và gửi request DELETE tới Lambda, Lambda function sẽ xóa tất cả objects trong bucket, sau khi nhận được response SUCCESS từ Lambda, CloudFormation sẽ tiếp tục xóa bucket rỗng. Đây là một demo hoàn chỉnh cho thấy sức mạnh của Custom Resource trong việc mở rộng khả năng của CloudFormation.
Task Details
- Demo with Template CloudFormation Native
- Create CloudFormation template
- Deploy CloudFormation Template
- Upload Objects to S3 Bucket
- Delete stack
- Verify kết quả
- Demo with Template CloudFormation With Custom Resource
- Create CloudFormation Custom Resource template
- Deploy CloudFormation Custom Resource Template
- Upload Objects to S3 Bucket
- Delete stack
- Verify kết quả
Trước khi vào việc ngay với CloudFormation Custom Resource, mình sẽ demo với CloudFormation Native để thấy được lỗi cũng như hạn chế của nó. Bạn nào đã gặp lỗi này rồi có thể bỏ qua step này và vào việc luôn với CloudFormation Custom Resource nha.
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Demo CloudFormation Native'
Parameters:
BucketName:
Type: String
Default: d-s3-cmp-cleaning-on-delete-bucket
Description: Name of the S3 bucket to create
Resources:
demoBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
- Mô tả các resource được tạo
- S3 Bucket (
demoBucket
):- Bucket Name: Default là
d-s3-cmp-cleaning-on-delete-bucket
- Copy nội dung Template phía trên và lưu lại với tên
s3-stack.yaml
- Tại CloudFormation Console -> Chọn Menu Stack -> Create stack
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/9828730e-4443-4b37-901b-182c1bd81850.png)
- Upload file
s3-stack.yaml
và nhấn Next![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/1246775f-312a-478c-8e31-7158ebc44a18.png)
- Nhập tên Stack:
d-cfn-cmp-custom-resource-demo
(Chú ý: Stack name không được trùng trong cùng 1 Region). ![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/8134039e-dc73-4c5f-aa7e-0a35f1b3b2c8.png)
- Next cho đến khi hoàn thành việc tạo Stack
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/8d20eaad-560b-43ba-a661-59c487bbc9ed.png)
- Chúng ta sẽ đợi cho đến khi Resource được tạo xong.
Sau khi đợi vài phút thì Stack đã tạo thành công!![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/1fb86042-f298-4925-bb50-780218392ce5.png)
1.3 Upload Objects to S3 Bucket
Sau khi stack được tạo thành công, chúng ta sẽ upload file vào bucket thông qua AWS Console:
- Tại CloudFormation Console -> Chọn Menu Resources -> Click chọn url của S3
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/faf13349-1535-4f4b-9f6d-beec77f6e5f6.png)
- Tại S3 bucket Console -> Menu Objects -> Click chọn Upload
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/eed5b924-d1d9-4af7-9d85-0ef47d3accb3.png)
- Add files và nhấn Upload
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/de3a0adb-b45b-4dcb-97ac-8f07f24db400.png)
- File đã upload thành công!!
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/a536f75e-7ed0-4ac8-ad98-e381b44f518b.png)
1.4 Delete stack
Bây giờ chúng ta sẽ xóa stack để thấy lỗi khi xóa bucket chứa object
Tại CloudFormation Console -> Menu Resources -> Click Delete
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/73045321-2ae0-4f23-bd1b-3bad83652e6e.png)
Chờ chút xíu để Cloudformation xóa stack
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/d435086f-8ecb-470d-a78a-bcebebeaf018.png)
1.5 verify kết quả
Đúng như dự đoán Cloudformation báo lỗi không xoá được non-empty S3 Bucket
This AWS::S3::Bucket resource is in a DELETE_FAILED state.
Resource handler returned message: "The bucket you tried to delete is not empty
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/33484051-3cf3-41d1-b4ef-5038657483cd.png)
Solution:
- Xóa thủ công từ Amazon S3 Console
- Sử dụng AWS SDK
- Sử dụng Custom Resource
- CloudFormation Template With Custom Resource
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Demo CloudFormation Custom Resource - delete a non-empty S3 Bucket'
Parameters:
BucketName:
Type: String
Default: d-s3-cmp-cleaning-on-delete-bucket
Description: Name of the S3 bucket to create
Resources:
demoBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
cleanupFunctionRole:
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'
- 'arn:aws:iam::aws:policy/AmazonS3FullAccess'
cleanupFunction:
Type: 'AWS::Lambda::Function'
Properties:
Handler: 'index.lambda_handler'
Role: !GetAtt cleanupFunctionRole.Arn
Code:
ZipFile: |
import json
import boto3
import urllib3
def lambda_handler(event, context):
print(f"Event received: {json.dumps(event)}")
try:
if event['RequestType'] == 'Delete':
bucket_name = event['ResourceProperties']['BucketName']
cleanup_bucket(bucket_name)
send_response(event, context, "SUCCESS", {})
else:
send_response(event, context, "SUCCESS", {})
except Exception as e:
print(f"Error: {str(e)}")
send_response(event, context, "FAILED", {})
def cleanup_bucket(bucket_name):
s3 = boto3.client('s3')
try:
paginator = s3.get_paginator('list_objects_v2')
page_iterator = paginator.paginate(Bucket=bucket_name)
for page in page_iterator:
if 'Contents' in page:
objects = [{'Key': obj['Key']} for obj in page['Contents']]
s3.delete_objects(
Bucket=bucket_name,
Delete={'Objects': objects}
)
print(f"Deleted {len(objects)} objects")
except s3.exceptions.NoSuchBucket:
print("Bucket already deleted")
def send_response(event, context, status, data):
response = {
'Status': status,
'Reason': f'See CloudWatch Log Stream: {context.log_stream_name}',
'PhysicalResourceId': 'CustomResourcePhysicalID',
'StackId': event['StackId'],
'RequestId': event['RequestId'],
'LogicalResourceId': event['LogicalResourceId'],
'Data': data
}
http = urllib3.PoolManager()
try:
http.request(
'PUT',
event['ResponseURL'],
headers={'Content-Type': ''},
body=json.dumps(response)
)
except Exception as e:
print(f"Failed to send response: {str(e)}")
Runtime: 'python3.9'
Timeout: 300
MemorySize: 128
cleanupS3BucketOnDelete:
Type: Custom::S3BucketCleanup
Properties:
ServiceToken: !GetAtt cleanupFunction.Arn
BucketName: !Ref BucketName
bucketDependency:
Type: Custom::BucketDependency
DependsOn: demoBucket
Properties:
ServiceToken: !GetAtt cleanupFunction.Arn
BucketName: !Ref BucketName
- Mô tả các resource được tạo
- S3 Bucket (
demoBucket
):- Bucket Name: Default là
d-s3-cmp-cleaning-on-delete-bucket
- IAM Role (
cleanupFunctionRole
):- Role cho Lambda function
- Có 2 managed policies: Lambda basic execution và S3 full access
- Lambda Function (
cleanupFunction
):- Runtime: Python 3.9
- Timeout: 5 phút
- Memory: 128MB
- Code xử lý:
- List objects trong bucket
- Delete objects
- Send response về CloudFormation
- Custom Resources:
cleanupS3BucketOnDelete
: Trigger Lambda để cleanup bucketbucketDependency
:- Đảm bảo bucket tồn tại trước khi cleanup (DependsOn)
- Điều khiển thứ tự xóa khi DELETE stack
- Trigger cleanup trước khi xóa bucket
- Xử lý race condition trong quá trình tạo/xóa resources
- Copy Template phía trên và lưu lại với tên
s3-custom-resource-stack.yaml
- Tại CloudFormation Console -> Chọn Menu Stack -> Create stack
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/9828730e-4443-4b37-901b-182c1bd81850.png)
- Upload file
s3-custom-resource-stack.yaml
và nhấn Next![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/5641c41e-bc7d-4607-bc7b-e9f5003b8065.png)
- Nhập tên Stack:
d-cfn-cmp-custom-resource-demo
(Chú ý: Stack name không được trùng trong cùng 1 Region).![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/0a3be00f-f254-4646-a02f-b69418d4b2c3.png)
- Next cho đến khi hoàn thành việc tạo Stack
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/463bc544-0859-451c-a4f1-d39245385ca5.png)
- Chúng ta sẽ đợi cho đến khi Resource được tạo xong.
Sau khi đợi vài phút thì Stack đã tạo thành công!
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/404035fb-2e74-44bc-8e19-12da4fcce03a.png)
2.3 Upload Objects to S3 Bucket
Sau khi stack được tạo thành công, chúng ta sẽ upload file vào bucket thông qua AWS Console:
- Tại CloudFormation Console -> Chọn Menu Resources -> Click chọn url của S3
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/6b61a9c8-f3cf-4efe-954f-00d21b84fe56.png)
- Tại S3 bucket Console -> Menu Objects -> Click chọn Upload
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/08ecc851-2328-4bc7-9038-6dbcd462840b.png)
- Add files và nhấn Upload
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/b242eb6d-76fa-49a5-8d80-9d22c4098766.png)
- File đã upload thành công!!
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/467fdfc4-2b65-4156-95c8-ef62d4929c19.png)
2.4 Delete Custom Resource stack
Bây giờ chúng ta sẽ xóa stack để xem còn bị lỗi khi xóa non-empty bucket nữa không nha!
Tại CloudFormation Console -> Menu Resources -> Click Delete
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/9144137b-32cb-49b4-a83b-a48be52db80b.png)
Pop-up confirm: Chọn Delete
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/05016f33-8aee-4ca2-ac3d-903515cb36f3.png)
Chờ chút xíu để Cloudformation xóa stack
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/c21e06ae-8950-4820-962d-745b4cfea2b1.png)
2.5 verify kết quả
Quả là "phép thuật" của Custom Resource, đã xoá được non-empty S3 Bucket thành công!!!
- CloudFormation Console -> Menu Events - updated
![](https://static.cloudmentor.pro/assets/20241230-cloudformationl-custom-resource-s3-cleanup/4a0e0413-60cd-4890-a838-c95abad4a675.png)
- CloudFormation Console -> Menu Resources
Challenge
Demo Custom Resource lần này lambda chỉ đơn giản là xóa objects trong S3 bucket. Trong thực tế, S3 cleanup phức tạp hơn nhiều với versioning, delete markers, object size... Mọi người có thể level up với các thử thách sau:
- Mở rộng Lambda function xử lý phức tạp hơn:
- Xoá versioned objects, Delete Markers
- Xoá object có kích thước lớn tối ưu nhất
- Thêm logging chi tiết
- Thêm các tính năng:
- Backup objects trước khi xóa
- Xóa theo điều kiện (prefix, age, size)
- Thông báo qua SNS khi xóa objects
- Best practices cho Lambda deployment:
- Tách Lambda code ra package riêng
- Upload code lên S3 và reference trong CFN
- Sử dụng SAM để deploy Lambda
Tổng Kết
Qua bài lab này, chúng ta vừa khám phá:
- Khả năng "phép thuật" của Custom Resource: nơi CloudFormation và Lambda/SNS tạo nên điều kỳ diệu trong việc xử lý các yêu cầu phức tạp
- Chinh phục thử thách delete "non-empty S3 bucket": từ mission impossible thành mission completed với Lambda function
- Nghệ thuật tích hợp Lambda-CloudFormation: như một cặp đôi ăn ý, cùng nhau xử lý mọi thử thách
Kết luận: Custom Resource - "Người hùng thầm lặng" của CloudFormation, biến những điều tưởng chừng bất khả thi thành có thể!
Tài liệu Tham khảo: