Configuration Management and IaC: CloudFormation Custom Resources
1. CloudFormation – Custom Resources
🤔 Custom Resources가 뭔가요?
비유로 이해하기: Custom Resources는 CloudFormation의 "확장 플러그인" 입니다.
- CloudFormation이 기본 지원하지 않는 리소스도 다룰 수 있게 해줌
- 스택 생성/수정/삭제 시점에 원하는 커스텀 로직 실행 가능
언제 사용하나요?
| 사용 사례 | 설명 |
|---|---|
| 미지원 리소스 정의 | CloudFormation이 아직 지원하지 않는 AWS 리소스를 다룰 때 |
| 외부 리소스 프로비저닝 | 온프레미스 리소스, 서드파티(3rd party) 리소스 등 AWS 외부 리소스 관리 |
| Create / Update / Delete 커스텀 로직 | 스택 라이프사이클 단계마다 Lambda로 원하는 스크립트 실행 |
대표 활용 예시
S3 버킷 삭제 시나리오:
CloudFormation 스택 삭제
│
↓
S3 버킷 삭제 시도 → ❌ 실패 (버킷이 비어있지 않음)
→ Custom Resource로 해결!
│
↓
Custom Resource (Lambda) 실행
• S3 버킷의 모든 객체 삭제
• 완료 후 CloudFormation에 신호 전송
│
↓
S3 버킷 삭제 성공 ✅템플릿에서 정의하는 방법
# 방법 1: AWS::CloudFormation::CustomResource
Resources:
MyCustomResource:
Type: AWS::CloudFormation::CustomResource
Properties:
ServiceToken: !GetAtt MyLambdaFunction.Arn
# 방법 2: Custom::MyCustomResourceTypeName (✨ 권장)
Resources:
EmptyS3Bucket:
Type: Custom::EmptyS3Bucket
Properties:
ServiceToken: !GetAtt EmptyBucketLambda.Arn
BucketName: !Ref MyBucket💡 Tip:
Custom::이름형식을 권장합니다. 리소스의 용도를 이름으로 명확히 표현할 수 있어 가독성이 좋습니다.
Custom Resource는 어떻게 정의하나요?
| 속성 | 설명 |
|---|---|
| ServiceToken | CloudFormation이 요청을 보낼 대상 (Lambda ARN 또는 SNS ARN) — 필수 |
| (리전 제약) | ServiceToken은 반드시 같은 리전(Region) 에 있어야 함 |
| Input 데이터 파라미터 | Lambda/SNS로 함께 전달할 추가 입력값 (선택) |
Resources:
EmptyS3Bucket:
Type: Custom::EmptyS3Bucket
Properties:
# 필수: 요청을 받을 Lambda/SNS의 ARN
ServiceToken: !GetAtt EmptyBucketLambda.Arn
# 선택: 커스텀 로직에서 사용할 추가 파라미터
BucketName: !Ref MyBucket
Region: !Ref AWS::Region활용 사례 — S3 버킷 비우고 삭제하기
🚨 문제 상황
• S3 버킷에 객체가 남아있으면 버킷을 삭제할 수 없음
• 비어있지 않은 버킷을 지우려면 → 내부 객체를 먼저 모두 삭제해야 함
• CloudFormation Stack 삭제 시 이 과정에서 막힘
✅ 해결책
• Custom Resource를 활용해 CloudFormation이 버킷을 삭제하기 전에
Lambda로 버킷의 모든 객체를 먼저 비워줌동작 흐름
┌─────────────────────────────────────────────────────────────────┐
│ Use Case: Empty S3 Bucket on Delete │
│ │
│ ┌──────┐ │
│ │ User │ delete stack │
│ └───┬──┘ │
│ │ │
│ ↓ │
│ ┌──────────────────┐ │
│ │ CloudFormation │ │
│ │ Stack │ │
│ └────────┬─────────┘ │
│ │ 1. Delete 이벤트 트리거 │
│ ↓ │
│ ┌──────────────────────────────┐ │
│ │ Custom Resource │ │
│ │ (Lambda Function) │ │
│ │ │ │
│ │ → S3 버킷의 모든 객체 삭제 │ │
│ └────────┬─────────────────────┘ │
│ │ 2. empty bucket │
│ ↓ │
│ ┌──────────────────┐ │
│ │ S3 Bucket │ ← 이제 비어있음 │
│ └────────┬─────────┘ │
│ │ 3. CloudFormation이 안전하게 버킷 삭제 │
│ ↓ │
│ ✅ Stack 삭제 완료 │
│ │
└─────────────────────────────────────────────────────────────────┘Custom Resource 동작 구조
┌─────────────────────────────────────────────────────────────────┐
│ CloudFormation Custom Resource │
│ │
│ ┌──────────────────┐ │
│ │ CloudFormation │ │
│ │ Stack │ │
│ └────────┬─────────┘ │
│ │ │
│ ↓ 1. Create / Update / Delete 이벤트 발생 │
│ │ │
│ ↓ 2. ServiceToken으로 호출 │
│ ┌──────────────────────────────────────────┐ │
│ │ Lambda Function (가장 일반적) │ │
│ │ 또는 │ │
│ │ SNS Topic │ │
│ └────────┬─────────────────────────────────┘ │
│ │ │
│ ↓ 3. 커스텀 로직 실행 │
│ │ (S3 비우기, 외부 API 호출 등) │
│ │ │
│ ↓ 4. CloudFormation에 응답(Response) 전송 │
│ ┌──────────────────┐ │
│ │ CloudFormation │ │
│ │ (다음 단계 진행) │ │
│ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘Backend 옵션
| 백엔드 종류 | 특징 |
|---|---|
| Lambda Function | 가장 일반적, 유연한 로직 구현 가능 |
| SNS Topic | 메시지 기반으로 외부 시스템과 연동할 때 사용 |
Custom Resources – 내부적으로는 어떻게 동작할까?
CloudFormation과 Resource Provider(Lambda/SNS) 사이의 통신은 S3 Pre-signed URL 을 통해 이루어집니다.
핵심 포인트
| 단계 | 설명 |
|---|---|
| ① 요청 | Template developer가 create / update / delete를 CloudFormation에 요청 |
| ② 전달 | CloudFormation이 Resource Provider에 요청 전송 — Request 본문에 S3 Pre-signed URL 포함 |
| ③ 외부 호출 | Resource Provider(Lambda/SNS)가 원하는 외부 시스템(Whatever you want)으로 API 호출 |
| ④ 응답 업로드 | 결과를 JSON으로 만들어 S3 Pre-signed URL 로 S3에 업로드 |
| ⑤ 리스닝 | CloudFormation이 S3를 listen 하다가 응답을 받아 스택 작업을 이어감 |
💡 동기 응답이 아니라 비동기 + S3 콜백 패턴입니다. Resource Provider는 작업이 끝나면 반드시 Pre-signed URL로 응답을 업로드해야 하며, 업로드가 없으면 CloudFormation은 타임아웃까지 계속 listen 합니다.
Hands-On — Lambda 기반 Custom Resource
🎯 목표
• 비어있지 않은 S3 버킷도 CloudFormation으로 안전하게 삭제하기
🚨 제약
• S3 버킷은 객체가 남아있으면 삭제 불가
• 삭제하려면 내부 객체를 먼저 모두 비워야 함
🛠 구현
• Lambda 기반 Custom Resource 생성
• 스택 삭제 시 Lambda가 버킷의 모든 객체를 비움
• 그 다음 CloudFormation이 버킷을 안전하게 삭제핵심 정리
✅ Custom Resource를 쓰는 이유
• CloudFormation이 지원하지 않는 리소스 관리
• 스택 라이프사이클(Create/Update/Delete)에 커스텀 로직 삽입
• AWS 외부 리소스(온프레미스, 3rd party)까지 IaC로 통합
✅ 기억할 핵심 키워드
• ServiceToken → Lambda ARN 또는 SNS Topic ARN
• Custom::MyName 형식 권장
• Delete 시 정리 작업(예: S3 비우기)에 매우 유용2. CloudFormation – Dynamic References
🤔 Dynamic References가 뭔가요?
비유로 이해하기: Dynamic References는 CloudFormation 템플릿이 외부 저장소(SSM/Secrets Manager)에 보관된 값을 실시간으로 끌어다 쓰는 변수 참조 입니다.
- 템플릿에 비밀번호/API 키 같은 민감한 값을 하드코딩하지 않고 참조만 함
- CloudFormation이 Create / Update / Delete 시점에 값을 자동으로 가져옴
- 대표 예시: RDS DB Instance의 마스터 비밀번호를 Secrets Manager에서 가져오기
지원되는 참조 타입
| 타입 | 출처 | 용도 |
|---|---|---|
| ssm | SSM Parameter Store | 평문(Plaintext) 값 |
| ssm-secure | SSM Parameter Store | 암호화된(Secure String) 값 |
| secretsmanager | AWS Secrets Manager | Secrets Manager에 저장된 비밀 값 |
동작 흐름
┌─────────────────────────────────────────────────────────────────┐
│ CloudFormation – Dynamic References │
│ │
│ ┌────────────┐ │
│ │ Template │ '{{resolve:service-name:reference-key}}' │
│ └─────┬──────┘ │
│ │ │
│ │ create / update │
│ ↓ │
│ ┌──────────────────┐ │
│ │ CloudFormation │ │
│ └────────┬─────────┘ │
│ │ │
│ │ get value (reference-key) │
│ ↓ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ SSM Parameter Store │ │ Secrets Manager │ │
│ └──────────┬───────────┘ └──────────┬───────────┘ │
│ │ │ │
│ └──────────────┬───────────────┘ │
│ │ │
│ ↓ result │
│ ┌──────────────────┐ │
│ │ CloudFormation │ → 실제 값으로 치환 후 │
│ │ (스택 작업 계속)│ 리소스 프로비저닝 │
│ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘참조 문법
# 기본 형식
'{{resolve:service-name:reference-key}}'| 서비스 | 문법 |
|---|---|
| SSM (평문) | {{resolve:ssm:parameter-name:version}} |
| SSM Secure | {{resolve:ssm-secure:parameter-name:version}} |
| Secrets Manager | {{resolve:secretsmanager:secret-id:secret-string:json-key:version-stage:version-id}} |
# 예시: RDS DB 마스터 비밀번호를 Secrets Manager에서 참조
Resources:
MyDB:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceClass: db.t3.micro
Engine: mysql
MasterUsername: admin
MasterUserPassword: '{{resolve:secretsmanager:MyDBSecret:SecretString:password}}'
# SSM Parameter 참조 예시
MyEC2:
Type: AWS::EC2::Instance
Properties:
ImageId: '{{resolve:ssm:/myapp/ami-id:1}}'CloudFormation × Secrets Manager × RDS
RDS DB 인스턴스의 마스터 비밀번호를 안전하게 다루는 두 가지 방법이 있습니다.
Option 1 — ManageMasterUserPassword 사용
RDS/Aurora가 직접 Secret을 생성·관리·자동 회전(rotation) 까지 책임집니다. 가장 손이 덜 가는 방법.
┌──────────────────────────────────────────────────────────────────┐
│ Option 1 – ManageMasterUserPassword (자동 관리) │
│ │
│ ┌──────┐ │
│ │ User │ create Stack │
│ └───┬──┘ │
│ │ │
│ ↓ │
│ ┌──────────────────┐ │
│ │ CloudFormation │ │
│ └────────┬─────────┘ │
│ │ │
│ │ create DB and │
│ │ configure Username/Password │
│ ↓ │
│ ┌──────────────────┐ │
│ │ RDS │ ───── create Secret ────► ┌──────────────┐ │
│ └──────────────────┘ │ Secrets │ │
│ (RDS가 직접 Secret 생성 & 회전 관리) │ Manager │ │
│ └──────────────┘ │
└──────────────────────────────────────────────────────────────────┘Resources:
MyDB:
Type: AWS::RDS::DBInstance
Properties:
Engine: mysql
MasterUsername: admin
# 비밀번호를 직접 안 적음 — RDS가 자동으로 Secret 생성 & 관리
ManageMasterUserPassword: true✅ 장점
- Secret 생성/회전/연결을 RDS가 알아서 처리
- 비밀번호를 템플릿이나 코드에 노출할 필요 없음
Option 2 — Dynamic Reference 사용
먼저 Secret을 만들고, RDS DB에서 Dynamic Reference로 참조, 그 후 Secret과 DB를 연결(Attach) 하여 회전(rotation)이 가능하게 합니다.
┌──────────────────────────────────────────────────────────────────┐
│ Option 2 – Dynamic Reference (수동 연결) │
│ │
│ ① Secret이 먼저 생성됨 │
│ ┌──────────────┐ │
│ │ Secrets │ │
│ │ Manager │ ◄────── secret 생성 │
│ └──────┬───────┘ │
│ │ │
│ │ ② Reference secret in RDS DB instance │
│ │ ({{resolve:secretsmanager:...}}) │
│ ↓ │
│ ┌──────────────┐ │
│ │ RDS │ │
│ │ DB Instance │ │
│ └──────┬───────┘ │
│ │ │
│ │ ③ Secret을 RDS DB Instance에 link │
│ │ (rotation을 위한 연결) │
│ ↓ │
│ AWS::SecretsManager::SecretTargetAttachment │
│ │
└──────────────────────────────────────────────────────────────────┘Resources:
# ① Secret 생성
DBSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: MyDBSecret
GenerateSecretString:
SecretStringTemplate: '{"username":"admin"}'
GenerateStringKey: password
PasswordLength: 16
ExcludeCharacters: '"@/\'
# ② Dynamic Reference로 Secret 값을 RDS에 주입
MyDB:
Type: AWS::RDS::DBInstance
Properties:
Engine: mysql
MasterUsername: !Sub '{{resolve:secretsmanager:${DBSecret}:SecretString:username}}'
MasterUserPassword: !Sub '{{resolve:secretsmanager:${DBSecret}:SecretString:password}}'
# ③ Secret과 RDS를 연결 (자동 회전을 위해 필요)
SecretRDSAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref DBSecret
TargetId: !Ref MyDB
TargetType: AWS::RDS::DBInstanceOption 1 vs Option 2 비교
| 항목 | Option 1 — ManageMasterUserPassword | Option 2 — Dynamic Reference |
|---|---|---|
| Secret 생성 주체 | RDS가 자동 생성 | 사용자가 명시적으로 생성 |
| 세부 제어 | 제한적 | 비밀번호 정책, 이름 등 자유로운 제어 |
| 회전(rotation) | RDS가 자동 관리 | SecretTargetAttachment로 연결 필요 |
| 템플릿 복잡도 | 단순 | 상대적으로 복잡 |
| 추천 상황 | 빠르게 안전한 기본값을 원할 때 | Secret을 다른 서비스에서도 공유해야 할 때 |
핵심 정리
✅ Dynamic References가 좋은 이유
• 템플릿에 비밀 값을 절대 하드코딩하지 않음
• 값 변경 시 템플릿 수정 없이 외부 저장소만 업데이트
• Create / Update / Delete 시점에 항상 최신 값 사용
✅ 기억할 핵심 키워드
• '{{resolve:service-name:reference-key}}'
• ssm / ssm-secure / secretsmanager
• RDS 비밀번호 → ManageMasterUserPassword 또는 Dynamic Reference
• Secret 회전 연결 → AWS::SecretsManager::SecretTargetAttachment