프로젝트/EWHA 캡스톤 졸업프로젝트

[K-PaaS 공모전] AWS EC2에서 NCP NKS로: handDoc Kubernetes 마이그레이션 기록

rngPwns 2026. 9. 3. 12:15

1. 시작하며

졸업프로젝트 handDoc은 청각장애인의 비대면 진료를 지원하기 위해 개발한 서비스다.

서비스는 크게 다음 컴포넌트로 구성했다.

  • React 기반 프론트엔드
  • Spring Boot 기반 비즈니스 API
  • FastAPI 기반 AI 추론 서버
  • WebRTC 기반 화상 진료
  • WebSocket 기반 실시간 데이터 전달
  • AWS RDS MySQL
  • MongoDB
  • NAVER CLOVA / OpenAI 외부 API

초기에는 AWS EC2 한 대에서 Spring Boot와 FastAPI를 Docker 컨테이너로 실행하고, Nginx를 Reverse Proxy로 두어 서비스를 운영했다.

이후 K-PaaS 공모전에 참가하면서 기존 서비스를 Naver Cloud Platform의 Ncloud Kubernetes Service(NKS) 환경으로 마이그레이션했다.

NKS 클러스터 자체는 공모전에서 제공받았다. 따라서 이번 작업의 핵심은 Kubernetes 클러스터를 처음부터 프로비저닝하는 것이 아니라, 기존 AWS EC2 기반 애플리케이션을 제공받은 NKS 클러스터에 맞게 Kubernetes 워크로드로 재구성하고 외부에서 실제 서비스할 수 있는 환경까지 만드는 것이었다.

마이그레이션하면서 기존의

EC2
 ├── Nginx
 ├── Spring Boot Container
 └── FastAPI Container
 

구조를

NCP Public Load Balancer
        ↓
NGINX Ingress Controller
        ↓
   Ingress Routing
    ┌──────┴──────┐
    ↓             ↓
Spring Service  FastAPI Service
    ↓             ↓
Spring Pod      FastAPI Pod
 

구조로 변경했다.

이 글에서는 기존 AWS 환경을 NKS로 이전한 과정과 API 라우팅, WebSocket, TLS 및 애플리케이션 상태를 검증하면서 겪었던 문제를 정리한다.


2. 기존 AWS EC2 아키텍처

기존 handDoc 백엔드는 하나의 EC2를 중심으로 구성했다.

                         Client
                           │
                      HTTPS / WSS
                           ↓
                     handdoc.store
                           │
                           ↓
                         Nginx
                    Reverse Proxy
                     TLS termination
                           │
               ┌───────────┴───────────┐
               │                       │
             /api/*               /fastapi/*
               │                       │
               ↓                       ↓
          Spring Boot               FastAPI
       Docker Container         Docker Container
               │                       │
               │                 AI Inference
               │
        ┌──────┴──────┐
        ↓             ↓
 AWS RDS MySQL    MongoDB
 

Nginx는 외부 요청을 받아 경로에 따라 Spring Boot와 FastAPI로 전달했다.

예를 들어 개념적으로는 다음과 같은 구조였다.

/api/*      → Spring Boot
/fastapi/*  → FastAPI
 

WebSocket 역시 Nginx를 거쳐 FastAPI에 연결했다.

wss://handdoc.store/fastapi/ws
              ↓
            Nginx
              ↓
      FastAPI WebSocket
 

이 구조에서는 Nginx가 같은 EC2 안에서 실행되는 컨테이너를 대상으로 Reverse Proxy 역할을 했다.


3. NKS 마이그레이션에서 바뀐 것

AWS와 NKS에서 가장 큰 차이는 애플리케이션 실행 및 네트워크 관리 단위였다.

기존에는 EC2 한 대 안에 컨테이너가 함께 존재했다.

EC2

├── Spring Container
└── FastAPI Container
 

NKS에서는 Spring Boot와 FastAPI를 독립적인 Kubernetes 워크로드로 분리했다.

NKS Cluster

├── Spring
│   ├── Deployment
│   ├── Pod
│   └── Service
│
└── FastAPI
    ├── Deployment
    ├── Pod
    └── Service
 

그리고 외부 요청을 각 Service로 전달하기 위해 NGINX Ingress Controller를 사용했다.

즉 기존 Nginx의 역할이 없어진 것이 아니라 Kubernetes 방식으로 바뀌었다.

AWS

Nginx Reverse Proxy
       ↓
Docker Container
 

NKS

NGINX Ingress Controller
       ↓
Kubernetes Service
       ↓
Pod
 

이 차이를 이해하는 것이 이번 마이그레이션의 핵심이었다.


4. NKS 클러스터 접근

NKS 클러스터 자체는 공모전에서 제공받았다.

따라서 작업 범위는 다음과 같았다.

제공된 NKS Cluster
        ↓
클러스터 접근 환경 구성
        ↓
handDoc Kubernetes 리소스 구성
        ↓
Container Image 배포
        ↓
Deployment / Pod 구성
        ↓
Service 구성
        ↓
NGINX Ingress Controller
        ↓
Ingress Routing
        ↓
TLS / HTTP / WebSocket
        ↓
Frontend 연동
 

“NKS 클러스터를 구축했다.”

보다는

“공모전에서 제공받은 NKS 클러스터에 기존 AWS 애플리케이션을 Kubernetes 워크로드로 재구성해 마이그레이션했다.”

라고 표현하는 것이 정확하다.


5. Docker 이미지를 NCP Container Registry로 이전

기존 Spring Boot와 FastAPI는 이미 Docker 기반으로 구성돼 있었다.

따라서 애플리케이션 자체를 다시 만드는 것보다 기존 컨테이너 이미지를 Kubernetes에서 실행할 수 있도록 옮기는 작업이 중심이었다.

NCP에서는 Container Registry를 사용했다.

실제 사용한 Registry는:

handdoc.kr.ncr.ntruss.com
 

이었다.

전체 과정은 다음과 같다.

Spring / FastAPI Source
          ↓
      docker build
          ↓
      Docker Image
          ↓
NCP Container Registry
          ↓
      NKS Cluster
          ↓
       Deployment
          ↓
          Pod
 

즉 Docker를 Kubernetes로 대체한 것이 아니다.

Docker는 애플리케이션 패키징 단위로 유지하고, 컨테이너의 실행과 상태 관리를 Kubernetes가 담당하도록 변경한 것이다.


6. Spring Boot와 FastAPI 워크로드 분리

AWS에서는 두 애플리케이션이 같은 EC2 자원을 공유했다.

하지만 둘의 특성은 상당히 달랐다.

Spring Boot는 일반적인 비즈니스 API 요청을 처리하는 반면 FastAPI는 수어 인식을 위해 AI 모델을 로드하고 추론 작업을 수행한다.

따라서 NKS에서는 각각을 별도 Deployment와 Pod로 분리했다.

                 NKS

       ┌──────────┴──────────┐
       │                     │
Spring Deployment      FastAPI Deployment
       │                     │
       ↓                     ↓
 Spring Pod             FastAPI Pod
                             │
                        AI Model
                        OpenCV
                        MediaPipe
 

이렇게 분리하면서 애플리케이션별로 배포와 상태를 독립적으로 관리할 수 있게 됐다.

또한 Spring과 FastAPI 중 한쪽에 문제가 발생했을 때 다른 워크로드와 구분해서 로그와 상태를 확인할 수 있었다.


7. Service를 통해 Pod를 추상화

Pod를 분리한 다음에는 각 애플리케이션 앞에 Kubernetes Service를 구성했다.

Kubernetes에서는 Pod가 재생성될 수 있기 때문에 외부 컴포넌트가 특정 Pod IP를 직접 바라보도록 구성하지 않았다.

Spring Service
      ↓
 Spring Pod
 
FastAPI Service
      ↓
 FastAPI Pod
 

따라서 이후 Ingress도 Pod에 직접 요청하는 것이 아니라 Service를 대상으로 라우팅한다.

Ingress
   │
   ├──→ Spring Service
   │          ↓
   │      Spring Pod
   │
   └──→ FastAPI Service
              ↓
          FastAPI Pod
 

이렇게 함으로써 Pod가 재생성되더라도 Ingress가 개별 Pod의 위치를 알 필요가 없게 됐다.


8. NGINX Ingress Controller 구성

기존 AWS에서 Nginx가 담당하던 외부 요청 분기 역할은 NKS에서 NGINX Ingress Controller가 담당하도록 구성했다.

여기서 NGINX와 Ingress를 별개의 기술로 생각하면 헷갈릴 수 있다.

Ingress는 Kubernetes에서 외부 HTTP/HTTPS 요청을 어떤 Service로 보낼지를 선언하는 리소스이고, NGINX Ingress Controller는 그 Ingress 규칙을 실제로 처리하는 구현체다.

즉 구조는 다음과 같다.

Ingress Resource
       ↓
NGINX Ingress Controller
       ↓
Kubernetes Service
       ↓
Pod
 

handDoc에서는 API 성격에 따라 요청을 나눴다.

                     NGINX Ingress Controller
                               │
                   ┌───────────┴───────────┐
                   │                       │
                /api/*                /fastapi/*
                   │                       │
                   ↓                       ↓
             Spring Service          FastAPI Service
                   │                       │
                   ↓                       ↓
              Spring Pod              FastAPI Pod
 

FastAPI 쪽에는 일반 AI API뿐 아니라 WebSocket 요청도 존재하기 때문에 WebSocket 연결까지 고려해야 했다.


9. NCP Public Load Balancer는 어디서 등장했나

Ingress Controller 앞에는 NCP Public Load Balancer가 존재했다.

실제 환경에서는 다음과 같은 Load Balancer hostname을 확인했다.

ingress-ngi-ingress-ngin-74785-110604561-3909befa6408.kr.lb.naverncp.com
 

여기서 중요한 것은 내가 NCP 콘솔에서 애플리케이션용 Load Balancer를 별도로 만들어 Pod에 하나씩 연결한 구조가 아니라는 점이다.

NGINX Ingress Controller를 외부에 노출하기 위한 Service가 LoadBalancer 타입으로 구성되면서 NCP의 Load Balancer가 외부 진입점 역할을 하게 된 구조였다.

따라서 실제 요청 흐름은 다음과 같다.

Client
  ↓
NCP Public Load Balancer
  ↓
NGINX Ingress Controller
  ↓
Ingress Rule
  ↓
Kubernetes Service
  ↓
Pod
 

이 구조는 아키텍처에서도 그대로 표현할 수 있다.


10. TLS 구성

외부 서비스이기 때문에 HTTP뿐 아니라 HTTPS/WSS를 처리해야 했다.

NKS에서는 TLS 인증서를 Ingress 계층과 연결해 외부 요청을 HTTPS로 받을 수 있도록 구성했다.

인증서 관리에는 cert-manager와 Let's Encrypt를 사용했다.

개념적으로는 다음과 같다.

                 Let's Encrypt
                       │
                       ↓
                  cert-manager
                       │
                 TLS Certificate
                       │
                       ↓
Client → NCP LB → NGINX Ingress Controller
                       │
                       ↓
                    Service
                       │
                       ↓
                      Pod
 

즉 Let's Encrypt가 사용자 요청을 전달하는 네트워크 컴포넌트는 아니다.

인증서 발급/갱신을 담당하고 실제 HTTPS/WSS 트래픽은 Load Balancer와 NGINX Ingress Controller를 통해 애플리케이션으로 전달된다.


11. 기존 데이터베이스는 그대로 유지

이번 작업에서는 컴퓨팅 계층을 NKS로 옮기는 데 집중했고 기존 DB를 Kubernetes 내부로 이전하지 않았다.

기존에 사용하던

AWS RDS MySQL
MongoDB
 

을 그대로 유지했다.

따라서 Spring Boot의 위치만 달라졌다.

Before

AWS EC2
   │
Spring Boot
   ├──→ AWS RDS MySQL
   └──→ MongoDB
 

After

NCP NKS
   │
Spring Boot Pod
   ├────────→ AWS RDS MySQL
   └────────→ MongoDB
 

즉 최종 아키텍처는 모든 요소를 NCP로 옮긴 구조가 아니라 애플리케이션 실행 환경을 NKS로 이전하고 기존 외부 데이터 계층을 연동한 구조였다.


12. 외부 AI API 역시 NKS 밖에 유지

NAVER CLOVA와 OpenAI 역시 Kubernetes 내부에 배포된 구성요소가 아니다.

애플리케이션에서 외부 API를 호출한다.

NKS
 │
 ├── Spring Pod ──────┐
 │                    │
 └── FastAPI Pod ─────┼────→ External API
                      │
                      ├── NAVER CLOVA
                      └── OpenAI
 

따라서 아키텍처에서도 CLOVA와 OpenAI는 NKS 및 VPC 경계 밖의 External API로 표현하는 것이 맞다.


13. 최종 NKS 아키텍처

최종 구조를 한 번에 정리하면 다음과 같다.

                         ┌──────────────┐
                         │    Client    │
                         │React/Vercel  │
                         └──────┬───────┘
                                │
                          HTTPS / WSS
                                │
                                ▼
┌────────────────── Naver Cloud Platform ────────────────────┐
│                                                            │
│                          VPC                               │
│                                                            │
│                 NCP Public Load Balancer                   │
│                           │                                │
│                           ▼                                │
│                NGINX Ingress Controller                   │
│                           │                                │
│                 ┌─────────┴─────────┐                      │
│                 │                   │                      │
│              /api/*            /fastapi/*                  │
│                 │                   │                      │
│                 ▼                   ▼                      │
│         Spring Service       FastAPI Service               │
│                 │                   │                      │
│                 ▼                   ▼                      │
│           Spring Pod           FastAPI Pod                 │
│                                      │                     │
│                                AI Inference                │
│                                                            │
└────────────────────────────────────────────────────────────┘
                 │                   │
                 │                   └──────→ CLOVA / OpenAI
                 │
                 ├──────→ AWS RDS MySQL
                 └──────→ MongoDB


NCP Container Registry
       │
       ├──── Image ────→ Spring Deployment
       └──── Image ────→ FastAPI Deployment
 

14. Troubleshooting 1 — NKS로 옮겼는데 API가 정상적으로 전달되지 않았다

마이그레이션 과정에서 첫 번째로 확인해야 했던 것은 기존 AWS에서 사용하던 API 경로가 NKS에서도 동일하게 애플리케이션까지 전달되는지였다.

AWS에서는 요청 경로가 상대적으로 단순했다.

Client
  ↓
Nginx
  ↓
Spring Container
 

NKS에서는 중간 계층이 늘어났다.

Client
  ↓
NCP Public LB
  ↓
NGINX Ingress Controller
  ↓
Ingress Rule
  ↓
Service
  ↓
Pod
  ↓
Spring Controller
 

따라서 API가 실패했을 때 바로 Spring 코드를 수정하는 대신 요청이 어느 지점까지 도달했는지를 확인했다.


15. Nginx와 Pod 로그를 비교해 범위를 좁혔다

먼저 Ingress Controller와 백엔드 Pod의 상태 및 로그를 확인했다.

개념적으로 판단 기준은 다음과 같았다.

Ingress까지 요청이 안 옴
       ↓
LB / Domain / TLS 계층 확인


Ingress에는 요청이 있음
       ↓
Spring Pod에는 없음
       ↓
Ingress Rule / Service 확인


Spring Pod까지 요청이 옴
       ↓
404 발생
       ↓
Path / Controller Mapping 확인
 

이 방식의 장점은 정상인 계층을 하나씩 제외할 수 있다는 것이었다.

특히 기존 AWS와 Spring Boot 코드 자체는 동일했기 때문에 환경 변경으로 새로 추가된

Load Balancer
Ingress
Service
 

계층을 우선 확인했다.


16. Ingress Path와 Spring API Path 확인

handDoc Spring API는 /api/v2/... 형태의 endpoint를 사용하고 있었다.

따라서 Ingress가 /api 요청을 Spring Service로 전달할 때 원래 애플리케이션이 기대하는 경로를 유지하는지가 중요했다.

예를 들어 애플리케이션이

/api/v2/doctor
 

를 기대하는데 Ingress rewrite 과정에서

/v2/doctor
 

로 변경된다면 Spring에서는 해당 Controller를 찾을 수 없어 404가 발생할 수 있다.

따라서 다음 관계를 확인해야 했다.

Browser Request
      ↓
Ingress Path
      ↓
Rewrite 여부
      ↓
Spring Service
      ↓
Spring Controller Mapping
 

결국 단순히 “Spring이 안 된다”가 아니라 외부 URL과 내부 애플리케이션 path의 일관성을 맞추는 것이 핵심이었다.


17. Service의 Port Mapping도 함께 확인

Ingress가 올바른 Service를 바라보더라도 Service의 port와 컨테이너가 실제로 listening하는 port가 맞지 않으면 요청은 정상 전달되지 않는다.

따라서 다음 연결도 확인했다.

Ingress
   ↓
Service Port
   ↓
targetPort
   ↓
Container Port
   ↓
Spring/FastAPI
 

Kubernetes에서는 이런 중간 추상화 계층이 있기 때문에 단순히

“Pod가 Running이다.”

만 확인해서는 서비스 정상 여부를 알 수 없었다.


18. Troubleshooting 2 — WebSocket은 일반 HTTP와 달랐다

FastAPI에는 실시간 수어 데이터를 처리하기 위한 WebSocket endpoint가 있었다.

기존 AWS에서도 WebSocket을 사용하고 있었기 때문에 NKS에서도 동일한 기능을 유지해야 했다.

요청 흐름은 다음과 같다.

React
  │
  │ WSS
  ▼
NCP Public LB
  │
  ▼
NGINX Ingress Controller
  │
  ▼
FastAPI Service
  │
  ▼
FastAPI Pod
  │
  ▼
WebSocket Endpoint
 

일반 HTTP API가 정상이라고 해서 WebSocket도 자동으로 정상인 것은 아니었다.


19. WebSocket Upgrade와 timeout 확인

WebSocket은 최초에는 HTTP로 연결하지만 이후 프로토콜을 Upgrade해서 장시간 연결을 유지한다.

HTTP Request
     ↓
101 Switching Protocols
     ↓
WebSocket
     ↓
Persistent Connection
 

따라서 중간의 NGINX Ingress Controller에서도 이 특성을 고려해야 했다.

특히 확인한 것은

  • WebSocket Upgrade 처리
  • HTTP/1.1 연결
  • 연결 유지 시간
  • proxy timeout

등이었다.

AWS EC2의 Nginx에서 직접 처리하던 WebSocket 연결이 이제는 Kubernetes의 Ingress와 Service를 거치기 때문에 HTTP API 정상 여부와 WebSocket 정상 여부를 별도로 검증했다.


20. Troubleshooting 3 — 환경 변경 후 CORS

백엔드의 위치가 AWS에서 NCP로 바뀌면서 프론트와 백엔드의 Origin 관계도 달라졌다.

브라우저 입장에서는

https://frontend-domain
          ↓
https://backend-domain
 

이 서로 다른 Origin이면 CORS 정책이 적용된다.

기존 AWS 환경을 기준으로 작성된 허용 Origin이 NCP 테스트 환경과 맞지 않으면 브라우저에서 요청이 차단될 수 있다.

따라서 실제 프론트엔드에서 사용하는 Origin을 기준으로 서버 CORS 설정을 다시 확인하고 NCP 환경에 맞게 수정했다.

여기서도 단순히 *로 모든 Origin을 열기보다는 필요한 프론트엔드 Origin을 허용하는 방향으로 구성했다.


21. Troubleshooting 4 — 로컬 React에서 NCP를 테스트할 때 TLS 오류

NCP backend를 Vercel에 바로 연결하기 전에 로컬 React 환경에서 먼저 테스트했다.

CRA의 setupProxy.js target을 기존 AWS endpoint에서 NCP endpoint로 변경했다.

localhost:3000
      ↓
CRA Proxy
      ↓
NCP LB / Ingress
 

이 과정에서

DEPTH_ZERO_SELF_SIGNED_CERT
 

오류가 발생했다.

이 오류는 Spring Boot가 반환한 오류가 아니었다.

요청 흐름을 보면:

Browser
  ↓
React Dev Server
  ↓
Node Proxy  ← TLS certificate 검증 실패
  X
NCP Endpoint
 

였기 때문에 애플리케이션 코드가 아니라 개발 Proxy와 NCP endpoint 사이의 TLS 검증 문제라고 판단할 수 있었다.

로컬 테스트 환경에서만:

 
secure: false
 

를 적용해 NCP 연결을 검증했다.

중요한 점은 운영 TLS를 비활성화한 것이 아니라 개발용 Proxy에서만 인증서 검증을 완화했다는 것이다.


22. AWS에서 발생했던 FastAPI Healthcheck 문제는 별도로 구분

여기서 하나는 명확하게 구분해야 한다.

FastAPI AI 모델 초기화 때문에 Healthcheck가 실패했던 문제는 NKS에서 발생한 문제가 아니라 기존 AWS EC2 + Docker 환경에서 발생했던 문제다.

당시 FastAPI는 시작하면서 AI 모델을 초기화하는 데 약 7~15초가 필요했다.

하지만 Docker Healthcheck timeout이 3초로 설정되어 있었다.

FastAPI Start
      ↓
AI Model Loading
      │
      │ 7~15 sec
      │
      ├── 3 sec
      │      ↓
      │ Healthcheck timeout
      ↓
Model Ready
 

따라서 애플리케이션 자체는 정상적으로 초기화되고 있었지만 Healthcheck 기준이 서비스 특성과 맞지 않았다.

실패가 반복되면서 FailingStreak이 30까지 증가했다.

이를 다음과 같이 조정했다.

interval     : 30s
timeout      : 15s
start period : 60s
 

이후 모델이 준비될 시간을 확보한 뒤 Healthcheck가 수행되면서 정상 상태를 유지했다.

이 경험은 NKS 트러블슈팅과 별개의 AWS 운영 경험이지만, 이후 Kubernetes에서도 “컨테이너가 실행된 것과 실제 트래픽을 받을 준비가 된 것은 다르다”는 관점을 갖게 된 계기가 됐다.


23. 최종 검증

마이그레이션 완료 여부를 단순히 Pod 상태만으로 판단하지 않았다.

전체 요청 경로를 기준으로 확인했다.

1. Container Image
        ↓
2. Pod 실행
        ↓
3. Service 연결
        ↓
4. Ingress Routing
        ↓
5. TLS / HTTPS
        ↓
6. Spring REST API
        ↓
7. FastAPI AI API
        ↓
8. WebSocket / WSS
        ↓
9. 외부 DB
        ↓
10. 실제 Frontend
 

 
kubectl get pods
 

에서 Running이 나온다는 것은 배포 검증의 시작점이지 끝이 아니었다.


24. AWS와 NKS 비교

항목기존 AWSNCP NKS
실행 환경 EC2 NKS
애플리케이션 단위 Docker Container Deployment / Pod
Spring/FastAPI 동일 EC2 별도 워크로드
이미지 Docker Docker + NCP Container Registry
내부 접근 Container Network Kubernetes Service
외부 라우팅 Nginx Reverse Proxy NGINX Ingress Controller
외부 진입 EC2 NCP Public Load Balancer
Routing Nginx config Ingress Rule
TLS EC2 Nginx Ingress + cert-manager/Let's Encrypt
WebSocket Nginx NGINX Ingress → FastAPI Service
MySQL AWS RDS 기존 AWS RDS 유지
MongoDB 외부 DB 기존 DB 유지
Frontend Vercel Vercel
HPA X 적용했다고 쓰지 않음

25. 마이그레이션 트러블슈팅 요약

문제확인 방법원인 계층해결 방향
API 요청 실패/404 Ingress → Service → Pod 로그 및 path 확인 Ingress routing / path /api 경로와 Spring Controller path 일치 여부 확인 및 라우팅 수정
Service 연결 문제 Service port / targetPort / Container port 비교 Kubernetes Service 실제 애플리케이션 port와 매핑
WebSocket 연결/유지 HTTP와 WSS를 별도로 검증 Ingress/WebSocket Upgrade 및 timeout 관련 설정 확인
CORS Browser Network/Origin 확인 Application 실제 Vercel/NCP 환경에 맞게 Origin 수정
로컬 NCP TLS 오류 Proxy error 확인 CRA Proxy ↔ NCP 로컬 테스트에서만 인증서 검증 완화
FastAPI unhealthy (AWS) Docker 상태 + 로그 + 초기화 시간 비교 Docker Healthcheck 3초 timeout → 15초, start period 60초 등으로 조정

26. 이 마이그레이션에서 배운 것

이번 작업에서 가장 크게 배운 것은 Kubernetes 명령어나 YAML 문법 자체가 아니었다.

같은 애플리케이션이라도 실행 환경이 바뀌면 정상 여부를 판단해야 하는 계층이 달라진다는 것이었다.

AWS EC2에서는:

Nginx
  ↓
Container
 

정도였다면 NKS에서는:

Public Load Balancer
       ↓
NGINX Ingress Controller
       ↓
Ingress Rule
       ↓
Service
       ↓
Pod
       ↓
Application
 

까지 확인해야 했다.

그래서 장애가 발생했을 때도 무작정 설정을 수정하기보다

요청이 LB까지 들어오는가?
        ↓
Ingress가 요청을 받았는가?
        ↓
올바른 Service로 전달되는가?
        ↓
Service가 올바른 Pod를 가리키는가?
        ↓
Pod가 실제 요청을 받았는가?
        ↓
애플리케이션이 어떤 응답을 반환하는가?
 

순서로 문제 범위를 좁히는 것이 중요했다.

특히 API, WebSocket, CORS 문제가 처음에는 서로 다른 문제처럼 보였지만 결국 AWS에서 NKS로 이동하면서 달라진 요청 경로를 이해하는 과정이었다.


27. 마치며

이번 마이그레이션은 단순히 서버를 AWS에서 NCP로 옮긴 작업이 아니었다.

기존의

EC2
├── Nginx
├── Spring Docker
└── FastAPI Docker
 

구조를

NCP Public Load Balancer
        ↓
NGINX Ingress Controller
        ↓
    Ingress Rule
    ┌──────┴──────┐
    ↓             ↓
Spring Service  FastAPI Service
    ↓             ↓
Spring Pod      FastAPI Pod
 

구조로 재구성하면서 EC2 중심으로 이해했던 애플리케이션 배포를 Kubernetes의 Deployment-Pod-Service-Ingress 계층으로 다시 이해하게 됐다.

또한 기존 AWS RDS와 MongoDB, 외부 AI API를 유지하면서 컴퓨팅 계층만 NKS로 이전했기 때문에 단순한 신규 구축보다 기존 시스템과 새로운 환경 사이의 연결을 검증하는 작업이 중요했다.

결국 이번 경험에서 얻은 가장 큰 기준은 이것이었다.

Pod가 Running인 것과 사용자가 서비스를 정상적으로 사용할 수 있는 것은 다르다.

이미지가 정상적으로 Pull되는지, Pod가 실행되는지, Service가 올바른 Pod를 바라보는지, Ingress가 적절한 Service로 라우팅하는지, HTTPS와 WebSocket이 정상인지, 외부 DB와 API가 연결되는지까지 확인해야 비로소 마이그레이션이 완료됐다고 볼 수 있었다.

그리고 문제를 해결할 때도 애플리케이션부터 무작정 수정하기보다 요청의 실제 흐름을 따라가며 정상인 계층을 하나씩 제거하는 방식이 훨씬 효과적이었다.

AWS EC2에서 NCP NKS로 handDoc을 이전한 경험은 Kubernetes를 단순히 사용해 본 경험이라기보다, 하나의 실제 서비스를 컨테이너 단위에서 Kubernetes 워크로드 단위로 재구성하고 외부 트래픽이 사용자 기능까지 도달하는 전체 경로를 직접 연결해 본 경험이었다.