[Warm-up Lap] 월 0원의 이상과 현실의 벽: 초기 아키텍처 설계와 6개의 결정이 탄생하기까지

🏁 WARM-UP · LAP 00 / 07 · ⏱️ 9 min read

“소프트웨어 아키텍처를 설계할 때 가장 경계해야 할 함정은 ‘이상적인 가정’만으로 그림을 그리는 것입니다. 현실의 물리적 한계를 고려하지 않은 청사진은 첫 배포와 동시에 무너집니다.”
클라우드 콘솔을 열거나 코드를 한 줄 작성하기 전, 시스템이 마주할 인프라 환경의 제약과 트레이드오프를 면밀히 점검하지 않으면 예기치 못한 고정 비용 청구나 성능 병목이라는 암초를 만나게 됩니다.
Warm-up Lap이라는 부제처럼, 이 글은 본격적인 인프라 구축 이야기(Lap 1 ~ Lap 5)를 시작하기에 앞서 시스템의 토대를 냉정히 돌아보는 준비 과정입니다. 처음에 제가 순진하게 그렸던 ‘초기 구상 아키텍처(Initial Architecture)’가 현실의 벽과 어떻게 충돌했는지, 그리고 그 균열을 메우기 위해 왜 임기응변식 땜질 대신 6개의 핵심 아키텍처 결정(ADR: Architecture Decision Record)을 세우게 되었는지 그 배경과 설계 철학을 공유합니다.


1. 순진했던 초기 구상: “이러면 월 0원 블로그 완성 아닌가?”

처음 개인 기술 블로그와 앞으로 구축할 AI·퀀트 시스템의 기록 공간을 구상했을 때, 제 머릿속에 떠오른 청사진은 매우 단순하고 직관적이었습니다.

“방문자가 없으면 서버가 꺼지는 서버리스(Cloud Run)를 쓰고, 데이터베이스는 GCP가 평생 무료로 주는 Always Free VM(e2-micro)에 올리자. 그리고 한국 독자들을 위해 서버는 서울에 두고, 앞단에 무료 CDN(Cloudflare)을 붙이면 돈 한 푼 안 들고 빠른 블로그가 완성되겠지?”

클라우드 콘솔을 열기 전 제가 노트에 스케치했던 초기 청사진은 다음과 같았습니다.

📐 초기 구상 아키텍처 (The Naive Blueprint)

초기 구상 아키텍처: 순진했던 가정과 태평양 분열
초기 구상 아키텍처: 순진했던 가정과 태평양 분열
📐 원본 Mermaid 도식 구조 접기/펼치기
flowchart LR
    User["웹 방문자 / 브라우저 (한국)"] -->|"HTTPS"| CF["Cloudflare (무료 플랜)"]
    CF -->|"프록시 전달"| CR["GCP Cloud Run (서울 리전: asia-northeast3)<br/>• 사용자 근접 배치<br/>• Scale-to-Zero"]
    CR -->|"인터넷/사설 통신 (희망사항)"| DB["GCP Always Free VM (미국 오리건: us-west1)<br/>• e2-micro (1GB RAM)<br/>• MySQL 8.0 직접 설치"]
    CR -->|"미디어 저장"| GCS["Cloud Storage (GCS)<br/>• 서비스 계정 JSON 키 다운로드 연동"]

얼핏 보면 구글 클라우드의 서버리스 혜택과 영구 무료 티어를 영리하게 결합한 완벽한 ‘무과금 아키텍처’처럼 보였습니다.

하지만 이 순진한 구상은 클라우드 엔지니어링의 현실과 마주하자마자 산산조각 났습니다.


2. 현실의 벽: 배포 첫날 마주한 5대 치명적 결함

실제로 배포 스크립트를 작성하고 프로토타입을 테스트하자마자, 책이나 간단한 블로그 튜토리얼에서는 전혀 언급되지 않던 5가지 치명적인 장벽이 잇달아 터져 나왔습니다.

현실의 벽: 배포 첫날 마주한 5대 치명적 결함과 6대 ADR 해결책
현실의 벽: 배포 첫날 마주한 5대 치명적 결함과 6대 ADR 해결책
📐 원본 Mermaid 도식 구조 접기/펼치기
flowchart TD
    Init["초기 구상 아키텍처 배포 시도"] --> W1 & W2 & W3 & W4 & W5

    W1["💥 장벽 1: 커스텀 도메인 매핑 불가<br/>서울 리전 Cloud Run은 무료 도메인 매핑 미지원<br/>➔ 유료 로드밸런서(월 $18+) 강제 청구"]
    W2["💥 장벽 2: 태평양 횡단 쿼리 지연<br/>서울(Cloud Run) ↔ 미국(DB) RTT 140ms<br/>워드프레스 30회 쿼리 = 빈 화면 4.2초 지연"]
    W3["💥 장벽 3: 1GB RAM의 OOM 강제 다운<br/>MySQL 가동 즉시 메모리 고갈로 데몬 사망<br/>Cloud Run 헬스체크 실패로 컨테이너 폭주"]
    W4["💥 장벽 4: 보안 조직 정책 충돌<br/>GCP 기본 조직 정책이 서비스 계정 JSON 키 생성 차단<br/>플러그인의 GCS 미디어 연동 불능"]
    W5["💥 장벽 5: 무상태(Stateless) 컨테이너 함정<br/>새 이미지 배포할 때마다 설치했던 테마와 플러그인 증발<br/>정적 캐시 플러그인이 파일시스템 에러 유발"]
  1. 장벽 1: 커스텀 도메인 매핑 불가 (고정비 $18의 배신)
    • Cloud Run은 gcloud beta run domain-mappings를 통해 커스텀 도메인과 무료 관리형 SSL을 0원에 연결해 줍니다.
    • 하지만 GCP 공식 문서를 확인해 보니 서울(asia-northeast3) 리전은 이 기능을 지원하지 않았습니다.
    • 서울 리전에서 wealth-podium.com 도메인을 연결하려면 ‘전역 외부 애플리케이션 부하 분산기(HTTP(S) Load Balancer)’를 무조건 두어야 하는데, 이 로드밸런서는 트래픽이 0이어도 매달 고정 포워딩 룰 비용으로 최소 $18(약 25,000원)를 청구합니다. ‘월 유지비 0원’이라는 핵심 목표가 첫 단추부터 무너진 것입니다.
  1. 장벽 2: 태평양 횡단 SQL 쿼리 지연 (TTFB 4.2초의 대참사)
    • GCP의 Always Free e2-micro VM은 정책상 미국 3개 리전(us-west1, us-central1, us-east1)에서만 무료입니다.
    • 애플리케이션(Cloud Run)은 서울에, 데이터베이스(DB VM)는 미국 오리건에 둔 상태에서 페이지를 요청하자, 서울 ↔ 미국 간 왕복 지연(140ms)이 워드프레스의 30~40개 순차 SQL 쿼리에 그대로 곱해졌습니다 ($140text{ms} times 30text{회} = 4,200text{ms}$). 첫 화면이 뜨는 데만 4.2초 넘게 빈 화면이 지속되었습니다.
  1. 장벽 3: 1GB RAM의 메모리 고갈 (OOM Crash)
    • 평생 무료인 e2-micro 인스턴스는 RAM이 겨우 1GB에 불과합니다.
    • 기본 설정의 MySQL 8.0을 띄우자마자 OS 메모리가 고갈되어 리눅스 커널의 OOM(Out Of Memory) Killer가 데이터베이스 프로세스를 강제 종료시켰습니다.
  1. 장벽 4: 엔터프라이즈 보안 조직 정책과의 충돌 (IAM Keyless 장벽)
    • 컨테이너가 재시작되면 로컬 업로드 이미지가 날아가므로 Google Cloud Storage(GCS)로 미디어를 보내야 했습니다.
    • 기존의 모든 워드프레스 GCS 플러그인은 서비스 계정의 JSON 키 파일을 요구했으나, 구글 클라우드의 기본 보안 조직 정책(constraints/iam.disableServiceAccountKeyCreation)에 의해 키 생성이 전면 차단되었습니다.
  1. 장벽 5: 무상태(Stateless) 컨테이너의 데이터 증발
    • Cloud Run은 요청이 없으면 인스턴스가 0으로 꺼지고, 새 버전이 배포되면 기존 컨테이너가 파기되는 완전한 ‘무상태(Stateless)’ 환경입니다.
    • 관리자 화면에서 열심히 설치한 테마와 플러그인이 컨테이너가 재시작될 때마다 흔적도 없이 초기화되었습니다.

3. 임기응변식 땜질 대신 ‘6개의 결정(ADR)’을 세우다

눈앞에 닥친 에러들을 해결하는 가장 쉬운 방법은 상용 서비스를 결제하거나 비표준 꼼수(임시 스크립트, 로컬 우회)를 쓰는 것입니다.

  • 서울 리전 도메인 매핑이 안 되니 그냥 월 2만 원짜리 로드밸런서를 결제할 것인가?
  • DB가 죽으니 월 2만 원짜리 Cloud SQL을 쓸 것인가?
  • 보안 정책을 임의로 해제하고 위험한 서비스 계정 키를 서버에 방치할 것인가?

이런 식의 타협은 결국 “불필요한 고정비 유출과 기술 부채의 누적”으로 이어집니다.

소프트웨어 아키텍처에서 가장 중요한 것은 문제가 발생했을 때 근본 원인을 파헤치고, 트레이드오프를 정량적으로 계산하여 지속 가능한 원칙을 세우는 것입니다.

그래서 저는 개발을 잠시 멈추고, 이 5대 장벽을 격파하기 위한 6개의 공식 아키텍처 의사결정서(ADR: Architecture Decision Record)를 제정했습니다.

🧭 Track 01을 지탱하는 6개의 결정 (ADR-001 ~ ADR-006)

ADR 식별자 직면했던 근본 문제 제가 채택한 아키텍처 해법 (Trade-off & Decision) 배정된 상세 구현 편
ADR-001 서울 리전의 $18 로드밸런서 과금 & 4.2초 DB 지연 모든 리소스를 오리건(us-west1) 단일 리전으로 밀착 통합, 한국 체감 속도는 Cloudflare 서울 엣지(ICN) 캐싱으로 방어 Lap 01
ADR-002 1GB RAM 무료 VM의 MariaDB OOM 사망 2GB Swap 파일 구성 + 버퍼 풀 128MB 제한 등 초저메모리 my.cnf 튜닝으로 상시 점유 250MB 이내 압축 Lap 02
ADR-003 무거운 WP 초기화 쿼리로 인한 컨테이너 기동 지연 DB 쿼리 없는 순수 메모리 기반 /healthz 초경량 헬스체크 엔드포인트 분리 Lap 02
ADR-004 Cloud Run 리버스 프록시 뒤 :8080 포트 루프 Apache UseCanonicalName On 및 HTTPS 헤더 인식으로 외부 도메인 포트 정규화 Lap 04
ADR-005 Cloud Build 자동 배포 시 권한 거부 및 태그 충돌 불변 빌드 태그(${BUILD_ID}) 전략 및 Artifact Registry 전용 서비스 계정 최소 권한 부여 Lap 02
ADR-006 JSON 키 생성 차단 정책 & 이미지 2560px 에러 Keyless ADC(응용 기본 자격증명) 코어 패치 및 GD 엔진 기반 서브사이즈 후 일괄 GCS 동기화 Lap 03

4. 앞으로의 엔지니어링 여정: 미완성 프로토타입에서 완성형 프로덕션으로

설계 단계에서 마주한 시스템의 위험 요인과 병목 지점은 모두 식별되었습니다.

순진했던 초기 구상의 한계를 극복하고, 6대 ADR을 인프라와 코드로 구체화해 나가는 실전 구축기는 아래 순서로 전개됩니다.

[Warm-up Lap] ➔ [Lap 01] ➔ [Lap 02] ➔ [Lap 03] ➔ [Lap 04] ➔ [Lap 05] ➔ [Finish Line]
(초기 구상&ADR)  (리전 통합)   (DB&배포)   (미디어ADC)  (엣지보안)  (불변Prebake) (최종 완성 아키텍처)

준비는 끝났습니다. 첫 번째 의사결정이었던 Lap 01의 리전 통합 이야기부터 차근차근 풀어가 보겠습니다.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *