Spring MSA 요청 흐름으로 보는 Kubernetes Object

눅눅이·2026년 7월 4일

1편에서는 Docker Compose에서 Kubernetes로 넘어갈 때 어떤 관점 변화가 생기는지 정리했다.

핵심은 이거였다.

Docker Compose
  -> 컨테이너 실행 중심

Kubernetes
  -> 원하는 상태 유지 중심

이번 글에서는 Kubernetes Object를 하나씩 외우는 방식이 아니라, 실제 요청이 어떻게 흘러가는지를 기준으로 정리해보려고 한다.

Spring-React-MSA 구조를 기준으로 보면 대략 이런 흐름을 생각할 수 있다.

Browser
  -> Ingress
  -> Gateway Service
  -> Gateway Pod
  -> BFF / Authorization Server / User Service
  -> Redis / Postgres

Kubernetes Object들은 각각 따로 존재하는 것처럼 보이지만, 실제로는 이 요청 흐름 안에서 서로 연결된다.


1. 전체 흐름 먼저 보기

Spring MSA 서비스를 Kubernetes에 올렸다고 가정하면 전체 흐름은 대략 다음과 같다.

처음 Kubernetes를 보면 Object가 많아서 복잡해 보인다.

하지만 요청 흐름 기준으로 보면 역할이 나뉜 것뿐이다.

외부 요청을 받는 Object
  -> Ingress

Pod에 고정 주소를 제공하는 Object
  -> Service

애플리케이션 컨테이너를 실행하는 Object
  -> Pod

Pod 개수를 유지하는 Object
  -> Deployment

설정값을 주입하는 Object
  -> ConfigMap / Secret

데이터 저장 공간을 관리하는 Object
  -> PVC

즉 Kubernetes Object는 따로 외우는 것보다, “요청이 어디서 들어와서 어디로 가는가”를 기준으로 보는 게 훨씬 이해하기 쉽다.


2. Namespace는 리소스를 묶는 공간이다

먼저 Namespace가 있다.

Namespace는 Kubernetes 리소스를 논리적으로 나누는 공간이다.

예를 들어 Spring-React-MSA용 Namespace를 하나 만든다고 하면 다음처럼 볼 수 있다.

spring-msa Namespace
  - gateway
  - authorization-server
  - user-service
  - bff
  - redis
  - postgres
  - configmap
  - secret
  - ingress

Namespace를 나누면 리소스들을 프로젝트 단위로 묶어서 관리할 수 있다.

kubectl get pods -n spring-msa
kubectl get svc -n spring-msa
kubectl get ingress -n spring-msa

이렇게 하면 spring-msa Namespace 안에 있는 리소스만 확인할 수 있다.

로컬 Docker Compose에서는 하나의 compose 파일이 프로젝트 묶음 역할을 했다면, Kubernetes에서는 Namespace가 그 경계 역할을 해준다고 볼 수 있다.

물론 Namespace가 완전한 보안 경계는 아니다.

하지만 리소스를 구분하고, 환경을 나누고, 관리 범위를 잡는 데는 기본 단위로 많이 사용된다.


3. Ingress는 외부 요청의 입구다

Kubernetes 안의 Pod는 기본적으로 클러스터 내부 리소스다.

브라우저가 바로 Pod로 접근하는 구조가 아니다.

외부 HTTP 요청을 받으려면 보통 Ingress를 둔다.

Browser
  -> Ingress
  -> Service
  -> Pod

예를 들어 다음과 같은 도메인을 쓴다고 해보자.

api.localtest.me
auth.localtest.me
admin.localtest.me

Ingress는 요청의 Host나 Path를 보고 내부 Service로 라우팅한다.

api.localtest.me
  -> gateway-service

auth.localtest.me
  -> authorization-server-service

admin.localtest.me
  -> admin-frontend-service

Spring-React-MSA에서는 Gateway가 외부 API 진입점 역할을 하기 때문에, 대부분의 API 요청은 다음 흐름을 타는 것이 자연스럽다.

Browser
  -> Ingress
  -> Gateway Service
  -> Gateway Pod

여기서 중요한 점은 Ingress가 Pod로 직접 보내는 것이 아니라는 점이다.

Ingress는 보통 Service로 요청을 보낸다.


4. Service는 Pod 앞에 붙는 고정 주소다

Kubernetes에서 Pod는 언제든지 새로 생성될 수 있다.

Pod가 재시작되면 IP도 바뀔 수 있다.

그래서 다른 서비스가 Pod IP를 직접 바라보면 위험하다.

잘못된 생각
Gateway -> User Pod IP 직접 호출

Pod IP는 고정 주소로 보기 어렵다.

그래서 Kubernetes에서는 Pod 앞에 Service를 둔다.

Gateway Pod
  -> User Service
  -> User Pod

Service는 Pod들이 바뀌어도 고정된 접근 지점을 제공한다.

예를 들어 User Service가 있다면 Gateway는 User Pod IP를 몰라도 된다.

user-service:8081

이 이름으로 호출하면 Kubernetes가 뒤에 있는 User Pod로 트래픽을 보내준다.

즉 Service는 “Pod 앞에 붙는 고정 주소”라고 보면 된다.


5. Deployment는 Pod를 유지한다

실제 컨테이너가 실행되는 최소 단위는 Pod다.

하지만 보통 Pod를 직접 관리하지 않는다.

Spring Boot 서비스는 보통 Deployment로 관리한다.

Deployment
  -> ReplicaSet
  -> Pod
  -> Container

예를 들어 Gateway를 2개 유지하고 싶다면 Deployment에 다음처럼 선언할 수 있다.

replicas: 2

그러면 Kubernetes는 Gateway Pod가 항상 2개 떠 있도록 유지하려고 한다.

gateway-deployment
  -> gateway-pod-1
  -> gateway-pod-2

Pod 하나가 죽으면 Deployment가 다시 맞춰준다.

원하는 상태: Gateway Pod 2개
현재 상태: Gateway Pod 1개
Kubernetes: 하나 더 생성

이게 Kubernetes의 상태 유지 방식이다.

Spring-React-MSA 기준으로 보면 다음 서비스들은 Deployment로 관리하기 좋다.

Gateway
BFF
Authorization Server
User Service
Community Service
Auction Service
Stock Service
Frontend

이 서비스들은 애플리케이션 서버 성격이 강하다.

Pod가 새로 생성되어도 데이터 자체가 Pod 안에 강하게 묶여 있지 않다.

그래서 Deployment와 잘 맞는다.


6. ConfigMap과 Secret은 Pod에 설정을 주입한다

Spring Boot 애플리케이션은 실행될 때 여러 설정값이 필요하다.

예를 들면 다음과 같다.

SPRING_PROFILES_ACTIVE
POSTGRES_HOST
POSTGRES_PORT
REDIS_HOST
AUTHORIZATION_SERVER_URL

이런 일반 설정값은 ConfigMap으로 관리할 수 있다.

ConfigMap
  -> Pod 환경변수로 주입

반면 민감정보는 Secret으로 분리한다.

DB_PASSWORD
CLIENT_SECRET
JWT_SECRET

흐름으로 보면 다음과 같다.

ConfigMap / Secret
  -> Deployment
  -> Pod
  -> Spring Boot Application

즉 ConfigMap과 Secret은 직접 요청을 처리하는 Object는 아니다.

하지만 Pod가 정상적으로 실행되기 위해 필요한 설정값을 제공한다.

예를 들어 User Service Pod가 뜰 때 DB 접속 정보가 필요하다.

User Service Pod
  -> ConfigMap에서 DB Host 확인
  -> Secret에서 DB Password 확인
  -> Postgres Service로 연결

여기서 중요한 점은 Kubernetes 내부에서는 localhost를 쓰면 안 되는 경우가 많다는 것이다.

컨테이너 안에서 localhost는 자기 자신을 의미한다.

User Service에서 Postgres에 붙어야 한다면 보통 이런 식이어야 한다.

postgres:5432

또는 Namespace까지 명확히 쓰면 다음과 같다.

postgres.spring-msa.svc.cluster.local:5432

즉 Kubernetes에서는 서비스 간 통신 주소를 Service DNS 기준으로 잡아야 한다.


7. Spring MSA 내부 통신은 Service DNS를 탄다

Kubernetes 안에서 서비스끼리 통신할 때는 Service 이름을 사용한다.

예를 들어 같은 Namespace 안에 있다면 다음처럼 호출할 수 있다.

gateway-service
bff-service
authorization-server-service
user-service
redis
postgres

Spring-React-MSA 기준으로 보면 내부 통신은 대략 이런 식으로 이어진다.

Gateway
  -> BFF
  -> Authorization Server
  -> User Service
  -> Redis / Postgres

예를 들어 로그인 흐름을 단순화하면 다음처럼 볼 수 있다.

Browser
  -> Ingress
  -> Gateway Service
  -> Gateway Pod
  -> BFF Service
  -> BFF Pod
  -> Authorization Server Service
  -> Authorization Server Pod
  -> User Service
  -> User Service Pod
  -> Postgres Service
  -> Postgres Pod

여기서 각 서비스가 서로를 호출할 때 Pod IP를 직접 쓰지 않는다.

Service 이름을 사용한다.

authorization-server-service:9000
user-service:8081
postgres:5432
redis:6379

이게 Compose와 비슷해 보이지만, Kubernetes에서는 Service가 Pod들을 안정적으로 감싸고 있다는 점이 다르다.

Compose에서는 컨테이너 이름 기반 통신에 가깝고, Kubernetes에서는 Service DNS를 통한 클러스터 내부 통신에 가깝다.


8. Postgres는 StatefulSet과 PVC가 필요하다

애플리케이션 서버는 Deployment로 관리해도 된다.

하지만 DB는 다르다.

DB는 상태를 가진다.

Postgres Pod가 재시작되더라도 데이터는 유지되어야 한다.

그래서 Postgres는 보통 다음 조합으로 구성한다.

Postgres Service
  -> Postgres StatefulSet
  -> Postgres Pod
  -> PVC

각 역할은 이렇다.

Object역할
ServicePostgres에 접근할 고정 주소 제공
StatefulSetPostgres Pod를 안정적인 이름과 순서로 관리
PVCPostgres 데이터 저장 공간 요청

Postgres 컨테이너 내부의 데이터 경로는 보통 다음과 같다.

/var/lib/postgresql/data

이 경로를 PVC와 연결하면 Pod가 재생성되어도 데이터가 유지된다.

Postgres Pod 삭제
  -> 새 Postgres Pod 생성
  -> 기존 PVC 다시 연결
  -> 데이터 유지

이 부분이 일반 Deployment와 가장 큰 차이다.

Deployment는 Pod를 쉽게 갈아끼우는 데 적합하다.

StatefulSet은 상태가 필요한 Pod를 안정적으로 관리하는 데 적합하다.

로컬 개발에서는 Postgres도 Compose volume 하나로 충분했지만, Kubernetes에서는 저장소도 클러스터 리소스로 봐야 한다.


9. Redis는 목적에 따라 달라진다

Redis도 상태를 가질 수 있다.

하지만 Redis를 어떤 용도로 쓰는지에 따라 구성이 달라질 수 있다.

예를 들어 단순 캐시로만 사용한다면 Pod가 재시작되면서 데이터가 사라져도 큰 문제가 아닐 수 있다.

Redis = 단순 캐시
  -> Deployment로도 가능

하지만 세션 저장소, 인증 관련 임시 데이터, 메시지 처리 등으로 사용한다면 재시작 시 데이터 유실이 문제가 될 수 있다.

Redis = 세션 / 인증 흐름 / 중요 임시 데이터
  -> StatefulSet + PVC 고려

Spring-React-MSA에서는 BFF 세션이나 인증 흐름에서 Redis를 사용할 수 있기 때문에, 단순 캐시인지 세션 저장소인지 먼저 구분해야 한다.

정리하면 이렇다.

Redis 용도Kubernetes 구성
단순 캐시Deployment 가능
세션 저장소StatefulSet 또는 외부 Redis 고려
운영 환경Managed Redis 또는 HA 구성 고려

처음 로컬 Kubernetes 실습에서는 단순 Deployment로 시작할 수 있다.

하지만 운영 관점으로 가면 Redis도 가볍게 보면 안 된다.


10. readinessProbe는 트래픽을 받을 준비가 됐는지 확인한다

Docker Compose에서는 depends_on으로 실행 순서를 어느 정도 제어할 수 있다.

하지만 Kubernetes에서는 실행 순서보다 준비 상태가 중요하다.

Pod가 떠 있다고 해서 애플리케이션이 바로 요청을 받을 수 있는 것은 아니다.

Spring Boot 애플리케이션도 컨테이너는 실행됐지만 아직 초기화 중일 수 있다.

그래서 readinessProbe가 필요하다.

Pod 실행됨
  -> Spring Boot 초기화 중
  -> readinessProbe 실패
  -> Service 트래픽 전달 안 함
  -> 준비 완료
  -> readinessProbe 성공
  -> Service 트래픽 전달

즉 readinessProbe는 “이 Pod가 요청을 받아도 되는 상태인가?”를 판단한다.

예를 들어 Spring Boot Actuator를 사용한다면 다음과 같은 경로를 사용할 수 있다.

/actuator/health/readiness

흐름으로 보면 다음과 같다.

Service
  -> 준비된 Pod에만 트래픽 전달

이게 Kubernetes에서 중요한 이유는 배포와도 연결되기 때문이다.

새 버전 Pod가 아직 준비되지 않았는데 트래픽을 받으면 장애가 날 수 있다.

readinessProbe를 잘 잡아두면 Kubernetes는 준비된 Pod에만 요청을 보낸다.


11. livenessProbe는 Pod가 살아있는지 확인한다

readinessProbe가 “트래픽을 받을 준비가 됐는가”를 본다면, livenessProbe는 “애플리케이션이 살아있는가”를 본다.

예를 들어 애플리케이션이 데드락에 빠졌거나 내부적으로 멈춘 상태라면 컨테이너 프로세스는 살아있어도 정상 서비스가 아닐 수 있다.

이때 livenessProbe가 계속 실패하면 Kubernetes는 해당 컨테이너를 재시작할 수 있다.

livenessProbe 실패
  -> Kubernetes가 컨테이너 재시작

정리하면 이렇다.

Probe의미
readinessProbe요청을 받을 준비가 되었는지 확인
livenessProbe애플리케이션이 살아있는지 확인

Spring Boot 서비스에서는 Actuator health endpoint를 활용하면 좋다.

/actuator/health/liveness
/actuator/health/readiness

Kubernetes에서는 단순히 컨테이너가 떠 있는 것보다, 실제 서비스 가능한 상태인지 확인하는 것이 더 중요하다.


12. 배포 흐름은 Deployment가 중심이다

Kubernetes에서 Spring Boot 서비스를 새 버전으로 배포하면 Deployment가 중심이 된다.

예를 들어 Gateway 이미지가 새로 바뀌었다고 해보자.

gateway:1.0.0
  -> gateway:1.0.1

Deployment는 새 이미지로 Pod를 점진적으로 교체한다.

기존 Pod 유지
  -> 새 Pod 생성
  -> 새 Pod readiness 확인
  -> 기존 Pod 제거
  -> 새 버전으로 교체 완료

이 과정을 Rolling Update라고 볼 수 있다.

문제가 생기면 롤백도 가능하다.

kubectl rollout undo deployment/gateway -n spring-msa

상태 확인은 다음처럼 한다.

kubectl rollout status deployment/gateway -n spring-msa

즉 Kubernetes에서 배포는 단순히 컨테이너를 내렸다가 다시 올리는 것이 아니다.

서비스를 유지하면서 Pod를 교체하는 흐름이다.

이 부분은 나중에 GitHub Actions, GHCR, Argo CD와 연결된다.

Git Push
  -> CI 이미지 빌드
  -> Registry Push
  -> Manifest 변경
  -> Argo CD Sync
  -> Kubernetes Rollout

13. 장애가 나면 흐름을 거꾸로 보면 된다

Kubernetes에서 장애가 났을 때는 Object를 따로따로 보는 것보다 요청 흐름을 거꾸로 따라가는 게 좋다.

예를 들어 브라우저에서 API 호출이 실패했다면 다음 순서로 확인할 수 있다.

Browser
  -> Ingress
  -> Service
  -> Pod
  -> Application Log
  -> 내부 Service
  -> Redis / Postgres

확인 명령어는 대략 이런 식이다.

kubectl get ingress -n spring-msa
kubectl get svc -n spring-msa
kubectl get pods -n spring-msa
kubectl describe pod <pod-name> -n spring-msa
kubectl logs <pod-name> -n spring-msa

예를 들어 404가 난다면 Ingress Path나 Gateway 라우팅 문제일 수 있다.

404
  -> Ingress Host / Path 확인
  -> Gateway Route 확인

502나 503이 난다면 Service 뒤에 준비된 Pod가 없는 경우일 수 있다.

502 / 503
  -> Service Endpoint 확인
  -> Pod readiness 확인
  -> Pod 로그 확인

DB 연결 오류라면 ConfigMap, Secret, Service DNS를 봐야 한다.

DB Connection Error
  -> POSTGRES_HOST 값 확인
  -> Secret 비밀번호 확인
  -> postgres Service 확인
  -> Postgres Pod 상태 확인

Redis 연결 오류도 마찬가지다.

Redis Connection Error
  -> REDIS_HOST 값 확인
  -> redis Service 확인
  -> Redis Pod 상태 확인

Kubernetes에서 장애를 볼 때는 “어떤 Object가 문제인가?”보다 “요청이 어느 구간에서 끊겼는가?”를 먼저 보는 게 좋다.


14. Object 흐름을 한 번 더 정리하기

Spring-React-MSA 기준으로 Object 흐름을 다시 정리하면 다음과 같다.

1. Browser가 요청을 보낸다.

2. Ingress가 외부 요청을 받는다.

3. Ingress는 Host / Path 기준으로 Gateway Service에 요청을 넘긴다.

4. Gateway Service는 준비된 Gateway Pod로 트래픽을 보낸다.

5. Gateway Pod는 내부 라우팅 규칙에 따라 BFF, Authorization Server, User Service 등을 호출한다.

6. 각 Spring Boot 서비스는 Service DNS를 통해 서로 통신한다.

7. 설정값은 ConfigMap과 Secret에서 주입된다.

8. Postgres는 Service + StatefulSet + PVC 조합으로 구성된다.

9. Redis는 용도에 따라 Deployment 또는 StatefulSet으로 구성할 수 있다.

10. readinessProbe와 livenessProbe가 Pod 상태를 확인한다.

11. 배포 시 Deployment가 새 Pod를 만들고 기존 Pod를 점진적으로 교체한다.

12. 로그와 메트릭은 kubectl logs, Prometheus, Loki, Grafana 같은 도구로 확인한다.

이 흐름을 이해하면 Kubernetes Object들이 단순 암기 대상이 아니라는 게 보인다.

각 Object는 요청 처리 흐름 안에서 역할을 가진다.


15. 정리

Kubernetes Object를 처음 보면 종류가 많아서 복잡해 보인다.

하지만 Spring MSA 요청 흐름 기준으로 보면 구조는 단순하다.

외부 요청
  -> Ingress
  -> Service
  -> Pod
  -> 내부 Service
  -> Redis / Postgres

여기에 설정과 상태 관리 Object가 붙는다.

ConfigMap / Secret
  -> Pod 설정 주입

PVC
  -> DB 데이터 유지

Deployment
  -> 애플리케이션 Pod 개수 유지

StatefulSet
  -> 상태가 필요한 Pod 관리

readinessProbe / livenessProbe
  -> Pod 상태 확인

결국 Kubernetes Object는 각각 따로 떨어진 개념이 아니다.

애플리케이션을 클러스터 안에서 안정적으로 실행하고, 연결하고, 설정하고, 복구하고, 배포하기 위해 역할을 나눈 구조다.

1편에서는 Docker Compose에서 Kubernetes로 넘어갈 때의 관점 전환을 봤다.

이번 2편에서는 Kubernetes Object들이 실제 요청 흐름에서 어떻게 연결되는지 봤다.

다음 단계에서는 이 흐름을 실제 Manifest로 옮기게 된다.

예를 들면 Gateway 하나를 Kubernetes에 올리기 위해 다음 리소스들을 작성할 수 있다.

Namespace
ConfigMap
Secret
Deployment
Service
Ingress

그리고 Postgres까지 포함하면 다음 리소스가 추가된다.

StatefulSet
PVC
Service

이렇게 Kubernates의 Object 설명이 끝이 났다.

다음 글에서는

이번 글에서는 Spring-React-MSA 요청 흐름을 기준으로 Kubernetes Object들이 어떻게 연결되는지 정리했다.

Browser
  -> Ingress
  -> Service
  -> Pod
  -> 내부 Service
  -> Redis / Postgres

여기까지 이해하면 Kubernetes에서 요청이 어떤 경로로 흘러가는지 감을 잡을 수 있다.

하지만 Object 흐름을 아는 것만으로는 부족하다.

실제로 서비스를 운영하다 보면 다음 질문이 생긴다.

요청이 Ingress까지 들어왔는가?
Gateway에서 막혔는가?
BFF에서 에러가 났는가?
Redis 연결이 실패했는가?
Postgres 응답이 느린가?
어떤 Pod에서 ERROR 로그가 발생했는가?
CPU나 Memory 사용량은 정상인가?

이런 문제를 확인하려면 로그와 메트릭을 볼 수 있어야 한다.

로컬 Docker Compose에서는 docker logs만으로도 어느 정도 확인할 수 있었지만, Kubernetes에서는 여러 Pod와 Namespace에 서비스가 흩어져 있기 때문에 관측 도구가 필요하다.

그래서 다음 글에서는 Kubernetes 환경에서 로그와 메트릭을 보기 위한 관측 도구 구조를 정리해보려고 한다.

로그 흐름:
Pod 로그
  -> Promtail
  -> Loki
  -> Grafana

메트릭 흐름:
Pod / Node / Kubernetes Metrics
  -> Prometheus
  -> Grafana

특히 헷갈리기 쉬운 부분도 같이 정리할 예정이다.

Prometheus는 로그 수집기가 아니다.
Prometheus는 메트릭 수집기다.

로그는 Promtail이 수집하고,
Loki가 저장하며,
Grafana가 조회해서 보여준다.

즉 3편에서는 Spring-React-MSA Kubernetes 환경에서 Prometheus, Grafana, Loki, Promtail이 각각 어떤 역할을 하는지 정리한다.

profile
개발자가 아닌 탐구자

0개의 댓글