모놀리식을 NKS로 — 네 개의 계층으로 다시 세우기
요약 — 단일 배포에 묶여 작은 변경도 두렵던 서비스를 NCP의 NKS(Naver Kubernetes Service) 위로 옮겼다. 통째로 다시 쓰지 않고 인프라·DB·API·프론트엔드를 네 계층으로 나눴다. 인프라는 Terraform으로 코드화하고, DB는 관리형 Cloud DB로 떼어냈으며, 배포는 ArgoCD GitOps로 Git이 단일 진실이 되게 했다. 핵심은 성능 숫자가 아니라 "어디를 손대면 무엇이 바뀌는지"를 다시 알 수 있게 만든 것이다.
오래된 시스템에는 공통된 증상이 있다. 작은 변경이 무섭다. 한 줄을 고치려면 전체를 다시 올려야 하고, 무엇이 같이 따라 움직일지 아무도 자신하지 못한다. 빌드는 외부 SaaS 화면 안에서 돌고, 배포는 손으로 이어 붙인다. 이 서비스도 그랬다. 돌아가긴 하는데, 바꾸기가 두려운 상태였다.
그래서 결정은 "더 빠르게 만들자"가 아니었다. "다시 손댈 수 있게 만들자" 였다. 통째로 갈아엎는 재작성은 위험이 크고 중간에 멈출 수 없다. 우리는 대신 시스템을 네 개의 계층 — 인프라, 데이터, API, 프론트엔드 — 으로 나누고, 각 계층을 코드로 정의해 NCP의 NKS 위에 다시 세우기로 했다.

통째로가 아니라, 계층으로
모던화에서 가장 흔한 실수는 "현대적인 걸로 다시 짜자"며 모든 걸 한 번에 바꾸는 것이다. 그러면 멈출 수 없는 큰 베팅이 된다. 우리는 반대로 갔다. 경계를 먼저 긋고, 계층별로 떼어냈다.
프론트엔드는 사용자에게 보이는 화면, API는 비즈니스 로직, DB는 상태, 인프라는 그 셋이 돌아가는 바닥. 이 넷의 책임이 한 덩어리에 섞여 있으면 어디를 고쳐도 전부가 흔들린다. 그래서 각 계층이 독립적으로 빌드되고, 독립적으로 배포되고, 독립적으로 죽었다 살아나도 나머지가 멀쩡하도록 경계를 세우는 것 — 그게 이 작업의 전부였다.
인프라: 코드로 만든 NKS
가장 먼저, 인프라를 화면 클릭이 아니라 코드로 옮겼다. Terraform으로 NKS 클러스터, 네트워크, 관리형 DB, 접근 제어를 전부 선언했다. 클러스터는 Kubernetes 1.34, 워커 노드는 8 vCPU / 16GB짜리 4대로 시작했다.
네트워크는 보안을 기본값으로 깔았다. 워커 노드는 전부 Private Subnet에 있고 공인 IP가 없다. 외부에서 클러스터 안으로 직접 들어올 길이 없다는 뜻이다. 사용자 트래픽은 오직 NCP ALB를 거친 Ingress로만 들어온다.1 VPC 안은 용도별로 서브넷을 나눴다 — 워커용, 로드밸런서용, DB용. 그 사이의 통신은 ACG(보안 그룹)로 포트 단위까지 좁혔다.
Ingress 한 곳이 라우팅을 책임진다. /api로 시작하는 요청은 API 서비스(8080)로, 나머지는 프론트엔드(3000)로 보낸다. HTTP(80)로 들어오면 HTTPS(443)로 자동 리다이렉트한다. 이 규칙은 매니페스트 안에 적혀 있어서, 라우팅이 바뀌면 Git diff에 남는다.
인프라가 코드가 되자 한 가지가 달라졌다. 환경을 다시 만들 수 있게 됐다. "이게 어떻게 떠 있더라"를 사람 기억에 의존하지 않는다 — terraform apply가 답이다.
데이터: 관리형으로 떼어내다
DB는 클러스터 안에 두지 않았다. NCP의 Cloud DB for MySQL로 떼어내, 상태를 컴퓨트와 분리했다.2 파드는 언제든 죽었다 다시 떠도 되는 일회용이어야 하고, 데이터는 그래선 안 된다. 둘을 같은 생애주기에 묶지 않는 게 출발점이다.
운영 DB는 HA Multi-Zone으로 구성해 한 존이 죽어도 살아남게 했고, 개발 DB는 단독 인스턴스로 가볍게 뒀다. DB는 전용 DB 서브넷에 있고, ACG로 3306 포트를 VPC 내부에서만 열었다. 바깥에서는 보이지 않는다.
API는 환경 변수로 주입된 접속 정보로 DB에 붙는다. 스키마는 애플리케이션이 마음대로 바꾸지 못하게 잠갔다. Prisma 마이그레이션으로 스키마 변경을 명시적인 단계로 만들어, 앱이 뜰 때 테이블을 슬쩍 고치는 일은 없다.
앱: 컨테이너로 다시 빌드
API와 프론트엔드, 그리고 운영을 위한 백오피스까지 세 앱을 각각 컨테이너로 다시 빌드했다. 멀티스테이지 Docker 빌드로 빌드 단계와 런타임 단계를 분리해, 최종 이미지에는 실행에 필요한 것만 남겼다. 모든 컨테이너는 non-root 사용자로 돈다.
쿠버네티스가 앱의 생사를 제대로 판단하게 하려면 앱이 자기 상태를 정직하게 말해야 한다. 그래서 readiness·liveness·startup 프로브를 붙여, 쿠버네티스가 헬스 엔드포인트로 파드의 상태를 확인하게 했다. 기본 2 레플리카로 띄우고 HPA로 부하에 따라 파드를 늘린다. 비밀값은 Secret, 환경 설정은 ConfigMap으로 주입해 이미지에 설정을 굽지 않았다 — 같은 이미지가 개발에서도 운영에서도 그대로 뜬다.
API는 Node 기반 NestJS + Prisma로 구성했다. 프레임워크 선택 자체보다, 각 앱이 자기 Dockerfile과 자기 배포 수명주기를 갖게 된 것이 본질이다.
배포는 Git이 진실 — GitOps
여기까지 만든 매니페스트를 사람이 kubectl apply로 밀면, 다시 원점이다. 누가 무엇을 언제 바꿨는지 흩어진다. 그래서 배포를 ArgoCD GitOps로 옮겼다.3 클러스터의 상태는 Git 저장소가 정의하고, ArgoCD가 그 둘을 계속 맞춘다.
환경 차이는 Kustomize의 base / overlay로 다뤘다.4 공통 정의는 base에 두고, 개발·운영 차이(네임스페이스, 도메인, 이미지 태그, 프로파일)만 각 overlay가 덮어쓴다. 같은 base에서 두 환경이 갈라지니 "개발에선 됐는데 운영에선 안 돼"가 줄어든다.
ArgoCD는 자동 동기화에 self-heal과 prune을 켰다. 누가 클러스터를 손으로 바꿔 Git과 어긋나면, ArgoCD가 도로 Git 상태로 되돌린다. 드리프트가 스스로 복구된다는 뜻이다. 클러스터의 진실은 클러스터가 아니라 Git에 있다.
CI를 우리 안으로
남은 절반은 CI였다. 기존에는 소스가 외부 SaaS에 있었고 빌드는 GUI 화면 안에서 돌았다 — 통제권이 우리 손 밖이었다. 이걸 self-hosted GitLab CE와 NKS 위에 Helm으로 띄운 Jenkins로 가져왔다.5 소스도, 빌드도 우리 네트워크 안에서 돈다.
흐름은 단순하다. main 브랜치에 푸시 → Jenkins 웹훅 트리거 → 이미지 빌드 → **NCR(Container Registry)**에 푸시 → GitOps 저장소의 kustomization.yaml에서 이미지 태그를 새 빌드로 갱신 → ArgoCD가 약 3분 주기로 변경을 감지해 자동 배포. 빌드 번호가 곧 이미지 태그가 되어, 무엇이 떠 있는지 추적이 끊기지 않는다.
여기서 의도적으로 바꾸지 않은 게 하나 있다. CD는 그대로 ArgoCD를 뒀다. CI(소스·빌드)만 우리 안으로 가져오고 배포 메커니즘은 유지했다. 한 번에 다 바꾸지 않는 것 — 그게 멈추지 않고 옮기는 방법이다.
무엇이 달라졌나
가장 큰 변화는 Git 히스토리가 곧 배포 히스토리가 된 것이다. 무엇이, 언제, 왜 바뀌었는지가 한곳에 남는다. 인프라는 terraform apply로 다시 만들 수 있고, 앱은 같은 이미지가 환경만 갈아끼우며 뜨고, 드리프트는 self-heal이 되돌린다. "이거 어떻게 떠 있더라"라는 질문이 사람 기억에서 저장소로 옮겨갔다.
솔직한 한계도 남겨 둔다. 노드 풀은 아직 고정 4대로, 노드 단위 오토스케일은 적용하지 않았다 — 파드 오토스케일(HPA)까지가 현재 범위다. Terraform 상태도 아직 단순하게 관리한다. 모던화는 끝나는 작업이 아니라 다음 단계를 손댈 수 있는 상태로 만드는 작업이다. 우리가 산 건 속도가 아니라 그 여지다.
이런 NKS·NCP 기반의 인프라 현대화와 GitOps 전환은 클라우드 & 인프라와 제품 개발에서 다루는 일의 일부다. 핵심은 "현대적인 걸로 바꿨다"가 아니라, 그 뒤에도 계속 손댈 수 있게 만드는 것이다.
참고 자료
본문의 플랫폼 동작·구성은 아래 공식 문서를 기준으로 했고, 클러스터·DB·노드 스펙은 한 프로젝트의 실제 구성값이다.
Footnotes
-
Naver Cloud Platform — Kubernetes Service(NKS) 가이드. https://guide.ncloud-docs.com/docs/k8s-overview ↩
-
Naver Cloud Platform — Cloud DB for MySQL 가이드. https://guide.ncloud-docs.com/docs/clouddbformysql-overview ↩
-
Argo CD — Declarative GitOps CD for Kubernetes. https://argo-cd.readthedocs.io/en/stable/ ↩
-
Kustomize — Kubernetes native configuration management. https://kubectl.docs.kubernetes.io/references/kustomize/ ↩
-
Jenkins — Installing Jenkins on Kubernetes. https://www.jenkins.io/doc/book/installing/kubernetes/ ↩