4 분 소요

지금까지 user-service, order-service, catalog-service를 개발했다.

지금까지는 구성정보 파일을 application.yml 파일을 사용했었는데, 문제는 구성 정보가 변경되면 서비스 자체를 다시 빌드한 뒤에 배포해야 한다는 것이다.
이러한 점을 개선하기 위해 어플리케이션 내부에 구성파일을 가지고 있는 것이 아니라 외부에 있는 시스템을 통해 구성파일 정보를 관리할 수 있는 기능에 대해 살펴보자.

1. Spring Cloud Config

  • 분산 시스템에서 서버, 클라이언트 구성에 필요한 설정 정보(application.yml)를 “외부” 시스템에서 관리
  • 하나의 중앙화 된 저장소에서 구성요소 관리 가능
  • 구성 정보가 변경되었을 때, 각 서비스를 다시 빌드하지 않고 바로 적용 가능
  • 애플리케이션 배포 파이프라인을 통해 DEV(개발) - UAT(테스트) - PROD(배포) 환경에 맞는 구성 정보 사용

스크린샷 2022-10-04 오후 1 43 27

1) Local Git Repository

ecommerce.yml 파일 생성

로컬에 git-local-repo 라는 디렉토리를 생성한 후, 그 안에 ecommerce.yml 파일을 생성한다.

token:
  expiration_time: 86400000
  secret: user_token

gateway:
  ip: 192.168.2.1 # 본인 IP

🚨 주의
아래에서 local git repository에 등록할건데,
그 이후에 ecommerce.yml 파일의 내용이 변경되었다면, add - commit 을 해주어야 변경사항이 적용된다!
(push는 아직 진행하지 않는다.)

Local Git Repository에 등록

터미널을 켜서 이전에 생성한 git-local-repo 디렉토리로 이동한다.

$ git init
$ git add ecommerce.yml
$ git commit -m "update an application yaml file"

이렇게 하면, 아직 remote repository에는 올라가지 않고, local repository에만 올린 것이다.
아직까지는 local에서만 관리한다. (push 이후에 remote repository에 올라간다.)

🌟 참고) 우선순위
지금까지 만든 네 개의 서비스(user-service, order-service, catalog-service, gateway-service) 모두 application.yml 파일을 가지고 있다.
우선순위는 아래와 같다.

  1. application.yml
  2. application-name.yml
    - 예) user-service.yml, order-service.yml
  3. application-name-<profile>.yml
    - 예) user-service-dev.yml, order-service-prod.yml

2) config-service 프로젝트 생성

  • Spring Boot: 2.7.4
  • dependencies
    - Config Server

application.yml

server:
  port: 8888

spring:
  application:
    name: config-service
  cloud:
    config:
      server:
        git:
          uri: file:///Users/minju/study/msa/git-local-repo

spring.cloud.config.server.git.uri를 보면,
파일 프로토콜(file://) 을 이용해 불러오고자하는 파일의 경로를 지정할 수 있다.

파일의 경로는, 터미널에서 git-local-repo 디렉토리로 이동 한 뒤, pwd 커맨드로 알아낼 수 있다.
스크린샷 2022-10-04 오후 2 48 50

서버 기동

서버를 기동한 뒤, http://127.0.0.1:8888/ecommerce/default로 접속하면, 아래와 같이 ecommerce.yml 파일에 작성한 설정정보를 확인할 수 있다.
스크린샷 2022-10-04 오후 2 51 27

이때 엔드포인트를 살펴보면, /ecommerce/default인데,
/ecommercegit-local-repo 안에 있는 설정파일(ecommerce.yml)의 이름이고,
/default는 설정파일의 profiles로, 아무것도 설정하지 않았으니 /default 이고, 만약 ecommerce-dev.yml로 설정했다면 /dev가 되는 것이다.

3) user-service

pom.xml

pom.xml에 아래 dependency를 추가한다.

<!-- spring-cloud-config 설정 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

bootstrap.yml 추가

bootstrap.yml은 외부에 있는 Spring Cloud Config에 대한 정보를 등록해주는 역할로,
application.yml 보다 우선순위가 높다.

spring:
  cloud:
    config:
      uri: http://127.0.0.1:8888
      name: ecommerce

이 때, spring.cloud.config.uri에 있는 :8888config-service의 port 번호이다.
spring.cloud.config.name에는 읽어오고자하는 yaml 파일의 이름을 적는다.
(나는 git-local-repo 디렉토리 안에 ecommerce.yml 정보를 가져올 것이기 때문에 ecommerce를 입력했다.)

사실 bootstrap.yml에 적은 내용을 application.yml에 적어도 상관 없다.
하지만 지금 하고자 하는 작업의 핵심은,
원래 가지고 있던 application.yml의 특정 부분을 떼어서, 별도의 공용서버 역할을 하는 Spring Cloud Config(=config-service)를 이용해보는 것이다.

기존 application.yml 변경

ecommerce.yml에 토큰 정보를 넣어놨으니 config-service로 부터 해당 정보를 가져올 것이기 때문에, application.yml에서는 제거하자.

# token:
#  expiration_time: 86400000 # 60 * 60 * 24 * 1000 # 하루짜리 토큰
#  secret: user_token

UserController.java

config-service로부터 구성 정보를 잘 가져왔는지 테스트하기 위해 health-check 코드를 변경해보자.

@RestController
@RequestMapping("/") 
public class UserController {
    ...
    @GetMapping("/health-check")
    public String status() {
        return String.format("It's Working in User Service"
            + ", port(local.server.port) = " + env.getProperty("local.server.port")
            + ", port(server.port) = " + env.getProperty("server.port")
            + ", token secret = " + env.getProperty("token.secret")
            + ", token expiration time = " + env.getProperty("token.expiration_time")
        );
    }
}

서버 기동

service-discovery(=유레카 서버), gateway-service, config-service를 먼저 기동한 뒤,
user-service를 기동한다.

스크린샷 2022-10-04 오후 3 40 39
서버를 기동하게 되면 위 사진처럼 나오는데, 여기서 세 가지를 확인할 수 있다.

  • Configuration Server(=config-service)의 위치정보가 잘 표시되어있다.
  • 읽어온 구성정보 파일의 이름이 ecommerce이고 profiles는 default이다.
  • 읽어오고자하는 bootstrap의 위치 정보는 git-local-repo에 있는 ecommerce.yml로부터 가져온 것이다.


이제 health-check에 접속해보자.
스크린샷 2022-10-04 오후 3 50 43

🚨 주의
만약, ecommerce.yml의 내용이 변경되었다면, user-service도 재기동 해주어야한다.
하지만 Configuration Server의 구성 정보를 변경할 때마다 마이크로 서비스를 재기동 해야하는 것은 번거로운 일이다..🤔

2. Changed configuration values

변경된 configuration 정보를 가져올 수 있는 방법에는 어떤 것들이 있을까?

  • (마이크로 서비스) 서버 재기동
  • Actuator refresh
  • Spring cloud bus 사용 🌟

서버 재기동은 이전에 말했듯이 번거로운 방법이다.
이번 글에서는 Actuator에 대해 먼저 알아보고, 다음 글에서 Spring cloud bus에 대해 알아보자.

Spring Boot Actuator

  • Application 상태, 모니터링
  • Metric 수집을 위한 Http End point 제공

아래 사이트에서 다양한 End point를 확인할 수 있다.
https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html

user-service

Actuator 기능을 사용하기 위해서는 각각의 마이크로 서비스에 Actuator dependecy를 추가해주어야 한다.

pom.xml

<!-- Spring Boot Actuator -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

application.yml

management:
  endpoints:
    web:
      exposure:
        include: refresh, health, beans

refresh, health, beans 세 가지 actuator를 등록한 것이다.
이 때 refresh가 가장 중요한 설정인데, Configuration Server에 있는 값을 현재 마이크로 서비스에 refresh 하겠다는 의미이다.

WebSecurity.java

@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
    /**
     * 권한 부여
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 🌟 Actuator 에 대해서는 인증을 거치지 않고 바로 사용
        http.authorizeRequests()
            .antMatchers("/actuator/**")
            .permitAll();
        ...
    }
    ...
}

gateway-service

application.yml

Actuator 대해서는 인증 필터를 거지치 않기로 했으니, gateway-serviceapplication.yml에 아래 코드를 추가한다.

- id: user-service
  uri: lb://USER-SERVICE
  predicates:
    - Path=/user-service/actuator/**
    - Method=GET,POST
  filters:
    - RemoveRequestHeader=Cookie
    - RewritePath=/user-service/(?<segment>.*), /$\{segment}

테스트

gateway-serviceuser-service를 재기동 한 뒤, user-serviceapplication.yml에서 등록한 refresh, health, beans에 요청해보자.
health와 beans는 GET 요청이고, refresh는 POST 요청이다.

health

스크린샷 2022-10-04 오후 4 45 57

beans

스크린샷 2022-10-04 오후 4 46 47

refresh

refresh를 테스트 하기 위해서, 먼저 ecommerce.yml의 값을 바꿔보자.

기존

스크린샷 2022-10-04 오후 4 59 16
스크린샷 2022-10-04 오후 5 05 39
기존에는 token.secretuser_token 이었다.

변경

스크린샷 2022-10-04 오후 4 59 38
token.secretuser_token_changed로 변경한다.
(ecommerce.yml 코드 변경 후에 add -> commit 까지 진행해야 한다!)
스크린샷 2022-10-04 오후 5 00 34

refresh 요청

스크린샷 2022-10-04 오후 5 06 28

서버를 재기동하지 않고 /actuator/refresh 요청만으로 아래 사진처럼 변경사항이 적용된 것을 확인할 수 있다.
스크린샷 2022-10-04 오후 5 06 54



💛 개인 공부 기록용 블로그입니다. 👻

맨 위로 이동하기