본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 28. · 0 Views
CloudFormation 중첩 스택과 재사용 패턴
AWS CloudFormation의 중첩 스택, 교차 스택 참조, 조건문과 매핑, 사용자 정의 리소스, StackSets를 활용한 멀티 리전 배포까지 인프라 코드의 재사용 패턴을 초급자도 이해할 수 있게 설명합니다.
목차
1. 중첩 스택으로 모듈화
김개발 씨는 최근 회사의 AWS 인프라를 CloudFormation으로 관리하기 시작했습니다. 처음에는 하나의 템플릿 파일에 모든 리소스를 정의했는데, 어느새 파일이 3,000줄을 넘어가면서 수정할 때마다 식은땀이 나기 시작했습니다.
"이거 한 줄 잘못 고치면 전체 인프라가 날아가는 거 아닌가요?"
메인 스택 - 중첩 스택을 조립합니다 Resources: # VPC 모듈을 중첩 스택으로 불러옵니다 NetworkStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: https://s3.amazonaws.com/my-bucket/network.yaml Parameters: Environment: !Ref Environment VpcCidr: 10.0.0.0/16 # 데이터베이스 모듈을 불러옵니다 DatabaseStack: Type: AWS::CloudFormation::Stack DependsOn: NetworkStack Properties: TemplateURL: https://s3.amazonaws.com/my-bucket/database.yaml Parameters: VpcId: !GetAtt NetworkStack.Outputs.VpcId SubnetIds: !GetAtt NetworkStack.Outputs.PrivateSubnetIds
다음 코드를 살펴봅시다.
AWSTemplateFormatVersion: '2010-09-09'
김개발 씨는 입사 6개월 차 클라우드 엔지니어입니다. 회사에서 운영하는 서비스가 점점 커지면서 CloudFormation 템플릿도 덩달아 거대해졌습니다.
VPC 설정, 보안 그룹, EC2 인스턴스, RDS, ElastiCache, ALB까지 하나의 파일에 모두 담다 보니 스크롤을 내리는 것만으로도 현기증이 날 지경이었습니다. 어느 날 선배 개발자 박시니어 씨가 김개발 씨의 모니터를 보더니 물었습니다.
"개발 씨, 혹시 중첩 스택에 대해 들어본 적 있어요?" 그렇다면 중첩 스택이란 정확히 무엇일까요? 쉽게 비유하자면, 중첩 스택은 마치 요리 레시피북을 장르별로 나누는 것과 같습니다.
한식, 양식, 중식 레시피를 모두 한 권에 담으면 찾기도 어렵고 관리하기도 힘듭니다. 하지만 장르별로 책을 나누면 필요한 레시피만 빠르게 찾을 수 있고, 한식 레시피만 업데이트하고 싶을 때도 다른 책에 영향을 주지 않습니다.
중첩 스택이 없던 시절에는 어땠을까요? 개발자들은 수천 줄짜리 거대한 템플릿 파일 하나를 관리해야 했습니다.
누군가 네트워크 설정을 수정하다가 실수로 데이터베이스 설정까지 건드리는 일도 있었습니다. 더 큰 문제는 비슷한 인프라를 다른 프로젝트에서도 필요할 때였습니다.
복사-붙여넣기를 하다 보면 어느새 여러 버전의 템플릿이 제각각 존재하게 되었습니다. 바로 이런 문제를 해결하기 위해 중첩 스택이 등장했습니다.
중첩 스택을 사용하면 관심사의 분리가 가능해집니다. 네트워크 담당자는 network.yaml만, 데이터베이스 담당자는 database.yaml만 관리하면 됩니다.
또한 재사용성도 높아집니다. 한 번 잘 만들어둔 VPC 템플릿은 다른 프로젝트에서도 그대로 가져다 쓸 수 있습니다.
무엇보다 변경 영향 범위가 최소화된다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 NetworkStack 리소스를 보면 AWS::CloudFormation::Stack 타입을 사용합니다. 이것이 바로 중첩 스택을 선언하는 방법입니다.
TemplateURL에는 S3에 업로드된 자식 템플릿의 경로를 지정합니다. Parameters를 통해 부모 스택에서 자식 스택으로 값을 전달할 수 있습니다.
다음으로 DatabaseStack을 보면 DependsOn 속성이 눈에 띕니다. 데이터베이스는 VPC가 먼저 생성되어야 하므로 NetworkStack에 의존성을 걸어둔 것입니다.
그리고 !GetAtt NetworkStack.Outputs.VpcId를 통해 네트워크 스택에서 출력한 값을 받아옵니다. 실제 현업에서는 어떻게 활용할까요?
대부분의 기업에서는 네트워크, 보안, 컴퓨팅, 데이터베이스, 모니터링 등의 계층으로 스택을 분리합니다. 예를 들어 새로운 마이크로서비스를 추가할 때, 이미 만들어둔 네트워크 스택과 보안 스택 위에 컴퓨팅 스택만 새로 올리면 됩니다.
마치 레고 블록을 쌓듯이 말입니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 너무 잘게 쪼개는 것입니다. 스택 하나에 리소스가 2-3개밖에 없다면 오히려 관리 포인트만 늘어납니다.
또한 중첩 스택은 최대 500개의 리소스 제한이 부모 스택에도 적용된다는 점을 기억해야 합니다. 따라서 적절한 크기로 나누는 것이 중요합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언을 듣고 김개발 씨는 3,000줄짜리 템플릿을 5개의 중첩 스택으로 분리했습니다.
"이제 네트워크 수정할 때 데이터베이스 설정 건드릴 걱정 안 해도 되겠네요!" 중첩 스택을 제대로 이해하면 더 깔끔하고 유지보수하기 쉬운 인프라 코드를 작성할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 중첩 스택 템플릿은 반드시 S3에 업로드해야 합니다. 로컬 파일 경로는 사용할 수 없습니다.
- 자식 스택에서 부모 스택으로 값을 전달하려면 Outputs 섹션을 활용하세요.
- 스택 간 의존성이 있다면 DependsOn을 명시적으로 선언하는 것이 안전합니다.
2. 교차 스택 참조
김개발 씨가 중첩 스택에 익숙해질 무렵, 새로운 고민이 생겼습니다. 네트워크 팀에서 관리하는 VPC 스택과 자신이 관리하는 애플리케이션 스택이 서로 다른 시점에 배포되는데, 어떻게 VPC ID를 안전하게 참조할 수 있을까요?
매번 하드코딩하자니 VPC가 바뀔 때마다 수정해야 하고, 중첩 스택으로 묶자니 배포 주기가 맞지 않습니다.
VPC 식별자 Value: !Ref MyVPC Export: Name: !Sub ${AWS::StackName}-VpcId PublicSubnetId: Value: !Ref PublicSubnet Export: Name: !Sub ${AWS::StackName}-PublicSubnetId --- # app-stack.yaml - 값을 가져오는 스택 Resources: AppInstance: Type: AWS::EC2::Instance Properties: SubnetId: !ImportValue network-stack-PublicSubnetId SecurityGroupIds: - !ImportValue network-stack-WebSG
다음 코드를 살펴봅시다.
# network-stack.yaml - 값을 내보내는 스택
Outputs:
VpcId:
김개발 씨의 회사에서는 인프라 팀과 개발팀이 분리되어 있습니다. 인프라 팀은 VPC, 서브넷, 보안 그룹 같은 기반 인프라를 관리하고, 개발팀은 그 위에서 애플리케이션을 배포합니다.
문제는 두 팀의 배포 주기가 다르다는 것입니다. 인프라 팀의 이주임 씨가 설명합니다.
"우리는 한 달에 한 번 정도 네트워크 스택을 업데이트해요. 그런데 개발팀은 하루에도 몇 번씩 배포하잖아요.
중첩 스택으로 묶으면 우리가 배포할 때마다 개발팀 서비스에 영향을 줄 수 있어요." 그렇다면 교차 스택 참조란 정확히 무엇일까요? 쉽게 비유하자면, 교차 스택 참조는 마치 회사의 공용 게시판과 같습니다.
인사팀이 "올해 휴가일수: 15일"이라고 게시판에 공지를 올리면, 모든 부서에서 그 정보를 참조할 수 있습니다. 인사팀이 휴가일수를 변경하면 게시판만 업데이트하면 되고, 각 부서는 항상 최신 정보를 볼 수 있습니다.
교차 스택 참조가 없다면 어떻게 될까요? 개발자들은 VPC ID나 서브넷 ID 같은 값을 직접 하드코딩해야 했습니다.
vpc-0abc123def456 같은 값을 템플릿에 직접 넣는 것입니다. 문제는 인프라 팀이 VPC를 재생성하면 그 ID가 바뀐다는 것입니다.
그러면 모든 애플리케이션 템플릿을 일일이 찾아서 수정해야 했습니다. 바로 이런 문제를 해결하기 위해 교차 스택 참조가 등장했습니다.
교차 스택 참조를 사용하면 느슨한 결합이 가능해집니다. 각 스택은 독립적으로 배포되지만, 필요한 값은 안전하게 공유할 수 있습니다.
또한 **단일 진실 공급원(Single Source of Truth)**을 유지할 수 있습니다. VPC ID는 네트워크 스택에서만 관리하고, 다른 스택은 그 값을 참조하기만 하면 됩니다.
위의 코드를 자세히 살펴보겠습니다. 네트워크 스택의 Outputs 섹션을 보면 Export 블록이 있습니다.
여기서 Name은 전체 리전에서 고유해야 합니다. 그래서 보통 ${AWS::StackName}-VpcId 형태로 스택 이름을 접두어로 붙입니다.
이렇게 내보낸 값은 같은 리전의 다른 스택에서 참조할 수 있습니다. 애플리케이션 스택에서는 !ImportValue를 사용합니다.
network-stack-PublicSubnetId처럼 Export할 때 지정한 이름을 그대로 사용합니다. CloudFormation은 자동으로 해당 Export를 찾아서 값을 가져옵니다.
실제 현업에서는 어떻게 활용할까요? 대규모 조직에서는 보통 계층별로 스택을 분리합니다.
네트워크 스택이 VPC와 서브넷을 Export하면, 보안 스택이 그것을 Import해서 보안 그룹을 만들고 다시 Export합니다. 그러면 애플리케이션 스택은 보안 스택에서 보안 그룹을 Import해서 사용합니다.
마치 체인처럼 연결되는 것입니다. 하지만 주의할 점도 있습니다.
가장 중요한 규칙은 Export된 값을 사용하는 스택이 있으면 Export하는 스택을 삭제할 수 없다는 것입니다. 다른 스택에서 참조 중인 값을 갑자기 없애면 안 되기 때문입니다.
따라서 Export를 제거하기 전에 반드시 Import하는 모든 스택을 먼저 업데이트해야 합니다. 또 하나 기억할 점은 교차 스택 참조는 같은 리전 내에서만 동작한다는 것입니다.
서울 리전의 스택에서 도쿄 리전의 Export를 Import할 수는 없습니다. 멀티 리전 배포가 필요하다면 다른 방법을 사용해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 이주임 씨의 설명을 듣고 김개발 씨는 애플리케이션 템플릿에서 하드코딩된 VPC ID를 모두 ImportValue로 교체했습니다.
"이제 인프라 팀이 네트워크를 변경해도 우리 템플릿은 손댈 필요가 없겠네요!" 교차 스택 참조를 제대로 이해하면 팀 간 협업이 훨씬 수월해집니다. 각자의 영역을 독립적으로 관리하면서도 필요한 정보는 안전하게 공유할 수 있습니다.
실전 팁
💡 - Export 이름은 리전 내에서 고유해야 하므로 스택 이름을 접두어로 붙이는 것이 좋습니다.
- Import하는 스택이 있으면 Export하는 스택을 삭제할 수 없으니 의존성을 문서화해두세요.
- aws cloudformation list-exports 명령으로 현재 리전의 모든 Export를 확인할 수 있습니다.
3. 조건문과 매핑
김개발 씨는 이제 개발 환경과 운영 환경을 위한 템플릿을 각각 관리하고 있었습니다. 그런데 두 템플릿의 90%가 동일하고, 인스턴스 크기나 개수만 다를 뿐이었습니다.
"이걸 하나의 템플릿으로 관리할 수는 없을까요?" 박시니어 씨가 웃으며 대답합니다. "조건문과 매핑을 쓰면 되죠."
**조건문(Conditions)**과 **매핑(Mappings)**은 하나의 템플릿으로 여러 환경을 지원하게 해주는 기능입니다. 마치 같은 설계도로 소형 주택과 대형 주택을 지을 수 있는 것과 같습니다.
조건문은 "만약 운영 환경이라면"을, 매핑은 "리전별 AMI 목록"같은 조회 테이블을 제공합니다.
다음 코드를 살펴봅시다.
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
Environment:
Type: String
AllowedValues: [dev, staging, prod]
Mappings:
RegionMap:
ap-northeast-2:
AMI: ami-0c55b159cbfafe1f0
us-east-1:
AMI: ami-0123456789abcdef0
InstanceTypeMap:
dev: { Type: t3.micro, Count: '1' }
prod: { Type: t3.large, Count: '3' }
Conditions:
IsProd: !Equals [!Ref Environment, prod]
CreateAlarm: !Not [!Equals [!Ref Environment, dev]]
Resources:
WebServer:
Type: AWS::EC2::Instance
Properties:
InstanceType: !FindInMap [InstanceTypeMap, !Ref Environment, Type]
ImageId: !FindInMap [RegionMap, !Ref AWS::Region, AMI]
ProdOnlyAlarm:
Type: AWS::CloudWatch::Alarm
Condition: IsProd
Properties:
AlarmName: HighCPU-Production
김개발 씨는 한숨을 쉬었습니다. dev-template.yaml, staging-template.yaml, prod-template.yaml 세 개의 파일을 관리하고 있는데, 보안 그룹 규칙 하나를 수정하려면 세 파일을 모두 열어서 똑같은 작업을 반복해야 했습니다.
당연히 실수도 잦았습니다. 어느 날은 개발 환경에만 수정을 적용하고 운영 환경은 깜빡 잊어버린 적도 있었습니다.
박시니어 씨가 조언합니다. "조건문과 매핑을 쓰면 하나의 템플릿으로 모든 환경을 관리할 수 있어요." 그렇다면 매핑이란 정확히 무엇일까요?
쉽게 비유하자면, 매핑은 마치 메뉴판의 가격표와 같습니다. 음식점에서 "아메리카노"를 주문하면 레귤러는 4,000원, 라지는 5,000원입니다.
음식은 같은데 사이즈에 따라 가격이 다른 것처럼, CloudFormation에서도 같은 리소스지만 환경에 따라 설정값이 다를 수 있습니다. 매핑은 이런 조회 테이블을 정의하는 것입니다.
조건문은 또 무엇일까요? 조건문은 마치 신호등과 같습니다.
초록불이면 직진하고, 빨간불이면 멈추는 것처럼, 조건문은 "운영 환경이면 이 리소스를 생성하고, 아니면 건너뛰어라"라고 지시합니다. CloudWatch 알람처럼 운영 환경에서만 필요한 리소스가 있을 때 유용합니다.
위의 코드를 자세히 살펴보겠습니다. 먼저 Mappings 섹션을 봅시다.
RegionMap은 리전별로 다른 AMI ID를 정의합니다. 서울 리전(ap-northeast-2)과 버지니아 리전(us-east-1)의 동일한 OS라도 AMI ID가 다르기 때문입니다.
InstanceTypeMap은 환경별로 인스턴스 타입과 개수를 정의합니다. FindInMap 함수는 이 조회 테이블에서 값을 꺼내옵니다.
**!FindInMap [InstanceTypeMap, !Ref Environment, Type]**은 "InstanceTypeMap에서 Environment 파라미터 값을 키로 찾고, 그 안에서 Type을 가져와라"라는 뜻입니다. Environment가 prod라면 t3.large가, dev라면 t3.micro가 반환됩니다.
Conditions 섹션에서는 조건을 정의합니다. !Equals는 두 값이 같은지 비교하고, !Not은 결과를 반전시킵니다.
IsProd 조건은 Environment가 prod일 때만 true가 됩니다. CreateAlarm 조건은 Environment가 dev가 아닐 때 true가 됩니다.
리소스에 Condition 속성을 붙이면 조건이 true일 때만 해당 리소스가 생성됩니다. ProdOnlyAlarm은 IsProd가 true, 즉 운영 환경에서만 생성됩니다.
개발 환경에서 이 템플릿을 배포하면 알람 리소스는 아예 건너뜁니다. 실제 현업에서는 어떻게 활용할까요?
대부분의 기업에서는 환경별 인스턴스 크기 조절에 가장 많이 사용합니다. 개발 환경에서는 t3.micro로 비용을 아끼고, 운영 환경에서는 t3.xlarge로 성능을 확보합니다.
또한 리전별 AMI 관리에도 필수입니다. AWS는 리전마다 AMI ID가 다르기 때문에 매핑 없이는 멀티 리전 배포가 불가능합니다.
하지만 주의할 점도 있습니다. 매핑은 정적인 값만 담을 수 있습니다.
런타임에 동적으로 계산된 값은 넣을 수 없습니다. 또한 3단계 깊이까지만 지원됩니다 (첫 번째 키, 두 번째 키, 세 번째 키).
더 복잡한 구조가 필요하다면 SSM Parameter Store를 활용하는 것이 좋습니다. 조건문을 너무 많이 사용하면 템플릿이 복잡해집니다.
"이 조건이면 이것, 저 조건이면 저것, 또 다른 조건이면..." 식으로 가다 보면 나중에 읽기 어려운 스파게티 템플릿이 됩니다. 환경별로 완전히 다른 아키텍처가 필요하다면 차라리 템플릿을 분리하는 것이 나을 수도 있습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 세 개의 템플릿을 하나로 통합한 김개발 씨는 이제 수정 사항을 한 곳에서만 관리합니다.
"파라미터만 바꿔서 배포하면 되니까 실수할 일도 줄었어요!" 조건문과 매핑을 제대로 이해하면 DRY(Don't Repeat Yourself) 원칙을 인프라 코드에도 적용할 수 있습니다. 하나의 템플릿으로 모든 환경을 관리해 보세요.
실전 팁
💡 - 매핑의 키는 영숫자와 일부 특수문자만 허용됩니다. 공백이나 특수문자 사용에 주의하세요.
- 조건문에서 !If 함수를 사용하면 속성 값도 조건부로 설정할 수 있습니다.
- aws cloudformation validate-template 명령으로 배포 전에 문법 오류를 검사하세요.
4. 사용자 정의 리소스
김개발 씨는 새로운 요구사항을 받았습니다. CloudFormation 스택을 배포할 때 S3 버킷을 생성하고, 그 버킷에 초기 설정 파일을 자동으로 업로드해야 한다는 것입니다.
하지만 CloudFormation의 AWS::S3::Bucket 리소스에는 파일을 업로드하는 기능이 없습니다. "이건 CloudFormation으로는 안 되는 건가요?" 박시니어 씨가 답합니다.
"Custom Resource를 쓰면 돼요."
**사용자 정의 리소스(Custom Resources)**는 CloudFormation이 기본적으로 지원하지 않는 작업을 Lambda 함수로 처리하게 해주는 기능입니다. 마치 회사에서 전문 외주업체를 고용하는 것과 같습니다.
CloudFormation이 "이 작업 좀 해주세요"라고 Lambda에게 요청하면, Lambda가 작업을 수행하고 결과를 보고합니다.
다음 코드를 살펴봅시다.
ZipFile: |
import boto3
import cfnresponse
def handler(event, context):
try:
if event['RequestType'] == 'Create':
s3 = boto3.client('s3')
bucket = event['ResourceProperties']['BucketName']
s3.put_object(Bucket=bucket, Key='config.json',
Body='{"version": "1.0"}')
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
except Exception as e:
cfnresponse.send(event, context, cfnresponse.FAILED, {})
# Custom Resource 정의
InitializeConfig:
Type: Custom::InitConfig
Properties:
ServiceToken: !GetAtt InitFunction.Arn
BucketName: !Ref ConfigBucket
김개발 씨는 인프라 자동화에 점점 욕심이 생겼습니다. 스택 하나만 배포하면 서버도 뜨고, 데이터베이스도 생기고, 모든 초기 설정까지 완료되면 얼마나 좋을까요?
하지만 CloudFormation은 AWS 리소스를 생성하는 데는 탁월하지만, 사용자 정의 로직을 실행하는 것은 기본적으로 지원하지 않습니다. 박시니어 씨가 설명합니다.
"Custom Resource를 사용하면 CloudFormation과 Lambda를 연결할 수 있어요. CloudFormation이 Lambda에게 일을 시키고, Lambda가 결과를 보고하는 구조죠." 그렇다면 Custom Resource란 정확히 무엇일까요?
쉽게 비유하자면, Custom Resource는 마치 회사의 특수 업무 담당자와 같습니다. 일반 직원들이 처리할 수 없는 특수한 업무가 생기면, 전문가에게 의뢰합니다.
"이 서류 좀 처리해주세요"라고 요청하면, 전문가가 작업을 완료하고 "완료했습니다" 또는 "문제가 생겼습니다"라고 보고합니다. CloudFormation이 일반 직원이라면, Lambda 함수가 바로 그 전문가입니다.
Custom Resource가 없다면 어떻게 될까요? 개발자들은 CloudFormation 배포 후에 별도의 스크립트를 수동으로 실행해야 했습니다.
"스택 배포했으면 이 스크립트 돌려주세요"라는 식이었습니다. 당연히 실수하기 쉬웠고, 자동화도 불완전했습니다.
누군가 스크립트 실행을 깜빡하면 서비스에 문제가 생기기도 했습니다. 바로 이런 문제를 해결하기 위해 Custom Resource가 등장했습니다.
Custom Resource를 사용하면 완전한 자동화가 가능해집니다. 스택 배포 한 번으로 리소스 생성부터 초기 설정까지 모두 완료됩니다.
또한 스택 삭제 시 정리 작업도 자동화할 수 있습니다. S3 버킷을 삭제하기 전에 안의 파일을 먼저 비우는 것처럼 말입니다.
위의 코드를 자세히 살펴보겠습니다. 먼저 InitFunction은 일반적인 Lambda 함수입니다.
핵심은 handler 함수 내부입니다. **event['RequestType']**은 CloudFormation이 어떤 요청을 보냈는지 알려줍니다.
'Create', 'Update', 'Delete' 세 가지 값이 있습니다. 스택을 처음 생성하면 Create, 수정하면 Update, 삭제하면 Delete가 전달됩니다.
cfnresponse 모듈은 CloudFormation에게 결과를 보고하는 역할을 합니다. 반드시 SUCCESS 또는 FAILED를 응답해야 합니다.
응답하지 않으면 CloudFormation은 1시간 동안 기다리다가 타임아웃으로 실패 처리합니다. InitializeConfig 리소스가 바로 Custom Resource입니다.
Type은 Custom:: 으로 시작하면 어떤 이름이든 사용할 수 있습니다. ServiceToken에는 호출할 Lambda 함수의 ARN을 지정합니다.
Properties에 정의한 값들은 Lambda 함수의 event['ResourceProperties']로 전달됩니다. 실제 현업에서는 어떻게 활용할까요?
가장 흔한 용도는 S3 버킷 비우기입니다. S3 버킷은 안에 객체가 있으면 삭제할 수 없는데, Custom Resource로 삭제 전에 먼저 객체들을 비울 수 있습니다.
외부 API 호출도 많이 사용됩니다. Slack에 배포 알림을 보내거나, PagerDuty에 서비스를 등록하는 것처럼 AWS 외부 서비스와 연동할 때 유용합니다.
하지만 주의할 점도 있습니다. Custom Resource에서 가장 중요한 것은 반드시 응답을 보내야 한다는 점입니다.
예외가 발생해서 응답을 보내지 못하면 스택이 1시간 동안 멈춰있습니다. 따라서 반드시 try-except로 감싸고, except 블록에서도 FAILED 응답을 보내야 합니다.
또한 **멱등성(Idempotency)**을 고려해야 합니다. CloudFormation은 같은 Custom Resource를 여러 번 호출할 수 있습니다.
재시도 상황에서도 문제없이 동작하도록 Lambda 함수를 설계해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
Custom Resource를 적용한 김개발 씨는 이제 스택 하나만 배포하면 모든 초기 설정이 자동으로 완료됩니다. "이제 '스크립트 돌렸어요?' 하고 물어볼 필요가 없겠네요!" Custom Resource를 제대로 이해하면 CloudFormation의 한계를 넘어 완전한 인프라 자동화를 구현할 수 있습니다.
실전 팁
💡 - Lambda 함수에서 cfnresponse.send()는 반드시 호출해야 합니다. 빼먹으면 스택이 1시간 동안 멈춥니다.
- Delete 요청에서도 적절한 처리를 해야 스택 삭제가 원활합니다.
- CloudFormation은 기본적으로 Custom Resource 응답을 1시간 기다리니 빠른 응답을 보장하세요.
5. StackSets으로 멀티 리전 배포
김개발 씨의 회사가 글로벌 서비스를 시작하게 되었습니다. 서울, 도쿄, 싱가포르, 버지니아 총 4개 리전에 동일한 인프라를 구축해야 합니다.
"스택을 4번 배포하면 되는 건가요?" 박시니어 씨가 고개를 흔듭니다. "그러면 나중에 수정할 때 4번 작업해야 해요.
StackSets를 쓰면 한 번에 배포할 수 있어요."
StackSets는 여러 AWS 계정과 리전에 동일한 CloudFormation 스택을 한 번에 배포하는 기능입니다. 마치 프랜차이즈 본사에서 전국 지점에 동일한 인테리어 가이드를 배포하는 것과 같습니다.
한 번 정의한 템플릿으로 수십, 수백 개의 스택을 동시에 관리할 수 있습니다.
다음 코드를 살펴봅시다.
# StackSet 생성 (AWS CLI)
aws cloudformation create-stack-set \
--stack-set-name my-global-infra \
--template-body file://network.yaml \
--parameters ParameterKey=Environment,ParameterValue=prod \
--permission-model SERVICE_MANAGED \
--auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false
# 여러 리전에 스택 인스턴스 배포
aws cloudformation create-stack-instances \
--stack-set-name my-global-infra \
--regions ap-northeast-2 ap-northeast-1 ap-southeast-1 us-east-1 \
--deployment-targets OrganizationalUnitIds=ou-xxxxx
# 배포 상태 확인
aws cloudformation describe-stack-set-operation \
--stack-set-name my-global-infra \
--operation-id xxxxx-xxxxx
김개발 씨는 글로벌 배포라는 말에 살짝 긴장했습니다. 지금까지는 서울 리전 하나만 관리했는데, 갑자기 4개 리전을 담당하게 된 것입니다.
각 리전에 VPC, 서브넷, 보안 그룹, NAT Gateway를 동일하게 구성해야 합니다. "한 리전씩 들어가서 스택을 배포하면...
며칠이 걸리겠는데요." 박시니어 씨가 StackSets를 소개합니다. "한 번에 모든 리전에 배포하고, 수정도 한 번에 할 수 있어요." 그렇다면 StackSets란 정확히 무엇일까요?
쉽게 비유하자면, StackSets는 마치 프랜차이즈 본사의 인테리어 가이드와 같습니다. 스타벅스 본사에서 새로운 매장 디자인을 정하면, 전 세계 모든 지점에 동일한 디자인이 적용됩니다.
본사에서 "테이블 배치를 이렇게 바꾸세요"라고 지시하면 모든 지점이 일괄적으로 변경됩니다. StackSets도 마찬가지로, 본사(관리 계정)에서 템플릿을 정의하면 모든 지점(대상 계정/리전)에 동일하게 배포됩니다.
StackSets가 없다면 어떻게 될까요? 개발자들은 각 리전에 일일이 접속해서 스택을 배포해야 했습니다.
4개 리전이면 4번, 10개 계정이면 10번 작업입니다. 더 큰 문제는 일관성 유지였습니다.
어느 날 보안 그룹 규칙을 수정했는데, 서울과 도쿄는 수정하고 싱가포르는 깜빡 잊어버렸습니다. 리전마다 인프라가 제각각이 되는 것입니다.
바로 이런 문제를 해결하기 위해 StackSets가 등장했습니다. StackSets를 사용하면 중앙 집중식 관리가 가능해집니다.
하나의 템플릿으로 모든 리전과 계정을 관리합니다. 템플릿을 수정하면 모든 스택 인스턴스가 일괄 업데이트됩니다.
또한 자동 배포 기능도 있습니다. AWS Organizations와 연동하면 새로운 계정이 추가될 때 자동으로 스택이 배포됩니다.
위의 코드를 자세히 살펴보겠습니다. 먼저 create-stack-set 명령으로 StackSet을 생성합니다.
permission-model은 두 가지 모드가 있습니다. SELF_MANAGED는 각 대상 계정에 IAM 역할을 직접 생성해야 하고, SERVICE_MANAGED는 AWS Organizations와 연동해서 자동으로 권한을 관리합니다.
대규모 조직이라면 SERVICE_MANAGED가 편리합니다. auto-deployment를 활성화하면 Organizations에 새 계정이 추가될 때 자동으로 스택이 배포됩니다.
RetainStacksOnAccountRemoval은 계정이 조직에서 제거될 때 스택을 유지할지 삭제할지 결정합니다. create-stack-instances 명령으로 실제 스택을 배포합니다.
regions에는 배포할 리전 목록을, deployment-targets에는 대상 계정이나 OU(조직 단위)를 지정합니다. 실제 현업에서는 어떻게 활용할까요?
가장 흔한 용도는 보안 기준선 배포입니다. AWS Config 규칙, CloudTrail 설정, GuardDuty 활성화 등 모든 계정에 동일하게 적용해야 하는 보안 설정을 StackSets로 관리합니다.
또한 네트워킹 기반 구성도 많이 사용됩니다. 각 계정의 VPC, Transit Gateway 연결 등을 일괄 배포합니다.
하지만 주의할 점도 있습니다. StackSets 배포는 동시성 제어가 중요합니다.
한꺼번에 너무 많은 스택을 업데이트하면 API 제한에 걸릴 수 있습니다. MaxConcurrentCount나 MaxConcurrentPercentage 파라미터로 동시 배포 수를 조절해야 합니다.
또한 리전별 차이를 고려해야 합니다. 모든 서비스가 모든 리전에서 지원되는 것은 아닙니다.
일부 리전에서 지원하지 않는 리소스를 배포하면 해당 리전에서만 실패합니다. 따라서 템플릿 작성 시 리전 호환성을 확인해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. StackSets를 설정한 김개발 씨는 4개 리전에 동시에 네트워크 스택을 배포했습니다.
"와, 5분 만에 모든 리전에 VPC가 생성됐어요!" 다음에 보안 그룹 규칙을 수정할 때도 템플릿 한 번만 업데이트하면 됩니다. StackSets를 제대로 이해하면 글로벌 서비스의 인프라 관리가 훨씬 수월해집니다.
규모가 커질수록 StackSets의 가치는 더욱 빛을 발합니다.
실전 팁
💡 - StackSets 배포 시 장애 허용 개수(FailureToleranceCount)를 적절히 설정하여 일부 실패 시에도 배포가 계속되게 하세요.
- 리전별로 다른 파라미터가 필요하면 Parameter Overrides 기능을 활용하세요.
- Organizations의 Service Control Policy(SCP)가 StackSets 배포를 막지 않는지 확인하세요.
6. 템플릿 재사용 베스트 프랙티스
김개발 씨는 이제 CloudFormation의 고급 기능들을 모두 익혔습니다. 하지만 막상 실무에 적용하려니 어디서부터 시작해야 할지 막막합니다.
"기능은 알겠는데, 어떻게 조합해서 써야 하나요?" 박시니어 씨가 그동안 쌓아온 경험을 정리해서 알려줍니다. "이렇게 하면 몇 년 뒤에도 유지보수하기 좋은 템플릿을 만들 수 있어요."
VPC 식별자 - 다른 스택에서 참조 가능 Value: !Ref VPC Export: Name: !Sub ${ProjectName}-${Environment}-VpcId
다음 코드를 살펴봅시다.
# 모범 사례를 적용한 템플릿 구조
AWSTemplateFormatVersion: '2010-09-09'
김개발 씨는 6개월 전에 작성한 자신의 템플릿을 열어보다가 한숨을 쉬었습니다. "이게 뭐하는 파라미터지?
왜 이런 값을 썼더라?" 주석도 없고, 파라미터 이름도 불분명하고, 어떤 값을 넣어야 하는지도 알 수 없었습니다. 미래의 자신에게 빚을 진 기분이었습니다.
박시니어 씨가 옆에서 조언합니다. "좋은 템플릿은 처음 작성할 때 조금 더 공을 들이면 나중에 몇 배로 돌려받아요." 그렇다면 좋은 템플릿 설계란 정확히 무엇일까요?
첫 번째 원칙은 계층 분리입니다. 마치 건물을 지을 때 기초, 골조, 인테리어를 단계별로 하는 것처럼, 인프라도 계층별로 나눕니다.
보통 네트워크 → 보안 → 데이터 → 컴퓨팅 → 애플리케이션 순서로 스택을 구성합니다. 각 계층은 아래 계층에만 의존하고, 위 계층은 아래 계층을 참조합니다.
두 번째 원칙은 일관된 명명 규칙입니다. 리소스 이름, 태그, Export 이름 모두 예측 가능해야 합니다.
${ProjectName}-${Environment}-${ResourceType} 형태가 일반적입니다. 예를 들어 myapp-prod-vpc, myapp-prod-alb 처럼요.
이렇게 하면 AWS 콘솔에서 리소스를 찾기도 쉽고, 비용 분석도 태그 기반으로 할 수 있습니다. 세 번째 원칙은 파라미터 검증입니다.
잘못된 값이 들어오면 배포 초반에 실패하는 것이 좋습니다. AllowedValues로 선택지를 제한하고, AllowedPattern으로 형식을 검증합니다.
잘못된 VPC CIDR이 들어와서 30분 배포하다 실패하는 것보다, 시작 전에 바로 에러가 나는 것이 낫습니다. 네 번째 원칙은 Metadata를 활용한 UX 개선입니다.
AWS::CloudFormation::Interface를 사용하면 AWS 콘솔에서 파라미터를 그룹별로 보기 좋게 정리할 수 있습니다. 팀원 중 CLI보다 콘솔을 선호하는 분들을 위한 배려입니다.
다섯 번째 원칙은 충분한 문서화입니다. Description 필드를 적극 활용하세요.
템플릿 상단에는 버전, 작성자, 변경 이력을 기록합니다. 각 파라미터와 Output에도 설명을 달아둡니다.
6개월 후의 자신이 감사할 것입니다. 위의 코드에서 주목할 점을 살펴보겠습니다.
Description 필드에 버전 번호와 작성자 정보를 넣었습니다. 시멘틱 버저닝(v1.2.0)을 사용하면 변경 규모를 직관적으로 알 수 있습니다.
ParameterGroups를 정의하면 콘솔에서 파라미터가 논리적으로 그룹화되어 표시됩니다. AllowedPattern은 정규표현식으로 입력값을 검증합니다.
프로젝트 이름이 소문자로 시작하고, 길이가 3-21자 사이여야 한다는 규칙을 강제합니다. 잘못된 값이 들어오면 배포가 시작되기 전에 실패합니다.
Tags의 Name 값에 !Sub 함수를 사용해서 일관된 명명 규칙을 적용했습니다. 모든 리소스가 같은 패턴의 이름을 가지므로 AWS 콘솔에서 쉽게 찾을 수 있습니다.
실제 현업에서는 어떻게 관리할까요? 템플릿은 반드시 Git으로 버전 관리해야 합니다.
변경 이력을 추적하고, 문제가 생기면 이전 버전으로 롤백할 수 있어야 합니다. 많은 팀에서 GitOps 방식을 채택합니다.
main 브랜치에 머지되면 자동으로 배포되는 파이프라인을 구축하는 것입니다. 템플릿 테스트도 중요합니다.
cfn-lint로 문법 오류를 검사하고, cfn-nag로 보안 취약점을 점검합니다. 실제 배포 전에 Change Set을 생성해서 어떤 변경이 일어날지 미리 확인하는 것도 좋은 습관입니다.
모듈화 수준을 결정할 때는 팀 규모와 변경 빈도를 고려합니다. 2-3명이 관리하는 소규모 프로젝트라면 너무 잘게 쪼개면 오히려 복잡해집니다.
반면 수십 명이 협업하는 대규모 프로젝트라면 세분화된 모듈이 충돌을 줄여줍니다. 하지만 주의할 점도 있습니다.
과도한 추상화는 독이 됩니다. "나중에 필요할 것 같아서" 미리 만들어두는 유연성은 대부분 사용되지 않습니다.
YAGNI(You Aren't Gonna Need It) 원칙을 기억하세요. 지금 필요한 것만 구현하고, 필요해지면 그때 확장하면 됩니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 베스트 프랙티스를 적용해 템플릿을 정리한 김개발 씨는 1년 후, 신입 개발자에게 인프라를 인수인계하게 되었습니다.
"템플릿 보면 다 이해될 거예요. 주석도 충분하고, 구조도 명확하니까요." 신입 개발자가 고개를 끄덕입니다.
"정말 읽기 쉽네요!" 좋은 템플릿은 미래의 자신과 동료를 위한 선물입니다. 오늘 조금 더 공을 들여서 읽기 좋은 인프라 코드를 작성해 보세요.
실전 팁
💡 - cfn-lint와 cfn-nag를 CI/CD 파이프라인에 통합하여 자동으로 품질을 검사하세요.
- Change Set을 항상 먼저 생성하고 검토한 후 배포하는 습관을 들이세요.
- 템플릿 변경 시 Description의 버전 번호도 함께 업데이트하세요.
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Transit Gateway 피어링으로 글로벌 네트워크 구축
AWS Transit Gateway 피어링을 활용하여 전 세계 리전을 하나의 네트워크로 연결하는 방법을 알아봅니다. 글로벌 서비스 구축에 필요한 네트워크 아키텍처의 핵심을 쉽게 설명합니다.
Transit Gateway로 복잡한 네트워크 중앙 집중화 완벽 가이드
AWS Transit Gateway를 활용하여 복잡한 VPC 네트워크를 중앙에서 효율적으로 관리하는 방법을 알아봅니다. Hub-and-Spoke 아키텍처부터 라우팅 테이블 구성까지 실무에 필요한 모든 것을 다룹니다.
VPC 피어링으로 VPC 간 프라이빗 통신 완벽 가이드
AWS에서 서로 다른 VPC 간에 인터넷을 거치지 않고 프라이빗하게 통신하는 VPC 피어링의 개념부터 실전 구성까지 초급자도 이해할 수 있도록 설명합니다. 동일 리전과 교차 리전 피어링의 차이점, 라우팅 설정, CIDR 중복 문제 해결까지 실무에서 꼭 알아야 할 내용을 다룹니다.
CloudFormation으로 Infrastructure as Code 구현하기
AWS CloudFormation을 사용하여 인프라를 코드로 관리하는 방법을 배웁니다. 스택과 템플릿의 개념부터 실제 리소스 배포까지, 초급자도 쉽게 따라할 수 있는 실습 중심의 가이드입니다.
AWS Service Quotas로 리소스 한도 확인 및 증가 완벽 가이드
AWS 클라우드에서 각 서비스의 리소스 한도를 확인하고 필요할 때 증가 요청하는 방법을 알아봅니다. 프로덕션 환경에서 갑자기 리소스 생성이 막히는 상황을 예방하는 핵심 지식입니다.