Netflix EurekaによるSpring BootとFlaskのマイクロサービス・ディスカバリー

このガイドでは、マイクロサービス発見サービスであるNetflix Eurekaを利用して、Spring BootマイクロサービスとFlaskマイクロサービスを組み合わせ、全く異なるプログラミング言語とフレームワークで書かれたサービスの橋渡しをします。

エンドユーザ向けサービスは、エンドユーザ向けのSpring Bootサービスで、データを収集し、データアグリゲーションサービスに送信します。

ネットフリックス エウレカセリフディスカバリー

モノリスコードベースからマイクロサービス指向アーキテクチャに切り替えたとき、Netflix はアーキテクチャ全体をオーバーホールするのに役立つ多くのツールを構築しました。

そのうちの1つが、後に一般公開された「Eureka」です。

Netflix Eurekaは、サービス発見ツール(ルックアップサーバーまたはサービスレジストリとも呼ばれる)で、複数のマイクロサービスを登録し、それらの間のリクエストルーティングを処理することができる。

各サービスが登録される中心的なハブであり、各サービスはハブを介して他のサービスと通信する。

ホスト名やポートを使ってRESTコールを送るのではなく、Eurekaに委ね、ハブに登録されたサービス名を呼び出すだけでいいのです。

これを実現するために、典型的なアーキテクチャはいくつかの要素で構成されている。

Eureka Serverは、Eurekaラッパーがあればどの言語でもスピンオフできますが、最も自然なのはJavaで、Spring Bootを通じて行うことです。

各Eureka ServerはN個のEureka Clientsを登録することができ、各Eureka Clientsは通常個別のプロジェクトとなる。

Eureka Serverは、N個のEureka Clientを登録することができます。

ここでは、2つのクライアントを用意します。

  • エンドユーザー・サービス(JavaベースのEurekaクライアント)
  • データアグリゲーションサービス(PythonベースのEurekaクライアント)

EurekaはJavaベースのプロジェクトで、元々はSpring Bootソリューションのためのものなので、Python用の公式な実装はありません。

しかし、コミュニティ主導のPythonラッパーを利用することができます。

  • Netflix Eureka
  • PythonのEurekaクライアント

これを踏まえて、まずはEureka Serverを作成しましょう。

Eurekaサーバーの作成

Spring Boot を使って Eureka Server を作成・管理します。

まず、3つのプロジェクトを格納するディレクトリを作成し、その中にサーバー用のディレクトリを作成します。

$ mkdir eureka-microservices
$ cd eureka-microservices
$ mkdir eureka-server
$ cd eureka-server


eureka-server` ディレクトリが Eureka Server のルートディレクトリになります。

ここで、Spring BootのプロジェクトをCLIで起動します。

$ spring init -d=spring-cloud-starter-eureka-server


また、Spring Initializrを使用して、Eureka Serverの依存関係をインクルードすることもできます。

すでにプロジェクトがあり、新しい依存関係をインクルードしたいだけであれば、Mavenを使用している場合、追加します。

<dependency
<groupidorg.springframework.cloud</groupid
<artifactidspring-cloud-starter-eureka-server</artifactid
<version${version}</version
</dependency


また、Gradleを使っている場合は、次のようにします。

implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-eureka-server', version: ${version}


初期化のタイプに関わらず、Eureka Serverはサーバーとしてマークされるために、1つのアノテーションを必要とします。

EndUserApplicationファイルクラス (@SpringBootApplicationアノテーションのエントリーポイント) に、@EnableEurekaServer` を追加してください。

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


Eureka Servers のデフォルトポートは 8761 で、Spring Team もこれを推奨しています。

ただし、念のため application.properties ファイルにも設定しておきましょう。

server.port=8761


これで、サーバーを動かす準備ができました。

このプロジェクトを実行すると、localhost:8761にあるEureka Serverが起動します。

Note: サービスを登録しない場合、Eureka は UNKNOWN インスタンスが稼働していると誤認することがあります。

Spring BootでEurekaクライアント – エンドユーザーサービスを作成する

さて、サーバが起動し、サービスを登録できるようになったところで、Spring Bootでエンドユーザー・サービスを作ってみましょう。

Studentに関するJSONデータを受け取るエンドポイントを1つ用意します。

このデータは、成績の一般的な統計情報を計算するデータ集計サービスにJSONとして送信されます。

実際には、この操作はもっと手間のかかる操作に置き換えられ、専用のデータ処理ライブラリで行うのが理にかなっており、同じサービスで実行するよりも別のサービスを使用することが正当化されます。

ということで、戻ってEnd-User Serviceのためのディレクトリを作成しましょう。

$ cd..
$ mkdir end-user-service
$ cd end-user-service


ここで、CLIを使って新しいプロジェクトを開始し、spring-cloud-starter-netflix-eureka-client依存関係を含めます。

また、このアプリケーションは実際にユーザーと対面することになるので、web 依存関係も追加します。

$ spring init -d=web, spring-cloud-starter-netflix-eureka-client


また、Spring Initializrを使用して、Eureka Discovery Clientの依存関係を含めることもできます。

すでにプロジェクトがあり、新しい依存関係をインクルードしたいだけであれば、Mavenを使用している場合、追加します。

<dependency
<groupidorg.springframework.cloud</groupid
<artifactidspring-cloud-starter-netflix-eureka-client</artifactid
<version${version}</version
</dependency


または、Gradleを使用している場合は、以下を追加します。

implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-eureka-client', version: ${version}


初期化のタイプに関係なく、このアプリケーションをEureka Clientとしてマークするには、メインクラスに @EnableEurekaClient アノテーションを追加するだけです。

@SpringBootApplication
@EnableEurekaClient
public class EndUserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(EndUserServiceApplication.class, args);
    }

    @LoadBalanced
    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}


注:別の方法として、@EnableDiscoveryClient アノテーションを使用することもできます。

これは、Eureka、Consul、またはZookeperを参照することができ、どのツールが使用されているかに依存する。

また、ここでは @Bean を定義しているので、後でコントローラの中で RestTemplate@Autowire することができます。

この RestTemplate は、データ集計サービスに POST リクエストを送信するために使用される。

LoadBalancedアノテーションは、RestTeamplateがリクエストを送信する際にRibbonLoadBalancerClient` を使用することを意味します。

このアプリケーションはEurekaクライアントなので、レジストリの名前を付けたいと思います。

他のサービスはこの名前を参照することになります。

この名前は application.properties または application.yml ファイルで定義します。

server.port = 8060
spring.application.name = end-user-service
eureka.client.serviceUrl.defaultZone = http://localhost:8761/eureka


server:
    port: 8060
spring:
    application:
        name: end-user-service
eureka:
    client:
      serviceUrl:
        defaultZone: http://localhost:8761/eureka/


ここで、アプリケーションのポートを設定しました。

Eurekaがリクエストをルーティングするために必要なポートです。

また、他のサービスから参照されるサービス名も指定しました。

このアプリケーションを実行することで、Eureka Server にサービスが登録されます。

INFO 3220 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8060 (http) with context path ''
INFO 3220 --- [           main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8060
INFO 3220 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_END-USER-SERVICE/DESKTOP-8HAKM3G:end-user-service:8060 - registration status: 204
INFO 3220 --- [           main] c.m.e.EndUserServiceApplication          : Started EndUserServiceApplication in 1.978 seconds (JVM running for 2.276)
INFO 3220 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_END-USER-SERVICE/DESKTOP-8HAKM3G:end-user-service:8060 - Re-registering apps/END-USER-SERVICE
INFO 3220 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_END-USER-SERVICE/DESKTOP-8HAKM3G:end-user-service:8060: registering service...
INFO 3220 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_END-USER-SERVICE/DESKTOP-8HAKM3G:end-user-service:8060 - registration status: 204


これで、localhost:8761にアクセスすると、サーバーに登録されていることが確認できます。

では、Studentモデルを定義してみましょう。

public class Student {
    private String name;
    private double mathGrade;
    private double englishGrade;
    private double historyGrade;
    private double scienceGrade;

    // Constructor, getters and setters and toString()
}


生徒について、成績の平均、最小、最大などの要約統計量を計算したいと思います。

これには Pandas を使用します – とても便利な DataFrame.describe() 関数を利用します。

また、データ集計サービスから返されたデータを保持する GradesResult モデルも作成しましょう。

public class GradesResult {
    private Map<string, double="" mathGrade;
    private Map<string, double="" englishGrade;
    private Map<string, double="" historyGrade;
    private Map<string, double="" scienceGrade;

    // Constructor, getters, setters and toString()
}


このモデルは POST リクエストを受け取り、それを Student にデシリアライズして、まだ作成していないデータ集計サービスに送ります。

@Autowired
private RestTemplate restTemplate;


@RestController
public class HomeController {
    @PostMapping("/student")
    public ResponseEntity<string student(@RequestBody Student student) {
        GradesResult grades = restTemplate.getForObject("http://data-aggregation-service/calculateGrades", GradesResult.class);


return ResponseEntity
            .status(HttpStatus.OK)
            .body(String.format("Sent the Student to the Data Aggregation Service: %s 
And got back:
 %s", student.toString(), gradesResult.toString()));
    }
}


この @RestControllerPOST リクエストを受け取り、そのボディを Student オブジェクトにデシリアライズします。

これはまだ実装されておらず、Eureka に登録される予定です。

そして、その呼び出しの JSON 結果を GradesResult オブジェクトにパックします。

注:シリアライザーが与えられた結果から GradesResult オブジェクトを構築する際に問題がある場合、Jackson の ObjectMapper を使用して手動で変換する必要があります。

String result = restTemplate.postForObject("http://data-aggregation-service/calculateGrades", student, String.class);
ObjectMapper objectMapper = new ObjectMapper();
GradesResult gradesResult = objectMapper.readValue(result, GradesResult.class);


最後に、送信した student インスタンスと、結果から構築した grades インスタンスを表示します。

それでは、データ集計サービスを作成してみましょう。

Eurekaクライアントの作成 – Flaskによるデータ集計サービス

StudentをJSON形式で受け取り、Pandasの DataFrame に入力し、特定の処理を実行して結果を返す、データ集計サービスだけが必要なコンポーネントです。

プロジェクト用のディレクトリを作成し、仮想環境を立ち上げましょう。

$ cd..
$ mkdir data-aggregation-service
$ python3 -m venv flask-microservice


次に、仮想環境を有効にするために、activateファイルを実行します。

$ flask-microservice/Scripts/activate.bat


Linux/Macの場合

$ source flask-microservice/bin/activate


今回は簡単なFlaskアプリケーションを起動するので、FlaskとEurekaの依存関係を pip 経由で起動した環境にインストールしましょう。

(flask-microservice) $ pip install flask pandas py-eureka-client


そして、Flaskアプリケーションを作成します。

$ touch flask_app.py


次に、flask_app.py を開いて、Flask、Pandas、Py-Eureka Client のライブラリをインポートします。

from flask import Flask, request
import pandas as pd
import py_eureka_client.eureka_client as eureka_client


Flask と request を使って、受信したリクエストを処理し、レスポンスを返し、サーバーを起動します。

Pandasを使ってデータを集約し、py_eureka_clientを使ってFlaskアプリケーションを localhost:8761 上のEurekaサーバーに登録します。

それでは、このアプリケーションをEurekaクライアントとして設定し、生徒のデータを取得するための POST リクエストハンドラを実装してみましょう。

rest_port = 8050
eureka_client.init(eureka_server="http://localhost:8761/eureka",
                   app_name="data-aggregation-service",
                   instance_port=rest_port)


app = Flask(__name__)


@app.route("/calculateGrades", methods=['POST'])
def hello():
    data = request.json
    df = pd.DataFrame(data, index=[0])
    response = df.describe().to_json()
    return response


if __name__ == "__main__":
    app.run(host='0.0.0.0', port = rest_port)


Note: Flask が外部サービスとの接続を拒否しないように、ホストを 0.0.0.0 に設定する必要があります。

これは、@app.route()が1つあるだけの、かなり最小限のFlaskアプリです。

受信した POST リクエストの本文を request.jsondata 辞書に抽出し、そのデータで DataFrame を作成しました。

この辞書はインデックスを持たないので、手動でインデックスを設定します。

最後に、describe()関数の結果をJSONとして返します。

jsonifyは String ではなく、Responseオブジェクトを返すので、ここでは使用していません。

Response オブジェクトを送り返すと、余分な `Caracter が含まれます。

{"mathGrade":...}
vs
{"mathGrade":...}


これらは、デシリアライザーを混乱させないように、エスケープする必要があります。

eureka_clientinit()` 関数では、Eureka Server の URL を設定し、アプリケーション/サービスの名前とアクセスするためのポートを設定しました。

これは、Spring Bootアプリケーションで提供した情報と同じものです。

では、このFlaskアプリケーションを実行してみましょう。

(flask-microservice) $ python flask_app.py


そして、localhost:8761にあるEureka Serverを確認すると、登録されており、リクエストを受信できる状態になっています。

Eurekaを使ってSpring Boot ServiceからFlask Serviceを呼び出す

Eurekaに登録され、お互いに通信できるようになったので、エンドユーザー・サービスに POST リクエストを送りましょう。

$ curl -X POST -H "Content-type: application/json" -d "{"name" : "David", "mathGrade" : "8", "englishGrade" : "10", "historyGrade" : "7", "scienceGrade" : "10"}" "http://localhost:8060/student"


この結果、サーバーからエンドユーザーへの応答が返されます。

Sent the Student to the Data Aggregation Service: Student{name='David', mathGrade=8.0, englishGrade=10.0, historyGrade=7.0, scienceGrade=10.0}
And got back:
GradesResult{mathGrade={count=1.0, mean=8.0, std=null, min=8.0, 25%=8.0, 50%=8.0, 75%=8.0, max=8.0}, englishGrade={count=1.0, mean=10.0, std=null, min=10.0, 25%=10.0, 50%=10.0, 75%=10.0, max=10.0}, historyGrade={count=1.0, mean=7.0, std=null, min=7.0, 25%=7.0, 50%=7.0, 75%=7.0, max=7.0}, scienceGrade={count=1.0, mean=10.0, std=null, min=10.0, 25%=10.0, 50%=10.0, 75%=10.0, max=10.0}}


結論

このガイドでは、あるサービスが別のサービスに依存しているマイクロサービス環境を作成し、Netflix Eureka を使用してそれらをフックアップしてきました。

これらのサービスは、異なるフレームワークと異なるプログラミング言語を使って構築されていますが、REST APIを通じて、これらのサービス間の通信は簡単で容易です。

Eureka Serverを含むこれら2つのサービスのソースコードは、Githubで公開されています。

,>,>,>,>。

タイトルとURLをコピーしました