$ yh.log
[DOP-C02] Domain 2 - Configuration Management and IaC (설정 관리 및 인프라)

[DOP-C02] Domain 2 - Configuration Management and IaC (설정 관리 및 인프라)

AWSDOP-C02DevOpsIaCCloudFormationCustom Resources

작성자 : 오예환 | 작성일 : 2026-05-12 | 수정일 : 2026-05-12 | 조회수 :

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는 어떻게 정의하나요?

속성설명
ServiceTokenCloudFormation이 요청을 보낼 대상 (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에서 가져오기

지원되는 참조 타입

타입출처용도
ssmSSM Parameter Store평문(Plaintext) 값
ssm-secureSSM Parameter Store암호화된(Secure String) 값
secretsmanagerAWS Secrets ManagerSecrets 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::DBInstance

Option 1 vs Option 2 비교

항목Option 1 — ManageMasterUserPasswordOption 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