spring.application.name=Service04
server.port=8084
이 글은 Spring Cloud로 마이크로서비스 아키텍처(MSA)를 직접 구성한 실습 기록의 2부다. Eureka 서비스 디스커버리, API Gateway 라우팅, 로드밸런서 연동을 코드로 따라간다.
1. MSA vs SOA, 그리고 모니터링 도구
Spring Cloud를 활용한 MSA 설치 및 구성-2

마이크로서비스 기반 아키텍처(MSA) VS 서비스 지향 아키텍처(SOA)
* Microservices3Monitoring 도구
• Hystrix 대시보드
• 유레카(Eureka) 관리자 대시보드
• 스프링 부트 관리자 대시보드

2. API Gateway 라우팅 설정
Spring Cloud Gateway로 경로(Path) 기반 라우팅을 구성합니다. /hello/**는 8086 서비스로, /goodbye/**는 8087 서비스로 전달됩니다.
server:
port: 9000
spring:
cloud:
gateway:
routes:
- id: helloModule
uri: http://localhost:8086/
predicates:
- Path=/hello/**
- id: goodbyeModule
uri: http://localhost:8087/
predicates:
- Path=/goodbye/**
http://localhost:9000/hello
-> http://localhost:8086/hello
http://localhost:9000/goodbye
-> http://localhost:8087/goodbye
Gateway Server http://localhost:9000/hello or goodbye
Service #1 http://localhost:8086
Service #1 http://localhost:8087
ApiGateway 9001 - gateway, eurekaclient
Loadbalancer 9000 - loadbalancer, eurekaclient
EurekaServer 8090 - eurekaserver
Service01 8081 - eurekaclient, spring mvc
Service02 8082 - eurekaclient, spring mvc




4. 서비스 모델과 컨트롤러
각 마이크로서비스가 반환할 Member 모델과 REST 컨트롤러입니다.
package com.test.model;
public class Member {
private String id;
private String name;
}



package com.test.model;
public class Member {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private String name;
}


package com.test.controllers;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.test.model.Member;
@RestController
public class MemberController {
@RequestMapping(value="/member", method=RequestMethod.GET)
public Member getInfo() {
Member m = new Member();
m.setId("hong");
m.setName("홍길동");
return m;
}
}

5. pom.xml — 의존성 구성
Spring Boot 2.7 + Spring Cloud 2021.0.3 기반입니다. Eureka 클라이언트/설정 서버 의존성은 필요에 따라 주석을 해제합니다.
<?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.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.test</groupId>
<artifactId>Service01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Service01</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>






6. Eureka 서버와 클라이언트 설정
서비스 디스커버리를 담당하는 Eureka 서버(8090)와, 여기에 자신을 등록하는 각 서비스의 설정입니다. 서버는 자기 자신을 등록/조회하지 않도록 register-with-eureka=false로 둡니다.
spring.application.name=EurekaServer
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
server.port=8090


package com.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}




spring.application.name=Service01
server.port=8081
eureka.client.service-url.defaultZone=http://localhost:8090/eureka



spring.application.name=EurekaServer
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
server.port=8090
eureka.client.service-url.defaultZone=http://localhost:8090/eureka/
http://localhost:8080/member1
http://localhost:8080/member2
http://localhost:8090
http://localhost:8081/member
http://localhost:8082/member2






7. 클라이언트 사이드 로드밸런싱
LoadBalancerClient.choose("SERVICE")로 Eureka에 등록된 인스턴스 중 하나를 골라 호출합니다. 같은 서비스명(SERVICE01/02)이 여러 개면 요청이 분산됩니다. RestTemplate으로 실제 호출까지 이어지는 과정을 단계별로 보여줍니다.
package com.test.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Controller;
@Controller
public class ConsumerControllerClient {
@Autowired
private LoadBalancerClient loadBalancer;
public void getHello() {
ServiceInstance serviceInstance = loadBalancer.choose("SERVICE");
System.out.println(serviceInstance.getUri());
String baseUrl = serviceInstance.getUri().toString();
baseUrl = baseUrl = "/member";
// http://localhost:9000/member
// -> http://localhost:8081/member -> SERVICE01
// -> http://loaclhost:8082/member -> SERVICE02
}
}



package com.test.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.client.RestTemplate;
@Controller
public class ConsumerControllerClient {
@Autowired
private LoadBalancerClient loadBalancer;
public void getHello() {
ServiceInstance serviceInstance = loadBalancer.choose("SERVICE");
System.out.println(serviceInstance.getUri());
String baseUrl = serviceInstance.getUri().toString();
baseUrl = baseUrl = "/member";
// http://localhost:9000/member
// -> http://localhost:8081/member -> SERVICE01
// -> http://loaclhost:8082/member -> SERVICE02
RestTemplate restTemplate = new RestTemplate();
}
}
package com.test.controllers;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.client.RestTemplate;
@Controller
public class ConsumerControllerClient {
@Autowired
private LoadBalancerClient loadBalancer;
public void getMember() {
ServiceInstance serviceInstance = loadBalancer.choose("SERVICE");
System.out.println(serviceInstance.getUri());
String baseUrl = serviceInstance.getUri().toString();
baseUrl = baseUrl = "/member";
// http://localhost:9000/member
// -> http://localhost:8081/member -> SERVICE01
// -> http://loaclhost:8082/member -> SERVICE02
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response=null;
try{
response=restTemplate.exchange(baseUrl, HttpMethod.GET, getHeaders(),String.class);
} catch (Exception ex) {
System.out.println(ex);
}
System.out.println(response.getBody());
}
private static HttpEntity<?> getHeaders() throws IOException {
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
return new HttpEntity<>(headers);
}
}
package com.test;
import java.io.IOException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.ApplicationContext;
import org.springframework.web.client.RestClientException;
import com.test.controllers.ConsumerControllerClient;
@SpringBootApplication
public class LoadbalancerApplication {
public static void main(String[] args) throws RestClientException, IOException {
ApplicationContext ctx = SpringApplication.run(LoadbalancerApplication.class, args);
ConsumerControllerClient consumerControllerClient=ctx.getBean(ConsumerControllerClient.class);
System.out.println(consumerControllerClient);
for(int inx = 0; inx <= 10; inx++) {
consumerControllerClient.getMember();
}
}
}

Service
/member




spring.application.name=LoadBalancer1
server.port=9000
eureka.client.serviceUrl.defaultZone=http://localhost:8090/eureka
package com.test.controllers;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.client.RestTemplate;
@Controller
public class ConsumerControllerClient {
@Autowired
private LoadBalancerClient loadBalancer;
public void getMember() {
ServiceInstance serviceInstance = loadBalancer.choose("SERVICE");
System.out.println(serviceInstance.getUri());
String baseUrl = serviceInstance.getUri().toString();
baseUrl = baseUrl = "/member";
// http://localhost:9000/member
// -> http://localhost:8081/member -> SERVICE01
// -> http://loaclhost:8082/member -> SERVICE02
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response=null;
try{
response=restTemplate.exchange(baseUrl, HttpMethod.GET, getHeaders(),String.class);
} catch (Exception ex) {
System.out.println(ex);
}
System.out.println(response.getBody());
}
private static HttpEntity<?> getHeaders() throws IOException {
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
return new HttpEntity<>(headers);
}
}
package com.test;
import java.io.IOException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.ApplicationContext;
import org.springframework.web.client.RestClientException;
import com.test.controllers.ConsumerControllerClient;
@SpringBootApplication
public class LoadbalancerApplication {
public static void main(String[] args) throws RestClientException, IOException {
ApplicationContext ctx = SpringApplication.run(LoadbalancerApplication.class, args);
ConsumerControllerClient consumerControllerClient=ctx.getBean(ConsumerControllerClient.class);
System.out.println(consumerControllerClient);
for(int inx = 0; inx <= 10; inx++) {
consumerControllerClient.getMember();
}
}
}


//EurekaServer 미사용 코드 쿠현
package com.test;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import java.util.Arrays;
import java.util.List;
@SpringBootApplication
public class LoadBalancerClientApplication {
public static void main(String[] args) {
// SpringApplication.run(LoadBalancerClientApplication.class, args);
ConfigurableApplicationContext ctx = new SpringApplicationBuilder(LoadBalancerClientApplication.class)
.web(WebApplicationType.NONE)
.run(args);
WebClient loadBalancedClient = ctx.getBean(WebClient.Builder.class).build();
for(int i = 1; i <= 10; i++) {
String response =
loadBalancedClient.get().uri("http://example-service/hello")
.retrieve().toEntity(String.class)
.block().getBody();
System.out.println(response);
}
}
}
@Configuration
class DemoServerInstanceConfiguration {
@Bean
ServiceInstanceListSupplier serviceInstanceListSupplier() {
return new DemoInstanceSupplier("example-service");
}
}
@Configuration
@LoadBalancerClient(name = "example-service", configuration = DemoServerInstanceConfiguration.class)
class WebClientConfig {
@LoadBalanced
@Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}
class DemoInstanceSupplier implements ServiceInstanceListSupplier {
private final String serviceId;
public DemoInstanceSupplier(String serviceId) {
this.serviceId = serviceId;
}
@Override
public String getServiceId() {
return serviceId;
}
@Override
public Flux<List<ServiceInstance>> get() {
return Flux.just(Arrays
.asList(new DefaultServiceInstance(serviceId + "1", serviceId, "localhost", 8082, false),
new DefaultServiceInstance(serviceId + "2", serviceId, "localhost", 8083, false)));
}
}
EurekaServer and Ribbon



spring.application.name=Service04
server.port=8084
package com.test.controllers;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.test.model.Member;
@RestController
public class MemberController {
@RequestMapping(value="/goodbye", method=RequestMethod.GET)
public Member getInfo() {
Member m = new Member();
m.setId("lee");
m.setName("이순신");
return m;
}
}



server:
port: 9000
spring:
cloud:
gateway:
routes:
- id: helloModule
uri: http://localhost:8083/
predicates:
- Path=/hello/**
- id: goodbye
uri: http://localhost:8084/
predicates:
- Path=/goodbye/**


spring cloud config server github
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
https://github.com/spring-cloud/spring-cloud-config
GitHub - spring-cloud/spring-cloud-config: External configuration (server and client) for Spring Cloud
External configuration (server and client) for Spring Cloud - GitHub - spring-cloud/spring-cloud-config: External configuration (server and client) for Spring Cloud
github.com




server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/formin/MySpringClud.git




server:
port: 8089
spring:
application:
name: MyConfigClient
cloud:
config:
name: myconfig
profile: test
config:
import:
configserver:http://localhost:8888

package com.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class MyConfigClient1Application {
public static void main(String[] args) {
SpringApplication.run(MyConfigClient1Application.class, args);
}
@Value("${message.text:default}")
private String messageValue;
@RequestMapping(value="/")
public String getMessageValue() {
return messageValue;
}
}
java -jar jenkins.war





Amazon.com
Enter the characters you see below Sorry, we just need to make sure you're not a robot. For best results, please make sure your browser is accepting cookies.
www.amazon.com
Amazon.com
Enter the characters you see below Sorry, we just need to make sure you're not a robot. For best results, please make sure your browser is accepting cookies.
www.amazon.com
Amazon.com
Enter the characters you see below Sorry, we just need to make sure you're not a robot. For best results, please make sure your browser is accepting cookies.
www.amazon.com
Amazon.com
Enter the characters you see below Sorry, we just need to make sure you're not a robot. For best results, please make sure your browser is accepting cookies.
www.amazon.com
Amazon.com
Enter the characters you see below Sorry, we just need to make sure you're not a robot. For best results, please make sure your browser is accepting cookies.
www.amazon.com
마치며
정리하면 이번 2부에서는 Eureka(서비스 등록/발견) → Gateway(경로 라우팅) → LoadBalancer(인스턴스 분산 호출)로 이어지는 MSA의 핵심 삼각형을 코드로 구성했습니다. 각 조각이 독립 실행되면서도 Eureka를 통해 서로를 찾는다는 점이 모놀리식과의 결정적 차이입니다. 현대적으로는 Netflix OSS(Eureka/Ribbon) 대신 Spring Cloud LoadBalancer와 Kubernetes 기반 서비스 디스커버리로 대체되는 추세지만, 여기서 다룬 개념(등록·발견·라우팅·분산)은 그대로 유효합니다.
'푸닥거리' 카테고리의 다른 글
| 2023년기준 일학습병행제 - 1 (0) | 2023.01.26 |
|---|---|
| .NET TLS versioncompilation targetFramework httpRuntime targetFramework (0) | 2022.08.02 |
| Spring Cloud를 활용한 MSA 설치 및 구성-1 (0) | 2022.07.24 |
| 교육훈련기관 과정운영 점검을 위한 모니터링 (0) | 2022.07.21 |
| OOP(Object-Oriented Programming) 의 SOLID 원칙 (0) | 2022.07.10 |
댓글