이 글의 위치: 기술 블로그 → Infra/DevOps · OKE 인프라 학습 시리즈의 마무리 편. 앞에서 무료 클러스터 구축 → 인그레스·HTTPS → 채팅 배포까지 손으로 했다. 이번엔 그 반복 절차를 재사용 가능한 스킬로 묶은 이야기 — 자동화 자체를 아키텍처로 다룬다.
이 프로비저닝은 Claude Code 플러그인 스킬(oke-init)로 코드화돼 있다. 그리고 그 스킬은 하나의 원칙 위에 서 있다 — 클라우드 provider 계층만 교체하고, 그 위 쿠버네티스 계층은 건드리지 않는다.
1. 인프라에 적용한 Hexagonal — provider가 어댑터다
가장 마음에 드는 부분. 인프라를 provider 계층과 쿠버네티스 계층으로 분리해 두면, GCP→OCI 전환은 provider 계층만 갈아끼우면 된다. 그 위(ArgoCD·Traefik·서비스 배포)는 provider와 무관하게 그대로 재사용된다.
GCP: gke-init ─┐
├─→ k8s-init → argocd-init → traefik-init → service-deploy
OCI: oke-init ─┘ (이 계층은 provider 무관, 재사용)
실제로 GKE에서 OKE로 옮기면서 Traefik·cert-manager 설정은 거의 그대로 재사용했다. 스킬을 Hexagonal 용어로 매핑하면 이렇게 읽힌다.
| Hexagonal 요소 | oke-init에서 |
|---|---|
| Primary Adapter (Handler) | /oke-init 커맨드 |
| Inbound Port | 스킬의 argument-hint(입력 계약) |
| UseCase | setup-oke.sh의 7단계 절차 |
| Outbound Port | 리소스별 oci 서브커맨드 |
| Secondary Adapter | oci CLI |
| Domain | 네이밍 파생 규칙 + Always Free 불변식 |
즉 코드가 아니라 인프라 자동화에 Clean/Hexagonal을 문자 그대로 적용했다. provider(gke ↔ oke)는 서로 교체 가능한 Secondary Adapter인 셈이다.
2. GCP와 OCI는 같은 개념을 다르게 부른다
provider 계층을 분리하려면 두 클라우드의 개념 대응을 먼저 정확히 알아야 한다. 이 매핑 표가 스킬 문서의 뼈대다.
| 개념 | GCP (gke-init) | OCI (oke-init) |
|---|---|---|
| CLI | gcloud |
oci |
| 프로젝트 경계 | Project | Compartment + Tenancy |
| 레지스트리 | Artifact Registry | OCIR (<region-key>.ocir.io) |
| 네트워크 | 기본 VPC | VCN + public/private 서브넷 + IGW/NAT (클러스터보다 먼저) |
| CI 자격증명 | Workload Identity Federation (GitHub OIDC) | 배포 IAM 유저 + Auth Token |
특히 두 가지가 체감이 다르다. ① OCI는 클러스터 전에 네트워크(VCN)가 존재해야 한다. ② OCI엔 GitHub OIDC(WIF) 같은 1급 연동이 없어서, 배포 전용 유저 + Auth Token을 만들어 GitHub Secrets에 넣는 방식으로 CI를 붙인다. GCP의 WIF를 OCI에서 어떻게 대체하는가 — 이 우회 설계 자체가 스킬의 핵심 지식이다.
3. 무료가 기본값이다 — 실수로 과금되지 않는 설계
스킬의 기본값 자체를 Always Free로 승격했다. 플래그 없이 실행하면 관리비 $0 구성이 뜨고, 유료로 가려면 명시적으로 opt-in 해야 한다.
CLUSTER_TYPE="basic" # → BASIC_CLUSTER : 관리비 $0 (Enhanced=$0.10/hr)
NODE_SHAPE="VM.Standard.A1.Flex" # Ampere(arm64) : Always Free 대상
# 노드 총합 ≤ 4 OCPU / 24GB (2 OCPU × 2노드 = 4, 12GB × 2 = 24)
거기에 런타임 가드가 붙는다. BASIC_CLUSTER && A1.Flex인데 총 OCPU>4 또는 MEM>24면 "⚠️ Always Free 한도 초과" 경고를 띄우고, oci limits resource-availability get으로 A1 무료 여유를 사전 조회한다. "기본값을 어디에 두느냐"가 곧 정책이다 — 무료를 기본값으로 두면, 아무 생각 없이 실행해도 돈이 안 나간다.
4. 며칠치 삽질을 코드 주석으로 박제한다
스킬의 진짜 가치는 절차가 아니라 함정의 코드화다. 스크립트 한 줄에 며칠치 삽질이 압축돼 있다.
--wait-for-state의 진행 메시지가 stderr → OCID 캡처에2>&1붙이면 깨진다 (주석에 ✅/❌로 표기)- IDCS는
.localTLD를 거부(RFC 5322) → 배포 유저 이메일을oke.local에서example.com으로 바꾼 커밋이 실존 - cri-o가 unqualified 이미지명을 거부 → 퍼블릭 이미지도
docker.io/library/... - A1은 arm64 → 서비스 이미지는
linux/arm64빌드
이런 암묵지(tribal knowledge)를 사람 기억이 아니라 스크립트 가드·주석으로 옮기면, 다음 사람(그리고 6개월 뒤의 나)이 같은 함정에 다시 빠지지 않는다.
5. 버전 로그로 읽는 스킬의 성장사
재사용 스킬은 semver로 진화를 기록한다. plugin.json 버전 하나로 스킬의 역사가 읽힌다.
| 커밋 | 변경 | 버전 |
|---|---|---|
| gcp-init→gke-init 리네임 + oke-init 신규 | 스킬 추가 | 1.3.0 → 1.4.0 |
| OKE cloud-provider 패턴 + 무료 티어(Basic/A1) | 패턴/기능 추가 | 1.4.0 → 1.5.0 |
| Always Free 기본값 정합화 + OCID/arch 버그 수정 | 수정 | 1.5.0 → 1.6.0 |
규칙은 단순하다 — Patch=문구, Minor=패턴/스킬 추가, Major=리네임/삭제. 여기서 배운 것 하나: 마켓플레이스 매니페스트 동기화를 잊으면 상위 카탈로그가 stale 버전을 가리킨다(실제로 marketplace가 1.3.0을 가리키는 동안 플러그인은 이미 1.6.0이었다). 버저닝은 코드가 아니라 규율의 문제다.
정리 — 시리즈를 닫으며
- 인프라를 provider 계층 / K8s 계층으로 나누면 클라우드 전환이 계층 교체로 끝난다.
- 그 경계를 Hexagonal 포트/어댑터로 설계하면 자동화가 아키텍처가 된다.
- 무료를 기본값으로 두면 실수로 과금되지 않는다.
- 함정은 사람이 아니라 코드 주석·가드가 기억한다.
- semver는 재사용 스킬의 성장 로그다.
왜 다시 상시 가동 쿠버네티스로 옮겼는가에서 시작해, 무료 클러스터를 띄우고, 채팅 백엔드를 설계·배포하고, Flutter 클라이언트까지 붙였다. 인프라 한 겹, 백엔드 한 겹, 클라이언트 한 겹 — 그리고 그 전부를 다시 자동화 스킬 한 겹으로 감싼 셈이다.
답글 남기기