0. 들어가며
소프트웨어를 개발하는 방식은 끊임없이 변화해왔다. 초기에는 메인프레임 기반의 1)중앙집중식 시스템이 주를 이루었고, 이후 2)클라이언트-서버 아키텍처가 등장했으며, 현재는 클라우드 환경에 최적화된 3)분산 시스템 아키텍처가 대세를 이루고 있다. 특히 최근 몇 년 사이 마이크로서비스 아키텍처(Microservice Architecture, MSA)는 많은 조직과 개발자들의 관심을 받으며 실제 프로덕션 환경에 적용되는 사례가 늘어나고 있다.
이 글에서는 마이크로서비스 아키텍처를 이해하기 위한 기초 개념부터 시작해, 이를 실제로 구현할 수 있도록 도와주는 Spring Cloud 프레임워크까지 살펴볼 것이다. 단순한 이론 설명에 그치지 않고, 왜 이러한 아키텍처가 필요하게 되었는지, 어떤 문제를 해결할 수 있는지, 그리고 도입 시 고려해야 할 점은 무엇인지까지 종합적으로 다루고자 한다.
1. 소프트웨어 아키텍처

소프트웨어 아키텍처는 시스템의 기본 구조를 이루는 구성 요소들과 그 구성 요소들 간의 관계, 그리고 이를 설계하고 발전시키는 원칙과 가이드라인을 포함하는 개념이다. 건축물이 사용자의 요구사항을 만족시키면서도 안정적으로 유지되기 위해 구조적 설계가 필요한 것처럼, 소프트웨어도 복잡한 요구사항을 효과적으로 처리하고 유지보수 가능한 형태를 유지하기 위해 아키텍처 설계가 필요하다. 소프트웨어 아키텍처가 중요한 이유는 다음과 같다.
첫째, 이해관계자 간의 의사소통 수단을 제공한다. 개발자, 운영자, 기획자, 관리자 등 프로젝트에 참여하는 다양한 이해관계자들이 시스템에 대해 논의할 때 공통된 언어와 관점을 제공한다.
둘째, 시스템의 품질 속성을 결정한다. 성능, 확장성, 보안, 유지보수성, 신뢰성과 같은 비기능적 요구사항은 아키텍처 수준의 결정에 크게 영향을 받는다.
셋째, 초기 설계 결정을 문서화하여 향후 변경이나 확장 시 참조할 수 있는 기준을 마련한다.
소프트웨어 아키텍처는 시간이 지남에 따라 다양한 패턴과 스타일로 발전해왔다. 레이어드 아키텍처, 파이프-필터 아키텍처, 이벤트 기반 아키텍처, 마이크로커널 아키텍처, 마이크로서비스 아키텍처 등 각각의 패턴은 특정한 문제 영역과 요구사항에 최적화되어 있다. 특히 클라우드 환경이 보편화되면서 분산 시스템에 적합한 아키텍처 패턴들의 중요성이 더욱 부각되고 있다.
2. Cloud Native Architecture
클라우드 네이티브 아키텍처는 클라우드 컴퓨팅 환경의 장점을 최대한 활용하기 위해 설계된 아키텍처를 의미한다. 단순히 기존 애플리케이션을 클라우드 환경에 배포하는 것(리프트 앤 시프트, Lift and Shift)과 달리, 클라우드 네이티브 아키텍처는 처음부터 클라우드 환경에서 실행되는 것을 전제로 설계된다.
2.1. Cloud Native Architecture의 핵심 개념
클라우드 네이티브 아키텍처는 다음과 같은 핵심 개념을 기반으로 한다.
2.1.1. 확장성(Scalability)
시스템의 처리 능력을 유연하게 조절할 수 있는 능력을 말한다. 수직적 확장(Scale-up)은 개별 서버의 성능을 높이는 방식이고, 수평적 확장(Scale-out)은 서버의 개수를 늘리는 방식이다. 클라우드 네이티브 아키텍처는 특히 수평적 확장에 초점을 맞춘다. 트래픽이 증가하면 자동으로 인스턴스를 추가하고, 감소하면 불필요한 인스턴스를 제거하는 탄력적 확장(Elastic Scaling)이 가능해야 한다.
2.1.2. 탄력성(Elasticity)
시스템이 변화하는 워크로드에 자동으로 대응하여 리소스를 조정하는 능력을 의미한다. 예를 들어, 블랙프라이데이와 같은 특별 이벤트 기간에 트래픽이 급증하면 자동으로 더 많은 서버 인스턴스를 생성하고, 이벤트가 종료되면 다시 축소하는 것이 가능해야 한다.
2.1.3. 복원력(Resilience)
일부 구성 요소에 장애가 발생하더라도 전체 시스템이 지속적으로 작동할 수 있는 능력을 의미한다. 분산 시스템에서는 네트워크 지연, 서버 장애, 데이터베이스 연결 실패 등 다양한 유형의 장애가 발생할 수 있다. 복원력 있는 시스템은 이러한 장애를 격리하고, 자동으로 복구하며, 사용자에게 최소한의 영향만 미치도록 설계된다.
2.1.4. 민첩성(Agility)
비즈니스 요구사항 변화에 신속하게 대응할 수 있는 능력을 의미한다. 작은 단위의 서비스로 구성된 시스템은 특정 기능의 변경이나 추가가 전체 시스템에 영향을 미치지 않으면서 빠르게 이루어질 수 있다.
2.2. Cloud Native Architecture의 구성 요소
클라우드 네이티브 아키텍처를 구현하기 위해 필요한 주요 구성 요소들은 다음과 같다.
2.2.1. 컨테이너(Container)
애플리케이션과 그 실행 환경을 패키징하는 기술이다. 컨테이너는 호스트 운영체제의 커널을 공유하면서도 독립적인 실행 환경을 제공하므로, 가상머신에 비해 가볍고 빠르게 시작할 수 있다. Docker는 컨테이너 기술의 사실상 표준으로 자리잡았다.
2.2.2. 오케스트레이션(Orchestration)
여러 컨테이너의 배포, 관리, 확장, 네트워킹 등을 자동화하는 기술이다. Kubernetes는 컨테이너 오케스트레이션의 대표적인 도구로, 복잡한 컨테이너 환경을 효율적으로 관리할 수 있게 해준다.
2.2.3. 서비스 메시(Service Mesh)
마이크로서비스 간의 통신을 관리하고 제어하는 전용 인프라 계층이다. Istio, Linkerd 등의 서비스 메시 기술은 서비스 디스커버리, 로드 밸런싱, 인증, 암호화, 관찰 가능성(Observability) 등의 기능을 제공한다.
2.2.4. DevOps
개발(Development)과 운영(Operations)의 통합을 의미하는 문화이자 방법론이다. 클라우드 네이티브 환경에서는 개발팀과 운영팀의 긴밀한 협업이 필수적이며, CI/CD(지속적 통합/지속적 배포) 파이프라인을 통해 변경사항을 신속하고 안정적으로 프로덕션에 반영할 수 있어야 한다.
2.2.5. 인프라 as 코드(Infrastructure as Code)
서버, 네트워크, 데이터베이스 등의 인프라를 코드로 정의하고 관리하는 방식이다. Terraform, AWS CloudFormation 등의 도구를 사용하면 인프라 구성을 버전 관리하고, 자동화된 방식으로 프로비저닝할 수 있다.
3. Cloud Native Application
클라우드 네이티브 애플리케이션은 클라우드 네이티브 아키텍처 원칙에 따라 설계되고 구현된 애플리케이션을 의미한다. 이러한 애플리케이션은 다음과 같은 특징을 가진다.
3.1. 마이크로서비스 기반 설계
클라우드 네이티브 애플리케이션은 일반적으로 여러 개의 독립적인 마이크로서비스로 구성된다. 각 서비스는 특정 비즈니스 기능을 담당하며, 독자적으로 개발, 배포, 확장될 수 있다.
예를 들어, 이커머스 시스템을 클라우드 네이티브 방식으로 설계한다면, 사용자 관리 서비스, 상품 카탈로그 서비스, 주문 서비스, 결제 서비스, 배송 서비스 등으로 분리할 수 있다. 각 서비스는 독립적인 팀이 개발할 수 있고, 각자의 기술 스택을 선택할 수 있으며, 자신만의 데이터베이스를 가질 수 있다.
3.2. API 기반 통신
마이크로서비스 간의 통신은 잘 정의된 API를 통해 이루어진다. RESTful API나 gRPC와 같은 프로토콜을 사용하며, 각 서비스는 자신의 API를 통해 기능을 노출하고 다른 서비스의 API를 호출하여 필요한 기능을 사용한다.
3.3. 상태 분리(Statelessness)
가능한 한 상태를 서비스 외부에 저장하는 것을 지향한다. 상태를 내부에 보관하면 수평적 확장이 어려워지고, 장애 시 복구가 복잡해진다. 클라우드 네이티브 애플리케이션에서는 세션 정보나 애플리케이션 상태를 데이터베이스나 캐시와 같은 외부 저장소에 보관하는 것이 일반적이다.
3.4. 관찰 가능성(Observability)
분산 환경에서는 시스템의 내부 상태를 파악하기 어렵다. 따라서 로깅, 모니터링, 추적 등의 관찰 가능성 도구를 활용하여 시스템의 건강 상태를 지속적으로 확인할 수 있어야 한다. 각 서비스는 표준화된 형식으로 로그와 메트릭을 출력하고, 중앙 집중식 모니터링 시스템이 이를 수집하고 분석한다.
4. 12개의 Factors
클라우드 네이티브 애플리케이션을 개발할 때 참고할 수 있는 방법론으로 12 Factors가 널리 알려져 있다. 이는 2011년 Heroku의 엔지니어들이 정리한 것으로, 현대적인 웹 애플리케이션 개발 모범 사례를 담고 있다.
4.1. 코드베이스(Codebase)
하나의 애플리케이션은 하나의 코드베이스로 관리되며, 여러 환경(개발, 스테이징, 프로덕션)에 배포된다. 버전 관리 시스템(Git 등)을 사용하여 코드를 관리하고, 동일한 코드베이스에서 다양한 환경에 맞는 배포를 진행한다.
4.2. 종속성(Dependencies)
애플리케이션의 모든 종속성을 명시적으로 선언하고 격리한다. Java의 경우 Maven이나 Gradle의 의존성 관리 기능을 활용하여 필요한 라이브러리를 명시적으로 선언하며, 시스템 수준의 도구나 라이브러리에 암묵적으로 의존하지 않아야 한다.
4.3. 설정(Config)
설정 정보(데이터베이스 연결 정보, 외부 서비스 인증 정보 등)는 코드와 엄격하게 분리되어야 한다. 환경에 따라 달라질 수 있는 설정은 환경 변수나 외부 설정 파일을 통해 주입받는 방식으로 관리한다.
4.4. 백킹 서비스(Backing Services)
데이터베이스, 메시징 시스템, 캐시 등 애플리케이션이 사용하는 모든 리소스는 연결된 서비스로 취급한다. 이러한 백킹 서비스는 로컬과 원격을 구분하지 않고 동일한 방식으로 접근 가능해야 하며, 코드 변경 없이 한 서비스에서 다른 서비스로 교체할 수 있어야 한다.
4.5. 빌드, 릴리스, 실행(Build, Release, Run)
애플리케이션의 빌드, 릴리스, 실행 단계를 엄격하게 분리한다. 빌드 단계에서는 소스 코드를 실행 가능한 아티팩트로 변환하고, 릴리스 단계에서는 빌드 결과물과 환경별 설정을 결합하며, 실행 단계에서는 릴리스된 애플리케이션을 실행한다.
4.6. 프로세스(Processes)
애플리케이션은 하나 이상의 무상태(stateless) 프로세스로 실행되어야 한다. 세션 데이터나 파일 시스템에 저장된 임시 데이터와 같은 상태는 외부 서비스(데이터베이스, 분산 캐시 등)에 저장한다.
4.7. 포트 바인딩(Port Binding)
애플리케이션은 특정 포트에 직접 바인딩하여 서비스를 노출한다. 즉, 애플리케이션 자체가 웹 서버 기능을 내장하고 있어 별도의 서버 컨테이너에 배포할 필요가 없다. Spring Boot의 내장 톰캣이 이에 해당한다.
4.8. 동시성(Concurrency)
프로세스 모델을 통해 애플리케이션을 확장한다. 워크로드의 특성에 따라 다양한 유형의 프로세스를 사용할 수 있으며, 필요에 따라 프로세스 수를 조절하여 확장한다.
4.9. 폐기 가능성(Disposability)
애플리케이션 프로세스는 빠르게 시작되고, 정상적으로 종료될 수 있어야 한다. 이를 통해 탄력적인 확장과 안정적인 배포가 가능해진다.
4.10. 개발/프로덕션 환경 일치(Dev/Prod Parity)
개발, 스테이징, 프로덕션 환경의 차이를 최소화한다. 지속적인 배포를 위해서는 환경 간 차이로 인한 예상치 못한 문제를 방지해야 한다.
4.11. 로그(Logs)
로그를 이벤트 스트림으로 처리한다. 애플리케이션은 로그 파일에 직접 쓰지 않고 표준 출력(stdout)으로 이벤트 스트림을 출력하며, 실행 환경에서 이를 수집하여 최종 목적지(로그 분석 시스템 등)로 전달한다.
4.12. 관리 프로세스(Admin Processes)
데이터베이스 마이그레이션, 일회성 스크립트 실행 등의 관리 작업을 일회성 프로세스로 실행한다. 이러한 작업은 애플리케이션 코드베이스와 함께 버전 관리되고, 동일한 환경에서 실행되어야 한다.
5. Monolithic vs. Microservice

5.1. 모놀리식 아키텍처(Monolithic Architecture)
모놀리식 아키텍처는 전통적인 소프트웨어 개발 방식으로, 애플리케이션의 모든 기능이 하나의 코드베이스로 통합되어 하나의 실행 파일이나 패키지로 배포되는 구조를 말한다.
5.1.1. 모놀리식 아키텍처의 장점
- 개발 초기 단순함: 하나의 프로젝트에서 모든 코드를 관리하므로 초기 개발이 단순하고 빠르다.
- 통합 테스트 용이성: 모든 기능이 하나의 애플리케이션에 통합되어 있어 통합 테스트가 비교적 간단하다.
- 배포 단순함: 하나의 패키지를 빌드하여 배포하면 되므로 초기 배포가 단순하다.
- 성능: 서비스 간 통신이 프로세스 내에서 이루어지므로 분산 시스템에 비해 성능상 이점이 있다.
5.1.2. 모놀리식 아키텍처의 단점
- 복잡성 증가: 애플리케이션이 커질수록 코드베이스가 방대해지고 복잡해져 이해하고 수정하기 어려워진다.
- 배포 리스크: 작은 변경에도 전체 애플리케이션을 다시 빌드하고 배포해야 하며, 변경의 영향 범위가 넓어 리스크가 크다.
- 확장성 제한: 특정 기능만 확장해야 할 경우에도 전체 애플리케이션을 확장해야 하므로 비효율적이다.
- 기술 스택 고정: 전체 애플리케이션이 동일한 기술 스택을 사용해야 하므로, 새로운 기술 도입이 어렵다.
- 장애 격리 어려움: 일부 모듈의 장애가 전체 시스템의 장애로 이어질 수 있다.
5.2. 마이크로서비스 아키텍처(Microservice Architecture)
마이크로서비스 아키텍처는 애플리케이션을 여러 개의 작은 독립적인 서비스로 분할하는 방식이다. 각 서비스는 특정 비즈니스 기능을 담당하며, 독립적으로 개발, 배포, 확장될 수 있다.
5.2.1. 마이크로서비스 아키텍처의 장점
- 독립적인 개발과 배포: 각 서비스를 독립적으로 개발하고 배포할 수 있어 개발 속도가 빨라지고 배포 리스크가 줄어든다.
- 기술 스택의 다양성: 서비스별로 특성에 맞는 기술 스택을 선택할 수 있다.
- 탄력적 확장: 필요한 서비스만 선택적으로 확장할 수 있어 리소스 효율성이 높아진다.
- 장애 격리: 특정 서비스의 장애가 전체 시스템으로 전파되지 않도록 격리할 수 있다.
- 조직 구조와의 일치: 작은 팀이 각 서비스를 담당하여 애자일한 개발이 가능하다.
5.2.2. 마이크로서비스 아키텍처의 단점
- 분산 시스템의 복잡성: 서비스 간 네트워크 통신, 장애 처리, 트랜잭션 관리 등의 복잡성이 증가한다.
- 운영 오버헤드: 여러 서비스를 모니터링하고 관리해야 하므로 운영 부담이 커진다.
- 데이터 일관성 유지 어려움: 각 서비스가 독립적인 데이터베이스를 가지므로 분산 트랜잭션 관리가 복잡해진다.
- 테스트 복잡성: 서비스 간 의존 관계로 인한 통합 테스트가 어려워진다.
- 초기 개발 비용: 모놀리식 아키텍처에 비해 초기 설계와 개발에 더 많은 비용이 소요될 수 있다.
5.3. 모놀리식과 마이크로서비스의 선택 기준
모놀리식과 마이크로서비스 중 어떤 아키텍처를 선택할지는 다음과 같은 요소를 고려해야 한다.
- 팀 규모와 조직 구조: 소규모 팀이나 스타트업의 경우 모놀리식이 더 효율적일 수 있다.
- 애플리케이션 규모와 복잡성: 도메인이 복잡하고 많은 기능이 상호작용하는 경우 마이크로서비스가 적합할 수 있다.
- 변화의 빈도: 특정 부분이 자주 변경된다면 해당 부분을 독립적인 서비스로 분리하는 것이 유리할 수 있다.
- 확장 요구사항: 특정 기능의 확장 요구가 높다면 마이크로서비스 아키텍처가 유리하다.
6. Microservice Architecture의 이해
마이크로서비스 아키텍처는 단순히 기술적인 구조만을 의미하는 것이 아니라, 소프트웨어 개발과 조직 운영의 철학을 포함하는 포괄적인 개념이다.
6.1. 마이크로서비스의 핵심 원칙
6.1.1. 비즈니스 도메인 중심의 모델링
마이크로서비스는 기술 계층이 아닌 비즈니스 기능을 중심으로 모델링된다. 예를 들어, 전통적인 레이어드 아키텍처에서는 프레젠테이션 계층, 비즈니스 로직 계층, 데이터 접근 계층으로 분리하는 반면, 마이크로서비스 아키텍처에서는 주문 관리, 고객 관리, 상품 관리와 같은 비즈니스 기능 단위로 서비스를 분리한다.
6.1.2. 분산된 데이터 관리
각 마이크로서비스는 자체 데이터베이스를 소유한다. 이는 서비스 간 결합도를 낮추고, 각 서비스가 자신의 데이터 스키마를 독립적으로 진화시킬 수 있게 한다. 또한 서비스별로 최적화된 데이터 저장 기술을 선택할 수 있다.
6.1.3. 인프라 자동화
마이크로서비스 환경에서는 수많은 서비스를 수동으로 관리하는 것이 불가능하다. 따라서 컨테이너화, 오케스트레이션, CI/CD 파이프라인 등을 통한 자동화가 필수적이다.
6.1.4. 설계와 장애에 대한 내성
분산 시스템에서는 네트워크 지연, 서비스 장애 등 다양한 실패 상황이 발생할 수 있다. 마이크로서비스 아키텍처는 이러한 실패를 예상하고, 장애가 발생하더라도 시스템이 부분적으로 기능할 수 있도록 설계되어야 한다.
6.2. 마이크로서비스 통신 방식
6.2.1. 동기식 통신
REST, gRPC 등의 프로토콜을 사용하여 서비스 간 직접 통신하는 방식이다. 구현이 단순하고 직관적이지만, 호출된 서비스의 응답을 기다려야 하므로 결합도가 높아진다.
6.2.2. 비동기식 통신
메시지 큐나 이벤트 스트리밍 플랫폼(Kafka, RabbitMQ 등)을 통해 통신하는 방식이다. 서비스 간 결합도를 낮추고, 시스템의 탄력성을 높일 수 있다.
7. SOA vs. MSA
SOA(Service Oriented Architecture)와 MSA(Microservice Architecture)는 모두 서비스라는 개념을 기반으로 한다는 공통점이 있지만, 몇 가지 중요한 차이점이 있다.

7.1. 서비스 세분화(Granularity)
SOA는 일반적으로 더 큰 단위의 서비스를 지향하는 반면, MSA는 더 작고 세분화된 서비스를 지향한다. SOA에서는 재사용 가능한 비즈니스 기능을 서비스로 정의하는 데 중점을 두고, MSA는 비즈니스 기능을 더 작은 단위로 분할하는 데 중점을 둔다.
7.2. 통합 방식
SOA는 주로 ESB(Enterprise Service Bus)를 통한 중앙집중식 통합을 사용한다. ESB는 메시지 변환, 라우팅, 프로토콜 변환 등의 기능을 제공한다. 반면 MSA는 가벼운 프로토콜(HTTP/REST, gRPC)을 통한 직접 통신이나 메시지 브로커를 통한 비동기 통신을 선호한다.
7.3. 데이터 저장소
SOA는 서비스 간 데이터 저장소를 공유하는 경우가 많은 반면, MSA는 각 서비스가 독립적인 데이터 저장소를 가지는 것을 원칙으로 한다.
7.4. 배포 단위
SOA는 여러 서비스가 하나의 애플리케이션 서버에 배포될 수 있는 반면, MSA는 각 서비스가 독립적으로 배포된다.
8. Microservice Architecture Structures
마이크로서비스 아키텍처를 구성하는 핵심 구조 요소들을 살펴보면 다음과 같다.
8.1. 서비스 디스커버리(Service Discovery)
클라우드 환경에서는 서비스 인스턴스의 위치(IP 주소, 포트)가 동적으로 변할 수 있다. 서비스 디스커버리는 이러한 동적 환경에서 서비스의 위치를 찾을 수 있게 해주는 메커니즘이다. Netflix Eureka, Consul, ZooKeeper 등이 대표적인 도구다.
8.2. API 게이트웨이(API Gateway)
클라이언트 요청을 적절한 마이크로서비스로 라우팅하는 진입점 역할을 한다. 인증, 로깅, 요청 제한, 응답 변환 등의 부가 기능을 처리할 수 있다. Spring Cloud Gateway, Netflix Zuul 등이 사용된다.
8.3. 설정 서버(Configuration Server)
분산 환경에서 각 서비스의 설정 정보를 중앙에서 관리하는 역할을 한다. 설정 변경 시 서비스를 재배포하지 않고도 동적으로 설정을 갱신할 수 있다. Spring Cloud Config Server가 대표적이다.
8.4. 분산 추적(Distributed Tracing)
여러 마이크로서비스를 거치는 요청의 흐름을 추적하고 모니터링하는 기능이다. Zipkin, Jaeger 등의 도구를 활용하여 요청 지연이나 오류 발생 지점을 파악할 수 있다.
8.5. 회로 차단기(Circuit Breaker)
서비스 간 호출 시 장애가 발생한 서비스로의 지속적인 호출을 차단하고 대체 로직을 실행하는 패턴이다. Resilience4J, Hystrix 등이 사용된다.
9. Spring Cloud란?
9.1. 들어가며: Spring Cloud란 무엇인가?
마이크로서비스 아키텍처(MSA)를 채택하면 확장성과 민첩성이라는 이점을 얻지만, 동시에 분산 시스템의 복잡성이라는 새로운 과제에 직면하게 된다. 설정 관리, 서비스 간 통신, 장애 전파 방지 등 모놀리식 환경에서는 고려할 필요가 없었던 문제들을 어떻게 해결해야 할까? 이때 등장하는 개념이 바로 Spring Cloud이다.
Spring Cloud는 분산 시스템 환경에서 마이크로서비스를 구축할 때 발생하는 공통적인 문제들을 해결하고, 개발자가 비즈니스 로직에만 집중할 수 있도록 돕는 다양한 도구와 프레임워크의 집합이다. 즉, 마이크로서비스 아키텍처 구현에 필요한 다양한 패턴들을 이미 구현된 형태로 제공하여, 개발자가 매번 바퀴를 재발명하지 않아도 되게 해주는 종합 솔루션 패키지라고 할 수 있다.
중요한 점은 Spring Cloud는 Spring Boot 위에서 동작한다는 것이다. 따라서 프로젝트 구성 시 Spring Boot와 Spring Cloud 간의 버전 호환성을 반드시 확인해야 한다. 호환되지 않는 버전을 사용할 경우 예기치 않은 오류가 발생할 수 있으므로, 공식 문서를 통해 릴리즈 트레인(Release Train)별 호환 버전을 확인하는 것이 필수적이다. Spring Cloud는 London, Paris, Greenwich와 같은 도시 이름이나 2020.0.0(일명 Ilford)과 같은 연도 기반 버전 체계를 사용하는 릴리즈 트레인 방식으로 배포되며, 각 릴리즈 트레인은 특정 Spring Boot 버전과 호환되도록 설계되어 있다.
9.2. Spring Cloud의 핵심 패턴과 컴포넌트
Spring Cloud는 분산 시스템의 여러 문제를 해결하기 위해 다양한 프로젝트(컴포넌트)를 제공한다. 각 컴포넌트가 어떤 문제를 해결하는지 패턴 중심으로 살펴보면 다음과 같다.
9.2.1. 중앙 집중식 설정 관리 (Centralized Configuration)
컴포넌트: Spring Cloud Config
MSA 환경에서는 수많은 서비스가 각자 설정을 갖는다. 주문 서비스, 회원 서비스, 카탈로그 서비스마다 각각의 application.yml 파일이 존재하고, 이 파일들에는 데이터베이스 연결 정보, 외부 API 키, 기능 토글 등의 설정값이 포함된다. 문제는 이러한 설정값이 환경(개발, 테스트, 운영)에 따라 달라지고, 때로는 실행 중에 변경되어야 할 필요가 있다는 점이다.
Spring Cloud Config는 이러한 설정 파일들을 Git과 같은 외부 저장소에서 중앙 집중적으로 관리하도록 돕는다. Config Server는 설정 저장소(Git, 파일 시스템, Vault 등)와 연결되어 있고, 각 마이크로서비스(Config Client)는 기동 시 또는 설정 변경 시에 Config Server로부터 자신의 설정을 조회해간다. 이를 통해 애플리케이션 재배포 없이 설정을 동적으로 변경하고(Spring Cloud Bus와 연동 시), 모든 서비스의 설정을 일관되게 유지할 수 있다. 또한 설정 이력을 Git을 통해 관리할 수 있어 변경 사항 추적과 롤백도 용이하다.
9.2.2. 서비스 탐색 및 등록 (Service Discovery)
컴포넌트: Spring Cloud Netflix Eureka, HashiCorp Consul
클라우드 환경에서는 서비스 인스턴스가 동적으로 생성되고 사라지므로 IP 주소가 계속 변한다. 오토 스케일링으로 인해 새로운 인스턴스가 추가되거나, 장애로 인해 인스턴스가 제거되는 상황에서 서비스의 물리적 위치(IP와 포트)는 고정적이지 않다.
Eureka와 같은 Service Discovery 컴포넌트는 각 서비스가 스스로를 '서비스 레지스트리'에 등록하고, 다른 서비스의 위치 정보를 질의할 수 있는 전화번호부 역할을 한다. 서비스 인스턴스는 시작될 때 자신의 정보를 Eureka 서버에 등록하고, 주기적으로 하트비트를 보내 자신이 살아있음을 알린다. 다른 서비스가 특정 서비스를 호출해야 할 때는 Eureka 서버에서 해당 서비스의 인스턴스 목록을 조회하여 그중 하나를 선택해 호출한다. 이를 통해 서비스들은 서로의 IP를 직접 알 필요 없이 유연하게 통신할 수 있으며, 로드 밸런싱까지 자연스럽게 구현할 수 있다.
9.2.3. API 게이트웨이와 로드 밸런싱 (API Gateway & Load Balancing)
컴포넌트: Spring Cloud Gateway, Spring Cloud LoadBalancer
마이크로서비스 아키텍처에서는 각 서비스가 각자의 API를 노출한다. 만약 클라이언트(웹, 모바일 앱 등)가 각 서비스에 직접 요청을 보내게 되면, 인증, 로깅, CORS 처리 등 모든 서비스가 공통적으로 처리해야 하는 기능을 각각 구현해야 하는 비효율이 발생한다.
Spring Cloud Gateway는 모든 클라이언트 요청의 단일 진입점 역할을 하는 API 게이트웨이다. 클라이언트의 요청을 받아 적절한 마이크로서비스로 라우팅하고, 이 과정에서 인증/인가, 요청/응답 변환, 로깅, 요청 제한(Rate Limiting) 등 시스템의 공통 기능을 처리하여 내부 서비스들의 복잡도를 낮춘다. 또한, 서비스 간 통신 시에는 Spring Cloud LoadBalancer가 클라이언트 사이드에서 부하를 분산하여 특정 인스턴스로 트래픽이 몰리는 것을 방지한다. Spring Cloud LoadBalancer는 과거 Netflix Ribbon이 사용되었으나 현재는 공식적으로 Spring Cloud에서 유지보수하는 LoadBalancer로 대체되었다.
9.2.4. 선언적 REST 클라이언트 (Declarative REST Client)
컴포넌트: Spring Cloud OpenFeign
MSA에서는 서비스 간에 REST API를 통한 통신이 빈번하게 발생한다. 주문 서비스가 회원 서비스를 호출하여 사용자 정보를 조회하거나, 상품 서비스를 호출하여 재고 정보를 확인하는 식이다. 전통적인 방식으로는 RestTemplate이나 WebClient를 사용하여 HTTP 요청을 생성하고, 응답을 파싱하는 코드를 작성해야 한다.
OpenFeign은 간단한 인터페이스와 어노테이션만으로 REST 클라이언트를 구현할 수 있게 해주는 선언적 클라이언트다. 개발자는 Java 인터페이스를 만들고 @FeignClient 어노테이션을 추가한 후, 호출하려는 API의 메서드 시그니처를 정의하기만 하면 된다.
그러면 Spring이 자동으로 해당 인터페이스의 구현체를 생성하여 주입해준다. 이를 통해 개발자는 복잡한 HTTP 통신 코드를 작성할 필요 없이, 마치 자바의 인터페이스 메서드를 호출하듯이 다른 서비스를 쉽게 호출할 수 있다. Feign은 Spring Cloud LoadBalancer와 통합되어 서비스 디스커버리와 로드 밸런싱도 자동으로 처리한다.
9.2.5. 장애 허용 (Fault Tolerance - 서킷 브레이커)
컴포넌트: Resilience4j (과거 Hystrix)
분산 시스템에서는 하나의 서비스 장애가 연쇄적으로 다른 서비스에 전파되어 전체 시스템을 마비시키는 '연쇄 장애(Cascading Failure)'가 발생할 수 있다. 예를 들어, 주문 서비스가 회원 서비스를 호출하는데 회원 서비스에 장애가 발생하면, 주문 서비스는 응답을 기다리며 스레드가 점유되고, 결국 주문 서비스의 리소스가 고갈되어 장애가 발생할 수 있다.
서킷 브레이커(Circuit Breaker)는 특정 서비스에 장애가 감지되면, 해당 서비스로의 요청을 일시적으로 차단하고 미리 정의된 대체 로직(Fallback)을 실행하는 패턴이다. Resilience4j는 이러한 서킷 브레이커 패턴을 구현한 라이브러리로, 장애가 발생한 서비스에 대한 연속적인 호출을 차단하여 시스템 전체의 장애를 방지하고, 일정 시간 후 다시 요청을 보내 서비스가 복구되었는지 확인하는 과정을 자동화한다. 과거에는 Netflix Hystrix가 많이 사용되었으나 현재는 유지보수 모드로 전환되어 Resilience4j가 표준으로 자리잡았다.
9.2.6. 분산 추적 (Distributed Tracing)
컴포넌트: Micrometer Tracing (구 Spring Cloud Sleuth), Zipkin
하나의 요청이 여러 마이크로서비스를 거칠 때, 장애 발생 시 원인을 추적하기란 매우 어렵다. 예를 들어, 사용자의 주문 요청이 API 게이트웨이 → 주문 서비스 → 결제 서비스 → 재고 서비스를 거쳐 처리된다고 가정해보자. 어느 단계에서 지연이 발생했는지, 어떤 서비스에서 오류가 발생했는지를 로그만으로 파악하는 것은 거의 불가능에 가깝다.
Micrometer Tracing(기존 Spring Cloud Sleuth의 기능을 통합)은 모든 요청에 고유한 추적 ID(Trace ID)와 각 서비스 호출 단위에 스팬 ID(Span ID)를 부여하여 로그에 추가한다. 그리고 이 정보를 Zipkin과 같은 분산 추적 시스템으로 전송하면, Zipkin은 각 서비스 간의 호출 흐름과 지연 시간을 시각화된 그래프로 보여준다. 이를 통해 개발자는 병목 지점을 빠르게 식별하고, 장애 발생 시 영향 범위를 정확히 파악할 수 있다.
9.3. Spring Cloud vs. Kubernetes: 경쟁인가, 협력인가?

"Kubernetes가 제공하는 기능이 Spring Cloud의 기능을 대부분 포함하는 것 아닌가?" 라는 질문이 종종 제기된다. 결론부터 말하면, 두 기술은 경쟁 관계가 아닌, 서로 다른 계층에서 동작하는 상호 보완적인 협력 관계다.
- 쿠버네티스(Kubernetes): 인프라 계층(Infrastructure Layer)의 문제를 해결한다. 컨테이너 오케스트레이션 도구로서, 서비스의 배포, 스케일링, 네트워킹, 기본적인 헬스 체크 등 '운영'에 관련된 저수준(Low-level)의 기능을 담당한다. 쿠버네티스는 서비스 디스커버리를 위한 Kube-DNS/CoreDNS를 제공하고, 기본적인 로드 밸런싱도 지원한다. 비유하자면, 클라우드라는 도시의 운영체제(OS)와 같다.
- 스프링 클라우드(Spring Cloud): 애플리케이션 계층(Application Layer)의 문제를 해결한다. 서킷 브레이커, 선언적 클라이언트(Feign), 분산 추적, 고급 라우팅 등 애플리케이션 '내부'의 로직과 관련된 고수준(High-level)의 패턴을 제공한다. 이러한 기능들은 애플리케이션의 비즈니스 로직과 밀접하게 연관되어 있으며, 코드 레벨에서 세밀한 제어가 필요하다. 이는 OS 위에서 동작하는 애플리케이션을 만들기 위한 SDK 또는 프레임워크와 같다.
9.4. 결론: 함께 사용하여 시너지 극대화
현대적인 클라우드 네이티브 개발의 가장 일반적인 방식은 쿠버네티스와 스프링 클라우드를 함께 사용하는 것이다.
쿠버네티스가 인프라 레벨의 배포와 기본적인 서비스 연결을 안정적으로 처리하고, 그 위에서 동작하는 스프링 부트 애플리케이션이 스프링 클라우드의 컴포넌트들을 활용하여 애플리케이션 레벨의 복잡한 상호작용과 장애 허용 로직을 구현하는 것이다. 예를 들어, 쿠버네티스가 서비스의 기본적인 디스커버리를 제공하더라도, 애플리케이션 레벨에서 더 세밀한 라우팅 규칙이나 서킷 브레이커 패턴이 필요하다면 Spring Cloud Resilience4j를 함께 사용할 수 있다.
이 두 기술을 함께 사용함으로써, 개발자는 인프라 운영의 부담을 줄이고 안정적인 애플리케이션 로직 개발에 더욱 집중할 수 있다. Spring Cloud는 쿠버네티스 환경에서도 원활히 동작하도록 설계되어 있으며, 두 기술의 장점을 결합할 때 진정한 클라우드 네이티브 애플리케이션의 이점을 최대화할 수 있다.
마치며
이번 글에서는 마이크로서비스 아키텍처의 기초 개념부터 클라우드 네이티브 환경에서의 특징, 그리고 이를 구현하기 위한 Spring Cloud 프레임워크까지 광범위하게 살펴보았다. 마이크로서비스 아키텍처는 만능 해결책이 아니며, 모놀리식 아키텍처에 비해 분명한 트레이드오프가 존재한다. 따라서 무조건적인 도입보다는 팀의 상황, 시스템의 요구사항, 조직의 역량을 종합적으로 고려하여 적절한 아키텍처를 선택하는 것이 중요하다.
다음 글부터는 실제로 Spring Cloud의 각 모듈을 활용하여 마이크로서비스를 구축하는 방법을 실습 위주로 살펴볼 예정이다. 먼저 Service Discovery를 구현하는 Spring Cloud Netflix Eureka에 대해 자세히 알아보도록 하겠다.
'MSA > MSA 기본' 카테고리의 다른 글
| [BASIC #6] Configuration Service와 중앙 설정 관리 (0) | 2025.09.20 |
|---|---|
| [BASIC #5] 인증 처리와 JWT 기반 보안 (0) | 2025.09.20 |
| [BASIC #4][실습] E-commerce MSA 프로젝트 구조 설계 (0) | 2025.09.20 |
| [BASIC #3] API Gateway (0) | 2025.09.20 |
| [BASIC #2] Service Discovery (0) | 2025.09.20 |
