기본 콘텐츠로 건너뛰기

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 를 한번 쯤은 읽어보기를 것을 추천한다.



댓글

이 블로그의 인기 게시물

경력 개발자의 자기소개서에 대해서...

갑자기 뜬금없이 이런 글을 쓰다니 무슨 생각이야? 라고 생각하시는 분들이 있을지도 모르겠네요. 뜬금없음에 대한 변명은 잠시 접어두고 일단 오늘 쓰려고 하는 글을 시작해볼까 합니다. 개발자로 대충 16년을 그럭저럭 보내왔습니다. 시대적 상황으로 5년 차쯤에 대리로 처음 팀장을 시작했으니, 일반 개발자로 산 시간보다는 어쨌건 프로젝트 또는 팀의 리더로 산 시간이 더 많았던 것 같습니다. 그 기간 동안 남들보다 좀 심하게 회사를 많이 옮겨 다니다 보니 꽤 많은 면접을 볼 수 있는 경험이 있었고, 또 옮긴 회사가 대부분 팀을 리빌딩하는 곳이었다 보니 꽤 많은 채용절차에 관여할 기회가 있어서 어린 나이부터 비교적 많은 이력서를 검토했고 면접관으로도 여러 사람을 만날 수 있었습니다. 처음 면접을 보러 다니던 시절의 제 이력서의 자기소개서는 항상 "19XX년 봄 XX업계에 종사하시던 아버님과 집안일에 헌신적인 어머니의 유복한 가정에 1남 1녀의 막내로..." 로 시작되었습니다 (이 문장에 향수를 느끼시는 분들 많으실 거예요. ^^). 경력이 5년이 넘은 어느 날 도대체 이 문장을 왜 써야 하느냐는 의문이 생겨서 조금 바꾸긴 했습니다만, 그 뒤로도 꽤 오랜 세월을 이런 자기소개서가 항상 제 이력서에 붙어있었죠. 요즘 누가 저런 식으로 자기소개서를 써? 라고 생각하시는 분들 많으실 거로 생각해요. (대신 요즘은 대학 시절의 봉사활동이나 해외연수 이력이... 뭐 어차피 그놈이 그놈입니다.) 저런 자기소개서를 써야 한다는 것이 어디서 어떻게 시작된 것인지는 몰라도 회사를 그만두기 전인 2년 전까지도 약간의 표현은 다를지 모르지만 비슷한 문장으로 시작하는 자기소개서를 이력서에 첨부해서 보내는 지원자들을 볼 수 있었습니다. 이제 제가 뜬금없는 이런 글을 쓰게 된 이유를 밝히고 계속 진행해야겠네요. 블로그에 올릴 글을 준비하는 일이 생각보다 힘들어요. 블로그에 올리려고 준비한 주제에 맞는 소스를 작업하고 거기에 글을 입히다 보면 가끔

Springframework 5에서 바뀌는 것들에 대한 간단 정리 및 생각

Spring framework 5 에 대해 많은 분이 기대와 두려움을 가지고 계시지 않을까 생각합니다. 특히 기대를 하고 계신 분들은 Reactive Programming 지원을 기대하고 계시지 않은가 생각이 드는데요. 7월 초에 John Thompson 이란 분이 D-Zone에 아주 깔끔하고 멋지게 정리를 잘해서 글을 쓰셨더라구요. 해당 글은  https://dzone.com/articles/whats-new-in-spring-framework-5 에서 확인을 하실 수 있습니다. 혹시 Spring framework 5에서 달라지는 내용의 좀 더 자세한 내용이 필요하신 분들은 Spring framework github의 wiki 를 참고하시면 됩니다. 본 포스트는 언제나 그렇듯이 윗글에 대한 번역이 아닙니다. 그저 윗글을 다시 정리하면서 제 생각을 한번 정리해 놓은 포스트입니다. Spring framework 5는 현재 5.0.0.RC2(2017.07.23일 기준)까지 릴리즈된 상황입니다. Spring framework 5에서 크게 변화하는 내용을 John Thompson은 8가지로 깔끔하게 정리해주고 있습니다. 1. JDK 지원 버전의 업데이트 5버전은 원래 JDK 9 버전의 지원을 위해서 시작됐던 프로젝트로 알고 있는데 맞는지는 모르겠네요. JDK 9의 Release가 늦어져서 Spring framework 5가 먼저 Release 될 것으로 보이지만, JDK 9가 Release가 되면 언제건 적용할 수 있다고 합니다. 좀 아쉬운 부분은 JDK의 최소 버전은 JDK 8이라는 부분이 아닐까 싶네요. 이 때문에 Spring framework 5에 무관심한 분들도 많으실 거라고 생각합니다. 지금 진행하는 프로젝트는 JDK 8을 기반으로 합니다만, 최근까지 다니던 회사의 경우는 JDK 7까지가 업그레이드 한계였던 회사였습니다. 아마도 JDK 업그레이드를 쉽게 못 하시는 회사들이 많으니 "나랑은 관계없는 얘기군"

Gradle 을 이용해서 Multiproject 를 구성하는 방법 (중 하나)

개요 회사에서 Gradle 을 이용하게 된 이래로 계속 Multi-project 형태로 설정하여 진행을 해 오고 있다. 매번 멀티 프로젝트 형태로 만들어지고 있는 회사의 프로젝트들이다 보니 그때마다 다시 이전 빌드 스크립트를 보면서 만드는데 프로젝트들이 복잡하다보니 필요없는 설정들까지 복사해서 쓰고 있는 부분들이 있어서 한번 정리를 했으면 하고 있었다. 가장 기본적인 상태의 멀티프로젝트용 build.gradle 에 대한 여러가지 방법 중 한가지라 생각하고 참고가 된다면 좋겠다. 요구사항 기본 요구사항은 다음과 같다. 1) 멀티프로젝트는 디렉터리를 기반으로 아래와 같이 그룹으로 만들어 질 수 있어야 한다.   - Shared : 다른 프로젝트에서 Dependency로 추가될 수 있는 공통 라이브러리를 포함하는 라이브러리 모듈 그룹   - Web : Front 모듈 그룹   - Server : 주로 어플리케이션 간의 설정 등을 관리하는 Server 모듈 그룹   - Service : Web API 서버 모듈 그룹 2) 모든 Subproject 들은 Java project 이며, 프로젝트 명은 "모듈그룹명-모듈명" 으로 만든다. 즉, Server 모듈의 configuration-server 모듈이라면 server-configuration-server의 형태로 만들어지면 된다. 이후에 작업된 모든 내용은 Ubuntu 14.04 OS와 IntelliJ IDEA 15에서 작업되었다. (윈도우즈와 이클립스에서도 동일한 내용을 동작이 될 것으로 보인다. 다만, 테스트되지 않았을 뿐이다) Main Gradle Script 작업 1) 파일 구성   - build.gradle : gradle script 파일   - settings.gradle : sub project 관리를 위한 파일 2) build.gradle 파일 작성   - 우선은  프로젝트 기본 정보로 group 정보와 version 을 설정한다. 본인의