기본 콘텐츠로 건너뛰기

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



댓글

이 블로그의 인기 게시물

소프트웨어 장인 - 산드로 만쿠소

  출간일 : 2015년 9월 25일  328쪽 | 500g | 148*225*14mm ISBN-13 : 9791186659489 소프트웨어 장인 : 프로페셔널리즘, 실용주의, 자부심  원제로는 The Software Craftsman-Professionalism, Pragmatism, Pride  입니다. 이 책은 로버트 C. 마틴 시리즈로 열한 살부터 코딩을 시작했고 열아홉부터 코딩으로 돈을 번 경험이 있는 브라질 출신의 산드로 만쿠소 가 본인의 경험담을 통해서 소프트웨어 장인에 관해 이야기하고 있는 책입니다. 아는 동생의 가볍게 읽을 수 있는 책이라며 추천을 해줘서 읽게 된 책이죠. 평소 책을 고를 때 다른 것 보다 무게를 먼저 확인하고 나머지를 고르는 제 버릇때문인지 무게가 가볍다는 것을 먼저 이야기하면서 추천을 해 주더군요. 한동안 기술서적에 손을 못 대고 있는 상황에 있었습니다. 여러 가지 이유로 슬럼프가 좀 길게 온 탓에 기술 서적을 읽는 것은 물론 코딩 한 줄 손대는 것도 고통스러웠습니다. 회사 일이야 돈 벌어야 하니 어쩔 수 없이 하고 있지만, 집에 와서는 매일 인텔리제이를 켜놓고는 한 줄도 못 짜고 끄는 날이 대부분인 요즘이었습니다. 이럴 때 가볍게(?) 읽을 수 있는 책이라니 정말 반가웠습니다.  "그래! 이럴 때는 책을 읽으면서 뭔가 계기를 만들어야 해"라는 생각에 보게 되었습니다. 우선 빠르게 한번 읽고 난 다음에 포스트를 쓰고 있습니다. 이 책을 약간의 시간을 두고 한 번 더 읽어볼 생각인데, 아마도 그때는 다른 느낌이 있을지도 모르겠습니다. 지금부터 이 책을 읽으면서 느꼈던 점들을 지극히 주관적인 입장으로 정리를 한 번 해보려고 합니다. 이 책의 구성을 제 나름대로 보면 대충 아래와 같은 구성입니다. 1~3장은 애자일과 장인 정신에 대한 정의를 찾아가는 여정을 보여줍니다. 어떻게 해서 애자일이 나타났고 어떻게 소프트웨어의 장...

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

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

Edge server와 Ribbon, 그리고 API 구성

서론 여기서 말하는 Edge Server는 다양한 API 서버들을 연결하는 Gatekeeper와 비슷한 역할을 그 주요 기능으로 한다. 이 외에 구성에 따라서는 외부 클라이언트와의 연결에서 Security의 진입점 같은 역할도 하기는 하지만, 그 부분은 다른 포스트에서 정리해볼까 한다. 이번 포스트를 정리할 때 사용한 예제는 대부분을 Calista 블로그의  Building microservices with Spring Cloud and Netflix OSS, part 1 에서 참고하였다. 사실 위 블로그의 내용을 거의 똑같이 따라 하면서 정리했다고 보는 것이 맞을 것 같다. 이 포스트보다 정리가 잘 되어있는 만큼 영어에 어려움이 없는 개발자라면 Calista 블로그의 내용을 보는 것이 더 나을지도 모르겠다. 한가지 참고할 수 있는 자료가 더 있는데 다른 포스트에서도 소개했던 적이 있는 Devoxx 2015 의 동영상 자료가 있다. 제목은  Getting started with Spring Cloud by Josh Long 으로 1시간 정도 라이브 코딩으로 Josh Long이 열정적으로 설명해준다. 영어를 못하는 나도 어느 정도 따라갈 수 있었던 동영상으로 강추하고 싶은 동영상이다. 시작하기에 앞서서 좋은 블로그 자료를 공개해줘서 샘플을 만들고 정리하는 데 도움을 준 Calista 블로그에 감사의 마음을 전하며, 자료를 함부로 가져다 쓰게 된 점에 대해 매우 미안한 마음을 전한다. 이번 포스트를 정리하기 위해 사용한 샘플은 아래와 같은 조건으로 작업이 되었다. Configuration Server 와 Discover Server : 이전 포스트 참고 JDK 8 : 1.8.0_65 Intellij 15.0.3 Gradle build : 2.10 Spring IO : 2.0.1  Spring Boot : 1.3.2 (이전 포스트 작성후에 버전이 1.3.1에서 1.3.2로 업그레이드됐다) Spring ...