반응형

실제 서비스에 적용할 아키텍처를 고민하던 중 Spring 에서 지원해주는 기술을 알게 되었고 그것을 구현하는 내용입니다.
Spring Cloud Config 를 활용하면 환경변수들을 프로젝트 레포지토리 상에서 완전히 분리할 수 있습니다.
환경 변수 주입은 MSA 아키텍처와 관련해서 설계할 때 한번쯤은 생각 해보게되는 문제입니다.
여러 서비스가 어떻게 하면 환경 변수를 잘 주입 받을 수 있을까? 처음 제가 생각한 조건은 이러합니다.
- 환경 변수는 각 서비스의 레포지토리에 존재해서는 안된다.
- 환경 변수가 수정 되었을 때 수정된 내용을 잘 참조 할 수 있어야 한다.
- 여러 서버의 환경변수를 한 곳에서 체계적으로 관리할 수 있어야 한다.
- 민감한 환경변수는 통신을 할 때 암호화될 수 있어야 한다.

1. build.gradle
plugins {
id 'org.springframework.boot' version '2.7.5'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'java'
id 'war'
}
group = 'com.yuihmoo'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "2021.0.4")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-config-server' // Cloud Config Server 기능을 할 종속성
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-actuator' // 환경 변수 수정 시 재기동을 위한 종속성
implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap' // bootstrap.yml 사용을 위한 종속성
developmentOnly 'org.springframework.boot:spring-boot-devtools'
//Spring Bus 기능을 이용할 때만
implementation 'org.springframework.cloud:spring-cloud-starter-bus-kafka'
implementation 'org.springframework.cloud:spring-cloud-config-monitor'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
if (project.hasProperty('DEBUG_PORT')) {
tasks.named("bootJar") {
classpath configurations.developmentOnly
}
bootRun {
jvmArgs(["-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:${project.property("DEBUG_PORT")}"]) // 도커라이징 시 디버그를 위한 포트
}
}
jar {
enabled = false
}
# 위 그림에서 보듯 환경변수를 전파할 Server와 전파를 받는 입장의 Client 서버 두 종류로 구성 되어 있습니다.
# actuator 는 환경 변수가 수정 될 때 서버가 완전히 재 기동하지 않은 상태로 환경 변수를 Refresh 하게 도와줍니다.
2. ServletInitializer.class
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringCloudConfigApplication.class);
}
}
3. application.yaml
# Server
server:
port: 8888 # 서버 포트는 Client 서버와 겹치지 않게 임의로 설정해주시면 됩니다.
management:
endpoints:
web:
exposure:
include: '*'
# Spring
spring:
kafka:
bootstrap-servers: http://localhost:9092 # Kafka 를 통해 Spring Bus 기능을 활용할 때에만 사용합니다. (사용할 Kafka url)
devtools:
livereload:
enabled: true # devtool live reload 기능 활성화
remote:
restart:
enabled: true # 원격 재시작 기능 활성화
application:
name: config-server
cloud:
config:
server:
git:
uri: git@github.com:yuihmoo/yourRepo.git # 환경 변수 yaml 파일들을 저장할 Git Repo URL
default-label: main # Git Repo 의 디폴트 브랜치
force-pull: true # 로컬 복사본이 어떠한 이유로 오염되었을 때 강제 pull 을 받게 해주는 기능
timeout: 30
ignore-local-ssh-settings: true # Git 과 통신하기 위한 SSH 설정
private-key: |
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEA9/8IPDkz4DoH9XxusP94H9R7aSVqEJwHAOQnfyuvZblE8aZJ
T5hemeju4i1WBywc0zWlsg1nNmMCNiWe6VIIxud7LmMqvnBB5AdJFFYLbrkAusjM
mRzGQdH/+ae2+XANnf4xHnD9rvqwJSjOWc+czKtaJ8wVNv+WZP3//USCmltfhFzZ
AklnM6toGvwDNMJZi0cSikwzVnkWS9dAbaxlZ8RuU1rGF9IBA4fZivMCAQBNHhrx
vfbjYqTcZegPEBjrnT5FEivmdZa5I4W5vFFIXRTgtpjyvVdUDIaqgTjn9lTfZe1E
...
-----END RSA PRIVATE KEY-----
host-key: AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY...
host-key-algorithm: ecdsa-sha2-nistp256
bus:
enabled: true # Spring Bus 기능 활성화
- Spring Kafka Bus 기능을 사용하지 않을 경우에는 불필요한 프로퍼티들을 지워도 됩니다.
- 사실상 Spring Config Server 역할을 하는 경우 라이브 리로드 기능 & 디버그 기능은 크게 개발시에 필요하진 않습니다. 지워주셔도 무방합니다.
- default-label 같은 경우 환경변수 파일들을 저장할 레포지토리의 디폴트 브랜치와 맵핑하는 기능을 합니다.
- force-pull 같은 경우는 좀 특이한데, 원격 저장소의 내용이 로컬 저장소(내 PC)와 맞지 않거나 OS의 개입 등으로 인한 이유로 파일이 오염되었을 때, 덮어 쓰기를 강제로 시켜주는 기능입니다. 어차피 원격 저장소에 있는 내용을 가장 최신으로 유지할 것이고 신뢰할 수 있기 때문에 true로 설정합니다.
- 보안을 위해 RSA 인증 키를 사용합니다.

필자도 가장 헷갈렸던 부분입니다.
Spring 프레임워크는 bootstrap.yaml 과 application.yaml 둘중에 무엇을 읽는 것일까?
앞서 build.gradle에서 잠깐 설명 했듯이 bootstrap.yaml을 사용할 수 있게 종속성을 추가해 주었습니다.
Spring 프레임워크 버전이 올라가면서 bootstrap.yaml을 사용할 수 없게 되었는데 자료 조사를 하다가 저는 강제로라도 사용하는 것을 선택했습니다.
applicaton.yaml의 내용이 향후 길어져서 보기 불편할 수도 있고 bootstrap.yaml이 먼저 읽힌 다음 application.yaml을 읽고 기동되기 때문에 그 사이에 어떠한 프로퍼티를 밀어넣기도 용이해보여서 선택했습니다.
결국 중요한점은 bootstrap.yaml을 먼저 읽는다는 겁니다.
4. bootstrap.yaml
encrypt:
key: my_symmetric_key
- Spring Cloud Config Server는 암호화도 지원해주는데 서버가 기동되었을 때 특정 url로 body에 암호화하고 싶은 값을 보내면 암호화시킨 채 response로 돌려줍니다.
- 그 때 암호화 key로 쓸 문자열을 프로퍼티로 알려줍니다.
5. API
GET localhost:8888/user/dev
- 예를 들어 user-dev.yaml 파일을 불러올 때 사용합니다.
POST localhost:8888/actuator/refresh
- 환경 변수가 수정 되었을 때 갱신하기 위해 사용합니다.
POST localhost:8888/encyrpt
- body에 암호화 할 string을 넣어서 호출합니다.
POST localhost:8888/decrypt
- body에 복호화 할 encrypt를 넣어서 호출합니다.

현재 저장소와 Config Server 간의 통신만을 설명 드렸습니다.
다음 번엔 Client 서버를 구현하는 예제 글을 써보도록 하겠습니다.
반응형
'Java' 카테고리의 다른 글
[Gradle] 궁금한 Gradle Declaring Repositories (1) | 2023.08.09 |
---|---|
Spring boot + Minio + Docker 예제를 만들어보자(1) (0) | 2022.11.21 |
Spring Cloud Config 란 무엇인가 (0) | 2022.11.20 |
WAS란 그리고 그 종류들 (0) | 2022.06.20 |
JAVA 자주 까먹는 것들 (0) | 2022.02.25 |