$ 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