카샤의 만개시기

마이크로서비스 시작하기 (3편) - Spring Cloud Config로 설정 주입 본문

Java/MSA

마이크로서비스 시작하기 (3편) - Spring Cloud Config로 설정 주입

SKaSha 2019. 6. 15. 00:59

우리는 '12요소 방법론'을 배우면서 환경에 따라 DB나 스토리지 등 설정 정보들이 달라질수 있기 때문에 어플리케이션 구동 시에 설정 정보들이 주입될 수 있도록 해야한다고 배웠습니다.
이를 위해서 Spring Cloud Config를 이용하여 프로젝트를 구현해보도록 하겠습니다.

Spring cloud config는 분산 시스템에서 설정파일을 외부로 분리하는 것을 지원하기 때문에 외부 속성을 중앙에서 관리할 수 있으며 어플리케이션의 재배포 없이 적용이 가능합니다.

준비

Spring cloud config를 테스트해보기 위해서는 3개의 Git Repository가 필요합니다.

  1. Config Properties Files
  2. Spring Cloud Config Server
  3. Spring Cloud Config Client

1. Config Properties Files

해당 깃허브 저장소에서는 어플리케이션 구동 시 주입 될 설정 정보들을 선언합니다.
속성(properties, yml) 파일은 ${App Name}-${Profile}.${ext} 형식으로 생성해야 합니다.

  1. kasha-dev.yml
kasha:
  hello: dev world!
  1. kasha-operation.yml
kasha:
  hello: operation world!

2. Spring Cloud Config Server

Config Server 앱은 클라이언트 앱들이 구동 될 때 위에서 yml로 선언한 설정 정보들을 주입 해주는 역활을 한다.

2-1. 아래의 spring-cloud-config-server 의존성 파일을 넣어주자.

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

2-2. src/main/resources/application.yml 파일에 아래와 같이 선언해주자.

uri 경로는 설정 정보들이 저장되어 있는 첫번째 깃허브 저장소의 uri이다.

server:
  port: 4670
spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/SeoJinHyuk14/Msa-config

2-3. Main이 선언되어 있는 클래스에 @EnableConfigServer 어노테이션을 추가해준다.

@SpringBootApplication
@EnableConfigServer
public class MsaApplication {

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

}

2-4. 실행

서버를 실행한 후 http://localhost:4670/kasha/${profile}에 요청을 날려보자.
propertySources - source - kasha.hello의 값이 profile에 따라 다른 것을 알수 있다.

curl http://localhost:4670/kasha/dev
{
  "name": "kasha",
  "profiles": [
    "dev"
  ],
  "label": null,
  "version": "3e6827fd3dfc0e79e687094a7d67de787eeda25d",
  "state": null,
  "propertySources": [
    {
      "name": "https://github.com/SeoJinHyuk14/Msa-config/kasha-dev.yml",
      "source": {
        "kasha.hello": "dev world!"
      }
    }
  ]
}
curl http://localhost:4670/kasha/operation
{
  "name": "kasha",
  "profiles": [
    "operation"
  ],
  "label": null,
  "version": "3e6827fd3dfc0e79e687094a7d67de787eeda25d",
  "state": null,
  "propertySources": [
    {
      "name": "https://github.com/SeoJinHyuk14/Msa-config/kasha-operation.yml",
      "source": {
        "kasha.hello": "operation world!"
      }
    }
  ]
}

클라이언트 앱을 구현하여 실행하게 되면 클라이언트 앱은 서버 앱으로부터 config정보를 받아갈 것이고 그 과정에서 서버 앱의 콘솔에 아래와 같은 로그가 찍힐 것입니다.
아래 로그를 통해 서버 앱이 깃허브 저장소에서 kasha-dev.yml을 받아 클라이언트 앱에게 전달해준다는 것을 알수 있습니다.

Adding property source: file:/var/folders/bc/36p4bfkn7zq744ckr1k7jp840000gn/T/config-repo-12187904048800898306/kasha-dev.yml

3. Spring Cloud Config Client

이제 서비스를 제공해 줄 클라이언트 어플리케이션을 만들 차례입니다.

3-1. 아래와 같이 의존성 파일을 추가해주세요.

actuator를 추가하는 이유는 아래에서 언급하겠습니다.

dependencies {
    implementation('org.springframework.boot:spring-boot-starter-web')
    implementation('org.springframework.cloud:spring-cloud-starter-config')
    implementation('org.springframework.boot:spring-boot-starter-actuator')
}

3.2 bootstrap.yml 설정파일.

기존의 우리는 설정 파일을 application.yml로 이용하였지만 Client 앱에서는 bootstrap.yml을 이용할 것입니다.
스프링 클라우드는 bootstrap.yml을 파일에서 spring.application.name으로 지정 된 앱 이름을 찾고 설정 정보를 읽어올 수 있는 Config Server의 위치를 파악합니다. 그렇기 때문에 설정 서버의 위치를 파악한 뒤에 설정 정보를 읽어오기 위해 bootsrap.yml 파일이 application.yml 파일보다 먼저 로딩됩니다.

Cloud Foundary는 어플리케이션이 구동 될때 Config를 주입하는데 Environment와 Cli 2가지 표준 방법을 제공합니다. 하지만 Spring Boot 앱은 VCAP_SERVICES라는 또다른 환경 변수를 통해 Config를 주입할 수 있습니다.
yml파일에서 cloud config uri는 VCAP_SERVICES를 통하여 uri를 가져오지만 선언되어 있지 않을 경우 입력 된 상수(http://localhost:4670)로%EB%A1%9C) 입력됩니다.

server:
  port: 8080

spring:
  application:
    name: kasha
  cloud:
    config:
      uri: ${vcap.services.configuration-service.credentials.uri:http://localhost:4670}

3.3 설정 파일의 내용을 동적으로 출력할 컨트롤러

깃허브 저장소에서 선언한 속성 파일의 데이터를 잘 가지고 오는지 확인하기 위하여 간단한 RestController를 만들어 확인해보겠습니다.
그런데 컨트롤러에 @RefreshScope라는 애노테이션이 붙어 있습니다. 이는 actuator의 refresh 이벤트가 발생했을때 해당 클래스에서 주입되는 속성 Value들을 어플리케이션의 재시작이 없이도 실시간으로 반영되도록 만들어줍니다.

@RestController
@RefreshScope
public class ConfigTestController {

    @Value("${kasha.hello}")
    private String str;

    @GetMapping("/test")
    public String test() {
        return str;
    }
}

3.4 어플리케이션 재시작없이 원격 설정 변경을 위한 작업

아래와 같이 application.yml 파일에 속성을 추가하게 되면 http://localhost:8080/actuator/refresh 경로가 열리게 되고 이 경로에 POST로 요청을 하게 되면 설정 파일을 새로 읽어들여서 어플리케이션을 재기동하게 됩니다.

management:
  endpoints:
    web:
      exposure:
        include: refresh

3.5 실행

클라이언트 앱을 실행시킬때에는 저장소에서 읽어올 설정파일의 profile을 지정해야합니다.
IntelliJ를 이용한다면 Edit Configuration에서 Active Profile 값에 profile을 지정해줍니다. CLI 에서 실행한다면, build.gradle 파일을 아래와 같이 추가설정을 해주고 (환경변수로 지정해도 된다)

bootRun {
    systemProperties = System.properties
}

프로젝트 디렉토리로 가서 아래와 같이 실행시켜도 됩니다.

gradle bootRun -Dspring.profiles.active=dev

클라이언트 앱이 실행되면 아래와 같은 로그를 볼수 있습니다.
이는 http://localhost:4670 Config Server를 통하여 kasha-dev.yml 파일을 가져왔음을 알수 있다.

Fetching config from server at : http://localhost:4670
Located environment: name=kasha, profiles=[dev], label=null, version=3e6827fd3dfc0e79e687094a7d67de787eeda25d, state=null
Located property source: CompositePropertySource {name='configService', propertySources=[MapPropertySource {name='configClient'}, MapPropertySource {name='https://github.com/SeoJinHyuk14/Msa-config/kasha-dev.yml'}]}

그러면 구현한 RestController를 통하여 속성 데이터를 잘 가져왔는지 확인해보자.

curl http://localhost:8080/test
dev world!

잘 가져오고 있으니 kasha-dev.yml 파일의 kasha.hello 값을 dev changed world!로 바꾸어 push 해보자.
그런 다음에 클라이언트 앱에 설정 파일을 다시 가져오라고 요청해보자

curl -X POST http://localhost:8080/actuator/refresh

그러면 클라이언트 앱은 속성 파일을 다시 가져와 서버가 재구동 되는것을 볼수 있고 RestController에 다시 요청해보면 응답이 바뀐것을 알수 있다.

curl http://localhost:8080/test
dev changed world!

4. 보안

우리는 설정 파일 저장소에 접근하기 위하여 Config Server의 application.yml을 통하여 spring.cloud.config.server.git.uri를 설정하였습니다.
하지만 이는 public 저장소를 이용하였기 때문에 접속이 가능하였던것이며 private 저장소일 경우 spring.cloud.config.server.git.usernamespring.cloud.config.server.git.password를 입력해야 저장소에 접근 할 수 있습니다. (운영 서버의 경우 SSH Public Key를 이용해 접속하여도 됩니다.)

또한 우리는 Config Server 자체에도 HTTP 기본 인증으로 보호할 수 있습니다.
이를 위해 spring-boot-starter-security를 추가하여 security.user.namesecurity.user.password를 설정하면 됩니다.
스프링 시큐리티의 UserDetailsService 구현을 통하여 인증 방식을 원하는 대로 처리할 수도 있습니다.

5. Spring Cloud Bus

Config Client 앱에서 원격으로 설정을 주입하는 과정을 위해 /actuator/refresh를 호출하는 일련의 과정을 보았습니다. 하지만 쿠버네티스 등을 이용하여 서비스를 제공하여 클라이언트 앱이 수십개라면 /actuator/refresh 종단점을 앱 갯수만큼 호출하여야 할까요?
이를 위하여 Spring Cloud Bus를 사용하여 설정 변경 내용을 여러 클라이언트 앱에 한번에 적용할 수 있습니다.
스프링 클라우드 버스는 모든 서비스를 스프링 클라우드 스트림이 장착된 버스를 통해 연결하며 바인딩 추상화를 통해 RabbitMQ, Kafka, Reactor Project 등 다양한 메시징 기술을 지원합니다.

Config Server 프로젝트에 RabbitMQ용 클라우드 버스를 추가해줍니다.

dependencies {
    implementation('org.springframework.cloud:spring-cloud-starter-bus-amqp')
}

application.yml에 연결 설정을 해줍니다.

spring:
  rabbitmq:
    host: kasha-mq
    port: 5877
    username: user
    password: secret

이 연결 정보는 스프링 부트 AMQP 자동 설정을 통해 ConnectionFactory 인스턴스에 전달됩니다. 버스 클라이언트는 메시지가 전송되기를 기다리다가 새로고침 메시지를 받으면 RefreshScopeRefreshedEvent를 발생시켜 설정을 갱신합니다.

스프링 클라우드 이벤트 버스를 통한 새로고침 경로는 다음과 같습니다.

curl -X POST http://localhost:8080/actuator/bus/refresh

NEXT

다음 장에서는 어플리케이션 서버 다중화 환경에서 어떻게 세션을 공유하는지에 대해서 알아보도록 하겠습니다.

마이크로서비스 시작하기 (4편) - 세션 클러스터링

Comments