Claude에게 CloudFormation 코드 리팩토링을 부탁해 봤습니다.

Claude에게 CloudFormation 코드 리팩토링을 부탁해 봤습니다.

Claude에게 CloudFormation 코드 리팩토링을 부탁해 봤습니다.
2026.04.21

안녕하세요 클래스메소드 김재욱(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"

구성도

image1

변경 사항

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

콘솔에서 확인

CloudFormation 콘솔 화면에서 스택을 생성해 봅시다.

파라미터를 구성하는 화면은 깔끔하게 출력되는 것을 확인할 수 있습니다.

image2

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

image3

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

image4

마지막으로

이번 블로그를 통해 느낀 점은 꽤 명확했습니다. 단순히 Claude에게 “리팩토링 해줘”라고 맡기는 것만으로도 기본적인 구조 개선, 가독성 향상, 네이밍 정리까지는 상당히 안정적으로 수행된다는 점입니다. 특히 오래된 AWS CloudFormation 템플릿처럼 사람이 다시 보기 부담스러운 코드에 대해서는, 초벌 정리 용도로 충분히 활용할 수 있다는 인상을 받았습니다.

다만 여기서 중요한 포인트는 하나입니다. AI가 리팩토링을 "완성해준다"기보다, 사람이 검토하기 좋은 상태로 "정리해준다"에 가깝다는 점입니다.

가장 크게 체감된 부분은 다음과 같습니다.

  • 파라미터화 자동 제안
    사람이 일일이 고민해야 하는 부분을 자연스럽게 구조화해줍니다.

  • 일관된 네이밍 규칙 적용
    기존 코드에서 흔히 보이는 약어 남발 문제를 줄여줍니다.

  • 전체 구조 재정리
    리소스 간 관계가 훨씬 눈에 잘 들어오게 개선됩니다.

이 부분은 단순히 편의성 수준이 아니라, 코드 리뷰 시간 자체를 줄여주는 효과도 있다고 느껴졌습니다.

반대로 그대로 쓰기엔 주의가 필요한 부분도 있습니다.

  • 일부 리소스 설정이 AWS 베스트 프랙티스와 완전히 일치하지 않을 가능성
  • 환경에 따라 필요한 옵션이 누락되거나 과도하게 일반화될 수 있음
  • 운영 환경에서는 보안 설정(Security Group, NACL 등) 반드시 검증 필요

결론은 명확합니다. "그대로 배포"는 위험하고, 반드시 검증 후 사용하는 것이 전제입니다.

이번 글의 핵심은 다음 한 줄로 정리할 수 있습니다. 좋은 결과는 좋은 프롬프트에서 나온다 같은 Claude라도 프롬프트에 따라 결과 품질이 크게 달라집니다.

이번 블로그에서는 단순 리팩토링 요청이 아니라

  • 파라미터화
  • 구성도 생성
  • 네이밍 규칙 변경

까지 명시했기 때문에 훨씬 실용적인 결과를 얻을 수 있었습니다.

앞으로는 다음과 같은 흐름으로 활용하는 것이 현실적입니다.

  1. 기존 CloudFormation 코드 입력
  2. Claude로 1차 리팩토링 수행
  3. 사람이 검토 및 보완
  4. 테스트 배포
  5. 최종 템플릿 확정

정리하면 다음과 같습니다.

AI → 사람 → 검증 구조로 사용하는 것이 가장 안정적입니다.

이번 블로그를 통해 확인한 점은 분명합니다.

CloudFormation 리팩토링은 더 이상 완전히 수작업만의 영역이 아니다

물론 최종 책임은 여전히 사람에게 있지만, 초기 정리 단계에서는 AI를 활용하는 것이 훨씬 효율적입니다.
특히 오래된 템플릿이나 구조가 복잡한 인프라 코드일수록 효과를 더 크게 체감할 수 있습니다.

この記事をシェアする

関連記事