본문 바로가기
개발/MSA

[Spring Cloud] Spring Cloud Config 개념 및 구현

by 궁즉변 변즉통 통즉구 2024. 6. 15.
반응형

 

Spring Cloud Config 개념

출처: https://jaehun2841.github.io/2022/03/10/2022-03-11-spring-cloud-config/#Spring-Cloud-Config란

 

Spring Cloud Config란 분산 시스템에서 설정(Config)에 대한 외부 분리 및 중앙 저장소 역할을 지원한다.  

설정을 위한 별도의 서버(Config Server)를 구성하고, 실행 중인 애플리케이션(Config Client Application)이 Config Server에서 설정 정보를 받아와서 적용하는 방식으로 동작한다. 실행 중에 설정값 변경이 필요한 경우 설정 저장소(Config Repo)만 변경하고, 애플리케이션은 갱신만 하면 된다. 따라서 설정의 변경에 따른 애플리케이션의 재빌드 맟 재기동 불필요하다. 여러 서버의 설정 파일을 중앙 서버에서 일관되게 관리 가능하다는 관리의 편의성도 있다. 단, git 서버 같은 설정 저장소에 의한 장애 전파(SPOF)가 문제 될 수 있고, 우선순위에 따라 설정 정보가 덮어씌워질 수 있어서 설정 정보가 분산이 되지 않도록 잘 관리해야 한다.

설정정보 우선순위: 아래로 갈수록 우선순위가 높음

 

출처: https://madplay.github.io/post/introduction-to-spring-cloud-config

 

 

Spring Cloud Config 적용

적용 샘플은 위의 그림과 같이 크게 3가지 모듈로 구성한다. 모듈 및 프로젝트명은 아래와 같이 정했다

1. config-file: 설정 config yml 파일의 저장소, github을 활용할 예정

2. config-server: Spring Cloud Config Server, 설정 중앙 관리 서버

3. config-client: config-server에서 설정정보를 받아와 사용하는 Client Application

 

 

1. config-file 구성

아래와 같이 디렉토리 및 파일 구조로 생성한다. application명을 ‘app1’으로 하여 profile 별로(dev, real) 파일을 생성한 것이다.

 

yml 파일의 내용은 아래와 같이 샘플로 작성해준다.

 

github에 작성한 파일을 push해서 저장소로 활용한다.

 

 

2. config-server 구현

아래 순서대로 파일을 작성해준다. 기본적으로 SpringBoot 기반이다.

build.gradle.kts 파일

plugins {
	java
	id("org.springframework.boot") version "3.2.5"
	id("io.spring.dependency-management") version "1.1.4"
}

group = "springcloud.config.server"
version = "0.0.1-SNAPSHOT"

java {
	sourceCompatibility = JavaVersion.VERSION_17
}

configurations {
	compileOnly {
		extendsFrom(configurations.annotationProcessor.get())
	}
}

repositories {
	mavenCentral()
}

extra["springCloudVersion"] = "2023.0.1"

dependencies {
	implementation("org.springframework.cloud:spring-cloud-config-server")
	implementation("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5")
	compileOnly("org.projectlombok:lombok")
	annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
	annotationProcessor("org.projectlombok:lombok")
	testImplementation("org.springframework.boot:spring-boot-starter-test")
}

dependencyManagement {
	imports {
		mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}")
	}
}

tasks.withType<Test> {
	useJUnitPlatform()
}

 

application.yml 파일

server:
  port: 8888

spring:
  application:
    name: springcloud-config-server  # Confing Server  이름
  cloud:
    config:
      server:
        git:
          uri: https://github.com/xxxx/springcloud-config  # 설정파일이 있는 깃 주소
          default-label: main # 깃 주소의 브랜치 이름 
          search-paths: config-file/**  # 설정 파일들을 찾을 경로(github의 config-file 디렉토리 하위)

 

 

ConfigServerApplication.java 파일

@SpringBootApplication
@EnableConfigServer  // Spring Cloud Config Server 어노테이션
public class ConfigServerApplication {

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

}

 

이제 Spring Cloud Config 서버를 구동하고 테스트해본다. 아래와 같이 브라우저에서 http://localhost:8888/app1/dev를 입력하면 config-file에서 구성한 app1/app1-dev.yml 파일의 설정 정보를 읽어서 출력한다. 

아래 URL 형식으로도 정보를 가져올 수 있다. label은 Optional 값으로 git branch 정보를 의미한다. 

- /{application}/{profile}[/{label}]

- /{application}-{profile}.yml

- /{label}/{application}-{profile}.yml

- /{application}-{profile}.properties

- /{label}/{application}-{profile}.properties 

 

profile을 변경해서 http://localhost:8888/app1/real을 입력하면 메시지가 변경되는 것을 확인할 수 있다. 

 

 

3. config-client 구현

config-server와 동일하게 SpringBoot Application을 구성한다. 각 파일들을 아래와 같이 작성해준다. 

build.gradle.kts 파일 

plugins {
    java
    id("org.springframework.boot") version "3.2.5"
    id("io.spring.dependency-management") version "1.1.4"
}

group = "springcloud.config.client"
version = "0.0.1-SNAPSHOT"

java {
    sourceCompatibility = JavaVersion.VERSION_17
}

configurations {
    compileOnly {
        extendsFrom(configurations.annotationProcessor.get())
    }
}

repositories {
    mavenCentral()
}

extra["springCloudVersion"] = "2023.0.1"

dependencies {
    implementation("org.springframework.cloud:spring-cloud-starter-config")
    implementation ("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-web")
    compileOnly("org.projectlombok:lombok")
    annotationProcessor("org.projectlombok:lombok")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

dependencyManagement {
    imports {
        mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}")
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

 

application.yml 파일

server:
  port: 8080

spring:
  application:
    name: app1 # Config Repository의 application명
  profiles:
    active: dev # Config Repository의 profile
  config:
	  # config server 접속 정보 설정 "optional:" 제거할 경우 config server 접속 실패 시 에러
    import: optional:configserver:http://localhost:8888 


# @RefreshScope 통한 설정 동적 업데이트를 위한 Actuator Refresh 설정
management:
  endpoints:
    web:
      # base-path: /actuator
      exposure:
        include: "refresh"

 

SampleConfig.java 파일

불러온 설정 정보를 객체화하기 위한 클래스 파일이다. 

@Setter
@Getter
@ConfigurationProperties("app1.sample")
@RefreshScope  // 설정 정보 변경 시 리로딩(/actuator/refresh 호출)
@ToString
public class SampleConfig {
    private String message;
}

 

SampleController.java 파일

Client Application으로써 테스트를 위해 Controller클래스를 하나 생성해준 것이다. 불러온 설정 정보를 응답하고 log로 출력만 해준다.

@RestController
@RequiredArgsConstructor
@Slf4j
public class SampleController {

    private final SampleConfig sampleConfig;

// @Value로 참조 할 경우 application.yml의 "optional:configserver:..." 설정의 optional 안먹힘(config- server 접속 실패 시 구동 에러) 
//    @Value("${app1.sample.message}")
//    private String message;  

    @Value("${app2.profile.userName}")
    private String userName;

    @GetMapping("/config/app1")
    public ResponseEntity<String> configApp1() {
        log.info("{}", sampleConfig);
        //log.info("{}", message);
        return ResponseEntity.ok(sampleConfig.toString());
    }
}

 

ConfigClientApplication.java 파일

@SpringBootApplication
@EnableConfigurationProperties(SampleConfig.class) // 객체 설정 정보화를 위한 설정
public class ConfigClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigClientApplication.class, args);
    }
}

 

이제 config-client 어플리케이션도 테스트해본다. 테스트로 생성한 Controller의 http://localhost:8080/config/app1을 호출하면 app1의 dev profile(application.yml 파일에서 설정)의 설정 정보가 출력된다. 

 

 

설정 변경 테스트

다음으로 설정 동적 변경을 테스트해본다. 먼저 config-file에서 설정 정보의 내용을 다음과 같이 변경해준다. 기존 message 항목 값의 뒤에 '- changed!!'를 추가하고 github의 config-file에 반영한다. 

 

config-client에서 설정 정보 재로딩을 위해 actuator의 refresh를 호출(/actuator/refresh)한다. 

 

위 URL을 호출하면, config-client 로그를 보면 아래와 같이 재로딩에 대한 로그가 찍힌다.

 

다시 테스트 URL을 호출해보면 메시지가 변경된것을 확인할 수 있다.

 

설정 암호화

마지막으로 설정 암호화를 테스트해본다. 먼저 암호화를 위해 config-server의 application.yml 파일을 아래와 같이 수정한다. 기존 설정에 encrypt.enabled: false 설정과 encrypt.key 설정을 추가한다. encrypt.enabled: false 설정은 암호화를 사용하겠다는 의미이고, encrypt.key는 암복호화 시 사용할 대칭키를 작성해준다.

server:
  port: 8888

spring:
  application:
    name: springcloud-config-server  # Confing Server  이름
  cloud:
    config:
      server:
        git:
          uri: https://github.com/ws0110/springcloud-config  # 설정파일이 있는 깃 주소
          default-label: main # 깃 주소의 브랜치 이름
          search-paths: config-file/** # 설정 파일들을 찾을 경로
        encrypt:
          enabled: false # config(설정) 정보 암호화

encrypt:
  key: springcloud-config-enc-key  # config 정보 암호화를 위한 대칭키

 

이제 config-server를 재기동하고 암복호화를 테스트 해본다. 아래와 같이 POST방식으로 /encrypt로 암호화, /decrypt로 복호화가 가능하다. 필자는 아래 명령으로 'Dev-Username', 'Real-Username' 문자열을 암호화하고, 'Dev-Username' 암호화된 값을 복호화 해봤다. 

# 암호화 
curl -d "Dev-Username" -X POST "http://localhost:8888/encrypt"
curl -d "Real-Username" -X POST "http://localhost:8888/encrypt"

# 북호화
curl -d "8bb17ffb18e3047bd322c681ef4f83d2f81172bf3b636624c97562b5fac8cd6f" -X POST "http://localhost:8888/decrypt"

 

다음으로 암호화된 값을 실제 적용하기 위해 config-file을 작성해준다. 아래와 같이 config-file에 app2 디렉토리를 생성하고 하위에 app2-dev.yaml, app2-real.yaml 파일을 생성해줬다. 그리고 yaml파일 메시지에 위에서 암호화한 값을 적용해줬다. 암호화된 값을 적용할 때 암호화된 값 앞에 ‘{cipher}' 을 붙여줘야 한다. 

 

config-client에서 이제 app2의 설정을 불러오기 위해 application.yml파일을 변경한다.

server:
  port: 8080

spring:
  application:
    name: app2 # Config Repository의 application명 => 테스트를 위해 app2로 변환
   ...

 

테스트를 위해 config-client의 SampleController로 아래와 같이 수정해준다.

@RestController
@RequiredArgsConstructor
@Slf4j
public class SampleController {

    private final SampleConfig sampleConfig;

    @Value("${app2.profile.userName}")
    private String userName;   // app2 설정

    @GetMapping("/config/app1")
    public ResponseEntity<String> configApp1() {
        log.info("{}", sampleConfig);
        return ResponseEntity.ok(sampleConfig.toString());
    }

    // app2 테스트를 위한 메소드 추가 
    @GetMapping("/config/app2")
    public ResponseEntity<String> configApp2() {
        log.info("{}", userName);
        return ResponseEntity.ok(userName);
    }
}

 

이제 config-client 재기동 해주면 되는데 이때 암복호화 대칭키를 실행 환경변수로 설정(ENCRYPT_KEY)해준다. ENCRYPT_KEY 값은 config-server의 application.yml에서 설정한 값과 동일한 값을 적용하면 된다.

 

이제 테스트 URL을 호출해보면 아래와 같이 암호화해서 설정한 config-file의 값이 복호화되서 출력되는 것을 확인할 수 있다.

 

전체 테스트 소스는 다음에서 확인이 가능하다.

https://github.com/ws0110/springcloud-config

반응형

댓글