MSA와 Spring Cloud MSA

junto·2024년 9월 7일
0

spring

목록 보기
30/30
post-thumbnail

MSA(MicroService Architecture)

1. 기존 Monolithic Archiecture 한계

  • 모놀리식 구조란 소프트웨어를 하나의 시스템으로 구축하여 개발하는 방식을 말한다. 하나의 시스템에 모든 설정과 비즈니스 로직이 포함된다.
  • 간단한 프로젝트에는 적용하기 좋으나, 프로젝트가 점점 커지고 요구사항이 많아질수록 아래 문제가 생긴다.
    • 작은 수정사항에도 전체 빌드 및 배포가 이뤄져야 해서 시간이 오래 걸림
    • 시간이 지날수록 방대한 양의 코드가 몰려 있어 유지보수 어려움
    • 일부 오류가 전체에 영향을 미치는 점 (단일 오류 포인트)
    • 특정 서비스(ex: 주문)만 스케일 아웃하고 싶지만 세세히 설정하기 어려움

2. MSA 등장 및 특징

  • 이러한 단점으로 인해 MSA(MicroService Architecture) 구조가 등장하게 되었다. MSA는 소프트웨어 시스템을 여러 작은 독립적인 서비스로 분할하여 개발하고 배포하는 방식이다.

  • 가장 큰 Monolithic Architecture와 가장 큰 차이점은 아래와 같은 특징을 가진다는 것이다.

1) 작고 한 가지 일에 주력

  • 각 서비스는 가능한 한 한 가지 일에 주력해야 기능 추가, 변경에 대해 유연하게 대처할 수 있으며, 독립적인 배포 이점을 살릴 수 있다.
  • 서비스를 작게 만들수록 서비스 간 상호 의존성이 낮아지기 때문에 유연하고 확장성 있는 구조를 가지게 된다.
  • 다른 서비스에 장애가 발생하더라도 각 서비스는 독립적이기 때문에 전체 시스템에 장애가 생기지 않는다.

2) 자율성

  • 서비스끼리 상호 의존성을 줄이기 위해 네트워크 통신으로 API를 호출하게 된다. 다른 서비스 변경 없이 특정 서비스만 변경하고 배포할 수 있다면, 각 서비스에게 적절한 역할과 책임을 부여한 것이다.
  • 또한 각 서비스마다 부여된 책임을 다하기 위해 적절한 기술 스택을 사용할 수 있다는 장정을 가진다. 성능이 중요한 곳에선 NoSQL을 사용할 수 있고, 데이터 무결성 제약이 중요하다면 관계형 DB를 사용할 수도 있다. 언어 또한 자유롭게 선택할 수 있다.

정리하자면 MSA는 Monolithic Architecture에 비해 배포 용이성, 확장성, 유연성, 고가용성(SPOF 줄임), 기술 선택 자율성 등을 꼽을 수 있다. 하지만 MSA 구조를 항상 적용해야 하는 건 아니다. 설계의 복잡성뿐만 아니라 분산 트랜잭션, 분산 캐시 일치, 서비스 간 통신 비용 등 여러 극복해야 할 과제가 많다.

  • https://www.youtube.com/watch?v=CM47-1UpgOc
    • Vingle의 예시처럼 모놀리식 구조에서는 프론트 페이지 요청 증가, API 요청 증가 등 이를 나누어서 Scale Up 하기 어려웠고, 프론트앤드 코드만 수정해도 백엔드 코드까지 배포해야 하며 단위 테스트를 실행하는데 최소 30분 이상 걸렸던 문제를 MSA 아키텍처로 해결한 사례이다.

Spring Cloud MSA 시작하기

MSA 환경 구성 요소

1. Spring Cloud Gateway

  • 요청을 URL 경로에 따라 서비스에 분배하는 역할을 수행한다.

2. Spring Cloud Eureka Server

  • 모니터링 서버로 Eureka Client 설정을 한 클라이언트들을 한눈에 볼 수 있다.
  • Spring Cloud Gateway는 오토스케일링된 인스턴스의 존재를 모르지만, Eureka Server가 Gateway에 목록을 전달하여 로드밸런싱 기능을 수행할 수 있다.

3. Spring Cloud EureKa Client

  • 독립된 서비스를 제공하는 스프링 부트 애플리케이션은 Eureka Server에 등록되어 모니터링 대상이 된다.
  • 아래서 설명하겠지만 Eureka Client이면서 동시에 Config Server에서 설정 파일을 읽으므로 Config Client이기도 하다.

4. Spring Config Server

  • 서비스마다 스프링 설정을 다르게 적용하기 위해 Config Server를 사용한다. 서버에 설정을 주입할 때 Valut, Private Git Repository, RDB, Redis, File 등 다양한 방법을 사용할 수 있다.

  • 여기선 Git Repository를 private로 만들어 {애플리케이션이름-프로필.yml} 파일을 읽어 들인다. 저장소에 SSH 공개 키를 등록하고 Çonfig Server에서는 Private Key를 이용해 설정 파일에 접근할 수 있다.

5. Spring Config Client

  • Config Server로 부터 설정 변수를 주입받는다. Config Client이면서 동시에 Spring Cloud Eureka client이기도 하다.

Spring Cloud MSA 환경 간단하게 구성하기

1. Spring Config Server

  • 내부망을 사용한다면 Security 설정을 하지 않아도 되겠지만, 내부망을 사용하지 않는다면 인증된 사용자만 접근할 수 있도록 Spring Security를 사용하는 것이 권장된다.
  • Config Server로 동작할 수 있도록 build.gradle에 Spring Config Server 종속성을 추가한다.
    implementation 'org.springframework.cloud:spring-cloud-config-server'
  • Main 클래스에 @EnableConfigServer 어노테이션을 통해 Config Server로 등록한다.
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {

  public static void main(String[] args) {
    SpringApplication.run(ConfigServerApplication.class, args);
  }
}
  • Config Server는 private Git Repository에 있는 설정 파일을 읽어 각 독립된 서비스를 만들 때 설정 파일을 읽어올 수 있도록 해야 한다. 따라서 private Git Repository url과 비밀 키를 application.properties에 등록해야 한다.
spring.application.name=ConfigServer

server.port=9000

spring.cloud.config.server.git.uri=git@github.com:ji-jjang/msa-config.git
spring.cloud.config.server.git.ignoreLocalSshSettings=true
spring.cloud.config.server.git.private-key=\
  -----BEGIN RSA PRIVATE KEY-----\n\
MIIJKQIBAAKCAgEAxERtVxWXnQ0m3inzKOWpVf+ulVQpjixCubVn+MLpIQZUYoeP\n\
...생략
0OxtdUwADW7AesDVfxyszMQMGdp2tA8A8ssexRdX8NEXV7eYC0wcCiEopi65\n\
-----END RSA PRIVATE KEY-----
  • Config Client는 http://ip:port/저장소이름/저장소환경을 통해 Config Server에 접근하며, 여기서 ip와 port는 Config Server의 값을 입력하고, 저장소 이름과 저장소 환경은 Config Repository에 해당하는 내부 파일이름을 넣어야 한다.
이름-환경.yml
이름-환경.properties
  • localhost:9000/애플리케이션이름/환경로 접근하면 설정 파일의 key, value 값이 JSON 형태로 표시되는 것을 확인할 수 있다.
  • 독립된 서비스는 각각 Config Server를 통해 private git repository에 있는 설정 파일을 읽어 들인다고 생각하면 된다.

2. Spring Config Client

  • Config Client는 Config Server에 repository에 있는 설정 파일을 읽어들이기 위해 config client 종속성을 추가해야 한다.
ext {
    set('springCloudVersion', "2023.0.3")
}

dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-config'
}
dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}
  • Config Server와 연결하기 위해 설정 파일을 다음과 같이 추가한다. 내부망이 아니기에 인증된 사용자 아이디와 비밀번호를 사용해 연결하는 방법을 사용했다.
server.port=8081
spring.application.name=service1
spring.profiles.active=dev
spring.config.import=optional:configserver:http://admin:1234@localhost:9000

3. Spring Eureka Server

  • Eureka Server는 MSA를 구성하는 각각의 서비스들을 모니터링 할 뿐만 아니라 오토 스케일링되는 여러 서비스들을 라우팅 대상이 될 수 있도록 Gateway에 알려주는 역할도 한다.
  • Eureka Server로 등록하기 위해 아래 의존성을 추가한다. 모니터링 서버이므로 관리자만 접근해야 하며 내부망을 사용하지 않는다면 Security 설정을 추가로 해야 한다.
ext {
    set('springCloudVersion', "2023.0.3")
}

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

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}
  • Main Class에 @EnableEurekaServer 어노테이션을 통해 Eureka Server로 등록한다.
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}
  • server port를 지정해 주고, 유레카 서버에 만들 때 자신의 상태도 모니터링 대상이 되므로 (즉, 서버면서 클라이언트이다) 연결 설정을 해주어야 오류가 발생하지 않는다.
server.port=8761
eureka.client.register-with-eureka=false # Eureka 서버가 클라이언트로서 자기 자신을 다른 Eureka 서버에 등록할지 여부
eureka.client.fetch-registry=false # Eureka 서버로부터 레지스트리 정보를 가져올지 여부
eureka.client.service-url.defaultZone=http://admin:1234@localhost:8761/eureka
  • localhost:8761에 접속하면 유레카 서버 대시보드가 나타난다. 여기에 등록된 클라이언트 정보들을 볼 수 있다.

4. Spring Eureka Client

  • 기존 Config Client로 설정을 읽어 들인 서비스를 Eureka Client에 등록해야 모니터링 대상이 된다.
  • 필요한 Eureka Client 종속성을 추가하고, @EnableDiscoveryClient 어노테이션을 통해 Eureka Client로 등록한다.
ext {
    set('springCloudVersion', "2023.0.3")
}

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

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigClientApplication {

  public static void main(String[] args) {
    SpringApplication.run(ConfigClientApplication.class, args);
  }
}
  • 설정 파일을 통해 Eureka Client를 Eureka Server에 등록해야 한다.
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://admin:1234@localhost:8761/eureka

5. Gateway

  • 여러 MicroService 앞에서 클라이언트의 모든 요청을 받은 후 url에 맞는 경로로 특정 마이크로서비스에 전달해 주어야 한다.
  • Gateway의 경우 단순히 요청을 전달하는 I/O 처리만 하게 되므로 논블록킹 방식으로 동작하는 WebFlux와 Netty 엔진을 사용하는 것이 더 효율적이다.
  • 필요한 종속성 추가 및 eureka server와 연결을 등록해 주어야 한다.
ext {
    set('springCloudVersion', "2023.0.3")
}

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

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}
  • 해당 설정을 추가하기만 하면, Netty Server를 실행하고 NonBlock 방식으로 요청을 처리한다. 이 경우 기존 RDB 설정으로 하면 제대로 동작하지 않으니 관련된 추가 기술 학습이 필요해진다.
spring.application.name=SpringGateWay
server.port=8080

eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://admin:1234@localhost:8761/eureka
  • 경로 설정은 application.properties, yml, java code 모두 가능하지만 여기선 간단히 properties 파일로 알아본다.
server.port=8080

spring.cloud.gateway.routes[0].id=orderService
spring.cloud.gateway.routes[0].predicates[0].name=Path
spring.cloud.gateway.routes[0].predicates[0].args.pattern=/orders/**
spring.cloud.gateway.routes[0].uri=http://localhost:8081

spring.cloud.gateway.routes[1].id=userService
spring.cloud.gateway.routes[1].predicates[0].name=Path
spring.cloud.gateway.routes[1].predicates[0].args.pattern=/users/**
spring.cloud.gateway.routes[1].uri=http://localhost:8082
  • 게이트 웨이 라우트를 0번부터 시작해서 하나씩 늘려가며 등록하면 된다.

  • 또한, 로드밸런싱 라우팅도 진행할 수 있는데 Gateway는 오토스케일링된 인스턴스를 알 수 없으므로 먼저 Eureka Server와 연결한 뒤 Eureka Server로부터 인스턴스 정보들을 받아와야 한다.

ext {
    set('springCloudVersion', "2023.0.3")
}

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

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://admin:1234@localhost:8761/eureka
  • 같은 인스턴스를 만들 때는 같은 application 이름이지만 다른 port를 사용하게끔 구성한다. orderService를 여러 Scale Out하여 등록했다면, 이제는 gateway route uri가 특정 ip, 포트에 매칭되는 게 아닌 어플리케이션 서버 이름으로 등록하며 Eureka Server에 등록된 여러 orderService 인스턴스에 요청이 각각 분배되는 방식으로 동작한다.
spring.cloud.gateway.routes[0].id=orderService
spring.cloud.gateway.routes[0].predicates[0].name=Path
spring.cloud.gateway.routes[0].predicates[0].args.pattern=/orders/**
spring.cloud.gateway.routes[0].uri=lb://ORDERSERVICE

참고자료

profile
꾸준하게

0개의 댓글