MSA Configuration

모놀리식 서버 기반의 성능 테스트를 진행하고 MSA 리팩토링을 계획했지만, 사실 성능의 개선 폭은 엄청 높지는 않을 것 같았다. 왜냐하면 앱의 구조가 단순하기 때문이다. 그래도 MSA 환경에서의 테스트 및 해당 환경에서의 모니터링과 기능 확장을 통한 성능의 수직, 수평적 확장에 대한 방향을 익히는 것이 주 목적이기 때문에 천천히 나아가볼 예정.

1. MSA

MSAMicroservice Archietecture의 약자로, 애플리케이션을 느슨히 결합된 서비스의 모임으로 구조화하는 서비스 지향 아키텍처(SOA) 스타일의 일종인 소프트웨어 개발 기법이다. 여기서 핵심은, 애플리케이션을 느슨히 결합한다는 것이다. 왜 느슨히 결합해야 하는지, 이것이 왜 핵심인지는 MSA와 대조되는 개념인 모놀리식(Monolithic) 아키텍처와 비교하면 좋다.

1) MSA vs Monolithic Architecture

흔히 개발하면 생각할 수 있는 방식인 모놀리식 구조는 모듈별로 개발을 하고, 개발이 완료된 웹 어플리케이션을 하나의 결과물로 패키징 하여 배포되는 형태다. 모듈 단위의 기능 개발이기 때문에 하나의 프로세스 내에서 개발, 테스트, 배포가 처리된다. 하나의 프로세스라는 관점은 심플함이라는 장점을 지니지만, 강하게 묶여있기 때문에 기능 단위의 영향이 곧 전체 프로세스의 여파로 미치게 된다. 예를 들면 기능 하나의 작은 부분에 에러가 생기면 전체 프로세스의 강제 종료로 이어질 가능성이 존재한다.

위에서 언급한 느슨한 결합이, 모놀리식의 치명적인 단점을 커버하는 MSA의 장점으로 발휘될 수 있다는 것이다. 구체적으로 들어가면 다양한 전략이 존재하겠지만 마이크로서비스 인스턴스 간의 결합도가 낮아짐으로써 기능의 독립성이 확보되고 예외 상황의 대처법이 다양해지면서 기능의 확장을 쉽게 고려할 수 있다.

2) MSA 자체로 성능 개선이라 할 수 있는가?

당연히 아니겠지만, 조금 더 정립하기 위해서 내가 알고 있는 개념과 현재 진행 중인 프로젝트의 현황을 감안해서 자체적으로 사고 논의를 해보고 결론을 정리해봤다. 덤으로 기업의 입장에서 어떤 아키텍처의 선택이 이득일 지를 같이 생각하면서 논의해봤다.

찬성 측 결론

  • 확장성 및 유연성: 개별 서비스 확장 및 성능 최적화 용이. 특정 서비스만 성능 개선 가능.
  • 장기적인 비용 절감 및 수익성 향상: 초기 비용 증가 가능성 있으나, 장기적으로 운영 효율성 및 유지보수 비용 절감.
  • 간접적 성능 개선 가능성: MSA가 TPS나 RPS 같은 성능 지표를 즉각적으로 향상시키지는 않으나, 병렬 처리 및 서비스 분리를 통해 장기적으로 성능 개선 가능.

반대 측 결론

  • 직접적인 성능 개선 한계: MSA 전환만으로는 성능 지표의 즉각적인 향상은 기대할 수 없음.
  • 초기 비용 및 관리 부담: 초기 인프라 투자와 관리 복잡성 증가. 성능 개선보다 관리 부담이 커질 가능성 있음.
  • 성능 개선과 다른 접근: MSA는 성능보다는 독립성, 관리 편리성, 유연성을 목표로 하며, 성능 개선을 위해서는 추가적인 코드 최적화 필요.

종합 결론

MSA는 확장성, 유연성, 관리 효율성 등에서 장점이 있으며, 성능 개선을 직접 보장하지는 않지만, 장기적으로 운영 효율성 증가로 인해 성능 향상이 가능할 수 있음.

내가 집중한 부분은 수평적 확장의 용이함이었다. 채팅 앱의 특성 상, 단순 메세징 외에도 알람, 구독 관리 등의 추가 기능을 생각할 수 있기 때문에 덧붙일 기능을 감안해서 MSA 아키텍처의 전환은 장기적으로 이득을 가지고 올 것으로 생각했다.

3) MSA Migration Plan

모놀리식 서버에서 기능 단위로 인스턴스를 분리하며, 상호 간의 통신은 필요하면 카프카를 활용한다. 기능이라 함은 단순 도메인 단위가 아닌, 최대한의 추상화를 통한 관심사 분리를 실현한다. 예를 들어, 기존 모놀리스에서는 회원 엔티티에 JWT 기반 인증 서비스 로직까지 강하게 얽혀있었다. 이것을 우선, 인증 방식에는 JWT 외에도 다양한 인증 방식이 있으므로, JWT 검증과 회원 로직을 분리한다.

2. Spring Cloud

스프링 클라우드는 마이크로서비스 아키텍처(MSA)를 쉽게 구현하고 운영할 수 있도록 도와주는 프레임워크다. 주요 기능은 서비스 디스커버리, API 게이트웨이, 분산 설정 관리 등을 지원하며, 이를 통해 마이크로서비스 간의 통신, 장애 대응, 로드 밸런싱 등을 손쉽게 처리할 수 있게 한다.

1) 구성 요소 개요

  1. Spring Cloud Netflix Eureka
    • 서비스 디스커버리 기능을 제공. 마이크로서비스들이 동적으로 서로를 찾을 수 있도록 돕는 레지스트리 역할.
  1. Spring Cloud Config
    • 분산 설정 관리. 중앙 저장소에서 애플리케이션 설정을 관리하고, 각 서비스에서 필요할 때 불러올 수 있게 해줌.
  1. Spring Cloud Gateway
    • API 게이트웨이 역할. 요청을 라우팅하고, 인증/인가, 로깅, 필터링 등의 기능을 제공.
  1. Spring Cloud OpenFeign
    • HTTP 클라이언트로서, 마이크로서비스 간 통신을 쉽게 처리할 수 있도록 선언적 방식으로 HTTP 호출을 지원.
  1. Spring Cloud Sleuth
    • 분산 트레이싱을 제공하여, 마이크로서비스 간 호출 흐름을 추적하고 성능을 분석할 수 있음.
  1. Spring Cloud Circuit Breaker
    • 서킷 브레이커 패턴을 구현하여, 장애 발생 시 시스템을 보호하고 복구 가능한 구조를 만듦.

MSA를 위해 내가 도입한 스프링 클라우드 의존성은 API Gateway, Config, Eureka 이렇게 총 3가지이며, 각각의 인스턴스 간의 통신은 다음 포스팅에서 후술할 Kafka를 통해 데이터 정합성을 맞추려고 한다.

2) Spring Cloud Config

Spring Cloud Config는 분산 시스템에서 외부화된 설정 정보를 서버 및 클라이언트에게 제공하는 시스템이다. 설정 서버는 외부에서 모든 환경에 대한 정보들을 관리해주는 중앙 서버이고, 설정 클라이언트. 기본적으로 설정 정보 저장을 위해 Git을 사용하도록 되어있어서 손쉽게 외부 도구들로 접근 가능하고, 버전 관리도 가능하다.

물론 Git 외에도 다른 도구들도 있지만, 활용 및 관리가 편하고 즉각적인 반영이 쉽다는 부분에서 나는 Git을 활용하였다.

우선, 설정 정보를 관리할 서버를 구성해야 한다. 해당 서버에게 필요한 의존성 및 어노테이션 할당은 다음과 같다.

dependencies {
    implementation 'org.springframework.cloud:spring-cloud-config-server'

// ...
@EnableConfigServer
@SpringBootApplication
public class ChatConfigServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ChatConfigServerApplication.class, args);
    }

}

사실 이것만 하면 끝(...)이긴 한데, 이러면 너무 간단해지기 떄문에, 스프링 시큐리티를 통한 인메모리 인증으로 해당 서버 기반의 UI 접근에 대한 보안을 구축할 수도 있다.

중요한 것은, 이제 해당 서버가 중앙 관리의 역할을 맡게 하기 위한 설정 정보를 세팅하고 각 클라이언트가 접근할 수 있도록 해야 한다. 무슨 말이냐면, 설정 정보는 보통 중요하고 민감한 내용을 담고 있고, 이를 한 번에 관리하기 위해 중앙 관리를 위한 저장소를 별개로 구축해야 한다. 여기서 아까 언급한 Git이 편하다고 한 이유는, Git의 레포지토리를 생성해서 해당 저장소를 private 등으로 세팅하면 아까 말한 설정 정보 저장소를 마련할 수 있게 되는 것이다.

정리하자면, 설정 서버는 설정 정보 저장소를 접근하기 위한 일종의 관문 역할이 되며, 그 관문의 열쇠 역할을 하는 정보를 설정 정보의 yml 파일 등에 세팅하는 것이다. 언급한 열쇠 내용을 작성하는 방법은 다양하다. 보통은 비대칭 키를 활용하는데, 나는 Git에서 제공하는 토큰을 기반으로 작성했다.

# config server

spring:
  cloud:
    config:
      server:
        git:
          username: kimD0ngjun
          password: # 깃 토큰 혹은 비대칭 공개키 암호
          uri: # 설정 정보 저장소 링크

이렇게 작성 후, 각각의 인스턴스의 설정 파일은 설정 정보 저장소의 명명 규칙에 따라 명칭profiles active를 작성하고 설정 서버의 도메인을 import한다.

# config client

spring:
  application:
    name: user

  profiles:
    active: dev

  config:
    import: # config server domain

3) Spring Cloud Eureka

Spring Cloud Eureka는 마이크로서비스 환경에서 서비스 디스커버리(Service Discovery)를 담당하는 구성 요소다. 넷플릭스에서 오픈 소스로 공개한 유레카 서버와 클라이언트를 기반으로 하며, 마이크로서비스들이 서로를 쉽게 찾고 통신할 수 있게 해준다.

주의할 점은, Prometheus 같은 모니터링 툴과는 다르다는 것이다. 각 서비스의 성능, 자원 사용량, 트래픽 같은 메트릭스 데이터를 수집하고 시각화하는 데 사용되며, 서비스의 메트릭 데이터를 수집하여 모니터링하고 알람을 설정해 시스템의 상태를 실시간으로 파악할 수 있게 한다.

Eureka는 마이크로서비스 인스턴스의 헬스 체크 정도에 집중하며, 모니터링의 목적보다는 인스턴스 간의 상호 조율에 더 집중하며 Ribbon과 연동한 로드밸런싱을 제공한다.

우선 Eureka 서버를 생성해야 하는데, 역시나 간단하다. 의존성 추가하고 어노테이션 할당하면 끝(...)

dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'

// ...
@EnableEurekaServer
@SpringBootApplication
public class ChatServerEurekaApplication {

    public static void main(String[] args) {
        SpringApplication.run(ChatServerEurekaApplication.class, args);
    }

}

Eureka의 서버와 클라이언트 간의 제어에 대한 설정은 Eureka 서버의 설정 파일(yml)에서 수행할 수 있다.

eureka:
  client:
    register-with-eureka: false  # 유레카 서버 자신을 다른 유레카 서버에 등록하지 않음
    fetch-registry: false         # 유레카 서버가 다른 유레카 서버로부터 레지스트리 정보를 가져오지 않음

  server:
    enable-self-preservation: false           # Self-Preservation 모드 비활성화, 비정상 인스턴스 즉시 제거
    response-cache-update-interval-ms: 5000   # 응답 캐시를 5초마다 업데이트
    eviction-interval-timer-in-ms: 10000      # 10초마다 비정상 인스턴스 제거

이제 Eureka 클라이언트를 활성화해서 인스턴스를 클라이언트로 등록해야 한다. 각각의 인스턴스마다 의존성을 추가하고 어노테이션을 할당해준다.

dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    
// ...
@EnableDiscoveryClient
@SpringBootApplication
public class ChatServerUserApplication {

    public static void main(String[] args) {
        SpringApplication.run(ChatServerUserApplication.class, args);
    }

}

위의 과정까지 따라가면 정상적으로 인스턴스를 Eureka 클라이언트로써 등록할 수 있다. 나는 아직 인프라의 규모가 그리 크지 않아서 로드밸런싱 세팅까지 나아가진 않았는데, 향후 계획 중에 있다. Eureka 서버는 UI를 통해 등록한 Eureka 클라이언트의 관리를 쉽게 할수 있다.

4) Spring Cloud API Gateway

MSA에서 인스턴스로 분리하게 되면, 보통은 서버의 도메인이 달라지기 마련이다. 자연스럽게 클라이언트에서는 도메인이 여러 개인 것을 관리하는 것에 대한 책임이 커질 수 있기 떄문에 요청 경로에 따라 맞는 인스턴스로 경로를 재지정해 흘려보내주는 Spring API Gateway가 맡는다.

API Gateway는 마이크로서비스 인스턴스의 전면부에 나서 클라이언트의 요청을 받아오며 필터 등을 거쳐 경로 별로 각 인스턴스에 흘려보낸다. 필터 구성을 통해 인스턴스 별로 로직을 다양화시킬 수 있는 분리의 역할과 CORS 설정을 전역화할 수 있는 통합의 역할을 동시에 맡는다. 물론 개별적으로 CORS 설정도 가능하다.

통상적으로 스프링 프레임워크에서의 API Gateway 세팅은 톰캣이 아닌 네티 기반으로 세팅한다. 이는 API Gateway의 모듈들이 Spirng WebFlux에서 동작하도록 설계됐는데, WebFlux는 기본적으로 리액티브 프로그래밍을 전제로 한다. 왜냐하면 요청이 몰려오는 것에 대한 논블로킹 처리비동기 이벤트 루프를 통한 빠른 응답과 확장성을 위해서다.

dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
    implementation 'org.springframework.cloud:spring-cloud-function-context'

// ...


상세한 코드 및 관련한 트러블 슈팅 정리는 다음 포스팅에:)

profile
scientia est potentia / 벨로그 이사 예정...

0개의 댓글