現時点では、Java テクノロジー スタックの場合、マイクロサービスを構築するのに最適な選択肢は Spring Boot であり、Spring Boot は実装事例が多いマイクロサービス フレームワーク Spring Cloud とともに使用されるのが一般的です。
Spring Cloud は完璧に見えますが、実際に開発してみると、Spring Cloud には次のような深刻な問題があることが簡単にわかります。
サービス ガバナンス関連のロジックは、次のような SDK に存在します。 Spring Cloud Netflix として機能し、ビジネス コードと密接に結合されています。
SDK はビジネス コードに対して侵入しすぎています。SDK がアップグレードされて下位互換性がなくなると、たとえビジネスがロジックは変わりません。何も変わっていません。
さまざまなコンポーネントが眩しく、品質にもばらつきがあり、学習コストが高すぎ、コンポーネント間でコードを完全に再利用することは困難です。ガバナンス ロジックを実装するためだけに SDK を使用するのは、あまり良い選択ではありません。
は Java テクノロジー スタックにバインドされており、他の言語も接続できますが、サービス ガバナンスに関連するロジックは手動で実装する必要があり、の原則に準拠していません。マイクロサービスは「複数の言語で開発可能」です。
Spring Cloud は単なる開発フレームワークであり、マイクロサービスに必要なサービスのスケジューリングやリソース割り当てなどの機能は実装されておらず、これらの要件は Kubernetes などのプラットフォームの助けを借りて満たす必要があります。 Spring Cloud と Kubernetes には重複する機能があり、機能が競合するため、2 つの間のスムーズなコラボレーションが困難になります。
Spring Cloud に代わるものはありますか?持っている!イスティオです。
Istio は、ガバナンス ロジックをビジネス コードから完全に分離し、独立したプロセス (サイドカー) を実装します。デプロイメント中、Sidecar とビジネス コードは同じ Pod 内に共存しますが、ビジネス コードは Sidecar の存在をまったく認識しません。これにより、ビジネス コードへのガバナンス ロジックの侵入がゼロになります。実際、コードが侵入しないだけでなく、実行時に 2 つの間に結合がありません。これにより、サービス ガバナンスの問題を気にすることなく、異なる言語やテクノロジー スタックを使用して異なるマイクロサービスを開発できるようになり、非常にエレガントなソリューションであると言えます。
つまり、「なぜ Istio を使用するのか」という疑問は簡単に解決されます。Istio は、ビジネス ロジックとサービス ガバナンス ロジックの結合や、言語間をうまく実現できないなど、従来のマイクロサービスの問題点を解決してくれるからです。 . そしてとても使いやすいです。 Kubernetes をマスターすれば、Istio の使用方法を学ぶのは難しくありません。
マイクロサービス アーキテクチャでは、サービス間の通信は比較的大きな問題であり、通常は RPC または RESTful API を使用して実装されます。
Spring Boot では RestTemplate を使用してリモート サービスを呼び出すことができますが、この方法は直感的ではなく、コードが複雑です。言語間の通信も大きな問題です。Dubbo などの一般的な Java RPC フレームワークよりも gRPC の方が便利です。軽量で使いやすく、コードの可読性が高く、Istio や Kubernetes とうまく統合できます。Protobuf と HTTP2 のサポートにより、パフォーマンスもかなり優れているため、今回は Spring Boot の問題を解決するために gRPC を選択しました。マイクロプロセッサの問題 サービス間通信の問題また、gRPC にはサービス ディスカバリやロード バランシングなどの機能はありませんが、Istio はこの点で非常に強力であり、両者は完全な補完関係を形成しています。
さまざまな grpc-spring-boot-starter が Spring Boot と Istio の統合に未知の副作用をもたらす可能性があることを考慮して、今回は grpc-spring-boot-starter を使用しませんでした。 Spring Boot は直接手書きでした。 gRPC と Spring Boot を統合するためにサードパーティのフレームワークを使用したくない場合は、私の簡単な実装方法を参照してください。
まず Spring Initializr を使用して親プロジェクト spring-boot-istio を確立し、gRPC の依存関係を導入します。 pom ファイルは次のとおりです:
<?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> <modules> <module>spring-boot-istio-api</module> <module>spring-boot-istio-server</module> <module>spring-boot-istio-client</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> </parent> <groupId>site.wendev</groupId> <artifactId>spring-boot-istio</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-istio</name> <description>Demo project for Spring Boot With Istio.</description> <packaging>pom</packaging> <properties> <java.version>1.8</java.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-all</artifactId> <version>1.28.1</version> </dependency> </dependencies> </dependencyManagement> </project>
次に、パブリック依存関係モジュール spring-boot-istio-api を作成します。pom ファイルは次のとおりで、主に gRPC の依存関係がいくつかあります:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-boot-istio</artifactId> <groupId>site.wendev</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-boot-istio-api</artifactId> <dependencies> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-all</artifactId> </dependency> <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency> </dependencies> <build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.6.2</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.6.1</version> <configuration> <protocArtifact>com.google.protobuf:protoc:3.11.3:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.28.1:exe:${os.detected.classifier}</pluginArtifact> <protocExecutable>/Users/jiangwen/tools/protoc-3.11.3/bin/protoc</protocExecutable> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
src/main/proto フォルダーに移動し、このフォルダーに hello.proto を作成し、次のようにサービス間のインターフェイスを定義します。
syntax = "proto3"; option java_package = "site.wendev.spring.boot.istio.api"; option java_outer_classname = "HelloWorldService"; package helloworld; service HelloWorld { rpc SayHello (HelloRequest) returns (HelloResponse) {} } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; }
これは非常に簡単で、名前を送信し、その名前を含むメッセージを返すだけです。
次に、サーバー コードとクライアント コードを生成し、それらを java フォルダーに置きます。この部分については、gRPC の公式ドキュメントを参照してください。
API モジュールが利用可能になると、サービス プロバイダー (サーバー) とサービス コンシューマー (クライアント) を開発できます。ここでは、gRPC と Spring Boot を統合する方法に焦点を当てます。
1) サーバー側
ビジネス コードは非常に単純です:
/** * 服务端业务逻辑实现 * * @author 江文 * @date 2020/4/12 2:49 下午 */ @Slf4j @Component public class HelloServiceImpl extends HelloWorldGrpc.HelloWorldImplBase { @Override public void sayHello(HelloWorldService.HelloRequest request, StreamObserver<HelloWorldService.HelloResponse> responseObserver) { // 根据请求对象建立响应对象,返回响应信息 HelloWorldService.HelloResponse response = HelloWorldService.HelloResponse .newBuilder() .setMessage(String.format("Hello, %s. This message comes from gRPC.", request.getName())) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); log.info("Client Message Received:[{}]", request.getName()); } }
ビジネス コードに加えて、gRPC も開始する必要があります。アプリケーションがサーバーを起動すると同時に。まず、サーバー側で起動、シャットダウン、その他のロジックを記述します:
/** * gRPC Server的配置——启动、关闭等 * 需要使用<code>@Component</code>注解注册为一个Spring Bean * * @author 江文 * @date 2020/4/12 2:56 下午 */ @Slf4j @Componentpublic class GrpcServerConfiguration { @Autowired HelloServiceImpl service; /** 注入配置文件中的端口信息 */ @Value("${grpc.server-port}") private int port; private Server server; public void start() throws IOException { // 构建服务端 log.info("Starting gRPC on port {}.", port); server = ServerBuilder.forPort(port).addService(service).build().start(); log.info("gRPC server started, listening on {}.", port); // 添加服务端关闭的逻辑 Runtime.getRuntime().addShutdownHook(new Thread(() -> { log.info("Shutting down gRPC server."); GrpcServerConfiguration.this.stop(); log.info("gRPC server shut down successfully."); })); } private void stop() { if (server != null) { // 关闭服务端 server.shutdown(); } } public void block() throws InterruptedException { if (server != null) { // 服务端启动后直到应用关闭都处于阻塞状态,方便接收请求 server.awaitTermination(); } } }
gRPC の起動、停止、その他のロジックを定義した後、CommandLineRunner を使用してそれを Spring Boot の起動に追加できます:
/** * 加入gRPC Server的启动、停止等逻辑到Spring Boot的生命周期中 * * @author 江文 * @date 2020/4/12 3:10 下午 */ @Component public class GrpcCommandLineRunner implements CommandLineRunner { @Autowired GrpcServerConfiguration configuration; @Override public void run(String... args) throws Exception { configuration.start(); configuration.block(); } }
インスタンスを取得して対応する操作を実行する必要があるため、gRPC ロジックを Spring Bean として登録します。
このように、Spring Boot を起動すると、CommandLineRunner の存在により、gRPC サーバーも同時に起動することができます。
2) クライアント
ビジネス コードも非常にシンプルです:
/** * 客户端业务逻辑实现 * * @author 江文 * @date 2020/4/12 3:26 下午 */ @RestController @Slf4j public class HelloController { @Autowired GrpcClientConfiguration configuration; @GetMapping("/hello") public String hello(@RequestParam(name = "name", defaultValue = "JiangWen", required = false) String name) { // 构建一个请求 HelloWorldService.HelloRequest request = HelloWorldService.HelloRequest .newBuilder() .setName(name) .build(); // 使用stub发送请求至服务端 HelloWorldService.HelloResponse response = configuration.getStub().sayHello(request); log.info("Server response received: [{}]", response.getMessage()); return response.getMessage(); } }
在启动客户端时,我们需要打开gRPC的客户端,并获取到channel和stub以进行RPC通信,来看看gRPC客户端的实现逻辑:
/** * gRPC Client的配置——启动、建立channel、获取stub、关闭等 * 需要注册为Spring Bean * * @author 江文 * @date 2020/4/12 3:27 下午 */ @Slf4j @Component public class GrpcClientConfiguration { /** gRPC Server的地址 */ @Value("${server-host}") private String host; /** gRPC Server的端口 */ @Value("${server-port}") private int port; private ManagedChannel channel; private HelloWorldGrpc.HelloWorldBlockingStub stub; public void start() { // 开启channel channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build(); // 通过channel获取到服务端的stub stub = HelloWorldGrpc.newBlockingStub(channel); log.info("gRPC client started, server address: {}:{}", host, port); } public void shutdown() throws InterruptedException { // 调用shutdown方法后等待1秒关闭channel channel.shutdown().awaitTermination(1, TimeUnit.SECONDS); log.info("gRPC client shut down successfully."); } public HelloWorldGrpc.HelloWorldBlockingStub getStub() { return this.stub; } }
比服务端要简单一些。
最后,仍然需要一个CommandLineRunner把这些启动逻辑加入到Spring Boot的启动过程中:
/** * 加入gRPC Client的启动、停止等逻辑到Spring Boot生命周期中 * * @author 江文 * @date 2020/4/12 3:36 下午 */ @Component @Slf4j public class GrpcClientCommandLineRunner implements CommandLineRunner { @Autowired GrpcClientConfiguration configuration; @Override public void run(String... args) { // 开启gRPC客户端 configuration.start(); // 添加客户端关闭的逻辑 Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { configuration.shutdown(); } catch (InterruptedException e) { e.printStackTrace(); } })); } }
业务代码跑通之后,就可以制作Docker镜像,准备部署到Istio中去了。
在开始编写Dockerfile之前,先改动一下客户端的配置文件:
server: port: 19090 spring: application: name: spring-boot-istio-clientserver-host: ${server-host}server-port: ${server-port}
接下来编写Dockerfile:
1) 服务端:
FROM openjdk:8u121-jdk RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo 'Asia/Shanghai' >/etc/timezone ADD /target/spring-boot-istio-server-0.0.1-SNAPSHOT.jar /ENV SERVER_PORT="18080" ENTRYPOINT java -jar /spring-boot-istio-server-0.0.1-SNAPSHOT.jar
主要是规定服务端应用的端口为18080,并且在容器启动时让服务端也一起启动。
2) 客户端:
FROM openjdk:8u121-jdk RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo 'Asia/Shanghai' >/etc/timezoneADD /target/spring-boot-istio-client-0.0.1-SNAPSHOT.jar /ENV GRPC_SERVER_HOST="spring-boot-istio-server"ENV GRPC_SERVER_PORT="18888"ENTRYPOINT java -jar /spring-boot-istio-client-0.0.1-SNAPSHOT.jar \ --server-host=$GRPC_SERVER_HOST \ --server-port=$GRPC_SERVER_PORT
可以看到这里添加了启动参数,配合前面的配置,当这个镜像部署到Kubernetes集群时,就可以在Kubernetes的配合之下通过服务名找到服务端了。
同时,服务端和客户端的pom文件中添加:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> </configuration> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>dockerfile-maven-plugin</artifactId> <version>1.4.13</version> <dependencies> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1</version> </dependency> </dependencies> <executions> <execution> <id>default</id> <goals> <goal>build</goal> <goal>push</goal> </goals> </execution> </executions> <configuration> <repository>wendev-docker.pkg.coding.net/develop/docker/${project.artifactId}</repository> <tag>${project.version}</tag> <buildArgs> <JAR_FILE>${project.build.finalName}.jar</JAR_FILE> </buildArgs> </configuration> </plugin> </plugins> </build>
这样执行mvn clean package时就可以同时把docker镜像构建出来了。
有了镜像之后,就可以写部署文件了:
1) 服务端:
apiVersion: v1 kind: Servicemetadata: name: spring-boot-istio-server spec: type: ClusterIP ports: - name: http port: 18080 targetPort: 18080 - name: grpc port: 18888 targetPort: 18888 selector: app: spring-boot-istio-server ---apiVersion: apps/v1 kind: Deploymentmetadata: name: spring-boot-istio-server spec: replicas: 1 selector: matchLabels: app: spring-boot-istio-server template: metadata: labels: app: spring-boot-istio-server spec: containers: - name: spring-boot-istio-server image: wendev-docker.pkg.coding.net/develop/docker/spring-boot-istio-server:0.0.1-SNAPSHOT imagePullPolicy: Always tty: true ports: - name: http protocol: TCP containerPort: 18080 - name: grpc protocol: TCP containerPort: 18888
主要是暴露服务端的端口:18080和gRPC Server的端口18888,以便可以从Pod外部访问服务端。
2) 客户端:
apiVersion: v1 kind: Servicemetadata: name: spring-boot-istio-client spec: type: ClusterIP ports: - name: http port: 19090 targetPort: 19090 selector: app: spring-boot-istio-client ---apiVersion: apps/v1 kind: Deploymentmetadata: name: spring-boot-istio-client spec: replicas: 1 selector: matchLabels: app: spring-boot-istio-client template: metadata: labels: app: spring-boot-istio-client spec: containers: - name: spring-boot-istio-client image: wendev-docker.pkg.coding.net/develop/docker/spring-boot-istio-client:0.0.1-SNAPSHOT imagePullPolicy: Always tty: true ports: - name: http protocol: TCP containerPort: 19090
主要是暴露客户端的端口19090,以便访问客户端并调用服务端。
如果想先试试把它们部署到k8s可不可以正常访问,可以这样配置Ingress:
apiVersion: networking.k8s.io/v1beta1 kind: Ingressmetadata: name: nginx-web annotations: kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/use-reges: "true" nginx.ingress.kubernetes.io/proxy-connect-timeout: "600" nginx.ingress.kubernetes.io/proxy-send-timeout: "600" nginx.ingress.kubernetes.io/proxy-read-timeout: "600" nginx.ingress.kubernetes.io/proxy-body-size: "10m" nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: dev.wendev.site http: paths: - path: / backend: serviceName: spring-boot-istio-client servicePort: 19090
Istio的网关配置文件与k8s不大一样:
apiVersion: networking.istio.io/v1alpha3 kind: Gatewaymetadata: name: spring-boot-istio-gateway spec: selector: istio: ingressgateway servers: - port: number: 80 name: http protocol: HTTP hosts: - "*" ---apiVersion: networking.istio.io/v1alpha3 kind: Virtual Servicemetadata: name: spring-boot-istio spec: hosts: - "*" gateways: - spring-boot-istio-gateway http: - match: - uri: exact: /hello route: - destination: host: spring-boot-istio-client port: number: 19090
主要就是暴露/hello这个路径,并且指定对应的服务和端口。
首先搭建k8s集群并且安装istio。我使用的k8s版本是1.16.0,Istio版本是最新的1.6.0-alpha.1,使用istioctl命令安装Istio。建议跑通官方的bookinfo示例之后再来部署本项目。
注:以下命令都是在开启了自动注入Sidecar的前提下运行的
我是在虚拟机中运行的k8s,所以istio-ingressgateway没有外部ip:
$ kubectl get svc istio-ingressgateway -n istio-system NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEistio-ingressgateway NodePort 10.97.158.232 <none> 15020:30388/TCP,80:31690/TCP,443:31493/TCP,15029:32182/TCP,15030:31724/TCP,15031:30887/TCP,15032:30369/TCP,31400:31122/TCP,15443:31545/TCP 26h
所以,需要设置IP和端口,以NodePort的方式访问gateway:
export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}') export SECURE_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="https")].nodePort}') export INGRESS_HOST=127.0.0.1export GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT
这样就可以了。
接下来部署服务:
$ kubectl apply -f spring-boot-istio-server.yml $ kubectl apply -f spring-boot-istio-client.yml $ kubectl apply -f istio-gateway.yml
必须要等到两个pod全部变为Running而且Ready变为2/2才算部署完成。
接下来就可以通过
curl -s http://${GATEWAY_URL}/hello
访问到服务了。如果成功返回了Hello, JiangWen. This message comes from gRPC.的结果,没有出错则说明部署完成。
以上がSpring Boot + gRPC を使用してマイクロサービスを構築およびデプロイする方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。