Spring Cloudでマイクロサービスを構成する（３）：Service Discovery編

Spring Cloudシリーズの第３回です。今回はService Discoveryです。

### 概要

今回は、Eureka（[Spring Cloud Netflix Eureka](https://github.com/Netflix/eureka/wiki) )でService Discoveryのクラスタを立てます。

![services.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534936/54235d59-1257-f2dc-c961-c15d8a8949c8.png)


Spring Cloudではここ最近で方針転換があり、Spring Cloud Netflix関連プロダクトがメンテナンスモードになり、代替のプロダクトへの移行段階にあります。

[Spring Cloud Netflix Projects Entering Maintenance Mode](https://spring.io/blog/2018/12/12/spring-cloud-greenwich-rc1-available-now#spring-cloud-netflix-projects-entering-maintenance-mode)

今回は、その中でも次の世代に生き残った Eurekaを使って`Service Discovery`のクラスタをdockerで立てていきます。
（最もよく知られている Service Discovery に [consul](https://www.consul.io/) がありますが、SpringBootとConsulの連携については、後の楽しみにとっておきましょう。）


### 環境

以下が動く環境を前提とします。

* docker & docker-compose
* JDK11

[前回の投稿](https://qiita.com/takayoshi-shiraki/items/960f1a30925a3867c61d)の続きですので、前回のゴール地点のソースを手元に用意し、それをベースとします。


### 作業ステップ

本記事では以下のステップで進めていきます。

1. Eurekaのひな型作成
1. 実装（アノテーションの付加と設定ファイルの記述）
1. Dockerイメージ化とdocker-componentへの追加
1. APIを多重化し、Serivce Discoveryに登録する
1. Eurekaのクラスタリング


### ステップ1：Eurekaのひな型作成

[Spring Initializr](https://start.spring.io/) の出番です。

**Artifact** と **Name** だけ`discovery`に指定し、あとは
**Dependencies** に`Eureka Server`と`SpringBoot Actuator`を追加しましょう。
ActuatorはEurekaに必須ではありませんが、何かと必要になるものなので追加しておきます。

![eureka.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534936/048bce77-968e-8d6c-264b-8f893b3e2016.png)


あとは`GENERATE`するとスケルトンのdiscovery.zipがダウンロードできます。
　

### ステップ2：実装（アノテーションの付加と設定ファイルの記述）

今回はあまりやることがありません。２つだけ。

まず１つ目。Applicationクラスにアノテーションを１つ付けます。

```java:discovery/src/main/java/com/example/discovery/DiscoveryApplication.java
package com.example.discovery;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
// ★ ↑ これを追加

@SpringBootApplication
@EnableEurekaServer              // ★ これを追加
public class DiscoveryApplication {

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

2つ目は、設定ファイルを書きます。

```yml:discovery/src/main/resources/application.yml
server:
  port: ${PORT:8761}

eureka:
  shouldUseDns: false
  instance:
    hostname: ${CONTAINER_NAME:localhost}
    preferIpAddress: false
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: ${ZONE:http://localhost:8761/eureka}
  server:
    enableSelfPreservation: true
spring:
  main:
    banner-mode: "off"
  cloud:
    loadbalancer:
      ribbon:
        enabled: false

management:
  endpoints:
    web:
      exposure:
        include: "*"
```

いくつかわざとらしく後で環境変数で書き換えられるようにしていますが、基本はデフォルト値で動くようにしています。


※`discovery/src/main/resources/application.properties`は不要になるので、削除してください。

さて、ここまでくればEurekaサーバを起動できます。

```console
$ cd discovery
$ ./mvn clean spring-boot:run
```

http://localhost:8761 にアクセスしてみてください。Eurekaのホーム画面が出ればOKです。


### ステップ3：Dockerイメージ化とdocker-componentへの追加

この段階でさっさとDockerイメージ化をしてdocker-composeで操れるようにしておきましょう。
前回のディレクトリ構造に以下のように`discover`ディレクトリを配置します。

その上で、Dockerfileを作成し、とdocker-compose.ymlに追記します。

```text
spring-msa
├── account
│   └── ・・・
├── db
│   └── init
├── docker-compose.yml   <-- ★追記する
└── sd                  <-- ★配置する
    ├── Dockerfile   <-- ★作成する
    ├── HELP.md
    ├── mvnw
    ├── mvnw.cmd
    ├── pom.xml
    └── src
        ├── main
        │   ├── java
        │   └── resources
        └── test
```

```text:discovery/Dockerfile
FROM openjdk:11-jdk-slim
ADD target/discovery-*.jar /discovery.jar
ENV CONTAINER_NAME=localhost \
    PORT=8761 \
    OPTS_ARGS=''

ENTRYPOINT ["java", "-jar", "/discovery.jar", "${OPTS_ARGS}"]
```

```text:docker-compose.yml
version: '3'
・・・
services:
  db:
    ・・・
  adminer:
    ・・・
  sd:
    image: spring-msa/eureka
    container_name: sd
    build:
      context: ./discovery
      dockerfile: Dockerfile
    environment:
      - CONTAINER_NAME=sd
      - PORT=8761
      - ZONE=http://sd:8761/eureka/
    ports:
      - "8761:8761"

  account:
    ・・・
```

これで準備OK.
mvn wrapperでJARを作ったあとに、Dockerイメージを作成します。

```console
$ cd discovery
$ .mvnw clean package
$ cd ..
$ docker-compose build sd
```

これでdocker-composeで起動・停止できるようになりました。

```console
(起動)
$ docker-compose up -d sd
(停止)
$ docker-compose stop sd
```

コンテナの8761ポートをホストの8761ポートにbindしているので、
http://localhost:8761 で先ほどのEurekaのダッシュボードが見れるはずです。

何かがおかしければdokerのログをみてください。

```console
$ docker-compose logs -f sd
```

### ステップ4：APIを多重化し、Serivce Discoveryに登録する

このままだとService Discoveryは立ち上がったものの、何もサービスが登録されていません。
そこで、前回作った`account`サービスを登録してみましょう。

`pom.xml` と `application.yml` に手をいれます。

```diff:account/pom.xml
@@ -15,6 +15,9 @@
 	<description>Demo project for Spring Boot</description>
 
 	<properties>
+		<spring-cloud.version>Hoxton.SR7</spring-cloud.version>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 		<java.version>11</java.version>
 	</properties>
 
@@ -47,6 +50,10 @@
 			<artifactId>lombok</artifactId>
 			<optional>true</optional>
 		</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>
@@ -65,6 +72,18 @@
 		</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>

```

```diff:account/src/main/resources/application.yml

spring:
  ・・・
  r2dbc:
     url: ${DB_URL:r2dbc:postgresql://localhost:5432/db}
     username: ${DB_USER:postgres}
     password: ${DB_PASSWD:postgres}
+  cloud:
+    loadbalancer:
+      ribbon:
+        enabled: false
 
 management:
   endpoints:
@@ -22,3 +26,16 @@ management:
   endpoint:
     health:
       show-details: always
+
+eureka:
+  client:
+    serviceUrl:
+      defaultZone: ${DISCOVERY:http://localhost:8761/eureka/}
+    should-use-dns: false
+  instance:
+    instanceId: ${CONTAINER_NAME:account}
+    hostname: ${CONTAINER_NAME:localhost}
+    preferIpAddress: false
+
```

```yml:docker-component.yml
services:
  ・・・
  account:
    image: spring-msa/account-api
    container_name: account
    build:
      context: ./account
      dockerfile: Dockerfile
    ports:
      - "9001:9001"
    depends_on:
      - db
    environment:
      - CONTAINER_NAME=account
      - PORT=9001
      - DB_USER=${DB_USER:-postgres}
      - DB_PASSWD=${DB_PASSWORD:-postgres}
      - DB_URL=${DB_URL:-r2dbc:postgresql://db:5432/db}
      - DISCOVERY=http://sd:8761/eureka  # ★ここを追加
    networks:
      - default

```

JARを作ってdocker-composeでdokcerイメージのビルドを含めて一気に起動してみましょう。
Service Discoveryも起動しておきます。

```console
$ docker-compose up -d sd
$ cd account
$ ./mvnw clean package
$ cd ..
$ docker-compose up -d --build account
```

http:/localhost:8761 にアクセスしてしばらくすると、ACCOUNT-APIが１インスタンス登録されているのがわかると思います。

では、さらにACCOUNT-APIサービスのインスタンスを2つ追加しましょう。
docker-compose.ymlのaccountサービスの記載をコピーして、サービス名などの名前やポート番号を書き換えます。
同じDockerイメージを使って、３つのDockerコンテナを動かします。
ポート等はdocker-compose.ymlの環境変数で外から指定することで、重複しないようにします。

|サービス名 | コンテナ名 | ポート番号 ||
|:---------|:---------|-----:|:--|
| account  | account  | 9001 ||
| account2 | account2 | 9002 |←これを追加|
| account3 | account3 | 9003 |←これを追加|



```yml:docker-component.yml
  ・・・
services:
  ・・・
  account:
  ・・・
  account2:
    image: spring-msa/account-api
    container_name: account2     # ★
    ports:
      - "9002:9002"     # ★
    depends_on:
      - db
    environment:
      - CONTAINER_NAME=account2     # ★
      - PORT=9002     # ★
      - DB_USER=${DB_USER:-postgres}
      - DB_PASSWD=${DB_PASSWORD:-postgres}
      - DB_URL=${DB_URL:-r2dbc:postgresql://db:5432/db}
      - DISCOVERY=http://sd:8761/eureka
    networks:
      - default
  account3:
    image: spring-msa/account-api
    container_name: account3     # ★
    ports:
      - "9003:9003"     # ★
    depends_on:
      - db
    environment:
      - CONTAINER_NAME=account3     # ★
      - PORT=9003     # ★
      - DB_USER=${DB_USER:-postgres}
      - DB_PASSWD=${DB_PASSWORD:-postgres}
      - DB_URL=${DB_URL:-r2dbc:postgresql://db:5432/db}
      - DISCOVERY=http://sd:8761/eureka
    networks:
      - default

```


追加したacocunt2、account3のサービスを起動します。

```console
$ docker-compose up -d account2 account3
```

http://localhost:8761 にアクセスするとインスタンス数が3になっているのが確認できると思います。


### ステップ5：Eurekaのクラスタリング

さて、Service Discoveryのサービスを立てることができましたが、1プロセスだと心もとないので、冗長化を試します。

これもdocker-compose.ymlの編集のみで対応可能です。

sdサービスのエントリをコピーして、ポート番号等を調整します。

|サービス名 | コンテナ名 | ポート番号 ||
|:---------|:---------|-----:|:--|
| sd  | sd  | 3001 ||
| sd2 | sd2 | 3002 |←これを追加|


```text:docker-compose.yml
version: '3'
・・・
services:
  db:
    ・・・
  adminer:
    ・・・
  sd:
    image: spring-msa/eureka
    container_name: sd
    build:
      context: ./discovery
      dockerfile: Dockerfile
    environment:
      - CONTAINER_NAME=sd
      - PORT=8761
      - ZONE=http://sd2:3001/eureka/  # ★自分以外のEurekaインスタンスをカンマ区切りで記載する
    ports:
      - "3001:3001"
  sd2:
    image: spring-msa/eureka
    container_name: sd2
    environment:
      - CONTAINER_NAME=sd2
      - PORT=3001
      - ZONE=http://sd:3001/eureka/  # ★自分以外のEurekaインスタンスをカンマ区切りで記載する
    ports:
      - "3002:3002"
  account:
    ・・・
    environment:
      ・・・
      - DISCOVERY=http://sd:3001/eureka,http://sd2:3002/eureka # ★すべてのEurekaインスタンスをカンマ区切りで羅列する
    ・・・
  account2:
    ・・・
    environment:
      ・・・
      - DISCOVERY=http://sd:3001/eureka,http://sd2:3002/eureka # ★すべてのEurekaインスタンスをカンマ区切りで羅列する
    ・・・
  account3:
    ・・・
    environment:
      ・・・
      - DISCOVERY=http://sd:3001/eureka,http://sd2:3002/eureka # ★すべてのEurekaインスタンスをカンマ区切りで羅列する
```

これでsdとsd2を起動しましょう。

localhost:3001 と localhost:3002 にアクセスすると、両方とも「DS Replicas」の欄に他方のEurekaサーバへのリンクが表示されていて、
ACOCUNT-APIアプリが、accout,account2,account3の3件登録されているのが確認できると思います。

![eureka-ha.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534936/b785c1c5-61dd-d24c-34bf-8c607681d525.png)


### さいごに

わりとシンプルに`Service Discovery`のクラスタができました。
ここまでのソースは以下に置いています。

https://github.com/Piecemeal-Technology-Inc/spring-msa/tree/add_service_discovery

次回はService Discoveryを利用してAPIに分散アクセス（ロードバランシング）する
「API Gateway」を追加しましょう。

　
ではでは。