4장 서비스 디스커버리

학습 내용

  • 유레카 서버를 내장한 애플리케이션 배포하기
  • 클라이언트 측 애플리케이션에서 유레카 서버에 연결하기
  • 고급 디스커버리 클라이언트 설정
  • 클라이언트와 서버 사이의 보안 통신하기
  • 가용성을 높이기 위한 설정 및 동료 간 복제 메커니즘
  • 다른 가용 존에 클라이언트 측 애플리케이션 인스턴스 등록하기

유레카 서버를 내장한 애플리케이션 배포하기

서버 측에서 유레카 서버 실행하기

1. 의존성 추가

dependencies {
	implementation 'org.springframework.cloud:spring-cloud-stater-netflix-eureka-server'
}

2. @EnableEurekaServer 애노테이션으로 유레카 서버 활성화

@EnableEurekaServer  
@SpringBootApplication  
public class DiscoveryApplication {
	public static void main(String[] args) {  
		SpringApplication.run(DiscoveryApplication.class, args);  
	}  
}

3. 유레카 디스커버리 클라이언트 비활성화 (디스커버리 서버에서는 필요하지 않음), 포트 설정 등의 환경설정

server:  
	port: ${PORT:8761}  
eureka:  
	client:  
		register-with-eureka: false # 유레카 클라이언트 비활성화  
		fetch-registry: false

4. java -jar 명령으로 서버 실행. 인스턴스 목록은 /eureka/apps API로 확인.

클라이언트 측에서 유레카 실행하기

  • 유레카 클라이언트는 자신을 유레카 서버에 등록하고 호스트, 포트, 상태 정보 URL, 홈페이지 URL을 전송
  • 유레카 서버는 heartbeat (생존신호) 메시지를 전송
  • 유레커 서버는 heartbeat 메시지를 받지 못하면 레지스트리에 해당 클라이언트를 삭제
  • 클라이언트는 서버로부터 레지스트리 목록을 가져와서 캐싱하고 주기적으로 변경사항 점검

1. 의존성 추가

dependencies {
	...
	implementation 'org.springframework.cloud:spring-cloud-stater-netflix-eureka-server'
	...
}

2. 유레카 클라이언트 활성화

  • @EnableDiscoveryClient : spring-cloud-common에 존재, 다른 클라이언트 구현체(컨설,주키퍼 등)을 지원
  • @EnableEurekaClient : spring-cloud-netflix에 존재, 유레카만 지원
@EnableDiscoveryClient  
@SpringBootApplication  
public class ClientApplication

3. 유레카 클리아언트 환경 설정 (포트, 서비스명, 디스커버리 주소)

spring:  
	application: name: client-service  
server:  
	port: ${PORT:8081}  
eureka:  
	client: service-url: default-zone: ${EUREKA_URL:http://admin:admin123@localhost:8761/eureka/} # 디스커버리 주소

4. 애플리케이션 실행 (리스닝 포인트는 인스턴스 시작 시 재정의 가능)

java -jar -DPORT=8081 client-0.0.1-SNAPSHOT.jar
java -jar -DPORT=8082 client-0.0.1-SNAPSHOT.jar

5. 유레카 서버 대시보드에서 서비스 등록 확인!

클라이언트 서비스 종료 시 등록 해제하기

클라이언트 서비스 종료하기 (actuator/shutdown API 활용)

1. actuator 의존성 추가

dependencies {
	...
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	...
}

2. 설정하기

사용할 actuator API 설정 후, /shutdown 활성화

management:
	endpoints: 
		web: 
			exposure: 
				include: health, info, metrics, shutdown
	endpoint:
		shutdown:
			enabled: true
  • 하지만 종료를 해도 유레카 서버에는 해당 서비스가 등록되어있음 (만료되지 않음)
  • Self-preservation mode (자기보호모드) : 네트워크 장애가 발생하여 서비스와 통신이 되지 않아도 해당 서비스를 해제되는 것을 방지하는 모드 (운영 환경에서는 활성화, 개발시에는 비활성화)
  • 따라서 Self-preservation mode 비활성화
eureka:  
	client: 
		register-with-eureka: false # 유레카 클라이언트 비활성화  
		fetch-registry: false
	server: 
		enable-self-preservation: false # 자기 보호 모드(네트워크 장애가 발생하여도 서비스 해제를 방지하는 모드) 비활성화

고급 컨피규레이션 설정

  • Server : eureka.server.*, 서버 관련 설정
  • Client : eureka.client.*, 클라이언트가 레지스트리에서 다른 서비스의 정보를 얻을 수 있는 설정
  • Instance : eureka.instance.*, 포트나 이름 등의 현재 유레카 클라이언트의 행동을 재정의하는 설정

레지스트리 갱신하기

  • 비활성화를 하긴 했지만 해제 시간이 오래 걸림
  • 클라이언트는 서버로 30초(기본값)마다 하트비트 전송 (lease-renewal-interval-in-seconds 설정)
  • 서버는 하트비트를 받지 못하면 90초(기본값)을 대기 후 서비스 등록 해제 (lease-expiration-duration-in-seconds 설정)
  • 아래와 같이 설정 가능
eureka:  
	client:  
		service-url:  
			default-zone: ${EUREKA_URL:localhost:8761/eureka/} # 디스커버리 주소  
	instance:  
		lease-renewal-interval-in-seconds: 1 # 디스커버리한테 1초마다 하트비트 전송  
		lease-expiration-duration-in-seconds: 2 # 디스커버리는 서비스 등록 해제 하기 전에 마지막 하트비트에서부터 2초 기다림

클라이언트에서 등록된 서비스 목록 출력하기

  • com.netflix.discovery.EurekaClient : 유레카 서버가 노출하는 모든 HTTP API를 구현
  • org.springframework.cloud.client.discovery.DiscoveryClient : 넷플릭스 EurekaClient를 대체, 모든 디스커버리 클라이언트용 API 구현
  • 예제 소스
@RestController  
public class TestController {  
	private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class);
	
	@Autowired  
	private DiscoveryClient discoveryClient;  
	
	@GetMapping("/ping")  
	public List<ServiceInstance> ping() {  
		List<ServiceInstance> instances = discoveryClient.getInstances("CLIENT-SERVICE");  
		LOGGER.info("INSTANCES: count = {}", instances.size());  
		instances.forEach(it -> LOGGER.info("INSTANCE: id={}, prot={}", it.getServiceId(), it.getPort()));  
		return instances;  
	}  
}

인스턴스 식별자 변경하기

스프링 클라우드 유레카는 다음과 같이 필드를 조합해 식별자를 자동 생성

${spring.cloud.hostname}:${spring.application.name}:${spring.application.instance_id}:${server.port}

IP 주소 우선하기

  기본적으로 인스턴스는 호스트명으로 등록된다. 그러나 DNS가 없는 경우에는 host 파일에 IP를 등록하지 않으면 인스턴스를 찾지 못하게 된다. 이에 대안으로 eureka.instance.prefer-ip-address 속성을 통해서 서비스의 IP 주소를 사용할 수 있다.

  서비스의 네트워크 위치를 결정하기 위한 방법으로 IP 주소를 사용하면 문제가 발생할 수 있다. 장비에서 하나 이상의 네트워크 인터페이스가 있을 경우에 발생한다. 이를 위해 컨피규레이션 파일에 무시할 패턴 또는 선호하는 네트워크 주소를 설정할 수 있다.

eh1로 시작하는 인터페이스 무시

spring:  
  cloud:  
		inetutils: ignored-interfaces: # 해당 인터페이스 무시 
			- eth1*

선호하는 IP 주소 설정(필수가 아닌 선호이다.)

spring:
	cloud:
		inetutils:
			preferred-networks: 
				- 192.168

응답 캐시

  • 유레카 서버는 기본적으로 인스턴스 리스트의 응답을 캐시한다
    • 즉, 새로운 인스턴스 호출 후 /eureka/apps API 호출 시 바로 표시가 되지 않음
  • 응답 캐시 타임 설정하기
eureka:  
	server:
		response-cache-update-interval-ms: 3000 # Response 캐싱 주기
  • 유레카 클라이언트에서도 레지스트리(인스턴스 목록, 서비스 목록)을 캐싱한다
  • 기본적으로 30초마다 비동기로 갱신
  • 설정 속성
    • registry-fetch-interval-seconds : 캐싱 시간 설정
    • disable-delta : 캐싱 시 변경된 부분만 업데이트할 지 여부 설정
eureka:  
	client:
		registry-fetch-interval-seconds: 3 # 서비스 목록 3초마다 캐싱  
		disable-delta: true #캐싱할 때 변경된 부분만 업데이트

클라이언트와 서버 간의 보안 통신 사용하기

지금까지 유레카 서버는 모든 클라이언트의 연결을 인증하지 않았다. 그러므로 기본 인증을 사용하기 위해 스프링 시큐리티 의존성을 추가하여 최소한의 보안을 적용해보자.

dependencies {
	...
	implementation 'org.springframework.boot:spring-boot-stater-security'
	...
}

그리고 http baisc authentication을 적용하기 위해 설정을 한다.

spring:  
	security:
		user:
			name: admin  
			password: admin123

이제 인증을 위해 클라이언트 application.yml에 아래와 같이 설정하자.

eureka:  
	client: 
		service-url: 
			default-zone: http://admin:admin123@localhost:8761/eureka/ # 디스커버리 주소

이 후에 SSL 연결 등 진보된 보안을 위해 '12장 API 보안 강화하기’에서 다룰 것이다.

안전한 서비스 등록하기

클라이언트에 SSL을 적용해보자.

  1. SSL 사설 인증서 생성하기 (예제로 JRE 폴더/bin/keytool을 이용해보자)
keytool -genkey -alias client -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore.p12 -validity 3650
  1. 위의 명령을 입력하고 나면 keystore.p12 파일이

유레카 API

스프링 클라우드 넷플릭스는 유레카 클라이언트가 JAVA, 스프링 프레임워크를 사용하지 않을 경우 (다른 프로그래밍 언어로 개발됐거나 배포 프로세스에서 등록된 서비스 목록 정보가 필요한 경우) 디스커버리 서버를 이용하기 위해 클라이언트 API를 제공한다.

클라이언트 API 목록

HTTP API 설명
POST /eureka/apps/appID 레지스트리에 새로운 서비스 인스턴스를 등록
DELETE /eureka/apps/appID/instanceID 레지스트리에서 서비스 인스턴스를 제거
PUT /eureka/apps/appID/instanceID 서버에 하트비트를 전송
GET /eureka/apps 등록된 모든 서비스 인스턴스의 상세 정보를 조회
GET /eureka/apps/appID 특정 서비스의 모든 인스턴스의 상세 정보를 조회
GET /eureka/apps/appID/instanceID 특정 서비스의 특정 인스턴스의 상세 정보를 조회
GET /eureka/apps/appID/instanceID/metadata?key=value 메타 정보 입력값을 갱신
GET /eureka/instances/instanceID 특정 ID를 사용하는 모든 등록된 인스턴스의 상세 정보를 조회
PUT /eureka/apps/appID/instanceID/status?value=DOWN 인스턴스의 상태를 갱신

복제와 고가용성

  • 디스커버리 서버의 장애에 대비해 적어도 두 개의 디스커버리 서버를 구성한다.
  • 유레카 디스커버리 서버는 다른 디스커버리 서버 간의 peer-to-peer 모델 기반으로 구성되어있다. 이는 모든 서버가 현재 서버 노드에 모든 서버에게 데이터를 복제하고 하트비트를 보낸다는 것을 의미한다.

예제 아키텍처

넷플릭스 주울 기반의 API 게이트웨이 적용하고 API 게이트웨이를 통해 하나의 서비스에 대해 각기 다른 존(zone)에 등록된 애플리케이션 인스턴스 간의 부하 분산 테스트를 할 수 있다.

다음 다이어그램은 예제 시스템의 아키텍처이다.

enter image description here

예제 애플리케이션 개발하기

'MSA' 카테고리의 다른 글

3장 스프링 클라우드 개요  (0) 2019.01.23
2장 마이크로서비스를 위한 스프링  (0) 2019.01.22
1장. 마이크로서비스 소개  (0) 2019.01.22

+ Recent posts