
Claude에게 CloudFormation 코드 리팩토링을 부탁해 봤습니다.
안녕하세요 클래스메소드 김재욱(Kim Jaewook) 입니다. 이번 블로그에서는 Claude에게 CloudFormation 코드 리팩토링을 부탁해 봤습니다.
부탁할 CloudFormation 코드
약 4년전에 테스트용으로 만든 AWS 네트워크를 구성하는 CloudFormation 코드입니다.
지금 보니까 수정하고 싶은 곳이 꽤 보이지만, 수동으로 하기보다는 Claude에게 부탁해 보려고 합니다.
AWSTemplateFormatVersion: "2010-09-09"
Description: VPC Network set
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "Network Configuration"
Parameters:
- VPCCIDR
- PublicSubnetACIDR
- PublicSubnetCCIDR
- PrivateSubnetACIDR
- PrivateSubnetCCIDR
ParameterLabels:
VPCCIDR:
default: "VPC CIDR"
PublicSubnetACIDR:
default: "PublicSubnetA CIDR"
PublicSubnetCCIDR:
default: "PublicSubnetC CIDR"
PrivateSubnetACIDR:
default: "PrivateSubnetA CIDR"
PrivateSubnetCCIDR:
default: "PrivateSubnetC CIDR"
#-------------------------------------------------------------------
#Input VPC, Subnet Parameters
#-------------------------------------------------------------------
Parameters:
VPCCIDR:
Type: String
Default: "10.0.0.0/16"
PublicSubnetACIDR:
Type: String
Default: "10.0.0.0/24"
PublicSubnetCCIDR:
Type: String
Default: "10.0.64.0/24"
PrivateSubnetACIDR:
Type: String
Default: "10.0.128.0/24"
PrivateSubnetCCIDR:
Type: String
Default: "10.0.192.0/24"
#-------------------------------------------------------------------
#Set VPC, InternetGateway, Subnet
#-------------------------------------------------------------------
Resources:
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: "true"
EnableDnsHostnames: "true"
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub "test-vpc"
InternetGateway:
Type: "AWS::EC2::InternetGateway"
Properties:
Tags:
- Key: Name
Value: !Sub "test-igw"
InternetGatewayAttachment:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
PublicSubnetA:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1a"
CidrBlock: !Ref PublicSubnetACIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "test-front-subnet-1a"
PublicSubnetC:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1c"
CidrBlock: !Ref PublicSubnetCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "test-front-subnet-1c"
PrivateSubnetA:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1a"
CidrBlock: !Ref PrivateSubnetACIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "test-application-subnet-1a"
PrivateSubnetC:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1c"
CidrBlock: !Ref PrivateSubnetCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "test-application-subnet-1c"
#-------------------------------------------------------------------
#Route Tables
#-------------------------------------------------------------------
FRONTRTB :
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "test-front-rtb"
APPRTB1A:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "test-application-rtb-1a"
APPRTB1C:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "test-application-rtb-1c"
FRONTRTBroute:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref FRONTRTB
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
#-------------------------------------------------------------------
#Route Tables Subnet Association
#-------------------------------------------------------------------
FRONTRTBAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PublicSubnetA
RouteTableId: !Ref FRONTRTB
FRONTRTB2Association:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PublicSubnetC
RouteTableId: !Ref FRONTRTB
APPRTB1AAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PrivateSubnetA
RouteTableId: !Ref APPRTB1A
APPRTB1CAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PrivateSubnetC
RouteTableId: !Ref APPRTB1C
#-------------------------------------------------------------------
#OutPut
#-------------------------------------------------------------------
Outputs:
# VPC
VPC:
Value: !Ref VPC
Export:
Name: !Sub "test-vpc"
VPCCIDR:
Value: !Ref VPCCIDR
Export:
Name: !Sub "test-vpc-cidr"
# Subnet
PublicSubnetA:
Value: !Ref PublicSubnetA
Export:
Name: !Sub "test-front-subnet-1a"
PublicSubnetACIDR:
Value: !Ref PublicSubnetACIDR
Export:
Name: !Sub "test-front-subnet-1a-cidr"
PublicSubnetC:
Value: !Ref PublicSubnetC
Export:
Name: !Sub "test-front-subnet-1c"
PublicSubnetCCIDR:
Value: !Ref PublicSubnetCCIDR
Export:
Name: !Sub "test-front-subnet-1c-cidr"
PrivateSubnetA:
Value: !Ref PrivateSubnetA
Export:
Name: !Sub "test-application-subnet-1a"
PrivateSubnetACIDR:
Value: !Ref PrivateSubnetACIDR
Export:
Name: !Sub "test-application-subnet-1a-cidr"
PrivateSubnetC:
Value: !Ref PrivateSubnetC
Export:
Name: !Sub "test-application-subnet-1c"
PrivateSubnetCCIDR:
Value: !Ref PrivateSubnetCCIDR
Export:
Name: !Sub "test-application-subnet-1c-cidr"
# Route
FRONTRTB:
Value: !Ref FRONTRTB
Export:
Name: !Sub "test-front-rtb"
APPRTB1A:
Value: !Ref APPRTB1A
Export:
Name: !Sub "test-application-rtb-1a"
APPRTB1C:
Value: !Ref APPRTB1C
Export:
Name: !Sub "test-application-rtb-1c"
Claude 프롬프트
총 231줄의 CloudFormation 코드를 Claude에게 리팩토링을 부탁해 봅시다.
프롬프트는 다음을 지정했습니다.
먼저 코드에 대한 적절한 수정이 필요할 것이고, 파라미터화와 구성도를 받아보고 싶기 때문에 이에 대한 프롬프트도 작성했습니다.
다음 AWS CloudFormation 템플릿을 리팩토링해줘.
- 가독성을 높이고
- 중복 제거하고
- 각 코드의 변수 값을 적절한 이름으로 바꿔줘
- 한글로 표기한 부분은 콘솔 화면에서 [??? ??? (ap-northeast-1a) CIDR] 뜰 수 있으니 영어로 표기해줘
- 가능한 경우 파라미터화 해줘
- 결과물의 구성도를 그려줘
- 결과는 YAML로 반환해줘
결과물
프롬프트를 통해 출력된 결과물은 다음과 같습니다.
Claude가 작성한 코드
AWSTemplateFormatVersion: "2010-09-09"
Description: >
VPC network stack.
Creates 2 public subnets (AZ-1a, AZ-1c) and 2 private subnets (AZ-1a, AZ-1c)
with an Internet Gateway and route tables.
# ----------------------------------------------------------------
# CloudFormation Console UI Settings
# ----------------------------------------------------------------
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "Network CIDR Configuration"
Parameters:
- ProjectName
- EnvName
- VpcCidr
- PublicSubnetAz1aCidr
- PublicSubnetAz1cCidr
- PrivateSubnetAz1aCidr
- PrivateSubnetAz1cCidr
ParameterLabels:
ProjectName:
default: "Project Name"
EnvName:
default: "Environment (dev / stg / prd)"
VpcCidr:
default: "VPC CIDR"
PublicSubnetAz1aCidr:
default: "Public Subnet (ap-northeast-1a) CIDR"
PublicSubnetAz1cCidr:
default: "Public Subnet (ap-northeast-1c) CIDR"
PrivateSubnetAz1aCidr:
default: "Private Subnet (ap-northeast-1a) CIDR"
PrivateSubnetAz1cCidr:
default: "Private Subnet (ap-northeast-1c) CIDR"
# ----------------------------------------------------------------
# Parameters
# ----------------------------------------------------------------
Parameters:
ProjectName:
Type: String
Default: "myapp"
Description: "Prefix used for all resource names"
EnvName:
Type: String
Default: "dev"
AllowedValues: ["dev", "stg", "prd"]
Description: "Deployment environment (dev / stg / prd)"
VpcCidr:
Type: String
Default: "10.0.0.0/16"
PublicSubnetAz1aCidr:
Type: String
Default: "10.0.0.0/24"
PublicSubnetAz1cCidr:
Type: String
Default: "10.0.64.0/24"
PrivateSubnetAz1aCidr:
Type: String
Default: "10.0.128.0/24"
PrivateSubnetAz1cCidr:
Type: String
Default: "10.0.192.0/24"
# ----------------------------------------------------------------
# Naming convention
# ----------------------------------------------------------------
# All resource names follow the pattern: !Sub "${ProjectName}-${EnvName}-<resource>"
# ----------------------------------------------------------------
# Resources
# ----------------------------------------------------------------
Resources:
# ---- VPC --------------------------------------------------------
Vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsSupport: true
EnableDnsHostnames: true
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${EnvName}-vpc"
# ---- Internet Gateway -------------------------------------------
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${EnvName}-igw"
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref Vpc
InternetGatewayId: !Ref InternetGateway
# ---- Public Subnets (Frontend Tier) -----------------------------
PublicSubnetAz1a:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref Vpc
AvailabilityZone: "ap-northeast-1a"
CidrBlock: !Ref PublicSubnetAz1aCidr
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${EnvName}-public-subnet-1a"
PublicSubnetAz1c:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref Vpc
AvailabilityZone: "ap-northeast-1c"
CidrBlock: !Ref PublicSubnetAz1cCidr
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${EnvName}-public-subnet-1c"
# ---- Private Subnets (Application Tier) -------------------------
PrivateSubnetAz1a:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref Vpc
AvailabilityZone: "ap-northeast-1a"
CidrBlock: !Ref PrivateSubnetAz1aCidr
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${EnvName}-private-subnet-1a"
PrivateSubnetAz1c:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref Vpc
AvailabilityZone: "ap-northeast-1c"
CidrBlock: !Ref PrivateSubnetAz1cCidr
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${EnvName}-private-subnet-1c"
# ---- Route Tables -----------------------------------------------
# Public: shared across both AZs, default route to IGW
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${EnvName}-public-rtb"
PublicDefaultRoute:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
# Private: separate table per AZ to support future NAT Gateway per AZ
PrivateRouteTableAz1a:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${EnvName}-private-rtb-1a"
PrivateRouteTableAz1c:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${EnvName}-private-rtb-1c"
# ---- Subnet Route Table Associations ----------------------------
PublicSubnetAz1aRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetAz1a
RouteTableId: !Ref PublicRouteTable
PublicSubnetAz1cRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetAz1c
RouteTableId: !Ref PublicRouteTable
PrivateSubnetAz1aRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnetAz1a
RouteTableId: !Ref PrivateRouteTableAz1a
PrivateSubnetAz1cRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnetAz1c
RouteTableId: !Ref PrivateRouteTableAz1c
# ----------------------------------------------------------------
# Outputs (importable by other stacks via Fn::ImportValue)
# ----------------------------------------------------------------
Outputs:
# VPC
VpcId:
Description: "VPC ID"
Value: !Ref Vpc
Export:
Name: !Sub "${ProjectName}-${EnvName}-vpc-id"
VpcCidrBlock:
Description: "VPC CIDR block"
Value: !GetAtt Vpc.CidrBlock
Export:
Name: !Sub "${ProjectName}-${EnvName}-vpc-cidr"
# Public Subnets
PublicSubnetAz1aId:
Description: "Public subnet ID (ap-northeast-1a)"
Value: !Ref PublicSubnetAz1a
Export:
Name: !Sub "${ProjectName}-${EnvName}-public-subnet-1a-id"
PublicSubnetAz1aCidrBlock:
Description: "Public subnet CIDR (ap-northeast-1a)"
Value: !Ref PublicSubnetAz1aCidr
Export:
Name: !Sub "${ProjectName}-${EnvName}-public-subnet-1a-cidr"
PublicSubnetAz1cId:
Description: "Public subnet ID (ap-northeast-1c)"
Value: !Ref PublicSubnetAz1c
Export:
Name: !Sub "${ProjectName}-${EnvName}-public-subnet-1c-id"
PublicSubnetAz1cCidrBlock:
Description: "Public subnet CIDR (ap-northeast-1c)"
Value: !Ref PublicSubnetAz1cCidr
Export:
Name: !Sub "${ProjectName}-${EnvName}-public-subnet-1c-cidr"
# Private Subnets
PrivateSubnetAz1aId:
Description: "Private subnet ID (ap-northeast-1a)"
Value: !Ref PrivateSubnetAz1a
Export:
Name: !Sub "${ProjectName}-${EnvName}-private-subnet-1a-id"
PrivateSubnetAz1aCidrBlock:
Description: "Private subnet CIDR (ap-northeast-1a)"
Value: !Ref PrivateSubnetAz1aCidr
Export:
Name: !Sub "${ProjectName}-${EnvName}-private-subnet-1a-cidr"
PrivateSubnetAz1cId:
Description: "Private subnet ID (ap-northeast-1c)"
Value: !Ref PrivateSubnetAz1c
Export:
Name: !Sub "${ProjectName}-${EnvName}-private-subnet-1c-id"
PrivateSubnetAz1cCidrBlock:
Description: "Private subnet CIDR (ap-northeast-1c)"
Value: !Ref PrivateSubnetAz1cCidr
Export:
Name: !Sub "${ProjectName}-${EnvName}-private-subnet-1c-cidr"
# Route Tables
PublicRouteTableId:
Description: "Public route table ID"
Value: !Ref PublicRouteTable
Export:
Name: !Sub "${ProjectName}-${EnvName}-public-rtb-id"
PrivateRouteTableAz1aId:
Description: "Private route table ID (ap-northeast-1a)"
Value: !Ref PrivateRouteTableAz1a
Export:
Name: !Sub "${ProjectName}-${EnvName}-private-rtb-1a-id"
PrivateRouteTableAz1cId:
Description: "Private route table ID (ap-northeast-1c)"
Value: !Ref PrivateRouteTableAz1c
Export:
Name: !Sub "${ProjectName}-${EnvName}-private-rtb-1c-id"
구성도

변경 사항
- 파라미터화
ProjectName,EnvName파라미터 추가 → 모든 리소스 이름을{ProjectName}-{EnvName}-*패턴으로 통일. 환경별(dev/stg/prd) 재사용 가능.EnvName은AllowedValues로 오타 방지.
- 변수명 개선
VPCCIDR→VpcCidr,PublicSubnetACIDR→PublicSubnetAz1aCidr등 카멜케이스 + 의미 명확화.FRONTRTB,APPRTB1A같은 약어 →PublicRouteTable,PrivateRouteTableAz1a등 서술적 이름으로 교체.
- 중복 제거 및 개선
!Sub "test-vpc"처럼 하드코딩된 문자열을 전부 파라미터 참조로 교체.Outputs의Value: !Ref VPCCIDR→Value: !GetAtt Vpc.CidrBlock으로 실제 배포된 값을 반환하도록 수정.DependsOn: InternetGatewayAttachment를PublicDefaultRoute에 명시 추가 (IGW 부착 전 라우트 생성 방지).
- 가독성
- 섹션 구분 주석 정비 및 한국어 설명 추가.
MapPublicIpOnLaunch: true를 퍼블릭 서브넷에 추가 (원본 누락 항목).- 프라이빗 라우팅 테이블에 NAT Gateway 확장 가능성 주석 추가.
콘솔에서 확인
CloudFormation 콘솔 화면에서 스택을 생성해 봅시다.
파라미터를 구성하는 화면은 깔끔하게 출력되는 것을 확인할 수 있습니다.

성공적으로 스택이 생성된 것을 확인할 수 있습니다.

마지막으로 생성한 VPC 구성을 확인해 보면 Claude가 그려준 구성도와 동일하게 VPC 구성이 생성된 것을 확인할 수 있습니다.

마지막으로
이번 블로그를 통해 느낀 점은 꽤 명확했습니다. 단순히 Claude에게 “리팩토링 해줘”라고 맡기는 것만으로도 기본적인 구조 개선, 가독성 향상, 네이밍 정리까지는 상당히 안정적으로 수행된다는 점입니다. 특히 오래된 AWS CloudFormation 템플릿처럼 사람이 다시 보기 부담스러운 코드에 대해서는, 초벌 정리 용도로 충분히 활용할 수 있다는 인상을 받았습니다.
다만 여기서 중요한 포인트는 하나입니다. AI가 리팩토링을 "완성해준다"기보다, 사람이 검토하기 좋은 상태로 "정리해준다"에 가깝다는 점입니다.
가장 크게 체감된 부분은 다음과 같습니다.
-
파라미터화 자동 제안
사람이 일일이 고민해야 하는 부분을 자연스럽게 구조화해줍니다. -
일관된 네이밍 규칙 적용
기존 코드에서 흔히 보이는 약어 남발 문제를 줄여줍니다. -
전체 구조 재정리
리소스 간 관계가 훨씬 눈에 잘 들어오게 개선됩니다.
이 부분은 단순히 편의성 수준이 아니라, 코드 리뷰 시간 자체를 줄여주는 효과도 있다고 느껴졌습니다.
반대로 그대로 쓰기엔 주의가 필요한 부분도 있습니다.
- 일부 리소스 설정이
AWS 베스트 프랙티스와 완전히 일치하지 않을 가능성 - 환경에 따라 필요한 옵션이
누락되거나 과도하게 일반화될 수 있음 - 운영 환경에서는
보안 설정(Security Group, NACL 등) 반드시 검증 필요
결론은 명확합니다. "그대로 배포"는 위험하고, 반드시 검증 후 사용하는 것이 전제입니다.
이번 글의 핵심은 다음 한 줄로 정리할 수 있습니다. 좋은 결과는 좋은 프롬프트에서 나온다 같은 Claude라도 프롬프트에 따라 결과 품질이 크게 달라집니다.
이번 블로그에서는 단순 리팩토링 요청이 아니라
파라미터화구성도 생성네이밍 규칙 변경
까지 명시했기 때문에 훨씬 실용적인 결과를 얻을 수 있었습니다.
앞으로는 다음과 같은 흐름으로 활용하는 것이 현실적입니다.
- 기존 CloudFormation 코드 입력
- Claude로 1차 리팩토링 수행
- 사람이 검토 및 보완
- 테스트 배포
- 최종 템플릿 확정
정리하면 다음과 같습니다.
AI → 사람 → 검증 구조로 사용하는 것이 가장 안정적입니다.
이번 블로그를 통해 확인한 점은 분명합니다.
CloudFormation 리팩토링은 더 이상 완전히 수작업만의 영역이 아니다
물론 최종 책임은 여전히 사람에게 있지만, 초기 정리 단계에서는 AI를 활용하는 것이 훨씬 효율적입니다.
특히 오래된 템플릿이나 구조가 복잡한 인프라 코드일수록 효과를 더 크게 체감할 수 있습니다.








