일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Spring MSA
- @DeleteMapping
- sftp
- 인텔리제이
- spring
- Microservice
- 파일업로드
- 마이크로서비스
- @PatchMapping
- 인텔리j
- @GetMapping
- 마스터링 스프링 클라우드
- FTP
- IntelliJ
- 업로드
- @PutMapping
- @PostMapping
- @RequestMapping
- MSA
- Java
- 클라우드 네이티브 자바
- Today
- Total
zerofunc
클라우드 네이티브 자바 3장 - 12요소 애플리케이션 설정 본문
혼돈스러운 설정
스프링에서 설정confinguraion 이란? 빈을 어떻게 연결할지 컨테이너에게 알려주는 역할을 하는 스프링의 다양한 애플리케이션 컨텍스트(Application Context)(http://bit.ly/2rjucwJ) 구현체에 대한 입력값
- XML 파일로 작성되어 ClassPathXmlApplicationContext(http://bit.ly/2rj2oZb) 를 통해 적용 가능
- 자바 파일로 작성되어 AnnotationConfigApplicationContext(http://bit.ly/2rjzd8j)를 통해 적용 가능 (자바 방식 설정^Java configuration^)
3장에서 다루는 설정은 12요소 애플리케이션(http://12factor.net/config)에서 정의한 설정을 의미
ex) 패스워드, 포트, 호스트 이름, 기능 활성화 여부 등 애플리케이션이 실행되는 환경에 따라 달라질 수 있는 값을 의미함
이러한 설정은 소스 코드 안에 만능 상수^magic constants^로서 내장되지 않는 편이 좋다.
12요소 애플리케이션에 따르면, 코드 베이스가 중요한 인증 정보를 노출하거나 수정하지 않고도 아무때나 오픈소스로 공개할 수 있는지를 기준으로 설정이 바르게 적용되어 있는지 판별할 수 있다고 한다.
스프링 프레임워크의 설정 지원
스프링은 PropertyPlaceholderConfigurer
클래스(http://bit.ly/2riQBKw)가 도입된 이후 12요소 애플리케이션 스타일의 설정을 지원해왔다. 애플리케이션이 정의되면 properties에 지정된 값으로 XML 설정에 있는 특정 형식의 문자열 값을 대체한다.
2003년부터 PropertyPlaceholderConfigurer
(http://bit.ly/2rj5ixb)를 제공했다.
스프링 2.5에서는 XML 네임스페이스를 지원했고, properties 파일을 위한 XML 네임스페이스를 지원했다.
덕분에 별도의 properties 파일에 정의된 속성값을 읽어서 XML에 있는 특정 문자열 값을 대체할 수 있게 되었다.
12요소 애플리케이션 스타일 설정의 목표?
- 컴파일 되는 애플리케이션 코드 안에 데이터 베이스 연결 정보, 포트 등의 만능 문자^magic strings^가 하드코딩되어 실행 환경이 바뀔 때 마다 설정 정보가 깨지는 위험을 제거하는 것
설정 정보 외부화의 장점
- 소스 코드를 재빌드하지 않아도 다른 설정 정보로 쉽게 교체 가능하다
설정정보 이해를 위한 PropertyPlaceholderConfigurer 클래스
PropertyPlaceholderConfigurer로 외부화된 properties 파일에 정의된 값 출력하기
configuration.projectName=Spring Framework
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
// classpath:는 jar 파일 안에 존재하는 파일을 참조함. file:, url:을 써서 컴파일된 코드 외부에 존재하는 파일도 참조가능
<context:property-placeholder location="classpath:some.properties"/>
<bean class="hj.configuration.configurationbasics.classic.Application">
<property name="configurationProjectName"
value="${configuration.projectName}"/>
</bean>
</beans>
public class Application {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("classic.xml");
}
public void setConfigurationProjectName(String pn) {
LogFactory.getLog(getClass()).info("the configuration project anme is "+ pn);
}
}
Environment 추상화와 @Value
스프링 3.0과 3.1에서 @Value 애노테이션과 Environment 추상화를 통해 자바 방식 설정으로 properties 파일에 정의된 값을 읽어올 수 있게 개선 됐다
Enivironment(http://bit.ly/2s3GiHf) 추상화는 실행되는 애플리케이션과 그 애플리케이션이 실행되는 환경 사이에 참조를 통한 런타임 간접지정을 제공한다.
- key/vlalue로 이루어진 map 처럼 동작한다
@PropertySource 애노테이션을 이용해 필요한 설정값을 직접 정의할 수 있다
@Value 애노테이션을 이용해 환경 변수값을 생성자, 세터 함수, 클래스 필드에 주입 가능하다
PropertySourcesPlaceholderConfigurer(http://bit.ly/2s3TQCz)를 등록하면 스프링 표현 언어나 properties 파일 문법으로 설정값을 읽을 수 있다.
- (PropertySourcesPlaceholderConfigurer 등록)[https://github.com/zerofunc/cloud-native-java-configuration/blob/master/configuration-basics/src/main/java/hj/configuration/configurationbasics/environment/Application.java]
@Configuration
//<1>
@PropertySource("some.properties")
public class Application {
private final Log log = LogFactory.getLog(getClass());
public static void main(String[] args) {
new AnnotationConfigApplicationContext(Application.class);
}
//<2>
@Bean
static PropertySourcesPlaceholderConfigurer pspc() {
return new PropertySourcesPlaceholderConfigurer();
}
//<3>
@Value("${configuration.projectName}")
private String fieldValue;
//<4>
@Autowired
Application(@Value("${configuration.projectName}") String pn) {
log.info("Application constructor: " + pn);
}
//<5>
@Value("${configuration.projectName}")
void setProjectName(String projectName) {
log.info("setProjectName: " + projectName);
}
//<6>
@Autowired
void setEnvironment(Environment env) {
log.info("setEnvironment:" + env.getProperty("configuration.projectName"));
}
//<7>
@Bean
InitializingBean both(Environment env,
@Value("${configuration.projectName}") String projectName) {
return () -> {
log.info("@Bean with both dependencies (projectName):" + projectName);
log.info("@Bean with both dependencies (env):" + env.getProperty("configuration.projectName"));
};
}
@PostConstruct
void afterProperties() {
log.info("fieldValue:" + this.fieldValue);
}
}
결과 값
- @PropertySource : classic.xml 파일 정의 에서 사용된 property-placeholdeㄱ와 비슷하게 properties 파일에서 PropertySource를 설정하는 역할을 한다
- PropertySourcesPlaceholderConfigurer는 BeanFactoryPostProcessor의 구현체. 스프링 빈 라이플 사이클 초기에 호출 돼야하므로 static 빈으로 등록해야한다.
- @Value : 클래스 필드에 사용 가능
- @Value : 생성자의 파라미터에사용 가능
- @Value : 세터 메소드의 파라미터에 사용 가능
- 스프링 Environment 객체를 주입받아서 속성 값을 가져올 수 있음
- @Values를 @Bean이 붙은 메소드의 파라미터에 사용 가능
프로파일
Environment에서 프로파일(http://bit.ly/2s3RjbM)을 사용 가능하다.
- 실행환경에따라 달라지는 빈과 빈 그래프를 프로파일 변경만으로 쉽게 적용 가능하다
- 한 번에 하나 이상의 프로파일 활성화가 가능하다
- 프로파일이 적용되지 않은 빈은 항상 활성화 된다
- 프로파일이 명시적으로 활성화되지 않으면 default 프로파일이 적용된 빈이 활성화 된다
- 프로파일 지정 가능 범위
- XML 파일
- 태그 클래스
- 설정 클래스
- 개별 빈
- @Bean이 붙은 메소드
프로파일을 사용해 환경에 따라 달라지는 빈을 묶어서 사용할 수 있다
ex) 로컬에서는 h2DB, 운영에서는 PostgreSQL 사용
- (활성화된 프로파일에 따라 다른 설정 파일과 빈을 사용하는 설정 클래스)[https://github.com/zerofunc/cloud-native-java-configuration/blob/master/configuration-basics/src/main/java/hj/configuration/configurationbasics/profile/Application.java]
@Configuration
public class Application {
private Log log = LogFactory.getLog(getClass());
@Bean
static PropertySourcesPlaceholderConfigurer pspc() {
return new PropertySourcesPlaceholderConfigurer();
}
@Configuration
//<1>
@Profile("prod")
@PropertySource("some-prod.properties")
//<2>
public static class ProdConfiguration {
@Bean
InitializingBean init() {
return () -> LogFactory.getLog(getClass())
.info("prod InitializingBean");
}
}
@Configuration
@Profile({"default", "dev"})
@PropertySource("some.properties")
public static class DefaultConfiguration {
@Bean
InitializingBean init() {
return () -> LogFactory.getLog(getClass())
.info("default InitializingBean");
}
}
//<3>
@Bean
InitializingBean which(Environment e,
@Value("${configuration.projectName}") String projectName) {
return () -> {
log.info("activeProfiles: '" + StringUtils.arrayToCommaDelimitedString(e.getActiveProfiles()) + "'");
log.info("configuration.projectName: " + projectName);
};
}
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.getEnvironment().setActiveProfiles("dev");//<4>
ac.register(Application.class);
ac.refresh();
}
}
결과 값
prod
프로파일이 활성화 됐을 때만 평가됨dev
프로파일이 활성화 됐거나 아무 프로파일도 활성화 돼있지 않을 때만 평가됨- InitializingBean : 현재 활성화된 프로파일을 기록해 properties 파일에 정의된 값을 읽어옴
dev
프로파일 활성화
다른 프로파일 활성화 방법
- SPRING_PROFILES_ACTIVE 환경변수
- -Dspring.profiles.active
- 서블릿 초기화 파라미터
스프링 부트 방식의 설정
(스프링 부트)[https://spring.io/projects/spring-boot]는 지정된 위치에 정의된 설정 정보를 자동으로 읽어옴
- 설정 정보의 우선 수위
- 명령행 인자
- java:comp/env에 있는 JNDI 설정
- System.getProperties()로 읽어오는 속성
- 운영체제의 환경 변수
- jar 외부에 존재하는 application.properties 파일 or application.yml 파일에 정의된 속성
- jar 내부에 존재하는 application.properties 파일 or application.yml 파일에 정의된 속성
- @Configuration이 붙은 클래스에 @PropertySource로 지정된 곳에 있는 속성
- SpringApplication.getDefaultProperties()로 읽어올 수 있는 기본값
특정 프로파일 활성화 시 src/main/resources/application-활성프로파일이름.properties
파일 속성 정보를 자동으로 읽어올 수 있다
클래스 패스에 (SnakeYAML 라이브러리)[https://bitbucket.org/asomov/snakeyaml] 가 있으면 YAML에서도 properties 파일과 같은 방식으로 속성정보를 가져올 수 있음
- YAML은 계층 구조로 표현함
- properties보다 간결함
- application.yml 파일
configuration:
projectName : Spring Boot
management:
security:
enabled: false
- yml 값을 POJO에 매핑해서 읽어오기
package hj.configuration.configurationbasics.boot;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
//<1>
@EnableConfigurationProperties
@SpringBootApplication
public class Application {
private final Log log = LogFactory.getLog(getClass());
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
@Autowired
public Application(ConfigurationProjectProperties cp) {
log.info("configurationProjectProperties.projectName =" + cp.getProjectName());
}
}
@Component
//<2>
@ConfigurationProperties("configuration")
class ConfigurationProjectProperties {
private String projectName;//<3>
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
}
- @EnableConfiguration : 스프링에게 @ConfigurationProperties가 붙은 POJO에 매핑하라고 알려준다.
configuration
으로 시작하는 설정 값은 @ConfigurationProperties(“configuration”)이 붙은 빝에 매핑된다.- ConfigurationProperties.projectName = configuration.projectName으로 지정된 설정값
결과
org.springframework.boot:spring-boot-starter-actuator
의존성 추가 후
http://127.0.0.1:8080/configprops에 접속하여 어떤 설정 정보가 사용 중인지 확인 가능하다
스프링 클라우드 설정 서버로 중앙 집중형 설정 사용하기
외부화해서 읽어오는 다양한 방식은 알았지만 문제가 남아있음
- 애플리케이션 설정 정보 변경 시 애플리케이션을 재시작해야 한다
- 설정이 분산돼있어 어디의 어떤 설정 정보를 변경해야하는지 파악하기 어렵다
- 추적성이 없다 ( 설정 정보의 수정 이력을 알 수 없음)
- 손쉬운 암/복호화 방법 X
스프링 클라우드 설정 서버
- 설정 정보 분산 문제 해결법
- 모든 설정 정보를 하나의 설정 디렉토리에 모아서 저장하고 모든 애플리케이션이 설정 디렉토리를 참조
- 추적성 문제 해결법
- SCM을 이용하여 설정 정보 변경 관리가 가능하고 추적성도 확보할 수 있다.
- 애플리케이션 재시작, 암/복호화 문제 해결법
스프링 클라우드 설정 서버와 설정 클라이언트가 존재한다.
- 설정 클라이언트
- 설정 서버가 제공한 REST API를 통해 설정 정보를 읽어옴
- 리프레시 스코프를 이용해 애플리케이션을 재시작 없이 설정 변경 사항 반영 가능
- 설정 서버
- 클라이언트와 설정 정보 저장소 사이에 위치함
- 클라이언트 <-> 서비스, 서비스 <-> 설정 정보 저장소로의 통신 과정에서 보안기능을 추가 가능하다
스프링 클라우드 설정 서버 같은 기술은 중요하지만 운영 부담을 증가시킨다. 클라우드 파운드리는 이런 운영 부담을 줄일 수 있게 스프링 클라우드 설정 서버에 바탕을 둔 설정 서버(Config Server)서비스를 제공하고 있다.
설정 서비스는 설정 값에 대한 프록시 역할을 한다.
org.springframework.cloud:spring-cloud-config-server
를 스프링 부트 애플리케이션의 의존관계로 추가 시 스프링 클라우드 설정 서버 기능을 사용할 수 있다.
- @EnableConfigServer 애노테이션으로 스프링 클라우드 설정 서버 기능을 추가한다.
- 설정 서버 applcation.yml 파일
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/cloud-native-java/config-server-configuration-repository
깃 저장소 위치를 지정한다.
- 스프링 클라우드 설정 서비스는 깃 저장소에서 설정파일을 찾는다.
- 깃 저장소 뿐만아니라 로컬 디렉토리나 다른 SCM도 가능하다.
- 설정 정보 저장소 URI는 -D옵션이나 환경 변수로도 지정 가능하다
스프링 클라우드 설정 클라이언트
spring.application.name으로 애플리케이션의 이름을 지정할 수 있다.
스프링 클라우드 기반 서비스는 실행할 때 src/main/resources/bootstrap.properties(또는 yml)
파일을 찾는다.
스프링 클라우드는 bootstrap.properties 파일에서 spring.application.name으로 지정된 서비스의 이름을 알아내고 설정 저보를 읽어올 수 있는 스프링 클라우드 설정 서버의 위치를 파악한다.
bootstrap.properties 파일은 application.properties 파일보다도 먼저 로딩된다. 그래야 설정 정보를 읽어올 수 있기 때문이다.
스프링 클라우드 설정 서버는 디렉토리에서 클라이언트의 spring.application.name으로 설정된 클라이언트 서비스 이름과 같은 이름의 설정 파일을 찾는다.
ex) foo-service 설정 클라이언트는 foo-service.yml or foo-service.properties 파일의 설정 정보가 적용된다.
설정 서비스 실행 후 http://localhost:8888/configuration-client/master 로 들어가면 설정 정보를 확인할 수 있다.
- 설정 클라이언트의 bootstrap.yml
spring:
application: name: configuration-client
cloud:
config:
uri: ${vcap.services.configuration-service.credentials.uri:http://localhost:8888}
스프링 클라우드 설정 서버는 모든 클라이언트에 공통으로 적용되는 값도 반환할 수 있다. 설정 서버의 저장송에 있는 application.properties
나 application.yml
파일에 있는 내용은 모든 클라이언트에게 공통으로 반환된다.
설정 서비스는 특정 클라이언트의 이름과 같은 이름의 properties 또는 yml 파일에 있는 설정값과 application.properties나 application.yml에 있는 값도 모두 포함해서 JSON 형태로 반환한다.
클라이언트의 프로파일에 따른 설정값도 구분해서 읽어올 수 있다.
ex) configuration-client 클라이언트에 dev 라는 프로파일로 설정된 값이 있으면 저장소에 configuration-client-dev.yml 파일의 값을 읽어온다.
보안
깃 저장소에 HTTP 기본 인증 같은 보안처리가 있다면 spring.cloud.config.server.git.username과 spring.cloud.config.git.password 값이 지정되어 있어야 깃 저장소에 접근할 수 있다.
스프링 클라우드 설정 서버도 HTTP 기본 인증으로 보호할 수 있다.
org.springframework.boot:spring-boot-starter-security
의존성 추가후
security.user.name과 security.user.password 값을 설정한다.
spring-boot-starter-security에 들어있는 시큐리티 기능을 가져와 UserDetailService 구현으로 인증 방식을 원하는 대로 처리할 수 있다.
스프링 클라우드 설정 클라이언트는 사용자 이름과 비밀번호를 spring.cloud.config.url 값으로 지정할 수 있다.
ex)http://user:sercret@host.com
새로고침 가능한 설정
중앙 집중형 설정 방식의 단점
- 기존 설정 정보에 의존하는 빈 객체에 변경이 즉시 반영되지 못함
@RefreshScope : 리프레시 스코프를 통해 이 문제를 해결함.
- 설정 서버 애플리케이션
@RestController
//<1>
@RefreshScope
public class ProjectNameRestController {
private final String projectName;
@Autowired
public ProjectNameRestController(
@Value("${configuration.projectName}") String pn) { //<2>
this.projectName = pn;
}
@RequestMapping("/project-name")
String getProjectName() {
return this.projectName;
}
}
- @RefreshSchope : 해당 어노테이션이 붙은 객체는 리프레시 이벤트 발생 시 설정 서비스로부터 설정값을 새로 읽어서 다시 생성될 수 있다.
- 설정 정보를 외부의 설정 서버를 통해 가져오지만 Environment 추상화 덕분에 PropertySource에서 가져오는 방식 그대로 읽어올 수 있다.
리프레시 스코프 안에 있는 빈은 RefreshScopeRefreshed 이벤트 타입의 ApplicationContext 이벤트를 통지받으면 새로 다시 생성된다.
기본 방식은 기존 빈을 완전히 페기하고 새로 만든다.
@RefreshScope이 붙지 않은 빈도 RefreshScopeRefreshed 이벤트에 반응할 수 있다.
- 설정 서버 클라이언트 애플리케이션
@Component
public class RefreshCounter {
private final Log log = LogFactory.getLog(getClass());
private final AtomicLong counter = new AtomicLong(0); // <1>
//<2>
@EventListener
public void refresh(RefreshScopeRefreshedEvent e) {
this.log.info("The refresh count is now at:" + this.counter.incrementAndGet());
}
}
- 이벤트 발생횟수 카운트
- RefreshScopeRefreshedEvent 이벤트 리스너
RefreshScopeRefreshedEvent 유발 방법
- 스프링 부트 액추에이터에서 제공하는 API : http://127.0.0.1/refresh 호출
- jconsole을 이요해 JMX의 refresh 종단점 호출
- 스프링 부트 액추에이터가 제공하는 refresh 단점
- 모두 ApplicationContext 기준으로 동작해서 n개의 클라이언트가 있을 때 설정 내용의 변경을 n개 클라이언트에게 모두 적용하려면 n개의 클라이언트에서 각각 refresh 종단점을 호출해야 한다.
스프링 클라우드 버스를 사용하면 설정 변경 내용을 여러 설정 클라이언트에 한 번에 적용할 수 있다.
스프링 클라우드 버스는 모든 서비스를 스프링 클라우드 스트림이 장착된 버스를 통해 연결한다.
스프링 클라우드 스트림은 바인딩 추상화를 통해 다양한 메시징 기술을 지원한다. 해당 내용은 10장 '메시징’의 ‘스프링 클라우드 스트림’ 절에서 자세히 다룸
- 래빗엠큐RabbitMQ
- 아파치 카프카^Apache Kafka^
- 리액터 프로젝트^Reactor Project^
스프링 클라우드 버스의 장점
- 메시지 버스에 한 번의 메시지만 발송하면 수천 대의 마이크로서비스가 스스로 설정 내용을 변경한다.
- 로컬 레빗앰큐 사용법
- org.springframework.cloud:spring-cloud-starter-bus-amqp를 의존관계로 추가
- 외부 레빗앰큐 사용법
- 스프링 클라우드 설정 서버에 아래와 같이 설정하면 모든 클라이언트에 전달됨
spring: rabbitmq: host: my-rmq-host port: 5672 username: user password: secret
스프링 부트 AMQP 자동 설정을 통해 ConnectionFactory 인스턴스에 전달된다.
스프링 클라우드 버스 클라이언트는 새로고침 메시지를 받으면 RefreshScopeRefreshedEvent 이벤트를 발생시켜서 설정을 갱신하다.
@BusConnectionFactory로 스프링 클라우드 버스 연결에 사용될 ConnectionFactory 인스턴스를 지정할 수 있다.
버스와 관계 없는 다른 인스턴스는 @Primary 애노테이션으로 지정해주면 된다.
스프링 클라우드 버스는 /bus/refresh 종단점을 제공한다.
종단점 호출 시 래빗엠큐 브로커에게 메시지 전송 -> 연결된 모든 노드에 새로고침 메시지 전달
curl -d{} http://127.0.0.1:8000/bus/refresh
정리
클라우드 네이티브 에플리케이션을 설정하는 여러 가지 방법을 알아봤다.
환경 설정 정보를 외부화해 결과물을 다시 빌드할 필요가 없어졌고, 애플리케이션은 주언진 환경에 맞게 동작한다
Written with StackEdit.
'IT > Programming' 카테고리의 다른 글
스프링 MSA 스터디 (0) | 2019.02.25 |
---|---|
Spring @RequestMapping을 간단히 줄인 @PostMapping @GetMapping @PutMapping @DeleteMapping @PatchMapping (0) | 2018.07.18 |