[Spring Boot] Spring Cloud Eureka Server

728x90

유레카란?

마이크로서비스가 서로를 찾을 때 사용되는 서비스 레지스트리의 이름이다. 유레카는 마이크로서비스 애플리케이션에 있는 모든 서비스의 중앙 집중 레지스트리로 작동한다. 유레카 자체도 마이크로서비스로 생각할 수 있고 더 큰 애플리케이션에서 서로 다른 서비스들이 서로를 찾는 데 도움을 주는 것이 목적이다.

 

유레카의 역할 때문에 서비스를 등록하는 유레카 서비스 레지스트리를 가장 먼저 설정하는 것이 좋다.

서비스 인스턴스가 시작될 때 해당 서비스는 자신의 이름을 유레카에 등록한다. 동일한 이름을 갖는 서비스 인스턴스가 여러개 생성될 수 있다.

 

어느 순간 다른 서비스가 some-service 를 사용해야 할 때 이 때 some-service 의 특정 호스트 이름과 포트 정보를 other-service 코드에 하드 코딩하지 않는다. 대신 other-service 는 some-service 라는 이름을 유레카에서 찾으면 된다. 그러면 유레카는 모든 some-service 인스턴스의 정보를 알려준다.

 

다음으로 oter-service 는 some-service 의 어떤 인스턴스를 사용할지 결정해야 한다. 이때 특정 인스턴스를 매번 선택하는 것을 피하기 위해 클라이언트 측에서 동작하는 로드 밸런싱 알고리즘을 사용한다. 이 때 사용될 수 있는 것이 넷플릭스 프로젝트인 리본(Ribbon)이다.

 

some-service 의 인스턴스를 찾고 선택하는 것은 other-service 가 해야할 일이지만, 이것을 리본에게 맡길 수 있다. 리본은 other-service 를 대신하여 some-service 인스턴스를 선택하는 클라이언트 측의 로드 밸런서이다. 그리고 other-service 는 리본이 선택하는 인스턴스에 대해 필요한 요청을 하면 된다.

 

유레카 서버 스타터 의존성을 아래와 같이 설정한다.


<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>
...

유레카 스타터 의존성이 지정되었으므로 이제 유레카 서버를 활성화시킨다. 애플리케이션이 시작되는 부트스트랩 클래스에서 @EnableEurekaServer 어노테이션을 추가한다.

@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistryApplication {

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

}

이제 애플리케이션을 실행하고 localhost:8080 포트로 접속을 하면 유레카 웹 대시보드가 나올 것이다.

유레카 대시보드에서 여러 정보를 제공하는데 특히 어떤 서비스 인스턴스가 유레카에 등록되었는지를 알려준다.

 

따라서 서비스를 등록할 때 기대한 대로 잘됐는지 확인하기 위해 유레카 대시보드를 확인하면 된다. 현재는 아무 서비스도 등록되지 않았기 때문에 "No instances available' 메시지가 나올 것이다.

유레카 구성하기

하나보다는 여러 개의 유레카 서버가 함께 동작하는 것이 안전하므로 SPOF 를 방지하기 위해서 유레카 서버들이 클러스터로 구성되는 것이 좋다. 기본적으로 유레카는 다른 유레카 서버로부터 서비스 레지스트리를 가져오거나 다른 유레카 서버의 서비스로 자기 자신을 등록할 수 있다.

 

프로덕션 설정에서는 유레카의 고가용성이 바람직하다. 그러나 개발 시에 두 개 이상의 유레카 서버를 실행하는 것은 불편하기도하고 불필요하다. 개발 목적으로는 하나의 유레카 서버면 충분하기 때문이다. 유레카 서버를 올바르게 구성하지 않으면 30초마다 예외의 형태로 로그 메시지를 출력한다. 유레카는 30초 마다 다른 유레카 서버와 통신하면서 자신이 작동 중임을 알리고 레지스트리 정보를 공유하기 때문이다.

 

개발 환경에서 유레카 환경 설정을 구성하는 코드는 아래와 같다. application.yaml 에 아래처럼 구성하자.

server:
  port: 8761
eureka:
  instance:
    hostname: localhost
  client:
    fetchRegistry: false
    registerWithEureka: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

server.port 속성을 8761 로, eureka.instance.hostname 속성을 localhost 로 설정했다. 유레카가 실행되는 호스트 이름과 포트를 나타낸다. 이 속성은 생략가능하고 만약 지정하지 않았다면 유레카가 환경 변수를 참고하여 생성한다.
속성 값을 확실하게 알려주기 위해서 지정하는 것을 권장한다.

 

eureka.client.fetchRegistry 와 eureka.client.regiterWithEureka 는 유레카와 상호 작용하는 방법을 알려주기 위해 다른 마이크로 서비스에 설정할 수 있는 속성들이다. 유레카도 마이크로 서비스이기 때문에 이 두 속성은 유레카 서버가 다른 유레카 서버와 상호 작용하는 방법을 알려주기 위해 사용할 수 있다.

 

이 두 속성의 기본값은 true 이다. 즉, 해당 유레카 서버가 다른 유레카 서버로부터 레지스트리 정보를 가져오며, 다른 유레카 서버의 서비스로 자신을 등록해야 한다는 것을 나타낸다. 여기서는 개발 환경 구성이기 때문에 다른 유레카 서버들이 필요 없으므로 두 속성의 값을 false 로 설정했다.

 

마지막으로 eureka.client.serviceUrl 속성을 설정했다. 이 속성은 영역(zone) 이름과 이 영역에 해당하는 하나 이상의 유레카 서버 URL 을 포함하고 이 값은 Map 에 저장된다. Map 의 키인 defualtZone 은 클라이언트(여기서는 유레카 자기 자신)가 자신이 원하는 영역을 지정하지 않았을 때 사용된다.

 

여기서는 유레카가 하나만 있으므로 defaultZone 에 해당하는 URL 이 유레카 자신의 URL 을 나타내며, 중괄호 안에 지정된 다른 속성(eureka.instance.hostname 과 server.port)의 값으로 대체된다. 따라서 defualtZone 은 http://localhost:8761/eureka/ 가 된다.

자체-보존 모드

eureka.server.enableSelfPreservation 속성이 있다. 유레카 서버는 서비스 인스턴스(유레카 서버의 클라이언트)가 자신을 등록하고 등록 생신 요청을 30초마다 전송하기를 기대한다.

 

즉, 해당 서비스가 정상적으로 동작하고 있는지 확인하는 것이다. 일반적으로 세 번 갱신하는 동안 서비스 인스턴스로부터 등록 갱신 요청을 유레카 서버가 받지 못하면 해당 서비스 인스턴스의 등록을 췻고하게 된다. 레지스트리에서 서비스 인스턴스가 삭제되는 것이다.

 

이렇게 중단되는 서비스의 개수가 임계값을 초과하면 유레카 서버는 네트워크 문제가 생긴 것으로 간주하고 레지스트리에 등록된 나머지 서비스 데이터를 보존하기 위해 자체-보존 모드가 된다. 따라서 추가적인 서비스 인스턴스의 등록 취소가 방지된다.

 

프로덕션 환경에서는 자체-보존 모드를 true 로 설정하는 것이 좋다. 실제로 네트워크 문제가 생겨서 유레카로의 갱신 요청이 중단되었을 때 나머지 활성화 된 서비스들의 등록 취소를 방지할 수 있기 때문이다.

 

하지만, 유레카를 처음 시작한 상태에 어떤 서비스도 등록되지 않았을 때는 오히려 문제가 발생할 수 있다. 이때는 eureka.server.enableSelfPreservation 속성을 false 로 설정하여 자체-보존 모드를 비활성화시킬 수 있다.

eureka:
    ...
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  server:
    enableSelfPreservation: false

네트워크 문제 외에도 여러 가지 이유로 유레카가 갱신 요청을 받을 수 없는 개발 환경에서는 이 속성을 false 로 설정하는 것이 유용하다. 서비스 인스턴스들의 상태가 자주 변경될 수 있는 개발 환경에서 자체-보존 모드를 활성화하면 중단된 서비스의 등록이 계속 유지되어 다른 서비스가 해당 서비스를 사용하려고 할 때 문제를 발생시킬 수 있기 때문이다.

개발 환경에서는 자체-보존 모드를 비활성화 시켜도 좋지만 프로덕션 환경에서는 활성화해야 한다.

프로덕션 환경의 스프링 클라우드 서비스

마이크로 서비스를 프로덕션 환경으로 배포할 때는 고려해야 할 부분이 많다. 유레카의 고가용성과 보안은 개발 시에는 중요하지 않은 관점이지만, 프로덕션에서는 매우 중요하다.

 

두 개 이상의 유레카 인스턴스를 구성하는 가장 쉽고 간단한 방법은 application.yaml 파일에 스프링 프로파일을 지정하는 것이다. 그리고 한 번에 하나씩 프로파일을 사용해서 유레카를 두 번 시작시키면 된다.

아래 코드는 스프링 프로파일을 사용해서 두 개의 유레카 서버를 구성했다.


eureka:
  client:
    service-url:
      defaultZone: http://${other.eureka.host}:${other.eureka.port}/eureka

---
spring:
  profiles: eureka-1
  application:
    name: eureka-1

server:
  port: 8761

eureka:
  instance:
    hostname: eureka1.tacocloud.com

other:
  eureka:
    host: localhost
    port: 8762

---
spring:
  profiles: eureka-2
  application:
    name: eureka-2

server:
  port: 8762

eureka:
  instance:
    hostname: eureka1.tacocloud.com

other:
  eureka:
    host: localhost
    port: 8762

공통 설정을 갖는 기본 프로파일에는 eureka.client.serviceUrl.defualtZone 을 설정하였다. 여기에 지정된 other.eureka.host 와 other.eureka.port 변수의 값은 그 다음에 있는 각 프로파일 구성에서 설정된 값으로 대체된다.

 

기본 프로파일 다음에는 두 개의 프로파일인 eureka-1 과 eureka-2 가 구성되어 있으며, 각 프로파일에는 자신의 포트와 eureka.instance.hostname 이 설정되어 있다. 그리고 각 프로파일에 설정된 다른 유레카 인스턴스를 참조하기 위해 other.eureka.host 와 other.eureke.port 속성도 설정되어 있다. 이 속성들은 프레임워크와는 관계없으며, 기본 프라일에 지정된 other.eureka.host 와 other.eureka.port 변수의 값을 대체하기 위해 필요하다.

 

eureka.client.fetchRegistry 와 eureka.client.regiterWithEureka 를 따로 설정하지 않아도 기본값이 true 이기 때문에 자동으로 구성 환경에 설정값으로 세팅된다. 따라서 각 유레카 서버가 다른 유레카 서버에 자신을 등록하고 레지스트리의 등록 정보를 가져온다.

728x90