본문 바로가기

개발(합니다)/Java&Spring

[spring boot 설정하기-18] spring cloud eureka(2) 설정 및 테스트 소스

반응형

지난번 포스팅에서는 이론에 관한 정리를 했고 이번 포스팅에는 구현에 관해 포스팅 하도록 하겠습니다.


1. Eureka Server

패키지 구조는 아래와 같습니다.

1-1. 의존성 추가

이전에 작성한 cloud config와 연동하기 위해서 추가적으로 의존성을 넣었습니다.

dependencies {

//    cloud config
    implementation 'org.springframework.boot:spring-boot-starter-amqp'
    implementation 'org.springframework.cloud:spring-cloud-bus'
    implementation 'org.springframework.security:spring-security-rsa'
    implementation 'org.springframework.cloud:spring-cloud-starter-bus-amqp'
    implementation 'org.springframework.cloud:spring-cloud-starter-config'
    implementation 'org.springframework.cloud:spring-cloud-stream-binder-rabbit'

    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.cloud:spring-cloud-starter'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.amqp:spring-rabbit-test'

//    eureka
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'

}

1-2. EurekaServerApplication.java

@EnableEurekaServer을 추가합니다.

package com.otrodevym.eurekaserver;

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

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

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

}

1-3. bootstrap.yml

config의 위치를 지정합니다.

spring:
  application:
    name: eurekaserver
#  profiles:
#    active: default
  #  config:
  #    import: optional:configserver:http://localhost:8888
  cloud:
    config:
      uri: http://localhost:8888  # Config Server 위치

encrypt:
  key: my_config_key

1-4. cloud config -> eurekaserver/eurekaserver.yml

eureka ha(고가용성)을 위해서 peer 구분과 port 구분을 했습니다.

my-config.name: default eureak
server:
  port: 8760
spring:
  profiles: default
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: "{cipher}08fde35c4f79cc080854c47871e241fc293fa157b5d9151be5a05fc5aed1d2bb"

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    shutdown:
      enabled: true

eureka:
  # instance:
  #   hostname: localhost
  server:
    wait-time-in-ms-when-sync-empty: 5    # Eureka Server 가 시작되고 Peer nodes 로부터 Instance 들을 가져올 수 없을 때 얼마나 기다릴지 (디폴트 3000ms, 운영 환경에선 삭제 필요)
      # 일시적인 네트워크 장애로 인한 서비스 해제 막기 위한 보호모드 해제 (디폴트 60초, 운영에선 삭제 필요)
      # 원래는 해당 시간안에 하트비트가 일정 횟수 이상 들어오지 않아야 서비스 해제하는데 false 설정 시 하트비트 들어오지 않으면 바로 서비스 제거
    enable-self-preservation: false
  client:
    register-with-eureka: false           # 유레카 서비스에 (자신을) 등록하지 않는다. (클러스터 모드가 아니므로) -> false 로 해도 피어링이 된다.
    fetch-registry: false                 # 레지스트리 정보를 로컬에 캐싱하지 않는다. (클러스터 모드가 아니므로)
    # serviceUrl:
    #   defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

# eureka:
#   # service instance에 대한 설정
#   instance:
#     statusPageUrlPath: /actuator/info
#     healthCheckUrlPath: /actuator/health
#   # eureka client 설정
#   client:
#     serviceUrl:
#       defaultZone: http://localhost:3000/eureka/
# # /actuator/info 호출 시 출력 된 Application 정보
# info:
#   app:
#     name: Account Example Application
#     version: 1.0.0
#     discription: This is a demo project for eurkea



---

my-config.name: peer1 eureak
server:
  port: 8762
spring:
  profiles: peer1
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: "{cipher}08fde35c4f79cc080854c47871e241fc293fa157b5d9151be5a05fc5aed1d2bb"

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    shutdown:
      enabled: true

eureka:
  instance:
    hostname: peer1
  server:
    wait-time-in-ms-when-sync-empty: 5    # Eureka Server 가 시작되고 Peer nodes 로부터 Instance 들을 가져올 수 없을 때 얼마나 기다릴지 (디폴트 3000ms, 운영 환경에선 삭제 필요)
      # 일시적인 네트워크 장애로 인한 서비스 해제 막기 위한 보호모드 해제 (디폴트 60초, 운영에선 삭제 필요)
      # 원래는 해당 시간안에 하트비트가 일정 횟수 이상 들어오지 않아야 서비스 해제하는데 false 설정 시 하트비트 들어오지 않으면 바로 서비스 제거
    enable-self-preservation: false
  client:
    register-with-eureka: false           # 유레카 서비스에 (자신을) 등록하지 않는다. (클러스터 모드가 아니므로) -> false 로 해도 피어링이 된다.
    fetch-registry: false                 # 레지스트리 정보를 로컬에 캐싱하지 않는다. (클러스터 모드가 아니므로)
    serviceUrl:
      defaultZone: http://peer2:8763/eureka/


---

my-config.name: peer2 eureak
server:
  port: 8763
spring:
  profiles: peer2
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: "{cipher}08fde35c4f79cc080854c47871e241fc293fa157b5d9151be5a05fc5aed1d2bb"

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    shutdown:
      enabled: true

eureka:
  instance:
    hostname: peer2
  server:
    wait-time-in-ms-when-sync-empty: 5    # Eureka Server 가 시작되고 Peer nodes 로부터 Instance 들을 가져올 수 없을 때 얼마나 기다릴지 (디폴트 3000ms, 운영 환경에선 삭제 필요)
      # 일시적인 네트워크 장애로 인한 서비스 해제 막기 위한 보호모드 해제 (디폴트 60초, 운영에선 삭제 필요)
      # 원래는 해당 시간안에 하트비트가 일정 횟수 이상 들어오지 않아야 서비스 해제하는데 false 설정 시 하트비트 들어오지 않으면 바로 서비스 제거
    enable-self-preservation: false
  client:
    register-with-eureka: false           # 유레카 서비스에 (자신을) 등록하지 않는다. (클러스터 모드가 아니므로) -> false 로 해도 피어링이 된다.
    fetch-registry: false                 # 레지스트리 정보를 로컬에 캐싱하지 않는다. (클러스터 모드가 아니므로)
    serviceUrl:
      defaultZone: http://peer1:8762/eureka/

1-5. 인텔리제이 실행 설정

VM options : -DSpring.profiles.active=default 
VM options : -DSpring.profiles.active=peer1 
VM options : -DSpring.profiles.active=peer2

2. Eureka Client

Client는 1개로 만들어서 같은 요청과 응답을 하는 하나의 서비스이지만
포트를 다르게 주어 요청과 응답이 서로 다른것을 확인하도로 구성합니다.

패키지 구성은 아래와 같습니다.

2-1. 의존성 추가

eureka Client도 Server와 마찬가지고 cloud config를 사용하기 위해 추가로 의존성을 넣었습니다.


dependencies {

//    cloud config
    implementation 'org.springframework.security:spring-security-rsa'
    implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'
    implementation 'org.springframework.cloud:spring-cloud-starter-bus-amqp'
    implementation 'org.springframework.cloud:spring-cloud-starter-config'
    implementation 'org.springframework.cloud:spring-cloud-bus'

    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.cloud:spring-cloud-starter'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

//    eureka client
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}

2-2. EurekaClientApplication.java

@EnableDiscoveryClient 유레카 클라이언트를 사용하기 위한 설정입니다.
EurekaClient와의 차이는 위쪽에 설명되어 있습니다.

@EnableFeignClients, @LoadBalanced 두 bean은 서비스 검색을 위해 추가했습니다.
유레카 레지스트레에서 등록되어 있는 정보를 가져와 이벤트 서비스의 위치를 알지 못해도 호출이 가능하게 합니다.

  • 스프링 디스커버리 클라이언트
    스프링 클라우드 표준 방식으로 @EnableDiscoveryClient를 사용하며
    @LoadBalanced로 RestTempate를 생성하여 사용합니다.
  • 넷플릭스 Feign 클라이언트
    @EnableFeignClients를 사용
package com.otrodevym.eurekaclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;


@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {

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

    @LoadBalanced       // 스프링 클라우드가 리본이 지원하는 RestTemplate 클래스 생성하도록 지시
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

}

2-3. bootstrap.yml

cloud config 설정을 잡아줍니다.


spring:
  application:
    name: eureka-service
  profiles:
    active: default
#  config:
#    import: optional:configserver:http://localhost:8888
  cloud:
    config:
#      enabled: false
      uri: http://localhost:8888 # Config Server

encrypt:
  key: my_config_key

#eureka:
#  instance:
#    # 랜덤값을 이용하여 instance id를 고유하게 재정의
#    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}

2-4. cloud config -> eurekaserver/eurekaserver-service.yml

eureka client도 여러개의 서비스를 분산하여 호출하기 위해 서로 다른 포트로 설정을 합니다.

my-config.name: default eureak
server:
  port: 8880
spring:
  profiles : default
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: "{cipher}08fde35c4f79cc080854c47871e241fc293fa157b5d9151be5a05fc5aed1d2bb"

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    shutdown:
      enabled: true

eureka:   ## 추가
  instance:
    prefer-ip-address: true       ## 서비스 이름 대신 IP 주소 등록
    lease-renewal-interval-in-seconds: 3  # 디스커버리한테 3초마다 하트비트 전송 (디폴트 30초)
    lease-expiration-duration-in-seconds: 2 # 디스커버리는 서비스 등록 해제 하기 전에 마지막 하트비트에서부터 설정된 시간(second) 기다린 후 서비스 등록 해제 (디폴트 90초)
  client:
    register-with-eureka: true    ## Eureka Server 에 서비스 등록
    fetch-registry: true          ## 레지스트리 정보를 로컬에 캐싱
    service-url:
      defaultZone : http://localhost:8760/eureka/

---

my-config.name: service eureak
server:
  port: 8881
spring:
  profiles : peer1
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: "{cipher}08fde35c4f79cc080854c47871e241fc293fa157b5d9151be5a05fc5aed1d2bb"

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    shutdown:
      enabled: true

eureka:   ## 추가
  instance:
    prefer-ip-address: true       ## 서비스 이름 대신 IP 주소 등록
    lease-renewal-interval-in-seconds: 3  # 디스커버리한테 3초마다 하트비트 전송 (디폴트 30초)
    lease-expiration-duration-in-seconds: 2 # 디스커버리는 서비스 등록 해제 하기 전에 마지막 하트비트에서부터 설정된 시간(second) 기다린 후 서비스 등록 해제 (디폴트 90초)
  client:
    register-with-eureka: true    ## Eureka Server 에 서비스 등록
    fetch-registry: true          ## 레지스트리 정보를 로컬에 캐싱
    service-url:
      defaultZone : http://localhost:8762/eureka/,http://localhost:8763/eureka/ 

---

my-config.name: service eureak
server:
  port: 8882
spring:
  profiles : peer2
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: "{cipher}08fde35c4f79cc080854c47871e241fc293fa157b5d9151be5a05fc5aed1d2bb"

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    shutdown:
      enabled: true

eureka:   ## 추가
  instance:
    prefer-ip-address: true       ## 서비스 이름 대신 IP 주소 등록
    lease-renewal-interval-in-seconds: 3  # 디스커버리한테 3초마다 하트비트 전송 (디폴트 30초)
    lease-expiration-duration-in-seconds: 2 # 디스커버리는 서비스 등록 해제 하기 전에 마지막 하트비트에서부터 설정된 시간(second) 기다린 후 서비스 등록 해제 (디폴트 90초)
  client:
    register-with-eureka: true    ## Eureka Server 에 서비스 등록
    fetch-registry: true          ## 레지스트리 정보를 로컬에 캐싱
    service-url:
      defaultZone : http://localhost:8762/eureka/,http://localhost:8763/eureka/ 

 

2-5. EurekaResClientContoller.java

요청에 대한 응답을 하는 컨트롤러입니다.

package com.otrodevym.eurekaclient;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletRequest;


@RestController
public class EurekaResClientContoller {

    @GetMapping("/res")
    public String resClient() {
        return "resClienbt1";
    }

    @GetMapping("/feign/res/{nick}")
    public String feignResClient(ServletRequest req, @PathVariable("nick") String nick) {

        return "Feign Nick : " + nick + " port is : " + req.getServerPort();
    }
}

2-6. EurekaReqClientController.java

요청을 하는 컨트롤러입니다.

package com.otrodevym.eurekaclient;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;


@RestController
@Slf4j
public class EurekaReqClientController {

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private RestTemplate restTemplate;

    private static final String EUREKA_SERVICE_NAME = "eureka-service";


    @Autowired
    private EurekaReqFeignClient eurekaReqFeignClient;

    @GetMapping("/ping")
    public List<ServiceInstance> ping() {
        List<ServiceInstance> instances = discoveryClient.getInstances("");
        log.info("INSTANCES: count ={}", instances.size());
        instances.forEach(it -> log.info("INSTANCES: id={}, port={}", it.getServiceId(), it.getPort()));
        return instances;
    }

    @GetMapping("/req")
    public String reqMessage() {
        String apiPath = "/res";
        ResponseEntity<String> ackMessage = restTemplate.getForEntity("http://" + EUREKA_SERVICE_NAME + apiPath,
                String.class);

        return "Service-A: inst001 호출" + " > " + ackMessage.getBody();
    }


    @GetMapping("/feign/{nick}")
    public String getNick(@PathVariable("nick") String nick) {
        return eurekaReqFeignClient.getYourNick(nick);
    }

}

2-7. EurekaReqFeignClient.java

Feign으로 요청하기 위한 인터페이스입니다.

package com.otrodevym.eurekaclient;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;


//@FeignClient("${eureka.instance.instance-id}")
@FeignClient("${spring.application.name}")
public interface EurekaReqFeignClient {
    @GetMapping(value = "/feign/res/{nick}")
    String getYourNick(@PathVariable("nick") String nick);
}

2-8. 인텔리 제이 설정

spring.profiles.active를 cloud config에 맞게 설정합니다.

-DSpring.profiles.active=default
-DSpring.profiles.active=peer1
-DSpring.profiles.active=peer2

3. 테스트 및 결과

3-1. docker 실행

rebbitmq를 위한 설정 전파를 위해 실행합니다.

3-2. cloud config 서버 실행

3-3. eureka server 실행

default와 peer1, peer2를 순차적으로 실행하고

cloud conifg 서버를 제대로 읽어왔는지 확인합니다.

3-4. eureka client 실행

eureka client도 순차적으로 실행하고

cloud config 서버 설정을 제대로 가져오는지 확인합니다.

3-5. eureka 기본 설정 : localhost:8760

eureka client 중 localhost:8880이 붙은걸 확인할 수 있습니다.

3-5. eureka peer 설정 : localhost:8762,localhost:8763

아래 결과를 보면 서로 다른 peer의 정보를 참고하고 있는걸 볼 수 있습니다.

반응형