Lab 1: VPC 하나만 ← 템플릿 기본 구조
Lab 2: VPC + Subnet ← Parameters, !Ref
Lab 3: 환경별 분기 ← Mappings, !FindInMap
Lab 4: 결과 출력 ← Outputs, Export
Lab 5: 전체 네트워크 ← Intrinsic Functions, DependsOn
Lab 6: EC2 + 웹서버 ← Cross Stack, User Data ← 이번 글
2편에서 만든 네트워크 스택 위에 EC2를 배포하고, 마지막에 전부 삭제한다. CLI 흐름과 비교하려면: 6. Security Group · 7. EC2 배포 · 8. 리소스 정리
Cross Stack Reference
2편에서 VpcId와 SubnetId를 Export했다. 다른 스택에서 이 값을 가져올 수 있다:
# 네트워크 스택에서 Export한 값
Export:
Name: lab05-VpcId
# EC2 스택에서 Import
VpcId:
Fn::ImportValue: lab05-VpcId
User Data
CLI에서는 --user-data에 스크립트를 직접 넣었다. CloudFormation에서는 Fn::Base64로 인코딩한다:
UserData:
Fn::Base64: |
#!/bin/bash
dnf update -y
dnf install -y httpd
KeyPair 준비
KeyPair는 CloudFormation 밖에서 미리 만들어야 한다. EC2 접속용 SSH 키라서 .pem 파일을 직접 받아야 하기 때문이다.
aws ec2 create-key-pair \
--key-name TutorialKey \
--query 'KeyMaterial' \
--output text \
--profile qa > TutorialKey.pem
chmod 400 TutorialKey.pem
실습: SG + EC2 배포
lab06-ec2.yaml:
AWSTemplateFormatVersion: "2010-09-09"
Description: Lab 6 - EC2 with User Data
Parameters:
NetworkStackName:
Type: String
Default: lab05
Description: Network stack name
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64
KeyName:
Type: AWS::EC2::KeyPair::KeyName
Resources:
WebServerSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Web Server SG
VpcId:
Fn::ImportValue: !Sub ${NetworkStackName}-VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
DBServerSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: DB Server SG
VpcId:
Fn::ImportValue: !Sub ${NetworkStackName}-VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref WebServerSG
- IpProtocol: tcp
FromPort: 22
ToPort: 22
SourceSecurityGroupId: !Ref WebServerSG
WebServer:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t2.micro
KeyName: !Ref KeyName
SubnetId:
Fn::ImportValue: !Sub ${NetworkStackName}-PublicSubnet1Id
SecurityGroupIds:
- !Ref WebServerSG
UserData:
Fn::Base64: |
#!/bin/bash
dnf update -y
dnf install -y httpd
rm -f /etc/httpd/conf.d/welcome.conf
echo "<h1>Hello from CloudFormation!</h1>" > /var/www/html/index.html
systemctl start httpd
systemctl enable httpd
Tags:
- Key: Name
Value: WebServer
DBServer:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t2.micro
KeyName: !Ref KeyName
SubnetId:
Fn::ImportValue: !Sub ${NetworkStackName}-PrivateSubnet1Id
SecurityGroupIds:
- !Ref DBServerSG
UserData:
Fn::Base64: |
#!/bin/bash
dnf update -y
dnf install -y mariadb105-server
systemctl start mariadb
systemctl enable mariadb
Tags:
- Key: Name
Value: DBServer
Outputs:
WebServerPublicIP:
Description: Web Server Public IP
Value: !GetAtt WebServer.PublicIp
DBServerPrivateIP:
Description: DB Server Private IP
Value: !GetAtt DBServer.PrivateIp
포인트 정리
AWS::SSM::Parameter::Value — CLI에서는
describe-images로 AMI ID를 찾았다. 이 타입을 쓰면 배포 시점의 최신 AMI를 자동으로 가져온다.AWS::EC2::KeyPair::KeyName — 이미 존재하는 KeyPair 목록에서 선택하는 파라미터 타입이다. CloudFormation이 키를 만들어주는 게 아니다.
SourceSecurityGroupId: !Ref WebServerSG — DB SG는 WebServer SG에서 오는 트래픽만 허용한다. CLI에서
--source-group으로 했던 것과 같다.FromPort / ToPort — CLI에서
--port 80은 내부적으로FromPort=80, ToPort=80으로 변환된다. CloudFormation은 AWS API를 직접 반영하기 때문에 이렇게 쓴다.
배포 (2편의 네트워크 스택이 먼저 배포되어 있어야 함)
aws cloudformation create-stack \
--stack-name lab06 \
--template-body file://lab06-ec2.yaml \
--parameters ParameterKey=NetworkStackName,ParameterValue=lab05 \
ParameterKey=KeyName,ParameterValue=TutorialKey \
--profile qa
# 웹서버 IP 확인
aws cloudformation describe-stacks \
--stack-name lab06 \
--query 'Stacks[0].Outputs' \
--profile qa
브라우저에서 WebServerPublicIP로 접속하면 "Hello from CloudFormation!"이 보인다.
Cleanup
CLI에서는 9단계를 역순으로 삭제했다. CloudFormation에서는:
aws cloudformation delete-stack --stack-name lab06 --profile qa
aws cloudformation wait stack-delete-complete --stack-name lab06 --profile qa
aws cloudformation delete-stack --stack-name lab05 --profile qa
끝. NAT GW, EIP, IGW, Subnet, RT, SG, EC2... 전부 자동으로 삭제된다.
| CLI (9단계) | CloudFormation |
| terminate-instances | delete-stack |
| delete-key-pair | |
| delete-nat-gateway + wait | |
| release-address | |
| delete-security-group x2 | |
| disassociate + delete-route-table | |
| detach + delete-internet-gateway | |
| delete-subnet x4 | |
| delete-vpc |
삽질 기록
실습하면서 만난 에러들이다.
KeyPair가 없어서 스택 생성 실패
Parameter validation failed: parameter value TutorialKey for parameter name KeyName does not exist.
AWS::EC2::KeyPair::KeyName 타입은 이미 존재하는 KeyPair를 참조한다. CLI로 미리 만들어야 한다.
Public IP가 할당 안 돼서 실패
Attribute 'PublicIp' does not exist.
Public 서브넷에 MapPublicIpOnLaunch: true가 빠져있었다. 네트워크 스택을 update-stack으로 수정하고, 실패한 EC2 스택은 삭제 후 재생성했다.
ROLLBACK 상태 스택은 업데이트 불가
create-stack이 실패하면 ROLLBACK_COMPLETE 상태가 된다. 이 상태에서는 update-stack이 안 된다. 삭제 후 다시 만들어야 한다.
반면 update-stack이 실패하면 UPDATE_ROLLBACK_COMPLETE 상태가 되는데, 이건 이전 상태로 자동 복원되므로 다시 update-stack할 수 있다.
| 실패 시 | 상태 | 대응 |
| create-stack | ROLLBACK_COMPLETE | 삭제 후 재생성 |
| update-stack | UPDATE_ROLLBACK_COMPLETE | 이전 상태로 복원, 다시 update 가능 |
마무리
CLI로 하나씩 만들던 인프라를 CloudFormation YAML로 선언적으로 관리하는 방법을 배웠다.
이 시리즈에서 배운 것
Parameters — 배포 시 값 주입,
!Ref로 참조Mappings — 환경별 조회 테이블,
!FindInMapOutputs + Export — 스택 간 값 공유,
Fn::ImportValueIntrinsic Functions —
!Ref,!GetAtt,!Sub,DependsOnUser Data —
Fn::Base64로 부트스트랩 스크립트Cross Stack Reference — 네트워크 스택과 EC2 스택 분리
다음 단계
CloudFormation으로 만든 VPC를 CDK로 다시 만들기 — TypeScript로 동일한 인프라를 작성해본다.
'Infrastructure' 카테고리의 다른 글
| CLI로 만든 VPC를 CloudFormation으로 다시 만들기 (2) — 전체 네트워크 (0) | 2026.03.27 |
|---|---|
| CLI로 만든 VPC를 CloudFormation으로 다시 만들기 (1) — 템플릿 기초 (0) | 2026.03.24 |
| AWS CLI로 AWS VPC 기본 아키텍처 구성하기 (0) | 2026.03.17 |
| AWS ECS 키워드 (0) | 2025.10.27 |
| 쉘 파일 이용하여 AWS Parameter store에 .env 파일 한번에 업로드 하기 (0) | 2025.08.22 |