2015. 11. 29.

Spring IO, Spring Boot, Spring Cloud 를 이용한 Configuration Server의 구축

서문

프로젝트를 진행하다보면, 배포 환경에 따른 설정 파일 관리에 여러가지로 어려움을 겪는 경우가 많다.
개발 환경설정파일에만 내용을 넣고 배포를 한다던가 배포시에 프로파일을 잘못 설정해서 배포가 된다던가 하는 실수들은 작지만 상당히 귀찮은 확인 작업을 동반하기도 한다.
그나마 자동화된 배포툴(Hudson 등등)을 사용하여 잘 짜여진 배포 프로세스를 만들어 놓은 훌륭한 회사에서는 그나마 이런 실수들을 줄일 수 있는 방법이 많지만, 아직도 쉘을 이용한 배포를 하는 우리 회사 같은 경우는 작업자의 실수를 가끔씩 겪을 수 밖에 없다. (이 경우 윗 사람으로 부터의 더 가혹한 소리들을 들어야만 한다. 부주의에 의한 실수이기에...)
특히, 실수가 일어난 경우 해당 내용을 확인하기 위해서는 배포되어있는 서버에 들어가 일일이 파일을 확인해야 하는데 이 작업 또한 만만찮은 귀찮은 작업 중 하나다.

모든 툴 들이 그렇듯 완벽하게 모두 해결해 주지는 않지만 Spring Cloud 의 Configuration 서버의 경우는 간단한 설정만으로도 꽤 괜찮은 솔루션을 제공해주고 있다.

이 글은 최대한 간단하게 만들어진 Configuration Server 에 대해서 정리하고자  작성된 글이다.
(거의 Spring Cloud 프로젝트 사이트의 Document 를 간단히 정리한 수준임을 밝혀둔다)

요구사항

- 버전 관리를 편리하게 하고 Gradle 을 간단히 유지하기 위해서 Spring IO 를 이용한다.
- Gradle 은 이전 포스트에서 작성했던 multi project 대응의 build.gradle 설정에서 시작한다.
  (이전글 보기)
- Configuration Server 는 Configuration Server의 local properties 를 이용한다. (github을 이용한 방법에 대한 설명은 인터넷에 굉장히 많고 spring cloud 프로젝트 사이트의 샘플도 이것으로 되어있으니 참고 바람)

Spring IO 설정

gradle 에서 사용할 다양한 버전 정보를 포함한 변수들을 저장하기 위한 gradle.properties 파일을 만들고 아래의 내용을 추가한다. 이 부분은 build.gradle 에 ext {} 를 이용해도 되나, 후에 이 블럭이 커져서 관리가 힘든 경우가 있어서 이번에는 gradle.properties 를 이용하는 것으로 했다.
dependencyManagementPluginVersion=0.5.4.RELEASE
springIoVersion=2.0.0.RELEASE


메인 프로젝트의 build.gradle의 buildscript 블럭에 아래의 내용을 추가한다.
buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath "io.spring.gradle:dependency-management-plugin:${dependencyManagementPluginVersion}"
    }
}

모든 프로젝트에 적용을 할 것이므로, allprojects 블럭을 아래와 같이 만든다.
allprojects {
    repositories {
        jcenter()
    }

    apply plugin: 'io.spring.dependency-management'

    dependencyManagement {
        imports {
            mavenBom "io.spring.platform:platform-bom:${springIoVersion}"
        }
    }
}

여기까지 진행하면 일단 Spring IO 를 쓸 수 있는 환경은 완료!!

Configuration Server 의 build.gradle 작업

자, 이제 Configuaration Server의 build.gradle 파일을 만들고 Spring Cloud 설정을 해보자.
이전 글에서 만들었던 config-server(server-config-server)  모듈을 이용하도록 하겠다.

우선 아래처럼 spring-cloud 에서 사용할 dependencyManagement 를 위해 mavenBom 추가를 위해 아래와 같이 메인 프로젝트의 build.gradle 과 gradle.properties를 수정해보자.
dependencyManagementPluginVersion=0.5.2.RELEASE
springIoVersion=2.0.0.RELEASE
# Spring Cloud Dependency Management BOM Version
cloudStarterBomVersion=Angel.SR4
allprojects {
    repositories {
        jcenter()
    }

    apply plugin: 'io.spring.dependency-management'

    dependencyManagement {
        imports {
            mavenBom "io.spring.platform:platform-bom:${springIoVersion}"

            // Spring Cloud 프로젝트를 위한 mavenBom 추가
            mavenBom "org.springframework.cloud:spring-cloud-starter-parent:$cloudStarterBomVersion"
        }
    }
}

자, 이제 config-server 모듈에 build.gradle 을 만들고 spring-cloud 의 dependency를 설정해보자.
dependencies {
    compile 'org.springframework.cloud:spring-cloud-starter-config'
    compile 'org.springframework.cloud:spring-cloud-config-server'
}

이제 gradle 설정을 다시 읽어보면 아래 프로젝트 구성과 config-server의 dependency 들은 아래와 같은 모습을 가지게 된다.




Configuration Server 의 프로그램 작업

config-server 모듈의 base package 에 spring boot 메인 클래스를 만들자.
편의상 ConfigServerApplication 이라는 클래스 파일을 생성하고 spring boot application 이므로 기본 annotation 과 메인 클래스를 작성하자.
(확인결과 default 패키지 즉 클래스패스에 package 없이 만들어진 경우 @SpringBootApplication 어노테이션은 사용할 수 없다.)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.context.annotation.Configuration;

/**
 * Created by road on 15. 11. 29.
 */
@Configuration
@EnableAutoConfiguration
@EnableConfigServer
public class ConfigServerApplication {

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

굉장히 간단하다. 자 이제 configuration server 의 설정을 위해서 bootstrap.yml 을 생성해보자.
spring:
  application:
    name: configServer
  profiles:
    active: native
    
server:
  port: 8888

여기서 중요한 부분은 spring.profiles.active 를 native로 주는 정도가 되겠다.
이제 기본적인 서버 작업은 마무리 되었다.

이제 Configuration Server 에 설정 정보를 올리고 Client가 사용하는 샘플을 만들어 보자.

Configuration Server에 Sample Client의 설정파일 만들기

테스트용으로 만들 프로그램은 sample 이라는 이름을 가진 프로그램으로 작업하려고 한다.
지원하는 profile 은 dev, prod 이며, 그 설정을 클라이언트의 spring active profile 과 연결하려고 한다.

우선 configuration server 에 설정을 보관할 디렉터리와 파일을 생성한다.
github 이 아닌 file base 로 동작하기 위해서 앞에서 active profile 을 native로 만들었었다.
이 경우 spring 이 파일을 찾는 경로는 spring.cloud.config.server.native.searchLocations의 설정을 따르게 되어있다.
Default 로는 [classpath:/, classpath:/config, file:./, file:./config]의 설정을 따르게 된다. 여기서는 classpath:/config 를 사용하게 하기 위해서 resources에 config 디렉터리를 생성하고 그 안에 sample.yml, sample-dev.yml, sample-prod.yml 세개의 파일을 생성하고 아래의 내용을 입력하여 테스트 하도록 하겠다.
이 경우 sample.yml 은 profile에 상관없이 default로 읽혀지는 설정들이다.
즉, http://localhost:8888/sample/default  하면 나오는 기본값이다.
나머지 두개는 active profile에 따라서 읽혀지는 설정들이다.
http://localhost:8888/sample/dev 에 접속해보면 알 수 있지만, profile에 관계없이 기본 default 설정은 모두 읽고 각 profile 에 맞는 설정을 읽는 방식으로 되어있다.
다시 말해 공통 설정은 해당 {application name}.yml 에 넣고 profile에 따라 달라지는 설정은 {application name}-{profile}.yml 에 파일에 넣으면 된다.

여기까지의 프로젝트 구조와 설정파일 내용은 아래와 같다.

server:
  port: 8088

sample:
  foo: 'this is dev foo'

sample:
  foo: 'this is prod foo'
  bar: 'this is prod bar'

이제 이것을 사용할 client 프로그램을 만들어 보자.
이 부분은 별다른 설명 없이 진행하겠다. service 그룹에 sample 모듈을 만들고 시작하겠다.
이전 포스트에서 설명했듯이 service 디렉터리에 sample 이라는 디렉터리를 만들면 서브 프로젝트가 생성되고 그곳에 build.gradle 과 기타 프로그램 작업을 진행한다.

Sample Application 작업

service 디렉터리 하위에 sample 이라는 디렉터리를 생성한 후에 gradle을 refresh 하면 해당 모듈이 생성된 것을 볼 수 있다.

그 하위에 build.gradle 파일을 만들고 아래와 같이 dependency 를 추가한다.
dependencies {
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile 'org.springframework.cloud:spring-cloud-starter-config'
}

나중에 sample로 RestController 작업할 거라서 spring-boot-starter-web 을 함께 포함시켰다.

이제 SampleApplication.java 파일을 다음과 같이 만든다. Environment 설정을 프린트 하는 더 세련된 방법이 있으니 각자 찾아보시길... 난 일단 Configuration Server에 등록한 정보 중 sample-prod.yml 에만 있는 sample.bar 값에 대한 default 처리를 위해서 우선은 따로 처리하였다.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by road on 15. 11. 29.
 */
@Configuration
@EnableAutoConfiguration
@RestController
public class SampleApplication {

    @Value("${sample.foo:this is foo by default}")
    private String fooValue = "";
    @Value("${sample.bar:this is bar by default}")
    private String barValue = "";

    @RequestMapping(value = "/configStatus")
    public Map getProperties() {
        Map configStatus = new HashMap<>();
        configStatus.put("foo", fooValue);
        configStatus.put("bar", barValue);
        return configStatus;
    };


    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }
}
이제 마지막으로 bootstrap.xml 을 resources 에 등록하여 설정을 마무리 해보자.
spring:
  cloud:
    config:
      uri: http://localhost:8888
  profiles:
    active: dev
  application:
    name: sample

그리고 하는 김에 설정이 어떻게 동작하는지 볼 겸 application.yml 도 만들어 server.port=8080만을 추가해보았다.
server:
  port: 8080
최종적인 프로젝트의 모습은 아래와 같이 된다.
이제 Sample Application 을 실행해보자. (당연히 Configuration Server 를 먼저 실행해야 한다)
그럼 마지막 부분에 port가 8088로 실행된 것을 확인할 수 있다.
분명이 SampleApplication의 application.yml에서 server.port 를 8080으로 설정했음에도 불구하고 Configuration의 sample.yml에 등록한 8088이 실행되었다.

자 이제 http://localhost:8088/configStatus 에 접속해보자.
결과는 {"bar":"this is bar by default","foo":"this is dev foo"} 라고 나온다.
foo 는 configuration server의 값을 읽었고, bar는 SampleApplication의 @Value에 설정된 default 값이 설정되어있다.

이제 SampleApplication의 active profile 을 prod로 변경하고 다시 실행하고 다시 http://localhost:8088/configStatus 에 다시 접속해보면,
결과는 {"bar":"this is prod bar","foo":"this is prod foo"} 가 나오는 것을 확인할 수 있다.

결론

지금까지 간단하게 Configuration Server를 설정하는 방법에 대해서 알아봤다.
앞에서 봤지만 기본 서버 포트를 비롯하여 대부분의 Client Application 설정을 Configuration Server를 이용해서 처리할 수 있다.(대부분이지 전부는 아니다. 예를 들어 당연하겠지만 spring cloud 관련 설정들은 Configuration Server에 할 수 없다.)

여러 Application으로 나뉘어 개발되어지는 서비스가 있다면, 간단하게 Configuration Server를 구성하는 것으로 Property 때문에 배포대상에 따라 다시 빌드 한다거나 내용을 확인하기 위해 모든 서버에 접속을 한다거나 하는 부담을 많이 덜 수 있을 것이다.

데이터베이스 Connection 정보라던가 하는 것들의 정보의 경우는 일반 텍스트로 할 경우 불안감이 생길 수 있는데 이 부분을 위해서 Configuration Server의 경우 암호화 복호화 기능을 지원한다.
이 부분은 간단하지만 다음번에 정리를 해 볼까 한다.

궁금하다면 http://projects.spring.io/spring-cloud/docs/1.0.3/spring-cloud.html#_encryption_and_decryption_2 에서 확인을 해 볼 수 있다.
또한, 이 외에도 Configuration이 변경되었을 때에 다시 적용할 수 있는 방법등에 대한 다양한 자료가 있으니 http://projects.spring.io/spring-cloud/docs/1.0.3/spring-cloud.html 를 한번 쯤은 읽어보기를 것을 추천한다.



댓글 없음:

댓글 쓰기