2024년 2월 23일

코딩 - Cloud Ready : Spring Config Server

Spring Boot는 마이크로서비스 아키텍처(Microservices Architecture) 를 구현하는 데 많은 도구와 기능을 제공하고 있다. 이를 통해 애플리케이션을 작은 독립적인 서비스로 분할하고, 이러한 서비스를 개발, 배포 및 관리하는 데 도움을 준다.



이러한 기능중에서 유연성, 안정성, 보안성 향상을 위하여 설정의 집중화를 구현하는 Spring Cloud Config는 애플리케이션의 설정을 외부화하고 중앙에서 관리할 수 있게 한다. 이를 통해 설정 변경을 용이하게하고 여러 환경 간에 설정을 공유할 수 있다.

Config Server 구성하기


◼︎ 환경
  • Model : MacBook Pro (14-inch, 2021)
  • CPU : Apple M1 Pro
  • MENORY : 16GB
  • DISK : 512 GB SSD
  • OS : macOS 13.2.4 (22F66)
  • TOOLS : Visual Studio Code, Java 11, Gradle, Docker
  • Version Control : GitHub
  • Programming Language : Java
  • Framework : Spring Boot 2.7.12, Spring Security 5.7.7
  • DBMS : MySql 8.0.33
  • Cloud : Oracle Cloud Free Tier

Spring Cloud Config 서버 프로젝트는  ① gradle 명령을 사용하여 새로운 자바 응용프로그램 프로젝트를 생성하고 여기에 관련 의존성과 @SpringBootApplication 를 구현하는 클래스를 생성하는 방법과 ② Spring Initializer 을 이용하여 프로젝트를 다운로드하는 방식이 있다. 

① Gradle

brew 을 이용하여 gradle 을 설치한다. 


# brew update
# brew install gradle
# gradle init --type java-application

 Select build script DSL:
  1: Kotlin
  2: Groovy
 Enter selection (default: Kotlin) [1..2] 2

 Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
 Enter selection (default: JUnit Jupiter) [1..4] 4

 Project name (default: studio-config): 
 Enter target version of Java (min. 7) (default: 21): 11
 Generate build using new APIs and behavior (some features may change in the next minor release)?   (default: no) [yes, no] yes

 > Task :init
 To learn more about Gradle by exploring our Samples at   https://docs.gradle.org/8.6/samples/sample_building_java_applications.html

 BUILD SUCCESSFUL in 2m 17s
 1 actionable task: 1 executed

다음으로 build.gradle 파일에 org.springframework.cloud:spring-cloud-config-server 의존성을 추가한다.

build.gradle

dependencies {
implementation 'org.springframework.cloud:spring-cloud-config-server'
// lombok
implementation 'org.projectlombok:lombok'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}

springCloudVersion 속성은 gradle.properties 에 정의했다.

springBootVersion=2.7.18
springDependencyManagementVersion=1.1.4
springCloudVersion=2021.0.8
sourceCompatibility=11


가장 어려웠던 부분이 spring cloud version 을 확인하는것 과정이었다. 

마지막으로 @EnableConfigServer 을 구현하는 서버 소스를 추가한다. (소스는 ② 생성되는 코드들을 참고했다)

package architecture.studio.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class ConfigServer {

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

}

② Spring Initializer

브라우저를 열고 https://start.spring.io/ 에 접속한다.


config server 의존성을 선택하여 새로운 config server 프로젝트를 생성한다. (2.x 버전은 지원하지 않고 있다.)

Config Server 프로젝트 생성

이미 프로젝트가 존재했고 해당 프로젝트에서 config 을 분리하는 것으로 목표로 했기 때문에 ① 방식을 참고하여 서브 프로젝트를 추가하고 생성된 프로젝트에 설정을 추가하는 방식으로 구현했다.

존재하는 spring boot 프로젝트에 config-server 를 추가하는 경우는 "Supported Versions" 참고하여 호환되는 Spring Cloud 버전을 확인 하려 하였으나 https://spring.io/projects/spring-cloud 문서가 더 도움이 되었다.  문서에 따르면 2.6.x, 2.7.x 버전은 2021.0.x aka Jubilee 버전이 지원하고 있다.


2021.0 정보는 https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-2021.0-Release-Notes 확인 가능한데  2024년 2월 기준으로 '2021.0.8' 이 가장 최신이다. 이제 해당 버전으로 의존성를 추가한다.

설정정보 저장소

대부분의 자료들이 github 을 저장소로 사용하여 config server 을 구성하고 있는데 로컬 파일을 사용하는 방식으로 구성했다. 로컬 파일들은 jar 파일에 포함되어 배포 될수 있도록 src/main/resources/config 경로에 [application]-[profile].yml 규칙으로 생성하였다. 주의 할것은 이 파일들은 "--" 문자를 포함하면 오류가 발생한다. 


config server 에 대한 설정은 application.yml 에 아래와 같이 설정하였다.

application.yml
---
logging:
level:
root: debug
---
server:
port: 8888
spring:
application:
name: studio-config
profiles:
active: native
cloud:
config:
server:
native:
searchLocations: classpath:/config, classpath:/config/{application}, classpath:/config/{application}/{profile}


웹 브라우져에서 http://localhost:8888/[application]/[profile] 형식으로 호출하여 프로파일에 따른 설정을 확인할 수 있다.

암호화 

비밀번호와 같은 중요 정보들을 암호화하여 설정 파일을 구성하기 위한 목적으로  Config Server 가 제공하는 암호화 기능을 사용하였다 

Config Server 는 암호화 구현을 위하여 대칭(공유) 키 또는 비대칭 키(RSA 키 쌍)를 사용하여 할 수 있다. 비대칭 키가 보안 측면에서 더 우수하지만, 대칭 키 방식은 application.yml(application.properties) 파일에  에 대칭 키 문자 하나만 설정하면 되기 떄문에 더 편리하다고 할 수 있다.

대칭 키를 설정은 encrypt.key 에 개인 키 암호를 설정해야 한다.  (또는 ENCRYPT_KEY 환경 변수를 사용하여 값을 지정하는 방법도 있다.)

비대칭 키 방식은  keystore (JDK 에 포함된 keytool 유틸리티로 생성) 을 사용하는 이유에서  아래 설정들이 필요하다.
  • encrypt.keyStore.location : keyStore 파일 위치
  • encrypt.keyStore.password : KeyStore 파일에 접근을 위한 비빌번호
  • encrypt.keyStore.alias : KeyStore 내의 엔트리를 식별하는 고유한 이름. KeyStore는 공개 키, 개인 키 쌍 및 이에 대한 인증서를 저장하는 데 사용되는데, 이러한 엔트리는 각각 고유한 이름인 alias로 식별됨
※KeyStore에 저장되는 개별 항목을 "엔트리(entry)"라고 한다. 이 엔트리는 주로 공개 키와 개인 키 쌍과 그에 대응하는 인증서를 포함한다.

KeyStore 를 생성하고 비대칭키를 설정하는 것은  "Spring Cloud Config - Creating a Key Store for Testing" 참고했다.

참고로 긴 기간동안 키가 유효하도록 아래와 같이 10년동안 유효하게 키 스토어를 생성했다. 

keytool -genkeypair -alias testalias -keyalg RSA -keysize 2048 \
  -dname "CN=ConfigServer, OU=none, O=none, L=none, ST=none, C=KR" \
  -keystore server.jks -keypass testkeypass  \
  -storepass teststorepass -validity 3650

생성된 키는 src/main/resources/key 경로에 복사하고 아래와 같이 application.yml 파일에 설정을 추가했다.

application.yml
keyStore:
location: classpath:/key/server.jks
password: *****
alias: studiokey
secret: *****

암호화된 문자는 접두사 {cipher} 사용하여 설정하고 Config Server 은 요청시 설정정보를 복호화하여 전송한다.

application.yml
spring:
datasource:
url: "{cipher}AQCnTtoaV9juWvUutYNWNVyx9Bd+E9zUf+Mf1"
username: "{cipher}AQCHbvX1Sa9VyjTfaFLP2uSesfTCe0Wn"
password: "{cipher}AQBUIaBt+nzhl+mbyOlSb5MStLWkvZ1wjW2iwByefg34aLs="


암호화

암호화를 위한 키 설정을 하고 나면 ConfigServer 가 제공하는 암호화와 복호화를 RESTful API 을 호출하여 데이터를 암호화 하고 값을 {cipher} 을 접두사로 하여 application.yml 파일에 값을 수정한다.  

Config Server 는 /encrypt 및 /decrypt 엔드포인트를 공개하고 있다(이들 엔드포인트는 안전하고 인증된 클라이언트만 접속 가능한 것으로 가정). 만약 설정 파일을 값을 암호화 하고자 하는 경우 아래와 같이 /encrypt 엔드포인트를 POST 로 호출하여 암호화 할 수 있다. 

curl -X POST \

  -H "Content-Type: application/json" \

  -d 'jdbc:mysql://xxx.xxx.xxx:3306/xxxdb?serverTimezone=Asia/Seoul' \          

  http://localhost:8888/encrypt


복호화 역시 동일한 방법으로 /decrypt 를 호출하면 된다.

Config Server 에서 설정 불러오기

Spring boot 프로그램에서 앞에서 생성한 Config Server 에서 설정을 읽어드리도록 하려면 ❶ Config Server 의 경우와 같이 org.springframework.cloud:spring-cloud-config-server 의존성을 추가하고 ❷
Config Server 에서 설정을 읽어드리도록 application.yml 설정을 추가한다. 

/application.yml

logging:
level:
root: INFO
spring:
application:
name: studio
config:
import: optional:configserver:http://localhost:8888


추가로 dev , prod 프로파일을 application.yml 이 아닌 테스트를 위하여 프로그램을 실행 할 떄 prod 인자를 사용하여 개발 과 운영 모드로 동작할 수 있도록 아래와 같은 설정을 build.gradle 에 추가하였다. 이를 통하여 개발과 운영을 동일한 코드로 구현하고 테스트를 위하여 프로그램을 실행할 떄 프로파일을 인자로 넘겨서 처리하도록 할 수 있었다.  

build.gradle

tasks.named("bootRun") {
if (project.hasProperty('profile')) {
jvmArgs = ["-Dspring.profiles.active=${project.getProperty('profile')}"]
}
}



./gradlew bootRun -Pprofile=dev

locathost:8888/studio/dev 에 해당하는 /config/studio-dev.yml 을 읽어드림.


./gradlew bootRun -Pprofile=prod 

locathost:8888/studio/prod 에 해당하는 /config/studio-prod.yml 을 읽어드림.


참고 설정 및 소스


댓글 없음:

댓글 쓰기