System Compleat.

고가용 서비스 - Spring Cloud - #2 Configure to Config server

Techs


(younjin.jeong@gmail.com, 정윤진)

언제나 설정은 프로세스 동작을 위한 필수 요소였다. 오래된 대부분의 서버 데몬들은 /etc/ 아래에 위치한 설정들을 필요로 했고, 이 설정들은 보통 데몬이 동작할 서버의 네트워크 정보와 같은 것들, 그리고 데몬 (또는 프로세스)의 기능을 조정하는 각종 플래그나 변수들로 이루어져 있다. 그리고 대부분의 경우 이 설정이 변경되어 적용이 필요하다면 프로세스의 재시작 또는 리로드가 필수적이다. 따라서 운영중인 서비스에 포함된 데몬의 설정 변경은 프로세스 재시작으로 인한 서비스에 순단, 또는 그 데몬의 캐시 사용 여부등에 따라 웜업(warm up)등의 작업, 그리고 필요에 따라 이중화를 통한 무중단을 고려해야 하는 등의 고급진 운영의 기술이 필요하다. 하지만 대부분의 사람들은 어려운 것 보다는 쉬운 방향으로 일을 처리하기 때문에, 서비스에 중단 시간을 사전에 공지하고 이 시간내에 설정의 변경, 프로세스의 재시작, 정상 동작의 확인과 같은 일을 수행한다. 따라서 서비스를 사용중인 사용자들은 서비스 중단을 경험하게 되는데, 이런 패턴은 고객을 위한 행동이라기 보다는 운영의 편의를 위한 방식이라고 할 수 있다. 

서버를 클라우드로 바뀌면 모든게 해결되는 줄 알던 시대가 있다. 다운 타임도 스르륵 사라지는 줄 알던 분들도 꽤 있었다. 그렇지 않다. 클라우드의 기본은 "무엇이든 언제든지 뽀개질 수 있다" 이며, 대신 "언제든 조달 가능한 리소스가 있다" 의 가치를 통해 뽀개짐을 새로운 대체재로 치환 하는 기술이 핵심이라고 할 수 있다. 따라서 모든 애플리케이션 및 서비스에 포함되는 컴포넌트는 "뽀개지면 요렇게" 의 사상을 가지고 만들어져야 궁극적인 제로 다운타임을 확보할 수 있을 것이다. 


그럼 그 "뽀개지면 요렇게" 는 어떻게 처리하는 것이 좋을까. 다음번에 자세히 소개하겠지만, Netflix 에서는 Simian Army 라는 도구를 운영한다. 이 도구가 하는 일은 무려 프로덕션 서비스에서 동작하는 서버들을 랜덤하게 죽인다. 물론 프로덕션 뿐만 아니라 새로운 서비스를 개발하거나 배포하기 전에도 테스트에 사용된다. 즉, 이 테스트를 통과하지 못하면 프로덕션에 배포될 수 없다. 우리 나라에 운영 하시는 분들은 아마 이런 구성을 상상이나 하겠는가. 잘 돌아가는 서버를 끈다니 이 무슨 천인 공노할 일인가 말이다. 라고 생각하시겠지. 다음번 소개 이전에 궁금하신 분들은 넷플릭스의 테크 블로그로. (http://techblog.netflix.com/2011/07/netflix-simian-army.html

Chaos Goriila - Netflix Simain Army

위의 이미지는 카오스 고릴라 (Chaos Gorillia) 의 이미지다. Simian Army 에서 카오스 고릴라가 하는 일은 아마존 웹 서비스의 특정 AZ(Availability Zone) 전체의 인스턴스를 다운시킨다. 즉, 데이터 센터 레벨에서의 고가용성을 프로덕션에서 테스트 한다는 말이 되겠다. 한국의 서비스 중에 이런 레벨의 고가용성을 유지하는 회사가 어디 있겠냐 말이다. 


어쨌든 애플리케이션의 설정 변경을 통한 빌드, 재배포, 그리고 이 재배포로 인해 발생하는 다운타임은 그다지 달가운 것이 아니다. 그리고 설정의 변경때마다 새로운 빌드와 새로운 배포를 해야 한다는것은, 만약 매뉴얼 작업으로 처리하고 있다면 꽤나 귀찮고 불편한 일일 것이다. application.properties 에 설정을 변경하면 새로운 jar 또는 war 를 만들어야 하고 만약 톰캣을 따로 사용하고 있다면 파일을 일단 서버에 전송한 후 특정 위치에 배치한 후 프로세스를 재시작한다. 뭐 서버 한두대 돌릴때야 별 문제 없는 방법일지도 모르지만 넷플릭스와 같이 수십, 수백, 수천대의 인스턴스가 동작하고 있는 상황이라면 어떨까. 이런건 업데이트 공지 한시간으로는 안된다. 

그리고 한가지, 아마존 닷컴이 업데이트 한다고 서비스 1시간 다운 시키면 아마 뉴스에 날거다. 주가는 떨어지고. 아마존 웹 서비스는 어떤가. 


우리는 이러한 문제가 왜 발생하는지 알아야 할 필요가 있다. 즉, 기능 업데이트를 제외하고 애플리케이션의 설정(또는 feature flag)을 바꿨기 때문에 서비스에 중단이 발생한다는 것은 사실 달가운 일이 아니라는 것이다. 그 귀찮은 배포와 프로세스 재시작이 기다리는 요단강을 하루에 수십번 넘는다는 것은 즐거운 일이 아니다. 따라서 이런 문제를 해결하기 위해, 2011년 쯤에 Heroku 를 만든 팀이 만들어낸 컨셉인 12 factor 라는게 있다. 내용은 한글로도 있으며, 페이지는 http://12factor.net 에 방문해 보면 현대의 애플리케이션이 어떻게 만들어져야 하는지에 대한 컨셉을 담고 있다. 그것 중, 오늘 이야기할 부분, 바로 설정과 관련된 핵심 그림은 아래와 같다. 

https://12factor.net/ko/build-release-run

2. Config 에서 따온 그림은 아니지만, 중요한 내용은 바로 여기에 있다. 설정과 코드는 분리 되어야 하는데, 이것이 합쳐진 것이 바로 릴리즈 라는 개념인 것이다. 이것이 왜 중요한지 간략하게 요약해 보면 다음과 같다. 

- application.properties 또는 application.yml 이라는 것이 본시 설정이지만 코드 영역에 포함 된 것이다. 

- 따라서 변경을 하면 새로 빌드해야 한다. 이것은 새로운 배포를 필요로 한다. 

- 대부분의 애플리케이션에서 프레임워크 기본 설정 외에 기능에 필요한 설정은 코드 내에 포함되는 경우가 많다. 

- 이것은 배포의 대상이 많으면 많을 수록 (예. 개발 / 테스트 / 프로덕션 환경) 서로 다른 버전의 설정이 코드에 반영되어야 하므로, 관리를 위해 다수의 repository 를 운영하게 된다. 

- 결국 코드는 seamless 하게 각 환경으로 배포될 수 없고, 이는 사람의 손과 복잡한 코드 저장소의 관리를 수행해야 하는 사태가 발생한다. 

- 이 모든것이 설정이 코드에 포함되어 있기 때문에 발생한다. 


물론 이런 문제를 방지해 주는 여러가지 기법들이 있지만, 전술했듯 사람은 쉬운 쪽으로 일을 한다. 특히 일이 급하면 더 그렇다. 설정을 따로 분리해 내는 것 보다 그냥 코드 안에 도메인이나 IP를 때려넣는 것이 더 편하다. "어? 내 PC에서는 잘 되는데...?" 


만약 설정 정보를 API 요청을 통해 한꺼번에 받아올 수 있는 웹 서비스가 있다면 어떨까. 그리고 이 웹 서비스는 코드 저장소를 설정 파일의 소스로 사용한다면 어떨까. 물론 파일 시스템을 사용할 수도 있다. 일단 이것이 오늘 소개할 스프링 클라우드 Config Server 이다. 넷플릭스에서는 Archaius (http://techblog.netflix.com/2012/06/annoucing-archaius-dynamic-properties.html) 라는 도구를 사용한다고 한다. 


스프링 클라우드에서 제공하는 Config server 에 대한 자세한 정보는 다음의 링크에서 얻을 수 있다. https://cloud.spring.io/spring-cloud-config/spring-cloud-config.html 


Config server 의 사용은 이전에 살펴본 넷플릭스 유레카와 마찬가지로 서버와 클라이언트로 구성되어 있으며, 스프링 이니셜라이저 (http://start.spring.io)를 사용한다면 매우 쉽게 사용이 가능하다. 순서는 아래와 같다. 

Spring Cloud Config Server

- http://start.spring.io 에 접근한다. 

- artifact 에 config-service 와 같은 그럴듯한 이름을 넣어준다. 

- Dependencies 에서 Config Server 를 찾아 추가한다. 


- Generate Project 를 눌러 Zip 파일을 다운로드하고, 이를 IDE 를 사용해서 연다. 

- config-service/src/main/java/com/example/ConfigServiceApplication.java 에 @EnableConfigServer 어노테이션을 추가한다. 

package com.example;

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


@SpringBootApplication
@EnableConfigServer
public class ConfigServiceApplication {

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

- application.properties 파일에 아래의 설정을 추가한다. 설정 파일이 위치한 코드 저장소는 github 를 사용했는데, 별도의 git 또는 파일 저장소를 사용할 수 있다. 자세한 내용은 위의 Config Server 설정 부분을 참조 

spring.application.name=config-server
spring.cloud.config.server.git.uri=https://github.com/younjinjeong/spring-cloud-event-sourcing-config
server.port=8888

- 애플리케이션을 빌드하고 실행한다. 

mvn clean package
java -jar target/config-server-0.0.1-SNAPSHOT.jar

따로 설명하지는 않았지만, 스프링 부트 애플리케이션을 구동하기 위해 별도의 WAS 를 사용하지 않았음에 주목한다. 최근 이런 Fat JAR 를 어떻게 서비스에 구동해야 하는지 여쭈어 보는 분들이 종종 계신데, 많이 목격되는 것은 다커(Docker) 를 사용하거나 Cloud Foundry 와 같은 런타임 플랫폼을 사용할 수 있다. 자세한 설명은 따로. 어쨌든 서비스가 정상동작 하는지 확인하기 위해서, 위의 github 를 사용하는 경우 다음의 링크를 통해 확인이 가능하다. http://localhost:8888/user-service/cloud

 

어쨌든 위와 같은 작업을 통해 이제 웹 요청을 통해 코드 저장소에 저장된 설정을 전달할 수 있는 서버가 준비되었다. 서버가 준비되었다면, 클라이언트를 연결해 본다. 


Config Server client 

- http://start.spring.io 로 접근 

- artifact 에 config-client 를 넣는다. 

- Dependencies 에 Config client 를 선택한다. 이외에 원하는 부트 애플리케이션을 만들기 위해 Web, Rest repo, H2 등 원하는 도구를 선택한다. 

- Generate project 를 눌러 프로젝트 파일을 다운로드 받고 압축을 해제하여 IDE 로 개봉한다. ㅎ 

- 별도의 어노테이션 추가는 필요 없지만, 설정 파일에 Config server 의 위치를 명시해야 한다. config-client/src/main/resources/bootstrap.properties 파일을 생성하고 (application.properties 가 아님에 주의) 아래와 같이 Config server 의 위치를 명시해 준다. 

spring.cloud.config.uri=http://localhost:8888
spring.application.name=config-client

- 만약 Github 나 별도의 코드 저장소를 사용한다면, config-client.properties 를 생성하고 아래와 같이 간단한 설정을 넣고 Config 서버에 연결된 코드 저장소에 push 한다. 

server.port=${PORT:9999}

- 이전에 만들어 둔 Config 서버를 시작한다. 

- 지금 생성한 config-client 애플리케이션을 시작한다. 

.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.4.0.RELEASE)

2016-09-19 12:28:37.699 INFO 3977 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at: http://localhost:8888
2016-09-19 12:28:42.443 INFO 3977 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=config-client, profiles=[default], label=master, version=afb483b02756c38f5c2ce3a3e0ad149aefe100c3
2016-09-19 12:28:42.443 INFO 3977 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource [name='configService', propertySources=[MapPropertySource [name='https://github.com/younjinjeong/spring-cloud-event-sourcing-config/config-client.properties'], MapPropertySource [name='https://github.com/younjinjeong/spring-cloud-event-sourcing-config/config-client.yml'], MapPropertySource [name='https://github.com/younjinjeong/spring-cloud-event-sourcing-config/application.yml']]]
2016-09-19 12:28:42.503 INFO 3977 --- [ main] com.example.ConfigClientApplication : No active profile set, falling back to default profiles: default

애플리케이션을 시작하면, 위와 같이 아름다운 Spring 아스키 이미지와 함께 메세지를 확인할 수 있는데, 내용인 즉슨 다음과 같다. 

Fetching config from server at: http://localhost:8888 

애플리케이션이 시작되면서 Config server 로 부터 설정 내용을 가져온다. 그리고 애플리케이션이 정상적으로 시작되었다면, 아래의 내용을 확인할 수 있을것이다. 

Tomcat started on port(s): 9999 (http) 

즉, 설정 파일의 내용을 스프링 애플리케이션이 참조하여 해당 애플리케이션의 포트를 9999로 설정한 것이다. 스프링 부트의 특징중 하나는 각종 설정의 오버라이드가 매우 쉽다는 점인데, 이것들은 서로 참조되는 순서가 있다. 첫번째로 참조되는 것이 바로 bootstart.properties 로, 이 파일에 있는 내용은 애플리케이션의 이름, config server 의 위치와 같이 거의 변경되지 않는 내용을 넣는다. 두번째로 참조되는 것이 이 경우에는 Config server 에 있는 설정 파일의 내용인데, 여기에서 역시 참조 순서가 있다. config server 가 참조하는 코드 저장소에 있는 설정 파일의 이름에 application.properties 가 있다면, 여기에 있는 내용은 모든 config server 를 참조하는 config client 들이 참조하는 전역 설정과 같은 것이다. 그리고 이와 함께 [application-name].properties 가 있다면, 이 application.properties 위에 설정을 merge 해서 사용한다. 이때 동일한 설정이 있다면 이기는 쪽은 애플리케이션이름.프로퍼티 파일이 되겠다. 

이러한 우선 순위 규칙에 따라 애플리케이션 설정의 참조 순서를 적용할 수 있으며, 더욱 중요한 것은 이를 통해 설정과 코드를 분리 할 수 있다는 것이다. 스프링 부트 애플리케이션은 시작할때 서버의 설정 내용을 참조하여 스스로를 설정하며, 이는 12 factor 애플리케이션의 config 부분에 취급되는 매우 중요한 내용중 하나가 되겠다. 그리고 이 포스팅에서 구체적으로 언급하지는 않지만, 각 설정 파일을 환경 별로 생성할 필요 없이 사용될 환경을 지정해서 사용하는 것도 가능하다. 이는 프로파일 이라고 불리는데, 위의 애플리케이션의 시작 로그를 살펴 보면 아래와 같은 로그가 남는것을 확인 할 수 있다. 

No active profile set, falling back to default profiles: default

따라서 여러분이 사용하는 환경 별로 test, staging, production, cloud 와 같은 형태로 구성하여 사용할 수 있다는 것이다. 


Config 에는 다소 민감한 내용들이 있을 수 있다. 이를테면 특정 애플리케이션의 패스워드, 데이터베이스 연결정보, 클라우드 서비스 공급자의 API 키와 같은 내용들이다. 이런 내용들을 보호하기 위해 암/복호화를 사용할 수도 있겠다. Config server 의 보안에 대해서는 아래의 링크를 살펴보기를 권고한다. https://cloud.spring.io/spring-cloud-config/spring-cloud-config.html#_security 


마지막으로, Config 서버는 애플리케이션의 재시작 없이 설정을 변경할 수 있는 메커니즘을 제공한다. 즉, 이미 온라인 상태에서 동작하고 있는 애플리케이션의 feature flag라던가, a/b 테스트 용도의 기능 변경과 같은 것들을 이 변경 가능한 설정에 적용할 수 있다. 적용하는 방법은 아래의 Josh 코드에서 살펴볼 수 있다. 이는 @RefreshScope 라는 어노테이션을 사용하는 것으로 가능하다. 아래의 간단한 코드는 message 라는 설정이 config repo 에 있고, 이를 즉시 변경할 수 있는 예제이다. 

@RefreshScope
@RestController
class MessageRestController {

@Value("${message}")
private String message;

@RequestMapping("/message")
String message() {
return this.message;
}
}

이렇게 구성된 코드는 설정 파일에서 (또는 config repo에서) message 로 지정된 내용을 가져와 /message 에 요청이 있을때 응답한다. 만약 예전의 방법으로 이를 구성한다면, 메세지를 변경할 때마다 (데이터베이스를 사용하던지) 아니면 설정을 수정/ 또는 코드를 수정하여 새로 빌드 후 배포해야 동작할 것이다. 하지만 Config client 에서는 이를 empty post 요청을 보냄으로서 처리가 가능하다. 이에 대한 설명은 아래의 링크를 참조한다. http://cloud.spring.io/spring-cloud-static/spring-cloud.html#_refresh_scope


유레카와 마찬가지로, config server 역시 '설정 및 적용'을 위한 하나의 마이크로 서비스 애플리케이션으로 생각할 수 있다. 이는 당연하게도 유레카 서비스와 함께 연동해서 사용할 수 있으며, 이 경우 더 좋은 가시성을 확보할 수 있다. 따라서 자바, 그중에서도 스프링을 사용하여 마이크로 서비스를 구현하려는 경우에는 바로 이 유레카와 config 서버 두개를 기본 리소스로 확보하여 마이크로 서비스 구조를 확장할 수 있는 방법을 제시한다. 


다음번에는 이렇게 구성된 서비스들 간 API 게이트웨이 및 마이크로 프락시 역할을 하는 넷플릭스의 Zuul 을 스프링 클라우드에서 사용하는 방법에 대해 소개해 보도록 하겠다. 도움이 되시길 바라며. 


(younjin.jeong@gmail.com, 정윤진) 



고가용 서비스 - Spring Cloud - #1 DNS to Eureka

Techs


(정윤진, younjin.jeong@gmail.com) 

언어와 기술의 홍수에서 살다보면 정작 뭐가 중요한지 잊어버리는 경우가 많다. 클라우드의 시대에는 이것이 점점 더 가속화 되었는데 그 대표적인 예가 운영자에게 코드를 배우도록 강요하고, 개발자에게 운영의 기술을 가지도록 하는 것이다. 게다가 클라우드 서비스 자체 뿐만 아니라 그 위에서 동작하는 수많은 새로운 도구들의 출현은 가만히 보고 있자면 숨이 막힐 지경이다. 그것들 중에 어느 포인트가 가장 재미가 있을까 생각을 해 보니, 역시 스프링 클라우드에 대해 이야기 해보는게 좋을것 같다. 

아래의 그림은 DNS가 어떻게 동작하는지 보여준다. 

http://www.thewindowsclub.com/dns-lookup


그림은 thewindowsclub.com 이라는 페이지에서 가져왔다. 예전에 그려둔게 있는것 같은데, 아무튼 없네. 

DNS는 인터넷의 시작점과 함께 존재했던 도구다. DNS를 모르는 개발자나 운영자는 없겠지만, 그것이 실제로 어떻게 동작하는지에 대해 이해하는것은 조금 다른 이야기니까 몇글자 적어보면, DNS는 일단 Domain Lookup System (또는 서비스)다. 모든 사용자 컴퓨터에 보관된 네트워크 정보는 크게 내 아이피 주소와 네트워크 마스크, 그리고 다른 네트워크로 넘어갈때 내 통신을 처리해 줄 게이트웨이의 주소, 그리고 이 DNS 서버의 주소를 기입하게 된다. 집에서 쓰는 공유기에는 편리하게도 DHCP 라는 프로토콜을 통해 이 내용들이 컴퓨터가 연결되면 자동으로 설정되지만, 대부분의 서버 네트워크에서는 이런 것들을 수동으로 설정하여 관리한다. 

어쨌든 www.mydomain.com 과 같은 주소를 브라우저에 넣게 되면 이는 네트워크 정보에 담겨져 있는 DNS 서버로 물어본다. 이때 순서가 있는데, .com .net .io 와 같은 최상위 도메인에 대한 정보를 가지고 있는 서버들을 ROOT DNS 라고 한다. 이 ROOT DNS 서버 정보들은 사전에 공개 되어 있으며, IANA 와 같은 기관에 의해 관리된다. 기본 동작은 이 ROOT 서버에 mydomain 에 관련된 정보를 어디서 찾아야 하는지 물어보고, ns.mydomain.com 과 같은 DNS 서버 주소를 찾게 되면 다시 이 ns.mydomain.com 에서 www 에 대응하는 IP 주소를 넘겨 받아 결국 www.mydomain.com 으로 직접 IP 연결을 통해 접근하게 된다. 

이 과정들은 bind9 과 같은 DNS 서버에서 recursive 라는 플래그를 통해 "내가 대신 물어봐 줄께" 를 켜거나 꺼는 방법으로 서버 관리자는 설정할 수 있다. 즉, 내 컴퓨터에 설정된 1차 및 2차 DNS 서버에서 www.mydomain.com 을 찾기위해 각각 다른 DNS 서버로 물어보는 동작을 대신 처리해 주고, 마지막 결과인 A 레코드, 즉 IP 주소만을 내 컴퓨터로 되돌려 주어 내 컴퓨터는 www.mydomain.com = IP (ex. 1.1.1.1) 과 같은 정보를 가지게 되는 것이다. 그러면 컴퓨터는 1.1.1.1 서버로 http GET 요청을 보내게 되고 해당 서버의 정보가 정확하다면 서버는 GET 요청을 처리해서 돌려주며, 이렇게 받은 데이터를 브라우저 화면에 표시하는게 브라우저에 도메인 주소를 찍을때 발생하는 동작들이다. 

일단 이러한 메커니즘이 왜 필요한지 생각해 볼 필요가 있다. 첫째로는 사람은 숫자보다는 문자를 더 잘 기억한다. 또, 그렇게 기억하는 것이 편리하다. www.amazon.com 이 54.239.25.208 보다 외우기가 쉽다. 두번째로, 어떤 사유에 의해서이건 도메인과 아이피 주소는 바뀐다. 그것이 장애에 의한 고가용성 처리를 위해서건, 단순히 서버를 KT에서 Amazon 으로 옮겨서건 간에 이름과 주소는 바뀔 수 있다. 예를 들면 내 이름은 바뀔 가능성이 매우 낮지만 (거의 없지만), 내가 사는 주소는 언제든 바뀔 수 있는 것이다. 따라서 DNS 는 이런 인터넷 상의 특정 서비스로의 접근을 위한 주소 해석 체계를 제공한다. 

이것은 인터넷에서 서비스간 연결을 위해 사용되기도 하지만, 서비스 내부에서 웹 서버가 데이터베이스 서버를 찾아갈 때 사용하기도 한다. 이 두가지는 보통 external / internal 이라는 용도로 구분하여 사용하곤 하는데, external 의 경우 www.service.com 과 같은 대표 도메인과 메일 처리를 위한 MX 레코드 등 인터넷으로 부터의 참조 목적을 위해 사용하고, internal 의 경우 db-1.service.com, web-1.service.com 과 같이 내부 리소스에 대한 정보를 제공하기 위해 사용된다. 하지만 대부분 internal 의 경우에는 DNS 를 사용하는 대신 DNS 이전에 참조 될 수 있는 /etc/hosts 를 사용하는 경우가 대부분이다. 이는 DNS 를 유지하는 것 보다 /etc/hosts 파일을 보수 하는 것이 더 쉽기 때문이다. 그 말인 즉슨, DNS 서버는 유지하고 관리하는데 추가적인 노력이 "꽤" 많이 드는 서비스 라는 것이다. 그리고 이 DNS 서비스에 등록되어 인터넷에서 "유일하게" 식별 될 수 있는 주소를 FQDN (Fully Qualified Domain Name) 이라고 하며 이는 서비스 코드 내에서 다른 서비스 참조 또는 다른 서비스에 API 요청을 수행할때 이 도메인 주소, 또는 hosts 에 등록된 주소를 사용했다. 

이전에 많이 사용하던 DNS 의 특징을 이 외에도 종합해 보면 다음과 같다. 

- 서비스가 정상적으로 동작하려면 레코드를 사전에 등록해서 사용해야 한다. 

- 최근에는 가능한 DNS 서버도 많이 있지만, 어쨌든 DNS 서버는 기본적으로 서비스에 대한 healthcheck 를 수행하지 않는다. 

- 레코드에 대한 변경이 발생하는 경우 업데이트 및 반영에 시간이 필요하다. 주로 TTL(time to live) 값을 통해 위에 설명한 "되물어보기" 를 피하기 위한 캐시 용도로 사용하는데, 만약 TTL 값이 1시간이라면 TTL 1시간 만료 직전 59분 59초에 이 요청을 수행한 클라이언트는 다음 1시간 동안 이전의 레코드를 캐시에 가지고 요청하게 된다. 즉, DNS 서버가 변경되었을때 클라이언트들에 즉시 업데이트 할 수 있는 메커니즘을 가지고 있지 않다. 

- 따라서 TTL 값을 짧게 잡으려고 하는데, 이 경우에는 DNS 서버에 심각한 부하가 발생할 수 있다. 

- 대표적으로 사용되는 bind9 의 경우 변경 사항의 업데이트를 위해서는 zone 파일의 리로드 또는 프로세스의 재시작이 필요하다. DNS 서비스 프로세스 재시작 해 본적 있는가봉가 



클라우드 이전에는 이런 구성은 사실 문제가 되는 경우가 매우 드물었다. 관리자가 서버 이전을 해야 하는데 TTL 이 기본인 1주일로 잡혀있는 것을 잊어버리고 IP 부터 변경하여 1주일 동안 서비스가 되니 마니 하는 장애 상황이 생길 정도로 말이다. 즉, 서버의 이동과 신규 추가가 발생하는 경우가 극히 계획적이고 자주 발생하지 않기 때문에 DNS를 업데이트를 자주 하지 않아도 '한번 설정하면 어지간하면 그대로 동작하는' 상태가 유지 되었던 것이다. 하지만 클라우드에서는 어떤가. 

external 의 용도로는 기존의 DNS 체계가 인터넷과 밀착되어 있기 때문에 이는 반드시 유지해야 하는 구성이다. 하지만 internal 의 경우, 오토 스케일링, 컨테이너의 사용 등으로 인해 특정 서비스에 연결된 서버의 정보가 수시로, 정말 수시로 변경되게 되고, 이에 대한 정보를 그때그때 IP 로 관리한다는 것은 말이 안되기 때문에 서비스-서버 정보를 매핑해 주는 역할이 필요하게 된다. 따라서 DNS 체계를 사용하려고 봤더니, 이게 업데이트와 업데이트의 반영을 위한 노력이 장난이 아닌것이다. 게다가 클라우드 서비스에서 서버나 컨테이너의 생성과 소멸은 지속적으로 반복되고, 그 생성과 소멸의 시점에 즉시 반영 되어야 그 의미가 있는 것이므로 종전의 DNS 를 사용하는 방법은 옳지 않다고 할 수 있다. 

이에 우리의 변태 엔지니어들 가득한 넷플릭스에서는 Simian Army의 공격에서도 살아남을 수 있는 Eureka 라는 서비스를 만들어 냈다. 이는 오픈 소스로 공개가 되어 있으므로, 아래의 링크를 참조해 보도록 하자. 

https://github.com/Netflix/eureka 



역시 그림은 나랑 안맞... 

아무튼 이 도구가 하는 역할은 Service discovery, 즉 언놈이 어떤 정보를 가지고 동작하는지에 대한 내용을 실시간으로 서비스에서 반영하는 도구다. 위의 DNS 역할은 서비스와 해당 서비스 애플리케이션이 동작하는 위치를 정보를 "요청하는 클라이언트에게만" 전해주는 정보였다. 유레카는 각 클라이언트가 자신의 정보를 유레카 서버에 보내고, 이 정보를 받은 유레카 서버는 각 클라이언트에게 업데이트된 정보를 전달해 주는 체계를 가지고 있다. 이는 다수의 데이터 센터에서 동작할 수 있어 높은 수준의 고가용성으로 지속적으로 서비스가 가능하며, 문제가 발생하여 일순간 서비스에 문제가 된 경우에도 각 클라이언트는 유레카 서버로 부터 받은 정보를 일정 시간동안 로컬에 보유하고 있어 다른 서비스에 연결하는데 문제가 되지 않는다. 

유레카는 서버와 클라이언트로 구성되고, 클라이언트는 자신의 정보를 서버에게, 서버는 클라이언트로 부터 받은 정보를 다른 클라이언트에게 전파하는 역할을 한다. 따라서 서비스 1에 더 많은 요청을 처리하기 위해 서비스를 이루는 서버 또는 컨테이너가 늘어나는 경우, 이 늘어난 컨테이너들에서 동작하는 유레카 클라이언트들은 자신이 동작하는 순간 서버에 자신의 정보를 전달하고 이 정보가 모든 클라이언트에 업데이트 되기 때문에 DNS + Load balancer 의 구성에서 보다 더 빠른 속도로 서비스-인, 서비스-아웃이 가능하다. 

이와 유사한 동작을 하는 도구들은 몇몇 있다. HashCorp의 Consul (https://www.consul.io/) 아파치 주키퍼(https://zookeeper.apache.org/) 등. Consul 의 경우에는 Cloud Foundry 에서도 Service discovery 용도로 사용되고 있는데, 이는 주로 Golang 을 사랑하는 분들에게 많이 이용되는 것 같다. Golang 에서의 Consul 을 사용한 서비스 디스커버리 예제는 이 링크에서 참조 할 수 있다. (http://varunksaini.com/consul-service-discovery-golang/


스프링 클라우드는 단순히 넷플릭스의 OSS 도구 뿐만이 아니라 다른 OSS 생태계에서 클라우드 기반 애플리케이션에 필요한 도구들을 함께 제공한다. 위에 열거한 모든 도구는 스프링 클라우드에서 제공하고 있으며, 이것이 의미하는 바는 위의 모든 도구들이 JVM 기반에서 동작할 수 있는 애플리케이션으로서 서비스에 제공될 수 있다는 의미다. 스프링 클라우드에서 Eureka 서버와 클라이언트를 구성하는 방법은 매우 간단한데, 아래의 단계로 각각 수행하면 된다. 

Eureka 서버 

- http://start.spring.io 에 접근한다. 

- artifact 에 discovery-service 라고 쓴다

- 오른쪽 Dependencies 에 Eureka Server 를 찾아 엔터를 눌러 추가한다. 

- Generate Project 를 눌러 zip 파일을 다운받고, 압축을 해제하여 프로젝트를 IDE, 이를테면 STS나 IntelliJ 와 같은 도구로 연다. 

- discovery-service/src/main/java/com/example/DiscoveryServiceApplication.java  에 @EnableEurekaServer 어노테이션을 추가한다 

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer

public class DiscoveryServiceApplication {

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

- discovery-service/src/main/resources/application.properties 에 아래와 같이 설정을 넣어준다. STS 를 사용하거나 IntelliJ IDEA ultimate 버전을 사용한다면 다양한 eureka 관련 설정 옵션을 확인할 수 있다. 

spring.application.name=discovery-service
server.port=${PORT:8761}

eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.server.enable-self-preservation=true


다 끝났다. Maven 이라면 mvn spring-boot:Run 을 사용하거나 IDE의 플레이 버튼을 눌러 애플리케이션을 실행하면 다음과 같은 Eureka 웹 콘솔을 확인할 수 있다. 

유레카 서버가 준비 되었으니, 클라이언트를 추가해 볼 차례다. 다른것은 그저 스프링 부트 애플리케이션을 만드는 것과 크게 다르지 않고, Dependencies 에 Discovery client 를 추가하면 된다. 

- http://start.spring.io 에 간다. 

- artifact 에 eureka-client 와 같은 애플리케이션 이름을 넣는다. 

- Devpendencies 에 eureka discovery 를 추가하고 Generate project 를 눌러 프로젝트를 다운 받아 압축을 풀고, IDE 로 연다. 

- @EnableEurekaClient 어노테이션을 추가한다. 

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class EurekaClientApplication {

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

- application.properties 를 수정한다. 

spring.application.name=eureka-client
server.port=${PORT:8989}

eureka.instance.hostname=${vcap.application.uris[0]:localhost}
eureka.instance.nonSecurePort=80
eureka.instance.metadataMap.instanceId=${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}}
eureka.instance.leaseRenewalIntervalInSeconds = 1
eureka.instance.lease-expiration-duration-in-seconds=5
eureka.instance.lease-renewal-interval-in-seconds=10
eureka.client.registryFetchIntervalSeconds = 5

스프링 부트 애플리케이션을 실행하고 eureka 서버의 웹 콘솔로 접근해 보면 eureka-client 가 추가된 것을 확인할 수 있다. 

동일한 클라이언트 애플리케이션을 다른 포트로 구동시켜 보자. mvn spring-boot:run -Dserver.port=8980 과 같은 형태로 쉽게 설정을 오버라이드 할 수 있다. 

그러면 EUREKA-CLIENT 라는 이름의 애플리케이션에 2개의 인스턴스가 생겨난 것을 확인할 수 있다. 이때 클라이언트 애플리케이션을 끄고 웹 콘솔을 리프레시 해 보면 등록된 클라이언트들의 정보가 사라진다. 

유레카를 통해 각 클라이언트에 전파되는 정보를 확인하고 싶다면 아래의 주소로 접근해 보자. 

http://localhost:8761/eureka/apps 

<applications>
<versions__delta>1</versions__delta>
<apps__hashcode>UP_1_</apps__hashcode>
<application>
<name>EUREKA-CLIENT</name>
<instance>
<instanceId>172.30.1.24:eureka-client:8980</instanceId>
<hostName>localhost</hostName>
<app>EUREKA-CLIENT</app>
<ipAddr>172.30.1.24</ipAddr>
<status>UP</status>
<overriddenstatus>UNKNOWN</overriddenstatus>
<port enabled="true">80</port>
<securePort enabled="false">443</securePort>
<countryId>1</countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwn</name>
</dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>10</renewalIntervalInSecs>
<durationInSecs>5</durationInSecs>
<registrationTimestamp>1474180884996</registrationTimestamp>
<lastRenewalTimestamp>1474181029539</lastRenewalTimestamp>
<evictionTimestamp>0</evictionTimestamp>
<serviceUpTimestamp>1474180884492</serviceUpTimestamp>
</leaseInfo>
<metadata>
<instanceId>eureka-client:8980</instanceId>
</metadata>
<homePageUrl>http://localhost:80/</homePageUrl>
<statusPageUrl>http://localhost:80/info</statusPageUrl>
<healthCheckUrl>http://localhost:80/health</healthCheckUrl>
<vipAddress>eureka-client</vipAddress>
<secureVipAddress>eureka-client</secureVipAddress>
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1474180884996</lastUpdatedTimestamp>
<lastDirtyTimestamp>1474180884463</lastDirtyTimestamp>
<actionType>ADDED</actionType>
</instance>
</application>
</applications>

이 정보들은 클라이언트가 부트될때 서버로 보내지는 정보들이며, 이는 각 유레카 클라이언트에 전파된다. 이와 같은 동작은 서비스에 어떤 애플리케이션이 얼마나 많은 숫자의 인스턴스로 동작하는지 즉각적인 확인을 가능하게 할 뿐만 아니라, 각 서비스간 연동을 위해 별도의 DNS 체계를 구축할 필요가 없다는 점이다. 유레카를 사용하는 경우, 클라이언트간 로드 밸런싱을 위해 별도의 FQDN을 사용하는 대신 http://EUREKA-CLIENT/your/api/endpoint 의 형태로 요청할 수 있기 때문이다. 

이 유레카 서비스 자체는 각 애플리케이션의 인스턴스 정보만을 공유한다. 이 자체로 "서비스 디스커버리"라는 부분의 역할에만 충실한 마이크로 서비스이며, 이 마이크로 서비스가 제공하는 기능을 통해 다른 컴포넌트들과 유기적으로 연동이 가능하다. 대표적인 것이 Zuul 과 Ribbon 인데, 이에 대해서는 다음에 다시 자세히 설명하는 걸로. 


결론적으로 이 유레카와 같은 도구는 서비스 인스턴스 (서버나 컨테이너와 같은 애플리케이션 기동의 베이스가 되는 리소스들)의 정보 매핑에 예전 레거시에서 사용하던 hosts 나 internal DNS 의 역할과 유사한 동작을 수행하지만 보다 빠르게 리소스의 상태가 변경되는 클라우드에 더욱 맞는 도구라고 할 수 있다. 그리고 이런 도구의 다양한 조합을 통해 애플리케이션의 고가용성을 구현할 수 있다. 스프링 클라우드는, 스프링 개발자라면 누구나 이니셜라이저를 통해 쉽게 유레카와 같은 도구를 사용할 수 있게 한다. 이전에도 언급했지만 Consul 과 Zookeeper 와 같은 도구 역시 스프링 클라우드에 포함되어 있다. 


다음번에는  Config server 에 대해 조금 더 살펴보는 것으로. 

(younjin.jeong@gmail.com, 정윤진)