ASHD Dev_Blog

인프라 일기 0편 : IAC 메커니즘(Terrafrom)

개발 일기 1편

이재룡
이재룡 Apr 19, 2026

[ Terraform vs Pulumi ]

IAC : Infrastructe As a Code

→ 인프라 환경 설정을 코드로

 

일단 현재 팀에서 기본적으로 사용하던 (Setting은 내가 안함 = 사용만 할 줄 안다 ㅎㅎ)

Pulumi와 비교해보면

일단 현재 팀에서 기본적으로 사용하고 있다 (Setting은 내가 안함 = 사용만 할 줄 안다 ㅎㅎ)

  1. 언어
      • Declarative(전체 설계도) : Terrafrom은 HCL이라는 자체 도메인 언어를 사용하면서 (like .tf)
        • → 조건문, 반복문 등을 사용할 때, for_each나 count 등 생소한 문법을 사용하며

        • 반복을 한다기 보다, 매번 다르게 적용이나 / 동일한 설정을 반복하는 것에 가까움
       
      • OOP(개별) : Pulumi는 Java, Go, Python, TypeScript 등등 기본적인 백엔드 언어 사용
        • → if / for in range 등을 사용하면서

        • 각 객체를 만들고 해당 객체를 다시 불러서 수정 또는 다시 사용하는 OOP 개념
       
  1. 상태 관리 파일
    1. IAC 프로그램은 무조건 상태 관리가 필수!

      → 현재의 상태와 코드의 상태를 보고 변화한 부분을 적용하기 때문에 SOT 역할이 필요하다!

       
      • 로컬 저장 : Terraform은 .tfState라는 파일로 해당 정보를 저장
        • → 여러 명이 동시 작업할 경우 DB Locking 등의 충돌 제어 필수

       
      • SAAS 관리 : Pulumi는 자체 관리형 서비스인 Pulumi Service에 상태를 저장
        • → 동시에 apply 하더라도, SOT를 외부 서비스가 가지고 있으므로 (자동 충돌 제어)

        • 일단 SOT 요청 → 받은 apply system 만 작업 → SOT update
        • 나머지는 대기 → 이후에 앞선 작업이 끝나면 → update된 SOT를 받음
       
  1. 패키지 및 모듈관리
      • Terraform Registry : (like DockerHub)
      • PackageManager : (그대로 Gradle, npm, pip) → 라이브러리처럼 인프라 코드 사용
 
 

[ 동작 명령어 세부 분석 ]

Terraform Core

 
  • 컴퓨터에 설치된 terraform.exe 파일

    1. terraform init

    2. terraform plan

    3. terraform apply

     

    1. Terraform Core 란? ("현장 소장")

    테라폼 코어는 우리가 터미널에서 terraform 명령어를 쳤을 때 실행되는 가장 핵심적인 두뇌(실행 파일)입니다. HashiCorp가 Go 언어로 개발한 오픈소스 소프트웨어 그 자체입니다.

    • 코어의 역할:
        1. 도면 읽기: 우리가 작성한 .tf 파일(HCL 문법)을 읽고 해석합니다.
        1. 장부 관리: 상태 파일(terraform.tfstate)을 읽고 관리합니다.
        1. 작업 순서도(Graph) 생성: A를 만들기 위해 B가 먼저 필요한지 의존성을 계산합니다. (예: VPC를 먼저 만들고, 그다음 EC2를 만들어야 함을 코어가 계산합니다.)
    • 코어가 못 하는 것: 코어는 AWS, GCP, Azure와 직접 통신할 줄 모릅니다. 코어는 오직 '계획'과 '명령'만 내리며, 실제 클라우드 인프라를 건드리는 것은 플러그인(AWS Provider 등)의 역할입니다.

    2. 명령어 실행 시 코어 내부에서 일어나는 변화

    방금 전 실습하셨던 main.tf를 기준으로, 각 명령어 단계에서 코어가 어떤 일을 하는지 구체적인 변화를 살펴보겠습니다.

    terraform init (준비 및 채용 단계)

    명령을 내리면 코어는 코드(main.tf)를 쭉 스캔합니다.

    • 코어의 생각: "음, 도면을 보니 AWS 리소스를 만들어야 하네. 나는 AWS랑 대화할 줄 모르니까 AWS 전용 외주 기술자(Provider Plugin)를 고용해 와야겠다."
    • 실제 변화:
        1. Terraform Registry에 접속해서 AWS Provider 플러그인을 다운로드합니다. (.terraform/ 폴더 생성)
        1. 다운로드한 기술자의 버전이 바뀌지 않도록 보증서를 작성합니다. (.terraform.lock.hcl 파일 생성)
        1. 이전에 만들어둔 장부(State)가 있는지, 백엔드(로컬 파일 또는 S3)와 연결을 준비합니다.

    terraform plan (비교 및 계획서 작성 단계)

    코어가 본격적으로 머리를 쓰는 가장 복잡한 단계입니다. 코어는 3가지 데이터를 꼼꼼하게 비교합니다.

    1. 원하는 상태 (Desired State): 우리가 방금 작성한 main.tf 코드
    1. 기록된 상태 (State): 지난번에 작업하고 기록해 둔 terraform.tfstate 장부
    1. 실제 상태 (Actual State): 플러그인을 시켜서 현재 AWS에 진짜로 존재하는지 확인해 온 결과 (Refresh)
    • 코어의 생각: "코드에는 EC2 1대가 있는데, 장부랑 실제 AWS를 확인해 보니 지금은 0대네? 그럼 EC2 1대를 '생성(Create)'해야겠다. 보안 그룹도 없으니 보안 그룹 먼저 만들고 EC2를 만들어야겠군."
    • 실제 변화:
        1. 의존성 그래프(DAG) 생성: 리소스 간의 연결 고리를 분석하여 작업 순서를 수학적인 그래프로 그립니다.
        1. Plan 출력: 터미널에 초록색(+), 노란색(~), 빨간색() 기호를 사용해 "이렇게 변경될 예정입니다"라는 가상 실행 결과(Dry-run)를 출력합니다. (여기서 실제 AWS에 변경이 일어나지는 않습니다.)

    terraform apply (작업 지시 및 장부 갱신 단계)

    이제 코어가 소매를 걷어붙이고 플러그인들을 지휘하여 실제 공사를 시작합니다.

    • 코어의 생각: "plan 단계에서 세운 순서도(Graph)대로 기술자(AWS Provider)에게 API 호출을 지시하자. 작업이 끝나면 결과를 장부에 적어야지."
    • 실제 변화:
        1. RPC(원격 프로시저 호출) 통신: 코어가 백그라운드에서 실행 중인 AWS 플러그인에게 "보안 그룹 A 생성해 줘!", "끝났으면 EC2 B 생성해 줘!"라고 명령을 보냅니다.
        1. 실제 API 호출: 지시를 받은 플러그인이 AWS의 API를 쏘아 실제 클라우드에 리소스를 만듭니다.
        1. State 업데이트: 모든 작업이 끝나면, 방금 생성된 리소스들의 실제 정보(예: 새로 할당된 퍼블릭 IP, 인스턴스 ID 등)를 받아와서 terraform.tfstate (장부) 파일에 덮어씁니다.
     
     
     

    참고 apply가 완료되면, 추가 되는 3개의 파일들

    • .terraform.lock.hcl
      • → package-lock.json과 비슷한 역할 : 버전 기록 (팀원과 버전 공유 및 업데이트 확인용)

    • terraform.tfstate
      • → 현재 상태 파일 (참고로 terraform destroy도 도 상태 변경으로 인식되어서, destory이 현재 상태가 빈 파일로)

    • terraform.tfstate.backup
      • → 이전 상태 파일 : 백업용

     
     
     
     
     

    ㅅvian iso 설치

    proxmox는

    • proxmox iso 자체를 이용한 운영체제 설치 (쉬움)
    • Devian 12를 이용한 내부 구조화 이후 세팅 (복잡함 but 디스크 분리 등 부가 설정 가능)

    두가지 방식으로 설치가 가능한데

     

    비교적 간단한 Proxmox를 iso로 설치하는 방식이 아닌 (이건 예전에 해봤으니까)

    Devian을 이용하는 방식으로 해보겠다.

    • CPU : 8, RAM : 12, disk 32 정도로 놓고!
    • 기본적인 machine과 bios는 기본 값을 사용하도록! + SCSI도 기본 Virtl0로!
    • Qemu Agent만 설정 해놓은 상태로!
    • 이전에 궁금했던, 네트워크 세부 설정이나, local-lvm 등의 기본 설정을 자세히 분석해 보면서

    사용하다가, 이 방식은 VM 위에 VM을 띄우는 방식이니까 효율성이 떨어지므로

    → 가상화 구성은 다시 원 서버로 돌아가서 할 생각이다!

     

    참고로 Qemu (Guest) Agent는

    HostOS(Proxmox) - QuestOS(Devian) 간의 통신용 demon으로

    Host에서 Guest내부 설정 등을

    • 보고
    • 명령 기반 제어가

    가능하도록 하는 백그라운드 프로그램이다!

     

    실제 서버에는 “Golden Image 기반의 템플릿 배포”라고 해서

     

    네트워크 기본 세팅 및 k8s 설치가 완료된 snapshot을 이용해

    해당 명령 체계로 초기화된 k8s node설치 + cloud-init을 이용한 네트워크 및 worker 세팅을 함께 사용하여

    worker나 control node를 자동화 방식으로 추가 및 삭제 등이 가능했다!

     

    CPU 설정 주의사항

    • 중첩 가상화 방식을 사용할 때는 가상 CPU를 사용할 경우 VT-x 같은 하드웨어 내 가속화 기술을 못 사용함

    → host로 설정 변경!

    • NUMA(Non-Uniform Memory Access) Architecture

    소켓에 따라 메모리에 대한 접근을 나누고, 특정 소켓에 특정 메모리는 접근이 빠른 대신, 다른 소켓에 할당된 메모리를 접근할 때 느림

    ⇒ total Core = Core x Socket

    • 같은 cpu 16을 만들때, 가상화 환경에서는 소켓 수가 적은 1x16이 더 유리!
     

    Disk partition

    파티션
    용량
    용도 및 설명
    / (루트)
    12 GB
    데비안 OS + Proxmox 패키지 + ISO 이미지 파일 (적정 용량)
    swap (스왑)
    1 GB
    메모리 부족 시 비상용 공간 (가상 머신 구동 시 필수)
    /home (홈)
    21 GB
    VM 데이터 전용 공간
     
    • /var/lib/vz proxmox 기본 저장 위치

    → 설치 후에 home으로 재배치 필요 /home/vm_data

     
     
     
     
     

    [ 메인 과제 ]

    우리 팀에서 주기적으로 단기 프로젝트 성으로 진행하던 유저 매칭 서비스를 상시 운영 서비스로 바꿔보려고 한다.

     

    그럼 바뀌는게 뭘까?

    1. 데이터를 기반으로 최적해를 매칭하고, 결과를 배포한 이전 방식과 달리, 매 순간마다 기준에 따라 매칭 데이터를 순위 별로 만들어야하는 방식
    1. 참여를 위해 단순 결제만 하던 방식이 아니라, 사용자가 credit을 활용해 원하는 동작을 하게하는 시스템
    1. 결제 데이터 정합성 → 이거는 이전 시즌을 운영할 때 문제가 많았다 ㅠㅠ
    1. 채팅 서비스 도입 → 이것도 지금 참 난관이다
     

    [ 이전에는 왜 결제에서 문제가 발생했나? ]

    1. 순차적으로 처리 검증이 안됐다

    일단 결제 시스템을 대충만 봐도 백엔드에서 여러 도메인의 복합 동작이 필요하다.

    • 매칭 신청 → 결제 창 → 백엔드에서 결제 + 매칭 상태 변경 + 포트원 api → 결제 완료/실패

     

    그러다보니,

    • 백엔드에서는 결제 완료라고 했는데,
    • 포트원에서는 아직 처리중이고,
    • 매칭 상태는 먼저 변경되고,
    • 다시 Verify 과정으로 확인해보니 이미 매칭 상태는 이미 결제가 되었는데?

    이런 상황이 단순 개발 환경에서 테스트 할때는 발생하지 않았지만, 실제 운영환경에서는 멀티스레딩 상황에서?는 문제가 발생했다 ㅠㅠ

    절대적인 개수 자체가 많지는 않아서 수동으로 맞췄는데 저거 백업하고 맞추는 게 너무나 힘들었던 기억이 있다.

     

    반성 : 이전 시즌은 내가 결제 담당이 아니었기 때문에 이해가 부족하기도 했고, 매칭 쪽을 개발한 나의 입장에서는 메소드를 넘겨주고 테스트 환경에서 “동작이 잘 되네” 에서 깊게 생각하지 않았던 것 같다.

     

    2. Verify를 포트원에 의존한다 + 재시도가 해결방법?

    기존 로직은 포트원에서 웹훅 받고 db 한번 보고 결제가 완벽하다 판단했다

    이게 완벽히 정합성을 챙길 수 있는가? 라는 고민을 해보니 답은 “아니다”였다!

    그럼 앞에서 언급한 에러처럼 이미 결제가 시작했는데 후에 verify에서 어긋나면 그냥 캔슬하면 그만인가?

     
     
     

    [ 그럼 어떻게 바꿀까? - 사전 단계 : Event와 Order의 분리 ]

    기존 payment 하나를 통합으로 사용하고 있던 구조를 분리!
    • Payment-Order (결제 주문 - 핵심 도메인)
      • 역할: 우리 서비스에서 결제되고, 상품의 소유가 바뀌고를 DB에 기록
    • Payment-Event (결제 이벤트 - PG 연동 인프라)
      • 역할: 외부 PG사(PortOne)와의 통신 이력(결제 성공/실패/환불/검증 webhook 등의 결과)을 기록
      •  

    그래서 나뉜게 무슨 장점을 가지느냐

    • "결제 실패 후 재시도" 처리가 매우 깔끔해진다

    분리했을 때는 Payment-Order는 하나만 존재하고, 그 아래에 Payment-Event가 2줄(1. FAILED 기록, 2. SUCCESS 기록) 쌓이게 된다

    즉 결제 기록을 계속해서 재발급 하는 것이 아닌 event 즉 PG사 요청만 번복하면 만사 해결!

     
    • Verify 처리 로직이 단순해진다.

    기존에는 물건정보와 결제정보가 한번에 관리되다 보니까 중복되었다고 결제를 취소하는 과정이

    물건 발급 → 사용자 소유로 변경 등의 모든 로직이 진행된 상황에서

    역방향으로 돌려야했다면, 포트원 웹훅을 imp_uid 가 unique되게 함으로써, 무조건 1개를 유지하고 중복이나, 문제가 발생했을 때는 순서상 로직 자체가 진행이 안된다!

    → event 처리 즉 A이후에 B가 되게 하는게 코드 단에서 설정해야했다면, 애초에 Payment-Event DB단에서 차단!

     

    즉 결제 과정은

    1. 우리 서버에서 merchant_uid 발급 = payment-order is PENDING
    1. 프론트에서 결제
    1. 포트원에서 return imp_uid to Front
    1. 우리 서버에서 payment-event를 imp_uid를 사용해서 저장
    1. payment-order is SUCCESS

    → 후에 event verify를 할때는 imp_uid 로 Portone API 사용

    순서다

     

    여기에 서비스의 정합성을 위해 몇 개의 로직을 이번에 추가 해보았다

    물론 정합성이 맞게 애초에 PAYMENT에서 문제가 없다면 필요가 없겠지만

    여러 호출을 통해 동작하므로, 꼬일 경우를 대비한 보호 및 검증 설계를 추가하자

     
     

    [ 그럼 어떻게 바꿀까? - 1단계 : Ledger의 도입 ]

    절대 장부(원장)를 만들자!

    Ledger는 Sorce Of Truth로 무조건적인 신뢰!

    • 크레딧 구매 요청부터
    • 결제를 요청하고, 검증하면서
    • 실제 크레딧 사용까지를 모든 유저를 대상으로 기록한 장부를 도입하자
     

    그리고 스케줄러를 사용해서 주기적으로 확인

    문제가 발생되면, 무조건 Ledger 기준으로 DB를 수정하면

    스케줄러 이후에는 절대적으로 무결한 데이터가 db에 형성될 수 있다!

    → Ledger기반으로 order, event 등의 payment관련 기록을 재조정하는 로직이 구현이 제일 어려울 거 같긴한데 다른 팀원이 맡아서 열심히 하는 중이다 ㅎㅈㅈ ㅎㅇㅌ!

     

    [ 그럼 어떻게 바꿀까? - 2단계 : Credit의 도입 ]

    결제 과정에서 가장 큰 문제가 뭘까 고민해봤더니 역시 유저의 돈 문제이다

    • 유저의 돈만 빠져나가고 크레딧이 안 들어오거나,
    • 한 번 결제했는데 크레딧이 두 번 들어오는 일은 절대 일어나서는 안된다

    이거는 원장으로 다 돌리면 해결되나?

     

    → 이것은 맞는 말이다

    • 스케줄러로 (원장 데이터 = DB데이터)가 되는 순간 데이터는 완벽하게 정합성을 보장한다
     

    근데 매 결제 타이밍 / 크레딧 사용 타이밍 원장 수준에서의 검사가 계속 되는게 맞는 구조인가?

    에 대한 답은 “아니다” 라고 생각한다.

     

    “원장”이라함은 절대적 Truth를 가지는 대신에, 모든 기록이 다 적혀있는 매우 큰 장부다 이걸 매 요청마다 n번씩 확인하면서 진행하는 것은 너무 비효율적이다.

     

    그럼 매 결제마다 원장 단계 이전에 검증을 한차례 진행하려면?

    비트 코인 방식에서 착안해서, 크레딧 자체에 상태 정보를 기록하자!

    • 크레딧에 순차적 상태값을 부여하고, 이를 이전 단계까지의 검증 완료 기준으로 삼아보자
     

    Credit 설계

    • 크레딧을 구매하는 수만큼 크레딧 생성!
    • 각 크레딧은 bulk 단위로 동작하면서
    • 상태 및 소유자, 구매 물품, 개당 가격 등의 기본 정보를 소유
    • 기본 정보를 바탕으로 paymentService에서 엔티티 변경 내역 기반 정보를 받아서
    • 크레딧의 정보와 비교 및 검증
     
    추가 설명

    과정에 대해 예시를 들어서 조금 더 설명해보면

    • 먼저 유저 A가 크레딧 5개 구매요청 → 5개의 빈 크레딧을 생성!
      • 크레딧에 [유저 A가 발급]이라는 정보를 넣고 X 5
    • 사용자가 클라이언트를 이용해서 결제를 시도할 때, 실제 유저 A가 merchant_id를 통해 실제 payment-order 정보 기반 검증
      • 실제로 5개인가 검증
    • 사용자가 구매를 시도할 때, 실제 PG와 통신 기록인 payment-event 확인 및 검증
    • 만약 포인트 같은 게 있다면 포인트가 해당 유저가 소유값을 db에서 확인 후에 1000이 차감되었는지도 확인 필요! → 일단 지금은 고려하지말기!
      • 검증
        • 소유자 확인 = 결제자
        • 실제로 pending인게 5개인가?
        • 개별가격 X 개수 = 실제 결제 금액
      • 크레딧에 [유저 A 결제 완료] 기록
      •  
    • 사용자가 크레딧을 상품 구매에 사용할 때,
      • 검증
        • 소유자 = 구매요청자
        • A가 활성 크레딧(결제 검증까지 된)을 상품 1의 가격 보다 많이 소유하고 있는지
        • 실제로 그 가격 만큼 차감되었는지 등을 확인 후에
      • 크레딧에 [유저 A 상품1 구매] 기록
      •  
    • 최종 기록
      • 검증
        • 최종 기록 구매 기록 및 소유자
        • 상품 1번이 유저 A 소유로 잘 이전되었는지
        • 상품 DB에서 1번의 갯수가 1개 차감 되었는지 등을 확인
      • 크레딧에 [사용 완료]
     

    이전 과정이 실패 했을 때는 어떻게 해야 하나? ⇒ 원장을 기반으로 DB table의 정합성을 맞추는 과정을 거치면 될 것 같다.

     

    추천 글

    BlogPro logo