MapleStory Finger Point

[IT 개발자를 위한 필독서 SSAFYdia] 모놀리스를 넘어: MSA로 확장성과 안정성을 잡다

2025. 6. 2. 10:41Archive/SSAFY

Generated by DALL-E

 

SSAFY 마지막 자율 프로젝트에서도 Backend/Infra 파트를 담당하면서 기존의 Monolithic 구조 대신 MSA(Microservices Architecture)를 도입하게 되었습니다.

사실 처음에는 단순히 분산 아키텍처를 적용해보고 싶다는 호기심이 들어서 도입을 고려하게 되었습니다.

기술적 호기심으로 시작했지만 막상 적용해 보니 생각보다 꽤 많은 이점을 체감할 수 있었습니다.

 

특히 MSA가 다음과 같은 상황에 잘 맞는 해답이라는 걸 알게 되었습니다:

  • 기능별로 서비스 경계를 명확하게 나눌 수 있다
  • 자주 바뀌는 기능은 다른 서비스에 영향 없이 배포할 수 있다
  • 장애가 나더라도 전체 시스템이 아닌 해당 서비스에만 영향을 줄 수 있다

 

이러한 이점을 가지고 있기 때문에 SSAFY 자율 프로젝트에 MSA를 도입하게 되었습니다.

 

 


 

왜 MSA였는가?

기존 Monolithic 구조의 문제는 명확합니다:

  • 기능 하나만 수정해도 전체 서비스를 다시 빌드 & 배포해야 한다는 부담
  • 서비스 간 의존성으로 인해 하나의 오류가 전체 장애로 이어지는 구조

 

이러한 상황을 개선하고자 우리는 서비스별 독립적인 배포, 장애 격리, 팀 간 병렬 개발 가능성을 장점으로 가진 MSA를 도입했습니다. 단순한 기술적 전환이 아니라 더 나은 개발 문화와 운영 방식을 만들어 볼 수 있는 기회이기도 합니다.

 

MSA 도입을 위한 설계 및 기술 스택

구축한 시스템 아키텍처는 다음과 같습니다:

 

 

🧭 Gateway: Spring Cloud Gateway로 요청 라우팅 담당

🔐 Auth 서비스: 인증, JWT 발급 및 검증 (Spring Security)

👤 Member 서비스: 회원 정보 관리

📦 Device 서비스: 기기 정보 관리

🫲 Gesture 서비스: 제스처 센서 데이터 및 제스처 정보 관리

🕰️ Routine 서비스: 루틴 정보 관리

🧰 공통 모듈: DTO, 예외, 유틸 등 재사용 가능한 라이브러리 관리

🚀 Redis: 캐시 및 인증 토큰 저장소

🐘 PostgreSQL: 각 서비스별 DB 분리 운영

🐳 Docker & Docker Compose: 컨테이너 기반 개발/운영 환경 구성

📊 Prometheus + Grafana + Loki: 모니터링 및 로그 수집

 

저희의 프로젝트를 이렇게 다양한 서비스와 기능들로 나눌 수 있었습니다.

 

통신 방식

서비스 간 통신은 기본적으로 FeignClient를 활용한 RESTful HTTP 기반으로 구성했습니다. 간단한 설정만으로 내부 서비스 호출이 가능하고 공통 인터페이스를 통해 유지보수도 쉬워졌습니다.

📌 예시 코드 - FeignClient 정의

@FeignClient(name = "member-service", url = "http://member-service:8080")
public interface MemberClient {
    @GetMapping("/api/members/{id}")
    MemberResponse getMemberById(@PathVariable("id") Long id);
}

 

구현 과정

서비스 분리

각 도메인의 책임을 명확히 하여 서비스로 분리했습니다. 예를 들어 회원 인증은 Auth 서비스가 사용자 정보 관리는 Member 서비스가 담당합니다.

공통 모듈 분리

모든 서비스에 공통적으로 필요한 기능들 (예외 처리, 응답 포맷, 공통 DTO 등)은 common-module로 분리하여 관리했습니다. 이를 통해 중복 구현을 줄이고 일관된 코딩 스타일을 유지할 수 있었습니다.

📌 예시 코드 - 공통 응답 포맷

public class CommonResponse<T> {
    private int status;
    private String message;
    private T data;

    public static <T> CommonResponse<T> success(T data) {
        return new CommonResponse<>(200, "Success", data);
    }
}

인증/인가

JWT 기반 인증을 적용해 Access Token, Refresh Token을 관리했습니다. 인증 서비스에서만 토큰 발급 및 검증을 처리하고 다른 서비스들은 토큰 검증만 수행하도록 구조를 분리했습니다.

📌 예시 코드 - JWT 토큰 검증

public boolean validateToken(String token) {
    try {
        Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
        return true;
    } catch (JwtException | IllegalArgumentException e) {
        return false;
    }
}

모니터링 및 로깅

  • Loki + Promtail: 서비스 로그 수집
  • Prometheus: 메트릭 수집
  • Grafana: 실시간 시각화 대시보드

 

이러한 시스템 덕분에 장애 발생 시 빠른 로그 추적과 원인 분석이 가능해졌습니다.

환경 구성 및 배포 전략

`.env.dev`, `.env.prod` 파일로 환경변수를 구분하고 `docker-compose.dev.yml` / `docker-compose.prod.yml` 파일로 환경별 실행 구성을 따로 관리했습니다.

📌 예시 코드 - Docker Compose 설정 일부

services:
  auth-service:
    build: ./auth-service
    container_name: auth-service
    env_file:
      - .env.dev
    ports:
      - "8081:8080"
    networks:
      - app-network

 

Eureka 없이 어떻게 구성했는가

서비스 디스커버리를 위해 많은 프로젝트에서 Eureka를 사용하지만 저희 프로젝트는 Spring Cloud Gateway 단독으로도 충분히 가능하다고 판단했습니다.

선택 이유

  • 운영 단순성 (Eureka 서버 자체의 유지보수 부담 제거)
  • 직접 라우팅 경로를 구성할 수 있는 유연성
  • 학습 곡선이 낮아 빠른 적용 가능

 

동적 라우팅 구성

`application.yml` 파일에서 각 서비스에 대한 라우팅 경로를 명시하고 이를 배포 자동화 시 스크립트에서 동적으로 교체하도록 구성했습니다.

📌 예시 코드 - Gateway 라우팅 설정

spring:
  cloud:
    gateway:
      routes:
        - id: auth-service
          uri: http://auth-service:8080
          predicates:
            - Path=/api/auth/**
        - id: member-service
          uri: http://member-service:8080
          predicates:
            - Path=/api/members/**

 

CI/CD 및 배포 전략

Jenkins 기반 CI/CD 파이프라인을 통해 MSA 전체 서비스를 자동으로 빌드하고 배포하도록 구성했습니다. 핵심 포인트는 다음과 같습니다:

 

📦 서비스 단위 Docker 이미지 빌드

🔄 Blue/Green 배포 전략 적용으로 무중단 배포

🛡️ 배포 후 readiness 체크 및 Nginx 프록시 전환

⏪ 문제 발생 시 `rollback.sh`를 통한 빠른 롤백 가능

 

📌 예시 코드 - readiness 체크 스크립트

#!/bin/bash
SERVICE_URL=http://localhost:8081/actuator/health
until curl -s $SERVICE_URL | grep UP; do
  echo "Waiting for service..."
  sleep 3
done

이로써 운영 중에도 사용자에게 영향 없이 배포와 전환이 가능해졌습니다.

 

도입 이후 변화와 얻은 인사이트

도입 후 다음과 같은 변화를 직접 체감할 수 있었습니다:

 

🔧 서비스 단위 배포가 가능해 개발/운영 속도 향상

🧩 장애 격리 덕분에 시스템 전체의 안정성 강화

👥 팀원 간 업무 분담과 협업이 명확해짐

📉 유지보수 시간과 비용 감소

 

물론 MSA가 만능은 아닙니다. 서비스 간 호출 비용 증가, 공통 로직 중복 문제, 배포 복잡성 등 새로운 고민도 생겼습니다. 하지만 이러한 문제들은 점진적으로 개선해 나갈 수 있을 것 같습니다.

 

회고 및 다음 단계

이번 프로젝트를 통해 MSA의 효과를 체감할 수 있었지만 다음 프로젝트에서는 더 나은 구조를 실현하고자 몇 가지 개선점을 적용해볼 계획입니다.

먼저 서비스 간 통신은 현재 REST 기반이지만 향후에는 gRPC를 도입해 성능을 개선하고 X-RayJaeger 같은 분산 트레이싱 도구로 병목 구간을 가시화할 예정입니다. 운영 측면에서는 Istio 서비스 메쉬Helm 기반 Kubernetes 구성을 통해 확장성과 효율성을 높이는 방향을 검토 중입니다.

또한 Kafka 기반 이벤트 메시징을 활용해 서비스 간 결합도를 낮추고 AlertManager를 통해 알림 체계를 강화해 장애 대응 속도도 함께 높일 계획입니다. 궁극적으로는 모든 서비스를 Kubernetes 환경에서 안정적으로 운영하는 것이 다음 목표입니다.

 

 


 

MSA는 단순히 기술 스택을 바꾸는 것이 아니라 아키텍처와 운영, 팀워크 전반에 변화를 요구하는 종합적인 접근입니다. 처음에는 도입 자체가 어렵고 낯설지만 그만큼 얻을 수 있는 장점도 분명합니다.

이번 경험을 통해 더 유연하고 안정적인 시스템을 만들 수 있었고 빠르게 성장하는 서비스에도 잘 대응할 수 있는 기반을 갖추게 된 것 같습니다!

 

 

👉 MSA를 도입할지 고민 중이라면 작은 서비스 분리부터 시작해보세요.

실제로 겪어보는 것이 가장 큰 배움입니다!

 

 

 


⭐ SSAFY의 다양한 소식을 확인해보세요!


@hellossafycial

 

삼성 청년 SW 아카데미

삼성 청년 SW 아카데미| 소프트웨어 교육, 취업 지원, 코딩 교육

www.ssafy.com