본문 바로가기
Spring Cloud

Spring Cloud - 서비스 디스커버리

by 장중앙 2021. 9. 14.

Netflix의 Eureka를 이용해서 디스커버리 서비스를 구현

클라우드 환경이 되면서 서비스가 오토 스케일링등에 의해서 동적으로 생성되거나 컨테이너 기반의 배포로 인해서, 서비스의 IP가 동적으로 변경.

이 때, 서비스 클라이언트가 서비스를 호출할때 서비스의 위치 (즉 IP주소와 포트)를 알아낼 수 있는 기능을 서비스 디스커버리(Service discovery)라고 부름

 

  • 클라이언트 사이드 디스커버리 패턴(Client-Side Discovery Pattern)
    • 서비스 인스턴스의 네트워크 위치를 찾고 로드밸런싱하는 역할을 클라이언트가 담당하는 방식
  • 서버 사이드 디스커버리 패턴(Server-Side Discovery Pattern)
    • 서버 쪽에서 디스커버리 로직을 구현한 방식

 

 

 

 

Eureka 서버와 클라이언트

discovery-service 프로젝트 생성

Eureka Server 활성화

#application.yml

server:
  port: 8761

spring:
  application:
    name: discoveryservice
    
eureka:
  client:
    register-with-eureka: false   # euraka의 registry에 등록할지 여부를 설정, 서버기 때문에 
    fetch-registry: false         # registy에 있는 정보를 가져올지 여부를 결정
// aplication.java

package com.example.deliveryservice;

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

@SpringBootApplication
@EnableEurekaServer
public class Application {

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

}

실행 Euraka Dashboad 확인

instances currently registered with Eureka를 확인하면 아직 등록된 인스턴스가 없는 것을 확인

 

Eureka Client 생성

Eureka Discovery Client와 웹서비스 구현을 위한 dependency 추가

# application.yml 포트번호를 지정

server:
  port: 9001
  
spring:
  application:
    name: test-user-service
    
eureka:
  client:
    register-with-eureka: true # 유레카 서버에 자신의 정보를 등록해줘야함
    fetch-registry: true 
    service-url:
      defaultZone: http://localhost:8761/eureka

or

# application.yml 랜덤 포트번호 사용

server:
  # 0으로 설정시 유효한 포트로 랜덤 설정함
  # 0으로 여러개 생성시 test-user-service:0 사용하기 때문에 동일한 클라이언트로 식별
  # 랜덤 포트를 사용하는 경우 유레카에서 식별할 수 있는 인스턴스 ID를 재정해 주는 것이 필요
  port: 0
  
spring:
  application:
    name: test-user-service
    
eureka:
  client:
    register-with-eureka: true # 유레카 서버에 자신의 정보를 등록해줘야함
    fetch-registry: true 
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    instance-id:${spring.application.name}:${spring.application.instance_id:${random.value}}

Eureka Client 활성화

// application.java

package com.example.testuserservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class Application {

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

}

실행 Euraka Dashboad 확인

instances currently registered with Eureka를 확인하면 application이 추가된것을 확인할 수 있음

* 아래의 EMERGENCY는 정보가 불일치 할 수도 있다는 의미

test-user-service 인스턴스를 하나 더 추가로 실행

같은 프로그램으로 여러개의 인스턴스를 실행 -> port번호 변경

STS Debug/Run Configurations 기능을 이용

실행시 9001, 9002의 포트로 2개의 인스턴스가 실행중이라는 출력

 

 

Netflix Zuul을 이용한 API Gateway 구현

first-service 프로젝트 생성

FirstServiceController 클래스 추가

// FirstServiceController

package com.example.firstservice;

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

@RestController
@RequestMapping("/")
public class FirstServiceController {
	@GetMapping("/welcome")
	public String welcome() {
		return "Welcpme to the First service";
	}
}

application.yml 작성

# application.yml

server:
  port: 8081
  
spring:
  application:
    name: my-first-service
    
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

 

Second-service 프로젝트 생성

SecondServiceController 클래스 추가

// SecondServiceController

package com.example.secondservice;

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

@RestController
@RequestMapping("/")
public class SecondServiceController {
	@GetMapping("/welcome")
	public String welcome() {
		return "Welcpme to the Second service";
	}
}

application.yml 작성

# application.yml

server:
  port: 8082
  
spring:
  application:
    name: my-first-service
    
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

Netflix ZUUL을 이용해 API Gateway 패턴 구현

zuul-service 프로젝트 생성

porm.xml의 의존성 수정

# porm.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
        #버전 변경
		<!-- <version>2.5.4</version> -->
		<version>2.3.12.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>com.example.zuul-service</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>com.example.zuul-service</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		#zuul 의존성 추가
		 <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-zuul -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            <version>2.2.9.RELEASE</version>
        </dependency>
		
		
		
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Application 클래스에 zuul활성화

# Application.java
package com.example.zuulservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableZuulProxy
public class Application {

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

application.yml에 route정보를 추가

# application.yml
server:
  port: 8000
  
spring:
  application:
    name: my-zuul-service
    
zuul:
  routes:
    first-service:
      path: /first-service/**
      url: http://localhost:8081
    second-serivce:      
      path: /second-service/**
      url: http://localhost:8082

실행시 8000포트의  zuul-service로 부터 라우팅되는 것을 확인 가능

 

로그 확인을 위한 Zuul filter

서비스 라우팅 전에 로그를 남기는 필터를 추가

zuul-service 프로젝트의 porm.xml에 lombok 의존성 추가

lombok 설치, 확인

zuul-service 프로젝트에 ZuulLoggingFilter 클래스 생성

// ZuulLoggingFilter

package com.example.zuulservice;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Component;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class ZuulLoggingFilter extends ZuulFilter {
	@Override
	public boolean shouldFilter() {
		return true;
	}

	@Override
	public Object run() throws ZuulException {
		log.info("start log >>>>");
		
		//	요청 URI 정보를 로그에 기록
		RequestContext ctx = RequestContext.getCurrentContext();
		HttpServletRequest request = ctx.getRequest();
		log.info(request.getRequestURI());
		
		log.info("<<< end log");

		return null;
	}

	@Override
	public String filterType() {
		return "pre";
	}

	@Override
	public int filterOrder() {
		return 0;
	}

}

zuul-service, first-serivce, second-service 실행 후 필터 테스트

first-serivce나 second-service 접속시 로그 필터확인

 

Spring Cloud Gateway를 이용한 API Gateway 패턴을 구현

apigateway-service 프로젝트 생성

application.yml 파일 작성

# application.yml

server:
  port: 8000
  
spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      routes:
      - id: first-service
        predicates:
        - Path=/first-service/**
        uri: http://localhost:8081
      - id: second-serivce
        predicates:
        - Path=/second-service/**
        uri: http://localhost:8082
      
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8761/eureka

 

기존의 first-service와 second-service에 대해 요청시 에러발생

 

 

서비스를 구분하기 위해 입력한 주소가 그대로 서비로 라우팅되는 것을 확인할 수 있음 ⇒ 라우팅된 서비스가 동작할 수 있도록 하기위해서 각 서비스에 서비스를 구분하는 정보를 받아들일 수 있도록 수정

 

first-service와 second-service의 맵핑 정보를 수정

// first-service
package com.example.demo;

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

@RestController
@RequestMapping("/first-service") //수정
public class FirstServiceController {
	@GetMapping("/welcome")
	public String welcom() {
		return "Welcome to the First Service";
	}
}
// second-service
package com.example.demo;

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

@RestController
@RequestMapping("/second-service") //수정
public class FirstServiceController {
	@GetMapping("/welcome")
	public String welcom() {
		return "Welcome to the Second Service";
	}
}

first-service, second-service를 재기동 후 동작 확인

* 단, 서비스를 직접 호출할 시에도 서비스 구분을 위해 부여한 경로를 포함해야하는 번거로움이 있음

 

Filter 1. java code를 이용한 Spring Cloud Gateway Filter

apigateway-service 프로젝트의 application.yml 파일 수정

# application.yml
server:
  port: 8000
  
spring:
  application:
    name: apigateway-service
#  cloud:
#    gateway:
#      routes:
#      - id: first-service
#        predicates:
#        - Path=/first-service/**
#        uri: http://localhost:8081
#      - id: second-serivce
#        predicates:
#        - Path=/second-service/**
#        uri: http://localhost:8082
      
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8761/eureka

FilterConfig 클래스 생성 - 라우팅 정보 및 요청/응답헤더 값을 설정

// Filterconfig

package com.example.apigatewayservice.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Filterconfig {
	@Bean
	public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) {
		return builder.routes()
				.route(r->r.path("/first-service/**")
						.filters(f->f.addRequestHeader("first-request", "first-request-header")
								.addResponseHeader("first-response", "first-response-header"))
						.uri("http://localhost:8081"))
				.route(r -> r.path("/second-service/**")
                        .filters(f -> f.addRequestHeader("second-request", "second-request-header")
                                       .addResponseHeader("second-response", "second-response-header"))
                        .uri("http://localhost:8082"))
                .build();
	}

}

first-service, second-service 프로젝트에 mesage()메서드 추가

필터에서 추가한 요청 헤더의 내용을 출력하는 메서드

// first-service - firstServiceController

package com.example.firstservice;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("/first-service")
public class FirstServiceController {
	@GetMapping("/welcome")
	public String welcome() {
		return "Welcpme to the First service";
	}
	
	@GetMapping("/message")
	public String message(@RequestHeader(value="first-service", defaultValue = "NONE") String header) {
		log.info(header);
		return "First Service Request Header"+ header;
	}
}
// second-service - secondServiceController

package com.example.secondservice;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;



import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("/second-service")
public class SecondServiceController {
	@GetMapping("/welcome")
	public String welcome() {
		return "Welcpme to the Second service";
	}
	
	@GetMapping("/message")
	public String message(@RequestHeader(value="second-service", defaultValue = "NONE") String header) {
		log.info(header);
		return "First Service Request Header"+ header;
	}
}

 

apigateway-service, first-service, second-service를 실행

apigateway로 접근시 log가 출력되고 response-Header가 변경되는것을 확인 

 

Filter 2. 설정파일을 이용한 Spring Cloud Gateway Filter 

기존의 프로젝트에서 수정하여 확인

FilterConfig 클래스에서 Configuration, Bean 어노테이션을 삭제 → 해당 클래스를 일반 클래스로 인식

// FilterConfig
package com.example.apigatewayservice.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//@Configuration
public class Filterconfig {
//	@Bean
	public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) {
		return builder.routes()
				.route(r->r.path("/first-service/**")
						.filters(f->f.addRequestHeader("first-request", "first-request-header")
								.addResponseHeader("first-response", "first-response-header"))
						.uri("http://localhost:8081"))
				.route(r -> r.path("/second-service/**")
                        .filters(f -> f.addRequestHeader("second-request", "second-request-header")
                                 .addResponseHeader("second-response", "second-response-header"))
                        .uri("http://localhost:8082"))
                .build();
	}

}

application.yml파일에 라이팅, 필터 정보 추가

# application.yml
server:
  port: 8000
  
spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      routes:
      - id: first-service
        predicates:
          - Path=/first-service/**
        uri: http://localhost:8081
        filters:
          - AddRequestHeader=first-request, first-request-header-from-yaml
          - AddResponseHeader=first-response, first-response-header-from-yaml
      - id: second-serivce
        predicates:
          - Path=/second-service/**
        uri: http://localhost:8082
        filters:
          - AddRequestHeader=second-request, second-request-header-from-yaml

postman을 이용하여  apigateway 서비스로 first-service, second-service의 welcome 페이지 요청

apigateway로 접근시 log가 출력되고 response-Header가 변경되는것을 확인 

 

Filter 3. Spring Cloud Gateway Custom Filter

Spring Cloud Gateway Filter의 인증, 로깅, 로케일 변경 등을 처리하는 사용자 정의 필터

요청 아이디(request.getId())와 처리상태코드(response.getStatusCode())를 반환하는 Pre필터와 Post필터를 추가

apigateway-service 프로젝트에 클래스, yml 작업

CustomFiler 클래스 생성

// CustomFilter
package com.example.apigatewayservice.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class CustomFilter extends AbstractGatewayFilterFactory<CustomFilter.Config> {
	
	// 설정 정보를 제공하는 클래스
	public static class Config {	
		// 설정 정보가 필요한 경우 명시
	}

	public CustomFilter() {
		super(Config.class);
	}
	
	// 필터의 동작을 정의한 메서드
	@Override
	public GatewayFilter apply(Config config) {
		// custom pre filter 
		return (exchange, chain) -> {
			// 요청이 전달되었을 때 요청 아이디를 로그로 출력
			ServerHttpRequest request = exchange.getRequest();
			ServerHttpResponse response = exchange.getResponse();
			log.info("Custom PRE FILTER: request id = {}", request.getId());
			
			// custom post filter
			// 응답의 처리상태코드를 로그로 출력
			return chain.filter(exchange).then(Mono.fromRunnable(() -> {
				log.info("Custom POST FILTER: response code = {}", response.getStatusCode());
			}));
		};
	}
}

application.yml등록

# application.yml
server:
  port: 8000
  
spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      routes:
      - id: first-service
        predicates:
          - Path=/first-service/**
        uri: http://localhost:8081
        filters:
          - CustomFilter
#          - AddRequestHeader=first-request, first-request-header-from-yaml
#          - AddResponseHeader=first-response, first-response-header-from-yaml
      - id: second-serivce
        predicates:
          - Path=/second-service/**
        uri: http://localhost:8082
        filters:
          - CustomFilter
          #- AddRequestHeader=second-request, second-request-header-from-yaml
          #- AddResponseHeader=second-response, second-response-header-from-yaml
      
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8761/eureka

first-service, second-service에 check()메서드 추가

@GetMapping("/check")
	public String check() {
		return "check is called in First/Second Service";
	}

apigateway-service, first-service, second-service 재기동

postman을 이용하여 apigateway 서비스로 check 서비스 요청후 동작 확인

 

Filter 4. Spring Cloud Gateway Global Filter

모든 라우팅에 적용(일반 필터, 커스텀 필터는 특정 라우팅에서만 동작)

공통적으로 적용해야 할 사항을 구현할 때 사용

모든 필터의 시작과 끝에서 동작

 

apigateway-service 프로젝트에서 수정

GlobalFilter 클래스 생성

코드는 CustomFilter와 유사함

//GlobalFilter
package com.example.apigatewayservice.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {
	
	// 설정 정보를 제공하는 클래스
	@Data
	public static class Config {	
		private String baseMessage;
		private boolean preLogger;
		private boolean postLogger;
	}


	public GlobalFilter() {
		super(Config.class);
	}
	
	// 필터의 동작을 정의한 메서드
	@Override
	public GatewayFilter apply(Config config) {
		// custom pre filter 
		return (exchange, chain) -> {
			// 요청이 전달되었을 때 요청 아이디를 로그로 출력
			ServerHttpRequest request = exchange.getRequest();
			ServerHttpResponse response = exchange.getResponse();
			
			log.info("Global Filter baseMessage = {}", config.getBaseMessage());
			
			if (config.isPreLogger()) {
				log.info("Global Filter is start ... request id = {}", request.getId());
			}
			
			
			// custom post filter
			// 응답의 처리상태코드를 로그로 출력
			return chain.filter(exchange).then(Mono.fromRunnable(() -> {
				if (config.isPostLogger()) {
					log.info("Global Filter is end ... status code = {}", response.getStatusCode());
				}
			}));
		};
	}

}

filter정보를 application.yml에 저장

# application.yml
server:
  port: 8000
  
spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      default-filters:
        - name: GlobalFilter
          args:
            baseMessage: Hello Spring Cloud Gateway Global Filter
            preLogger: true
            postLogger: true

      routes:
      - id: first-service
        predicates:
          - Path=/first-service/**
        uri: http://localhost:8081
        filters:
#          - CustomFilter
#          - AddRequestHeader=first-request, first-request-header-from-yaml
#          - AddResponseHeader=first-response, first-response-header-from-yaml
      - id: second-serivce
        predicates:
          - Path=/second-service/**
        uri: http://localhost:8082
        filters:
          #- CustomFilter
          #- AddRequestHeader=second-request, second-request-header-from-yaml
          #- AddResponseHeader=second-response, second-response-header-from-yaml
      
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8761/eureka

apigateway-serivce 재실행

postman을 이용해서 apigateway-serivce로 check 페이 요청 후 apigateway-service의 로그를 확인

 

 

필터 적용 순서

 

Spring Cloud Gateway - Eureka 연동

기존에 생성한 Eureka 서버인 discovery-service와 apigatewat-service, first-service, second-service를 연동

 

apigateway-service, first-service, second-service의 pom.xml 파일에 eureka-client 의존성 포함 여부 확인

# porm.xml
...
<dependency>

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

Eureka 클라이언트를 활성화 → apigateway-service, first-service, second-service의 application.yml

# application.yml
...
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka

Eureka를 통해서 서비스가 라운팅되도록 apigateway-service의 routes 정보(application.yaml)를 수정

# apigateway-service - application.yml

server:
  port: 8000
  
spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      default-filters:
        - name: GlobalFilter
          args:
            baseMessage: Hello Spring Cloud Gateway Global Filter
            preLogger: true
            postLogger: true

      routes:
      - id: first-service
        predicates:
          - Path=/first-service/**
        uri: lb://MY-FIRST-SERVICE
        filters:
          - CustomFilter
      - id: second-serivce
        predicates:
          - Path=/second-service/**
        uri: lb://MY-SECOND-SERVICE
        filters:
          - CustomFilter
      
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka

 

연동 테스트

discovery-service -> apigateway-service -> first/second-service 순으로 실행

인스턴스에 정상적으로 등록되는 것을 확인가능

 

로드밸런싱 기능

동일한 애플리케이션의 여러 인스턴스로 라우팅이되도록 수정

first-service, second-service에 application.yaml 파일에 랜덤 포트를 설정 → 유레카의 인스턴스 ID를 식별 가능한 형태로 설정

# first-service - application.yml

server:
# port: 8081
  port: 0
  
spring:
  application:
    name: my-first-service
    
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true 
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}}
# second-service - application.yml

server:
# port: 8082
  port: 0
  
spring:
  application:
    name: my-second-service
    
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true 
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}

실행될 여러 인스턴스중 확인을 위해 check()메서드에 현재 인스턴스에 할당된 서비스 포트를 출력하도록 수정(second-service에만 수정)

package com.example.secondservice;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("/second-service")
public class SecondServiceController {
	...
	Environment env;

	@Autowired
	public SecondServiceController(Environment env) {
		this.env = env;
	}

	...

	@GetMapping("/check")
	public String check(HttpServletRequest request) {
        log.info("check is called in Second Service");
        log.info("Server Port from HttpServletRequest: port = {}", request.getServerPort());
        log.info("Server Port from Environment: port = {}", env.getProperty("local.server.port"));
                
        return String.format("check is called in Second Service, Server port is %s from HttpServletRequest and %s from Environment", request.getServerPort(), env.getProperty("local.server.port"));
	}

}

first/second-service 재기동

second-service는 확인을 위해 2개 이상 기동 -> postman을 이용해 확인

apigateway-service의 포트인 8000으로 접근시 실행중인 second-service의 각각의 포트를 출력

 

 

댓글