Java

Spring Cloud Config Server 환경 변수를 분리해보자 (1)

Yukart 2022. 12. 1. 20:53
반응형
실제 서비스에 적용할 아키텍처를 고민하던 중 Spring 에서 지원해주는 기술을 알게 되었고 그것을 구현하는 내용입니다.

Spring Cloud Config 를 활용하면 환경변수들을 프로젝트 레포지토리 상에서 완전히 분리할 수 있습니다.
환경 변수 주입은 MSA 아키텍처와 관련해서 설계할 때 한번쯤은 생각 해보게되는 문제입니다.
여러 서비스가 어떻게 하면 환경 변수를 잘 주입 받을 수 있을까? 처음 제가 생각한 조건은 이러합니다.


  1. 환경 변수는 각 서비스의 레포지토리에 존재해서는 안된다.
  2. 환경 변수가 수정 되었을 때 수정된 내용을 잘 참조 할 수 있어야 한다.
  3. 여러 서버의 환경변수를 한 곳에서 체계적으로 관리할 수 있어야 한다.
  4. 민감한 환경변수는 통신을 할 때 암호화될 수 있어야 한다.

 

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 서버를 구현하는 예제 글을 써보도록 하겠습니다.

 

반응형