코딩공작소

MSA(6) - 서비스 디스커버리 본문

어플리케이션개발/MSA

MSA(6) - 서비스 디스커버리

안잡아모찌 2024. 7. 8. 20:10

서비스 디스커버리란 ?

애플리케이션이 사용하는 모든 원격 서비스의 주소가 포함된 프로퍼티 파일을 관리하는 것 처럼 단순하거나 저장소처럼 정형화된 것일 수 있다.

  • 수평확장 : 서비스 내 더 많은 서비스 인스턴스 및 컨테이너를 추가하는 것과 같은 조정
  • 회복성 : 비즈니스에 영향을 주지 않고 아키텍처와 서비스 내부의 문제로 충격을 흡수하는 능력

즉, 디스커버리의 이점은 해당 환경에서 실행 중인 서비스 인스턴스의 수를 빠르게 수평 확장할 수 있다.
또한, 회복성을 향상시켜 엔진이 사용 불가한 서비스를 우회해서 라우팅하기 때문에 다운된 서비스로 입은 피해를 최소화한다.

 

 

기존의 로드 밸런서에 대해

로드 밸런서는 중앙 집중식 네트워크 인프라스트럭처로 처리할 수 있는 대부분의 애플리케이션 크기와 규모를 가진 기업 환경에서 잘 작동한다. SSL 종료를 처리하고 서비스 포트 보안을 관리하는 데 여전히 중요한 역할을 한다.

하지만,
단일 장애 지점을 제공할 수 있고, 수평 확장 능력이 제한된다. 대부분 고정적으로 관리되고 있으며 새로운 서비스 인스턴스가 시작할 때 로드 밸런서에 등록되지 않는다.

 

 

클라우드에서 서비스 디스커버리

  • 고가용성 : 모든 인스턴스는 고가용성, 안정성, 확장성을 제공하고자 동일한 구성을 갖고 협업한다
  • P2P : 모든 노드는 서비스의 인스턴스의 상태를 상호 공유한다
  • 부하분산 : 요청을 동적으로 분산시켜 관리하고 있는 모든 서비스 인스턴스에 분배
  • 회복성 : 로켈에 캐싱하며, 디스커버리 서비스가 가용하지 않아도 애플리케이션은 여전히 작동할 수 있고 로컬 캐시에 저장된 정보를 기반으로 서비스를 찾을 수 있다.
  • 결함 내성 : 결함을 탐지하고 사람의 개입없이 조치되어야 한다

 

 

서비스 디스커버리 아키텍처

  • 서비스 등록 : 디스커버리 에이전트에 등록
  • 클라이언트의 서비스 주소 검색
  • 정보 공유 : 노드 간 서비스 정보를 공유
  • 상태 모니터링 : 서비스가 서비스 디스커버리에 상태를 전달하는 방법

 

서비스는 일반적으로 하나의 서비스 디스커버리 인스턴스에만 등록하며 클러스터 내 다른 노드에 전달하는 데이터 전파 방법으로 P2P모델을 사용한다.

더욱 견고한 접근 방법으로는 클라이언트 측 로드 밸런싱이 있다. 유레카와 함께 클라이언트 측 로드 밸런싱을 사용하는 장점은 서비스 인스턴스가 다운되면, 인스턴스가 레지스트리에서 제거된다는 것이다. 클라이언트 측 로드 밸런서는 레지스트리 서비스와 지속적으로 통신하여 자동으로 레지스트리를 업데이트한다.

 

  1. 디스커버리 서비스와 소통한 후 데이터를 서비스 소비자의 머신 로컬에 저장
  2. 캐시에서 서비스 위치 정보를 검색 -> 여러 서비스 인스턴스에 분배되도록 라운드 로빈 부하 분산 알고리즘처럼 단순한 알고리즘 사용
  3. 클라이언트는 주기적으로 서비스 디스커버리 서비스와 소통해서 서비스 인스턴스에 대한 캐시 갱신

 

 

 

스프링과 넷플릭스 유레카를 사용한 서비스 디스커버리

클라이언트 측 로드 밸런싱을 위해 스프링 클라우드 로드 밸런서를 사용
조직 서비스의 실제 위치는 서비스 디스커버리 레지스트리에 보관 -> 로드 밸런싱을 사용하여 각 서비스 인스턴스에서 레지스트리를 검색하고 캐시

스프링 유레카 구축

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<?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.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.optimagrowth</groupId>
    <artifactId>eurekaserver</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Eureka Server</name>
    <description>Eureka Server</description>
 
    <properties>
        <java.version>11</java.version>        
        <docker.image.prefix>ostock</docker.image.prefix>
        <spring-cloud.version>Hoxton.SR1</spring-cloud.version>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency> --> 애플리케이션 구성을 검색하려고 스프링 컨피그 서버에 연결하는 클라이언트를 include하도록 메이븐에 지시
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> --> 유레카에 라이브러리를 include하도록 메이븐에 지시
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-ribbon</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.netflix.ribbon</groupId>
                    <artifactId>ribbon-eureka</artifactId>
                </exclusion>
            </exclusions> --> 넷플릭스 리본 라이브러리를 exclude한다
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency> --> 스프링 클라우드 로드 밸런서 라이브러리를 include하도록 메이븐에 지시
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </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>
            <!-- This plugin is used to create a docker image and publish the image to docker hub-->
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>dockerfile-maven-plugin</artifactId>
                <version>1.4.13</version>
                <configuration>
                    <repository>${docker.image.prefix}/${project.artifactId}</repository>
                    <tag>${project.version}</tag>
                    <buildArgs>
                        <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                    </buildArgs>
                </configuration>
                <executions>
                    <execution>
                        <id>default</id>
                        <phase>install</phase>
                        <goals>
                            <goal>build</goal>
                            <goal>push</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
 
</project>
cs

 

1
2
3
4
5
6
7
8
9
spring:
    application:
     name: eureka-server --> 스프링 클라우드 컨피그 클라이언트가 찾고 있는 서비스를 알 수 있도록 유레카 서비스의 이름을 지정
    cloud:
        config: 
            uri: http://configserver:8071 --> 스프링 클라우드 컨피그 서버의 위치를 지정
       loadbalancer: --> 여전히 리본이 클라이언트 측 기본 로드 밸런서이므로 loadbalancer.ribbon.enabled를 사용하여 리본을 비활성화한다
            ribbon:
               enabled: false
cs

이는 스프링 컨피그 서버에서 구성을 검색하는 데 필요한 설정이며 src/main/resources/bootstrap.yml파일에 구성해야 한다.

 

 

src/main/resources/config/eureka-server.yml 파일 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
server :
    port : 8070 --> 유레카 서버의 수신 포트를 설정
eureka : 
    instance :
        hostname : eurekaserver --> 유레카 인스턴스의 호스트 이름 설정
    client :
        registerWithEureka : false --> 컨피그 서버가 유레카 서비스에 등록하지 않도록 지시
        fetchRegistry : false --> 컨피그 서버가 레지스트리 정보를 로컬에 캐시하지 않도록 지시
        serviceUrl : 
            defaultZone : --> 서비스 URL을 제공
                http://${eureka.instance.hostname}:${server.port}/eureka/
    server : 
        saitTimeInMsWhenSyncEmpty : --> 서버가 요청을 받기 전 초기 대기 시간을 
cs
  • server.port : 기본 포트를 설정
  • eureka.instance.hostname : 유레카 서비스의 인스턴스 호스트 이름 설정
  • eureka.client.registerWithEureka : 스프링 부트로 된 유레카 애플리케이션이 시작할 때 컨피스 서버를 유레카에 등록하지 않도록 설정
  • eureka.client.fetchRegistry : 유레카 서비스가 시작할 때 레지스트리 정보를 로컬에 캐싱하지 않도록 설정
  • eureka.client.serviceUrl.defaultZone : 모든 클라이언트에 대한 서비스 URL 제공
  • eureka.server.waitTimeInMsWhenSyncEmpty : 서버가 요청을 받기 전 대기 시간 설정

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.optimagrowth.eureka;
 
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);
    }
 
}
cs

스프링 컨피그 서비스를 먼저 실행해야 한다

 

 

<dependency>
	<groupId>org.springframework.cloud</groupId>
    <artifactId> --> 유레카 라이브러리를 포함하여 서비스가 유레카에 등록할 수 있게 한다.
    	spring-cloud-starter-netflix-eureka-client
    </artifactId>
</dependency>

조직 및 라이선싱 서비스의 pom.xml 파일에 스프링 유레카 의존성을 추가

 

그리고 각 조직/라이선싱 서비스의 bootstrap.yml 파일에 application.name을 추가해야한다.

spring : 
	application :
    	name : organization-service -> 유레카에 등록될 서비스의 논리적 이름
        profiles:
        	active : dev
    cloud :
    	config : 
        	uri : http://localhost:8071
spring : 
	application :
    	name : licensing-service --> 유레카에 등록될 서비스의 논리적 이름
        profiles:
        	active : dev
    cloud :
    	config : 
        	uri : http://localhost:8071

 

.

.

.

 

 

 

유레카 REST API

http://localhost:8070/eureka/apps/organization-service  엔드포인트를 호출하게 되면

인스턴스 IP주소와 현재 작동상태를 알 수 있다.

 

  • Discovery : RestTemplate 클래스를 사용하여 조직 서비스 호출
  • Rest : 향상된 스프링 RestTemplate으로 로드 밸랜서를 사용하는 서비스 호출
  • Feign : 넷플릭스 Feign 클라이언트 라이브러리를 사용

 

1. Discovery 사용

@EnableDiscoveryClient 애너테이션 추가
Discovery Client 및 스프링 클라우드 로드 밸런서 라이브러리 사용

여기서는 DiscoveryClient를 통해 밸런서와 상호 작용한다. 또한 ServiceInstance 클래스를 사용하여 서비스를 호출하는 데 쓸 수 있는 대상 URL를 만든다. 표준 스프링 RestTemplate으로 조직 서비스를 호출하고 데이터를 조회할 수 있다.

 

2. 로드 밸런서를 지원하는 스프링 REST 템플릿으로 서비스 호출

RestTemplate 클래스를 사용하려면 @LoadBalanced 애너테이션으로 RestTemplate 빈을 정의해야 한다. 해당 방식을 통하면 서비스 인스턴스에 대한 모든 요청을 라운드 로빈 방식으로 부하분산한다.

 

3. 넷플릭스 Feign 클라이언트로 서비스 호출

@EnableFeignClients를 사용한다.
그리고 클라이언트에 @FeignClient 애너테이션을 통해 서비스를 알려준다. 나머지 코드는 Feign 클라이언트가 대신해준다.

 

 

  • 서비스 디스커버리 패턴을 사용하여 서비스의 물리적 위치를 추상화
  • 유레카 같은 서비스 디스커버리 엔진은 클라이언트에 영향을 주지 않고 해당 환경에서 서비스 인스턴스를 원활하게 추가하고 삭제할 수 있다
  • 클라이언트 측 로드 밸런싱을 사용하면 서비스 호출을 수행하는 클라이언트에서 서비스의 물리척 위치를 캐싱하여 더 높은 성능 및 회복성을 제공할 수 있다
  • 유레카는 스프링 클라우드와 함께 사용할 때 쉽게 구축하고 구성할 수 있는 넷플릭스 프로젝트다