본 글은 NHN Injeinc 공식 기술 블로그에 5월 Tech Log로 기고 된 글입니다.
Intro
안녕하세요. 저는 NHN Injeinc의 DevOps Tech팀에서 SRE/DevOps Engineer로 근무중인 라모스입니다.
마이크로서비스 아키텍처(MSA)가 대세가 되면서, API Gateway와 클러스터 레벨에서의 요청 라우팅은 필수가 되었습니다. 마이크로서비스 아키텍처(MSA)는 대규모 애플리케이션을 독립적인 서비스 단위로 나누어 개발 및 배포 효율성을 높여줍니다. 이 구조에서 중요한 역할을 하는 것이 API Gateway와 외부 진입점 구성입니다.
백엔드 개발자와 DevOps 엔지니어 각자에게 친숙한 Gateway란 아래 두 기술이라 할 수 있겠습니다.
- Spring Cloud Gateway는 Spring 생태계에서 마이크로서비스 간 내부 라우팅과 필터링, 인증 등을 책임지는 경량 API Gateway입니다.
- 반면, Kubernetes Ingress Controller는 Kubernetes 클러스터 외부에서 내부 서비스로의 요청을 관리하는 대표적인 진입 지점입니다.
저는 현재 백엔드 개발과 더불어 DevOps 업무를 수행하고 있어 여러분이 양쪽 관점 모두를 아우를 수 있도록 이 글에서는 백엔드 개발자의 시각에서 Spring Cloud Gateway와 Kubernetes Ingress Controller라는 두 기술의 차이점과 통합 활용법을 살펴보고, 아래 두 개의 예제 프로젝트를 통해 실전 사례를 공유하고자 합니다.
설명에 사용되는 예제 MSA 프로젝트는 제가 직접 개발 및 배포하였으며 위와 같은 구성으로 NHN Injeinc의 통합 Kubernetes 플랫폼인 CONE-Chain으로 구성된 데모 환경의 Kubernetes Cluster에 배포되어 있습니다.
사용자가 inje-msa-demo-dev
라는 namespace에 위치한 dev 환경의 Book Service의 조회 API를 호출하면 해당 조회 API는 같은 네임스페이스 내부에 위치한 Author Service, Review Service의 API를 내부적으로 호출하여 받아온 결과를 조합하여 최종적으로 Book 조회 API의 응답으로 보내주는 것을 가정한 마이크로서비스 예시입니다.
예제 프로젝트 구조
예제 마이크로서비스 프로젝트 구조는 아래와 같으며, 세부적인 코드는 첨부된 링크를 통해 확인하실 수 있습니다.
ramos-sample-msa | Github
- Spring Cloud 기반 MSA 프로젝트
- Gateway + Eureka + Book / Author / Review Service로 구성
- Service Discovery 기반 동적 라우팅
- 각 마이크로서비스는 예제를 단순화하기 위해 CQRS 패턴에서의 조회 API(Query)로만 구성함.
- 각 서비스의 DB는 In-Memory 기반의 H2 Database에 샘플 데이터를 구성하였고, 향후 디테일한 아키텍처로는 내부적으로 MySQL 혹은 PostgreSQL 등을 구성하고 K8s Cluster 내 다른 namespace에 구성된 독자적인 바운더리 컨텍스트를 갖고 있는 마이크로서비스(리뷰 서비스, 저자 서비스 등)에서 Command(등록, 수정, 삭제 이벤트)가 발생하면 Kafka Connect와 같은 CDC(Change Data Capture) 파이프라인을 구성해 동기화 된 데이터를 기반으로 API의 응답 결과를 사용자에게 제공한다 가정합니다.
- Book Service는 비즈니스 로직 상 외부 서버인 Author Service와 Review Service를 REST API로 호출해야 하는데 각 마이크로서비스는 독립적으로 배포되기 때문에 배포 중 순간적으로 장애 상황이 일어날 수 있어 Circuit Breaker를 적용하여 더미 데이터로 구성된 Fallback 응답을 내려주는 방식으로 사용자에겐 장애 상황에 대한 여파를 최소화 할 수 있도록 적용함.
ramos-sample-msa-gitops | Github
- Kubernetes 상에서 GitOps 방식의 배포 및 자원 관리
- ArgoCD, Tekton Pipeline을 토대로 CI/CD를 구성하고 Spring Cloud 기반 MSA 프로젝트 구성 요소 각각에 대한 자원 관리 및 배포 시나리오를 작성함.
Ingress Controller
로 외부 요청 수신 → Gateway Server로 전달.- 각 서비스는 PVC/PV로 구성된 스토리지를 사용하고 OpenTelemetry 수집을 위해 Kustomization에서 각 마이크로서비스의 Deployment에 JVM Option으로 에이전트를 추가하도록 patch하여 배포함.
본격적으로 Spring Cloud Gateway와 Kubernetes Ingress Controller에 대해 예제와 함께 살펴봅시다.
Spring Cloud Gateway란?
Spring Cloud Gateway는 Spring 생태계 내에서 공식적으로 지원되는 API Gateway로, Netflix Zuul의 후속 역할을 수행합니다. 내부 서비스 간 라우팅을 담당하고, 비즈니스 로직 외부의 인증, 필터링, 로깅, 부하분산 등을 통합적으로 처리할 수 있는 기능을 제공합니다.
Spring Cloud Gateway는 다음과 같은 특징을 지닙니다.
- Netty 기반 비동기 처리: 성능이 중요한 마이크로서비스 환경에서 적합
- 간결한 설정: application.yml에서 라우팅, 필터 조건 등을 쉽게 설정 가능
- Predicate/Filter 체계: 요청 조건과 가공을 분리하여 구성
- Spring과의 자연스러운 통합: Spring Security, Resilience4j, Sleuth, Zipkin 등과의 연계 가능
동작 방식 요약을 요약한다면 다음과 같습니다.
클라이언트 요청 → Gateway → Predicate 매칭 → Filter 처리 → 대상 서비스 전달
실제 제가 예제로 구성했던 Gateway Server의 Filter 및 Router 설정은 아래와 같습니다.
server:
port: 8000
spring:
application:
name: inje-api-gateway
cloud:
gateway:
default-filters:
- name: GlobalLoggingFilter
args:
baseMessage: Spring Cloud Gateway GlobalLoggingFilter
preLogger: true
postLogger: true
routes:
- id: book-service
uri: lb://BOOK-SERVICE
predicates:
- Path=/book-service/books/**
- id: author-service
uri: lb://AUTHOR-SERVICE
predicates:
- Path=/author-service/authors/**
- id: review-service
uri: lb://REVIEW-SERVICE
predicates:
- Path=/review-service/reviews/**
이 구성은 /book-service/**
경로로 들어온 요청을 서비스 레지스트리에서 BOOK-SERVICE로 찾아 라우팅합니다. 필요 시 라우팅 대상에 AddRequestHeader
와 같은 filter를 추가하여 요청 헤더를 추가할 수도 있습니다.
더 자세한 사항은 공식 문서를 확인해주세요.
다음은 Gateway에서 요청을 처리할 때 남는 로그 예시입니다. GlobalLoggingFilter를 통해 요청 URI, Path, 응답 코드 등이 기록됩니다.
이처럼 Spring Cloud Gateway는 각 요청에 대해 정형화된 로그를 남김으로써, 장애 분석이나 트래픽 추적에 큰 도움을 줍니다. 저의 예제에선 구성하지 않았으나 일반적으로 이 레이어에서 애플리케이션 레벨에서의 인증/인가에 대한 전처리 및 라우팅 동작을 수행하곤 합니다.
Kubernetes Ingress Controller와 Istio Gateway
Ingress는 외부에서 들어오는 HTTP(S) 트래픽을 내부 서비스로 라우팅하는 Kubernetes 리소스입니다.
Ingress Controller는 이를 실제로 수행하는 실행체이며, 가장 많이 쓰이는 구현체는 nginx-ingress-controller
입니다.
Kubernetes 환경에서 외부 트래픽을 내부 서비스로 연결할 때 사용하는 리소스가 Ingress입니다. 하지만 Ingress 리소스 자체는 선언만 담당하고, 실제 라우팅 처리는 Ingress Controller가 수행합니다.
- Ingress 구성 요소
- Ingress 리소스: 도메인, 경로 기반 라우팅 정의
- Ingress Controller: nginx, traefik, istio 등 다양한 구현체가 있음
Istio Gateway와 같은 Service Mesh를 도입하지 않고, 단순히 Nginx Ingress Controller만으로도 충분히 외부 트래픽 진입과 라우팅 구성이 가능합니다.
즉, 모든 환경에서 반드시 Istio Gateway를 필수적으로 구성할 필요는 없습니다. 서비스의 복잡도나 트래픽 제어 필요성에 따라 선택적으로 구성하면 됩니다. Nginx Ingress Controller 단독 구성은 설정이 간단하고 학습 곡선이 낮으며, 소규모 또는 단순한 MSA 환경에 매우 적합합니다.
대표적인 활용 예
- Let's Encrypt를 통한 TLS 인증서 자동 발급
- 하나의 LoadBalancer로 여러 서비스 라우팅
- 외부 DNS와 도메인 기반 연계
저는 MSA로 구성한 예제 프로젝트에 대해 아래와 같이 각각 Ingress Controller로 Service를 노출하였습니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/affinity: cookie
nginx.ingress.kubernetes.io/session-cookie-hash: sha256
nginx.ingress.kubernetes.io/session-cookie-name: route
name: gateway-server-ing
spec:
ingressClassName: nginx
rules:
- host: dev-inje-msa-demo.cone-chain.com
http:
paths:
- backend:
service:
name: gateway-server-svc
port:
number: 8000
path: /gateway
pathType: Prefix
tls:
- hosts:
- dev-inje-msa-demo.cone-chain.com
secretName: secret-tls
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/affinity: cookie
nginx.ingress.kubernetes.io/session-cookie-hash: sha256
nginx.ingress.kubernetes.io/session-cookie-name: route
name: discovery-server-ing
spec:
ingressClassName: nginx
rules:
- host: dev-inje-msa-demo.cone-chain.com
http:
paths:
- backend:
service:
name: discovery-server-svc
port:
number: 8761
path: /discovery
pathType: Prefix
tls:
- hosts:
- dev-inje-msa-demo.cone-chain.com
secretName: secret-tls
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/affinity: cookie
nginx.ingress.kubernetes.io/session-cookie-hash: sha256
nginx.ingress.kubernetes.io/session-cookie-name: route
name: book-service-ing
spec:
ingressClassName: nginx
rules:
- host: dev-inje-msa-demo.cone-chain.com
http:
paths:
- backend:
service:
name: book-service-svc
port:
number: 9001
path: /book-service
pathType: Prefix
tls:
- hosts:
- dev-inje-msa-demo.cone-chain.com
secretName: secret-tls
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/affinity: cookie
nginx.ingress.kubernetes.io/session-cookie-hash: sha256
nginx.ingress.kubernetes.io/session-cookie-name: route
name: author-service-ing
spec:
ingressClassName: nginx
rules:
- host: dev-inje-msa-demo.cone-chain.com
http:
paths:
- backend:
service:
name: author-service-svc
port:
number: 9002
path: /author-service
pathType: Prefix
tls:
- hosts:
- dev-inje-msa-demo.cone-chain.com
secretName: secret-tls
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/affinity: cookie
nginx.ingress.kubernetes.io/session-cookie-hash: sha256
nginx.ingress.kubernetes.io/session-cookie-name: route
name: review-service-ing
spec:
ingressClassName: nginx
rules:
- host: dev-inje-msa-demo.cone-chain.com
http:
paths:
- backend:
service:
name: review-service-svc
port:
number: 9003
path: /review-service
pathType: Prefix
tls:
- hosts:
- dev-inje-msa-demo.cone-chain.com
secretName: secret-tls
Istio Gateway + Virtual Service 구성
한편, 서비스 메시 기능이 필요하거나, L7 트래픽에 대해 보다 정교한 제어(예: 라우팅 정책, 트래픽 분할, 장애 주입 등)를 원할 경우 Istio Gateway를 함께 사용하는 것이 좋습니다.
저는 이 예제에서 다음과 같이 Istio Gateway와 VirtualService를 함께 구성하여 활용하고 있습니다. 세부 사항은 아래와 같습니다.
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: inje-msa-demo-gw
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- dev-inje-msa-demo.cone-chain.com
port:
name: http
number: 80
protocol: HTTP
tls:
httpsRedirect: true
- hosts:
- dev-inje-msa-demo.cone-chain.com
port:
name: https
number: 443
protocol: HTTPS
tls:
credentialName: secret-tls
mode: SIMPLE
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: inje-msa-demo-vs
spec:
gateways:
- inje-msa-demo-gw
hosts:
- dev-inje-msa-demo.cone-chain.com
http:
- match:
- uri:
prefix: /discovery
route:
- destination:
host: discovery-server-svc
port:
number: 8761
- match:
- uri:
prefix: /
route:
- destination:
host: gateway-server-svc
port:
number: 8000
이와 같이 VirtualService를 각 마이크로서비스 경로(prefix) 기준으로 구성함으로써, 외부 도메인(dev-inje-msa-demo.cone-chain.com
)으로 들어온 요청을 내부 서비스로 정밀하게 프록시할 수 있습니다. 이는 다음과 같은 이유에서 유용합니다.
- Istio Gateway가 TLS 및 L7 라우팅을 담당하며, 각 마이크로서비스로의 직접 프록시 역할을 하도록 설정
- 각 서비스별 URL prefix 기반으로 명시적으로 라우팅을 구성하여 가독성과 유지보수성을 높임
- 필요 시 트래픽 제어 정책 (예: Fault Injection, Canary, Mirroring 등)도 VirtualService 단위로 유연하게 적용 가능
즉, 이 구성을 통해 Spring Cloud Gateway뿐 아니라 Discovery 서비스를 노출하고 필요 시. Book, Author, Review 서비스까지도 모두 외부 도메인을 통해 직접 접근 가능하게 만들 수 있으며, 상황에 따라 특정 서비스만 독립적으로 외부 노출하는 유연한 설계가 가능합니다.
이 설정은 dev-inje-msa-demo.cone-chain.com
도메인의 모든 요청을 Gateway Server(Spring Cloud Gateway)로 라우팅합니다.
참고로, Istio Gateway는 매우 강력한 기능을 제공하지만, 필수 구성 요소는 아닙니다. 오히려 설정이 복잡하고 학습 비용이 높은 편이기 때문에, 반드시 필요한 경우에만 도입하는 것이 좋습니다. 단순한 MSA 구조라면 Nginx Ingress Controller 단독 구성만으로도 충분히 운영 가능한 안정된 환경을 구축할 수 있습니다.
Ingress Controller, Istio Gateway, Spring Cloud Gateway를 활용한 전체 구성에 대해 다시 요약하자면 아래와 같습니다.
- 보안 및 도메인 제어는 Istio Gateway에서 담당 (TLS, 도메인 매핑 등)
- 기존 Nginx Ingress 기반 구성 자산을 유지하면서도, 필요 시 Istio로 전환하여 고급 트래픽 제어 기능(정책 기반 라우팅, 회로 차단, 트래픽 미러링 등)을 활용할 수 있는 유연한 구조
- Spring Cloud Gateway를 API 게이트웨이 레이어로 사용하여 인증, 필터링, 로깅 등 구현
- 각 마이크로서비스는 내부 ClusterIP로만 노출되어 보안적으로 안전
Gateway와 Ingress의 차이
항목 | Spring Cloud Gateway | Kubernetes Ingress |
---|---|---|
위치 | 서비스 내부 | 클러스터 외부 |
역할 | 내부 라우팅, 필터, 인증 | 외부 요청 진입 및 기본 라우팅 |
성능 | 애플리케이션에 종속 | 클러스터 공통 |
특징 | Spring 기반 필터 체인 활용 가능 | 도메인 기반 라우팅, TLS 처리에 특화 |
Ingress는 외부에서 들어온 요청을 클러스터 진입점에서 받아 Spring Cloud Gateway로 전달합니다. Spring Cloud Gateway는 서비스 레지스트리와 연동하여 실제 마이크로서비스로 요청을 라우팅하고 필요한 정책(필터, 인증 등)을 적용합니다.
통합 구조의 장점
- 보안 분리 : Ingress Controller에서 도메인 및 TLS 처리, Spring Cloud Gateway에서 중앙 로깅 및 인증/인가 처리
- 관심사 분리 : 클러스터 레벨 라우팅 vs 어플리케이션 레벨 라우팅
- 확장성 : 마이크로서비스 증설, 삭제 시에도 구조적 영향 최소화. 서비스 간 통신 구조를 유지하면서도 외부 진입점 조절 가능
- 관찰성 : Spring Cloud Gateway에서 필터 기반 로깅/모니터링이 가능하며 Istio를 기반으로 Prometheus, Grafana, Zipkin 등과 연계하여 시각화가 가능함.
- 분산 트레이싱 지원 : Jaeger 연계를 통해 각 요청이 Gateway → Book → Author / Review 순으로 흘러가는 구조를 시각화할 수 있음
실제 분산 트레이싱의 결과는 아래와 같이 확인할 수 있습니다.
예를 들어 위 Jaeger 트레이스를 보면 하나의 Gateway 요청이 각 마이크로서비스로 순차적으로 연결되어 처리되는 과정을 시간 단위로 추적할 수 있습니다.
또한 Kiali 대시보드를 통해 실시간 트래픽 흐름 및 서비스 간 호출 관계도 시각화 가능하며, 오류율/성공률 모니터링도 가능합니다.
Spring Cloud Gateway와 Eureka 없이 구성한다면?
이 예제에서는 Spring Cloud Gateway와 Eureka를 조합하여 API Gateway 및 서비스 디스커버리를 구현하였습니다. 하지만 실제 운영 환경에서는 다음과 같은 이유로 Eureka 또는 Spring Cloud 스택 없이도 유사한 구조를 구현할 수 있습니다.
- Kubernetes 자체가 DNS 기반의 서비스 디스커버리 기능을 기본 제공하므로, Eureka 같은 별도 레지스트리가 없어도 service-name.namespace.svc.cluster.local 형태로 내부 서비스를 탐색할 수 있습니다.
- Spring Cloud Gateway 없이도 Nginx, Envoy, Traefik 등 다양한 API Gateway 대안이 존재하며, 각자의 생태계에서 라우팅, 인증, 필터링 기능을 제공합니다.
- Service Mesh와 Ingress Controller만으로도 인증, 로깅, 트래픽 제어 등이 가능하며, Istio에서는 Envoy Proxy가 자동으로 이를 처리합니다.
현재 제가 구성한 구조는 Spring Cloud에 친숙한 백엔드 개발자에겐 이해하기 쉽지만, 다음과 같은 단점도 존재합니다:
- Spring Cloud 종속성 : Gateway, Eureka, 각 서비스가 모두 Spring Cloud 기반으로 구성되어 있어, Java/Spring 외 기술스택 확장에 제약이 생길 수 있습니다.
- 중복된 기능 : Kubernetes의 디스커버리, 트래픽 관리, 모니터링 기능과 일부 Spring Cloud 기능이 중복될 수 있어, 운영 관점에서 복잡성을 높일 수 있습니다.
- 확장성 제약 : 마이크로서비스 수가 늘어날수록 각 구성 요소의 연결 관리 및 장애 처리 설정이 많아지며, 서비스 메시나 Kubernetes 네이티브 방식보다 구조적으로 유연성이 떨어질 수 있습니다.
끝으로
Spring Cloud Gateway는 내부 마이크로서비스 간 통신의 중심 허브 역할을 하며, 인증, 로깅, 트래픽 제어 등 다양한 기능을 제공합니다. 반면 Kubernetes Ingress Controller는 클러스터 외부의 요청을 내부로 유입시키는 관문 역할을 합니다.
이 둘을 결합한 구조는 외부 트래픽과 내부 라우팅을 분리하면서도 유기적인 통합 아키텍처를 구성할 수 있으며, 운영 관점에서 보안성, 유연성, 가시성을 동시에 확보할 수 있습니다.
예제 구성과 같이 Spring Cloud Gateway와 Eureka를 사용하는 구조는 Spring 기반 마이크로서비스에는 친숙하지만, Kubernetes 자체의 기능만으로도 충분히 유사한 구조를 구성할 수 있습니다.
특히, Ingress Controller 단독 구성, 또는 Istio Gateway 등 Service Mesh 기반 통합 제어 방식은 팀의 요구 사항과 환경에 따라 유연하게 선택할 수 있으며, Spring Cloud 종속성을 벗어난 범용적인 아키텍처로의 전환도 고려해볼 수 있습니다.
이처럼 다양한 접근 방식을 이해하고, 자신이 운영하는 플랫폼의 성격에 맞는 최적의 구성을 선택하는 것이 MSA 운영의 핵심이라 할 수 있습니다.
이 글에서 제공한 예제 프로젝트들과 통합 Kubernetes 플랫폼인 NHN Injeinc의 CONE-Chain과 함께, 실시간 로그, 트레이싱, 서비스 메시 시각화 도구를 적극 활용하여 자신만의 MSA 구조를 체계적으로 설계해보시기 바랍니다. 감사합니다.