春季webflux和grpcð‫
#java #spring #microservices #grpc

ð�ð»完整列表已使用的内容:

Spring网络框架
Spring WebFlux反应性休息服务
gRPC Java Grpc
gRPC-Spring-Boot-Starter Grpc春季启动器
Salesforce Reactive gRPC Salesforce反应性GRPC
Spring Data R2DBC使用反应驱动程序
集成SQL数据库的规范
Zipkin开源,端到端分布的tracing
Spring Cloud Sleuth分布式跟踪的自动配置
Prometheus监视和警报
Grafana用于构成Prometheus
的所有内容的可观察性仪表板 Kubernetes自动部署,扩展和管理集装箱应用程序
Docker和Docker-Compose
Helm Kubernetes的包装经理
Flywaydb用于迁移

您可以在 GitHub repository 中找到源代码。
对于这个项目,让我们使用GRPC和PostgreSQL实施Spring Microservice。
以前有same one using Kotlin
在LL上非常接近,但使用17 Java和Spring Webflux。
GRPC非常适合低潜伏期和高吞吐量通信,因此对于效率至关重要的微服务非常有用。
默认情况下,消息用Protobuf编码。虽然Protobuf有效地发送和接收二进制格式。
春天没有开箱即用的GRPC入门者,我们必须使用社区,最受欢迎的是 yidongnan
LogNet ,两者都很好并且可以使用,
对于这个项目,选择了第一个。
对于可反应性的GRPC可用Salesforce reactive-grpc
在第一步,我们必须添加 gRPC Java Codegen Plugin for Protobuf Compiler

所有UI接口将在端口上可用:

Swagger UI:http://localhost:8000/webjars/swagger-ui/index.html

Swagger

引导或Abaiaqian26

Grafana

Zipkin UI:http://localhost:9411

Zipkin

Prometheus UI:http://localhost:9090

Prometheus

该项目的Docker-Compose文件:

version: "3.9"

services:
  microservices_postgresql:
    image: postgres:latest
    container_name: microservices_postgresql
    expose:
      - "5432"
    ports:
      - "5432:5432"
    restart: always
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=bank_accounts
      - POSTGRES_HOST=5432
    command: -p 5432
    volumes:
      - ./docker_data/microservices_pgdata:/var/lib/postgresql/data
    networks: [ "microservices" ]

  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    command:
      - --config.file=/etc/prometheus/prometheus.yml
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
    networks: [ "microservices" ]

  node_exporter:
    container_name: microservices_node_exporter
    restart: always
    image: prom/node-exporter
    ports:
      - '9101:9100'
    networks: [ "microservices" ]

  grafana:
    container_name: microservices_grafana
    restart: always
    image: grafana/grafana
    ports:
      - '3000:3000'
    networks: [ "microservices" ]

  zipkin:
    image: openzipkin/zipkin:latest
    restart: always
    container_name: microservices_zipkin
    ports:
      - "9411:9411"
    networks: [ "microservices" ]

networks:
  microservices:
    name: microservices

GRPC消息使用Protobuf序列化,Protobuf是一种有效的二进制消息格式,它在服务器和客户端上序列非常快,
及其序列化导致小消息有效载荷,在有限的带宽方案(例如移动应用程序)中很重要。
用于指定每个服务的RPC定义的接口合同将使用协议缓冲区定义。
每个微服务都将在此处为此定义一个原始文件。
首先,我们必须在proto文件中定义服务并对其进行编译,它最多具有一元方法和一台服务器流:

syntax = "proto3";

package com.example.grpc.bank.service;

import "google/protobuf/wrappers.proto";
import "google/protobuf/timestamp.proto";

service BankAccountService {
  rpc createBankAccount (CreateBankAccountRequest) returns (CreateBankAccountResponse);
  rpc getBankAccountById (GetBankAccountByIdRequest) returns (GetBankAccountByIdResponse);
  rpc depositBalance (DepositBalanceRequest) returns (DepositBalanceResponse);
  rpc withdrawBalance (WithdrawBalanceRequest) returns (WithdrawBalanceResponse);
  rpc getAllByBalance (GetAllByBalanceRequest) returns (stream GetAllByBalanceResponse);
  rpc getAllByBalanceWithPagination(GetAllByBalanceWithPaginationRequest) returns (GetAllByBalanceWithPaginationResponse);
}

message BankAccountData {
  string id = 1;
  string firstName = 2;
  string lastName = 3;
  string email = 4;
  string address = 5;
  string currency = 6;
  string phone = 7;
  double balance = 8;
  string createdAt = 9;
  string updatedAt = 10;
}

message CreateBankAccountRequest {
  string email = 1;
  string firstName = 2;
  string lastName = 3;
  string address = 4;
  string currency = 5;
  string phone = 6;
  double balance = 7;
}

message CreateBankAccountResponse {
  BankAccountData bankAccount = 1;
}

message GetBankAccountByIdRequest {
  string id = 1;
}

message GetBankAccountByIdResponse {
  BankAccountData bankAccount = 1;
}

message DepositBalanceRequest {
  string id = 1;
  double balance = 2;
}

message DepositBalanceResponse {
  BankAccountData bankAccount = 1;
}

message WithdrawBalanceRequest {
  string id = 1;
  double balance = 2;
}

message WithdrawBalanceResponse {
  BankAccountData bankAccount = 1;
}

message GetAllByBalanceRequest {
  double min = 1;
  double max = 2;
  int32 page = 3;
  int32 size = 4;
}

message GetAllByBalanceResponse {
  BankAccountData bankAccount = 1;
}

message GetAllByBalanceWithPaginationRequest {
  double min = 1;
  double max = 2;
  int32 page = 3;
  int32 size = 4;
}

message GetAllByBalanceWithPaginationResponse {
  repeated BankAccountData bankAccount = 1;
  int32 page = 2;
  int32 size = 3;
  int32 totalElements = 4;
  int32 totalPages = 5;
  bool isFirst = 6;
  bool isLast = 7;
}

GRPC的实际Maven依赖性:

<dependencies>
    <dependency>
        <groupId>net.devh</groupId>
        <artifactId>grpc-server-spring-boot-starter</artifactId>
        <version>2.13.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.salesforce.servicelibs</groupId>
        <artifactId>reactor-grpc-stub</artifactId>
        <version>1.2.3</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-protobuf</artifactId>
        <version>${java.grpc.version}</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-stub</artifactId>
        <version>${java.grpc.version}</version>
    </dependency>
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java-util</artifactId>
        <version>3.21.7</version>
    </dependency>
</dependencies>

和Maven Protobuf插件:

<plugin>
    <groupId>org.xolstice.maven.plugins</groupId>
    <artifactId>protobuf-maven-plugin</artifactId>
    <version>0.6.1</version>
    <configuration>
        <protocArtifact>com.google.protobuf:protoc:${protobuf.protoc.version}:exe:${os.detected.classifier}
        </protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>io.grpc:protoc-gen-grpc-java:${java.grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
        <protocPlugins>
            <protocPlugin>
                <id>reactor-grpc</id>
                <groupId>com.salesforce.servicelibs</groupId>
                <artifactId>reactor-grpc</artifactId>
                <version>1.2.3</version>
                <mainClass>com.salesforce.reactorgrpc.ReactorGrpcGenerator</mainClass>
            </protocPlugin>
        </protocPlugins>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
                <goal>compile-custom</goal>
            </goals>
        </execution>
    </executions>
</plugin>

该插件为您的每个GRPC服务生成一个类。
例如: reactorBankCountServiceGrpc 其中 bankAccountgrpcService 是proto文件中GRPC服务的名称。
此类包含客户端存根和服务器 inflybase 您需要扩展。
汇编完成后,我们可以实施GRPC服务。
@grpcservice 允许我们通过特定于此服务的拦截器列表,因此我们可以在此处添加 loggrpcintector
为了验证请求,让我们使用 Spring-boot-starter-validation 使用 Hibernate Validator

@Slf4j
@GrpcService(interceptors = {LogGrpcInterceptor.class})
@RequiredArgsConstructor
public class BankAccountGrpcService extends ReactorBankAccountServiceGrpc.BankAccountServiceImplBase {

    private final BankAccountService bankAccountService;
    private final Tracer tracer;
    private static final Long TIMEOUT_MILLIS = 5000L;
    private final Validator validator;

    @Override
    @NewSpan
    public Mono<CreateBankAccountResponse> createBankAccount(Mono<CreateBankAccountRequest> request) {
        return request.flatMap(req -> bankAccountService.createBankAccount(validate(BankAccountMapper.of(req)))
                        .doOnNext(v -> spanTag("req", req.toString())))
                .map(bankAccount -> CreateBankAccountResponse.newBuilder().setBankAccount(BankAccountMapper.toGrpc(bankAccount)).build())
                .timeout(Duration.ofMillis(TIMEOUT_MILLIS))
                .doOnError(this::spanError)
                .doOnSuccess(result -> log.info("created account: {}", result.getBankAccount()));
    }

    @Override
    @NewSpan
    public Mono<GetBankAccountByIdResponse> getBankAccountById(Mono<GetBankAccountByIdRequest> request) {
        return request.flatMap(req -> bankAccountService.getBankAccountById(UUID.fromString(req.getId()))
                        .doOnNext(v -> spanTag("id", req.getId()))
                        .doOnSuccess(bankAccount -> spanTag("bankAccount", bankAccount.toString()))
                        .map(bankAccount -> GetBankAccountByIdResponse.newBuilder().setBankAccount(BankAccountMapper.toGrpc(bankAccount)).build()))
                .timeout(Duration.ofMillis(TIMEOUT_MILLIS))
                .doOnError(this::spanError)
                .doOnSuccess(response -> log.info("bankAccount: {}", response.getBankAccount()));
    }


    @Override
    @NewSpan
    public Mono<DepositBalanceResponse> depositBalance(Mono<DepositBalanceRequest> request) {
        return request
                .flatMap(req -> bankAccountService.depositAmount(UUID.fromString(req.getId()), BigDecimal.valueOf(req.getBalance()))
                        .doOnEach(v -> spanTag("req", req.toString()))
                        .map(bankAccount -> DepositBalanceResponse.newBuilder().setBankAccount(BankAccountMapper.toGrpc(bankAccount)).build()))
                .timeout(Duration.ofMillis(TIMEOUT_MILLIS))
                .doOnError(this::spanError)
                .doOnSuccess(response -> log.info("bankAccount: {}", response.getBankAccount()));
    }

    @Override
    @NewSpan
    public Mono<WithdrawBalanceResponse> withdrawBalance(Mono<WithdrawBalanceRequest> request) {
        return request.flatMap(req -> bankAccountService.withdrawAmount(UUID.fromString(req.getId()), BigDecimal.valueOf(req.getBalance()))
                        .doOnNext(v -> spanTag("req", req.toString()))
                        .map(bankAccount -> WithdrawBalanceResponse.newBuilder().setBankAccount(BankAccountMapper.toGrpc(bankAccount)).build()))
                .timeout(Duration.ofMillis(TIMEOUT_MILLIS))
                .doOnError(this::spanError)
                .doOnSuccess(response -> log.info("bankAccount: {}", response.getBankAccount()));
    }

    @Override
    @NewSpan
    public Flux<GetAllByBalanceResponse> getAllByBalance(Mono<GetAllByBalanceRequest> request) {
        return request
                .flatMapMany(req -> bankAccountService.findBankAccountByBalanceBetween(BankAccountMapper.findByBalanceRequestDtoFromGrpc(req))
                        .doOnNext(v -> spanTag("req", req.toString()))
                        .map(bankAccount -> GetAllByBalanceResponse.newBuilder().setBankAccount(BankAccountMapper.toGrpc(bankAccount)).build()))
                .timeout(Duration.ofMillis(TIMEOUT_MILLIS))
                .doOnError(this::spanError)
                .doOnNext(response -> log.info("bankAccount: {}", response.getBankAccount()));
    }

    @Override
    @NewSpan
    public Mono<GetAllByBalanceWithPaginationResponse> getAllByBalanceWithPagination(Mono<GetAllByBalanceWithPaginationRequest> request) {
        return request.flatMap(req -> bankAccountService.findAllBankAccountsByBalance(BankAccountMapper.findByBalanceRequestDtoFromGrpc(req))
                        .doOnNext(v -> spanTag("req", req.toString()))
                        .map(BankAccountMapper::toPaginationGrpcResponse))
                .timeout(Duration.ofMillis(TIMEOUT_MILLIS))
                .doOnError(this::spanError)
                .doOnNext(response -> log.info("response: {}", response.toString()));
    }

    private <T> T validate(T data) {
        var errors = validator.validate(data);
        if (!errors.isEmpty()) throw new ConstraintViolationException(errors);
        return data;
    }

    private void spanTag(String key, String value) {
        var span = tracer.currentSpan();
        if (span != null) span.tag(key, value);
    }

    private void spanError(Throwable ex) {
        var span = tracer.currentSpan();
        if (span != null) span.error(ex);
    }
}

Bloom-RPC

拦截器是一个GRPC概念,允许应用程序与传入或发出的GRPC调用交互。
他们提供了一种丰富请求处理管道的方法。
我们可以添加GRPC拦截器,在这里我们实现 logGrpCinterceptor

@Slf4j
public class LogGrpcInterceptor implements ServerInterceptor {

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        log.info("service: {}, method: {}, headers: {}", call.getMethodDescriptor().getServiceName(), call.getMethodDescriptor().getBareMethodName(), headers.toString());
        return next.startCall(call, headers);
    }
}

并将其添加到全局 grpcglobalserverspector

@Configuration(proxyBeanMethods = false)
public class GlobalInterceptorConfiguration {

    @GrpcGlobalServerInterceptor
    public LogGrpcInterceptor logServerInterceptor() {
        return new LogGrpcInterceptor();
    }
}

Zipkin

微服务的服务层有一些方法,例如,使用数据列表,它具有两种方法,
一个返回 pageimpl 用于一元方法响应的一个返回流量用于GRPC流响应方法。
当前的弹簧版本支持@transactional r2dbc
注释 界面和实现如下:

public interface BankAccountService {
    Mono<BankAccount> createBankAccount(BankAccount bankAccount);

    Mono<BankAccount> getBankAccountById(UUID id);

    Mono<BankAccount> depositAmount(UUID id, BigDecimal amount);

    Mono<BankAccount> withdrawAmount(UUID id, BigDecimal amount);

    Flux<BankAccount> findBankAccountByBalanceBetween(FindByBalanceRequestDto request);

    Mono<Page<BankAccount>> findAllBankAccountsByBalance(FindByBalanceRequestDto request);
}
@Slf4j
@Service
@RequiredArgsConstructor
public class BankAccountServiceImpl implements BankAccountService {

    private final BankAccountRepository bankAccountRepository;
    private final Tracer tracer;

    @Override
    @Transactional
    @NewSpan
    public Mono<BankAccount> createBankAccount(@SpanTag(key = "bankAccount") BankAccount bankAccount) {
        return bankAccountRepository.save(bankAccount)
                .doOnSuccess(savedBankAccount -> spanTag("savedBankAccount", savedBankAccount.toString()))
                .doOnError(this::spanError);
    }

    @Override
    @Transactional(readOnly = true)
    @NewSpan
    public Mono<BankAccount> getBankAccountById(@SpanTag(key = "id") UUID id) {
        return bankAccountRepository.findById(id)
                .doOnEach(v -> spanTag("id", id.toString()))
                .switchIfEmpty(Mono.error(new BankAccountNotFoundException(id.toString())))
                .doOnError(this::spanError);
    }

    @Override
    @Transactional
    @NewSpan
    public Mono<BankAccount> depositAmount(@SpanTag(key = "id") UUID id, @SpanTag(key = "amount") BigDecimal amount) {
        return bankAccountRepository.findById(id)
                .switchIfEmpty(Mono.error(new BankAccountNotFoundException(id.toString())))
                .flatMap(bankAccount -> bankAccountRepository.save(bankAccount.depositBalance(amount)))
                .doOnError(this::spanError)
                .doOnNext(bankAccount -> spanTag("bankAccount", bankAccount.toString()))
                .doOnSuccess(bankAccount -> log.info("updated bank account: {}", bankAccount));
    }

    @Override
    @Transactional
    @NewSpan
    public Mono<BankAccount> withdrawAmount(@SpanTag(key = "id") UUID id, @SpanTag(key = "amount") BigDecimal amount) {
        return bankAccountRepository.findById(id)
                .switchIfEmpty(Mono.error(new BankAccountNotFoundException(id.toString())))
                .flatMap(bankAccount -> bankAccountRepository.save(bankAccount.withdrawBalance(amount)))
                .doOnError(this::spanError)
                .doOnNext(bankAccount -> spanTag("bankAccount", bankAccount.toString()))
                .doOnSuccess(bankAccount -> log.info("updated bank account: {}", bankAccount));
    }

    @Override
    @Transactional(readOnly = true)
    @NewSpan
    public Flux<BankAccount> findBankAccountByBalanceBetween(@SpanTag(key = "request") FindByBalanceRequestDto request) {
        return bankAccountRepository.findBankAccountByBalanceBetween(request.min(), request.max(), request.pageable())
                .doOnError(this::spanError);
    }

    @Override
    @Transactional(readOnly = true)
    @NewSpan
    public Mono<Page<BankAccount>> findAllBankAccountsByBalance(@SpanTag(key = "request") FindByBalanceRequestDto request) {
        return bankAccountRepository.findAllBankAccountsByBalance(request.min(), request.max(), request.pageable())
                .doOnError(this::spanError)
                .doOnSuccess(result -> log.info("result: {}", result.toString()));
    }

    private void spanTag(String key, String value) {
        Optional.ofNullable(tracer.currentSpan()).ifPresent(span -> span.tag(key, value));
    }

    private void spanError(Throwable ex) {
        Optional.ofNullable(tracer.currentSpan()).ifPresent(span -> span.error(ex));
    }
}

Bloom-RPC

R2DBC 是一种API,为关系数据库提供反应性的,非阻滞的API。
使用此功能,您可以在Spring Boot中将反应性API读取并以反应性/同步方式写入数据库。
bankrepository reactivesortingRepository 的组合,我们的自定义 bankpostgresrepository 实现。
对于我们的自定义bankPostgresrepository在此处使用的实现 r2dbcentityTemplate databaseclient
如果我们想像JPA provide一样具有类似的分页响应,
我们必须手动创建 pageimpl

Zipkin

public interface BankAccountRepository extends ReactiveSortingRepository<BankAccount, UUID>, BankAccountPostgresRepository {
    Flux<BankAccount> findBankAccountByBalanceBetween(BigDecimal min, BigDecimal max, Pageable pageable);
}
public interface BankAccountPostgresRepository {
    Mono<Page<BankAccount>> findAllBankAccountsByBalance(BigDecimal min, BigDecimal max, Pageable pageable);
}
@Slf4j
@Repository
@RequiredArgsConstructor
public class BankAccountPostgresRepositoryImpl implements BankAccountPostgresRepository {

    private final DatabaseClient databaseClient;
    private final R2dbcEntityTemplate template;
    private final Tracer tracer;

    @Override
    @NewSpan
    public Mono<Page<BankAccount>> findAllBankAccountsByBalance(@SpanTag(key = "min") BigDecimal min,
                                                                @SpanTag(key = "max") BigDecimal max,
                                                                @SpanTag(key = "pageable") Pageable pageable) {

        var query = Query.query(Criteria.where(BALANCE).between(min, max)).with(pageable);

        var listMono = template.select(query, BankAccount.class).collectList()
                .doOnError(this::spanError)
                .doOnSuccess(list -> spanTag("list", String.valueOf(list.size())));

        var totalCountMono = databaseClient.sql("SELECT count(bank_account_id) as total FROM microservices.bank_accounts WHERE balance BETWEEN :min AND :max")
                .bind("min", min)
                .bind("max", max)
                .fetch()
                .one()
                .doOnError(this::spanError)
                .doOnSuccess(totalCount -> spanTag("totalCount", totalCount.toString()));

        return Mono.zip(listMono, totalCountMono).map(tuple -> new PageImpl<>(tuple.getT1(), pageable, (Long) tuple.getT2().get("total")));
    }


    private void spanTag(String key, String value) {
        Optional.ofNullable(tracer.currentSpan()).ifPresent(span -> span.tag(key, value));
    }

    private void spanError(Throwable ex) {
        Optional.ofNullable(tracer.currentSpan()).ifPresent(span -> span.error(ex));
    }
}

对于错误处理GRPC启动器的错误提供了 grpcadvice ,标志着要检查的类别的类别以获取异常处理方法,
@grpcexceptionhandler 标记要执行的注释方法,如果抛出了指定的例外,
状态代码很好地描述了here

@GrpcAdvice
@Slf4j
public class GrpcExceptionAdvice {

    @GrpcExceptionHandler(RuntimeException.class)
    public StatusException handleRuntimeException(RuntimeException ex) {
        var status = Status.INTERNAL.withDescription(ex.getLocalizedMessage()).withCause(ex);
        log.error("(GrpcExceptionAdvice) RuntimeException: ", ex);
        return status.asException();
    }

    @GrpcExceptionHandler(BankAccountNotFoundException.class)
    public StatusException handleBankAccountNotFoundException(BankAccountNotFoundException ex) {
        var status = Status.NOT_FOUND.withDescription(ex.getLocalizedMessage()).withCause(ex);
        log.error("(GrpcExceptionAdvice) BankAccountNotFoundException: ", ex);
        return status.asException();
    }

    @GrpcExceptionHandler(InvalidAmountException.class)
    public StatusException handleInvalidAmountException(InvalidAmountException ex) {
        var status = Status.INVALID_ARGUMENT.withDescription(ex.getLocalizedMessage()).withCause(ex);
        log.error("(GrpcExceptionAdvice) InvalidAmountException: ", ex);
        return status.asException();
    }

    @GrpcExceptionHandler(DataAccessException.class)
    public StatusException handleDataAccessException(DataAccessException ex) {
        var status = Status.INVALID_ARGUMENT.withDescription(ex.getLocalizedMessage()).withCause(ex);
        log.error("(GrpcExceptionAdvice) DataAccessException: ", ex);
        return status.asException();
    }

    @GrpcExceptionHandler(ConstraintViolationException.class)
    public StatusException handleConstraintViolationException(ConstraintViolationException ex) {
        var status = Status.INVALID_ARGUMENT.withDescription(ex.getLocalizedMessage()).withCause(ex);
        log.error("(GrpcExceptionAdvice) ConstraintViolationException: ", ex);
        return status.asException();
    }

    @GrpcExceptionHandler(MethodArgumentNotValidException.class)
    public StatusException handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        var status = Status.INVALID_ARGUMENT.withDescription(ex.getLocalizedMessage()).withCause(ex);
        log.error("(GrpcExceptionAdvice) MethodArgumentNotValidException: ", ex);
        return status.asException();
    }

    @GrpcExceptionHandler(IllegalArgumentException.class)
    public StatusException handleIllegalArgumentException(IllegalArgumentException ex) {
        var status = Status.INVALID_ARGUMENT.withDescription(ex.getLocalizedMessage()).withCause(ex);
        log.error("(GrpcExceptionAdvice) IllegalArgumentException: ", ex);
        return status.asException();
    }
}

与GRPC一起使用的UI客户量很少,个人喜欢使用 BloomRPC
另一个有用的工具是 grpcurl grpcui

下一步让我们将我们的微服务部署到K8,
在此示例中,我们可以使用简单的多阶段文件来构建Docker映像:

Lens

FROM --platform=linux/arm64 azul/zulu-openjdk-alpine:17 as builder
ARG JAR_FILE=target/spring-webflux-grpc-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

FROM azul/zulu-openjdk-alpine:17
COPY --from=builder dependencies/ ./
COPY --from=builder snapshot-dependencies/ ./
COPY --from=builder spring-boot-loader/ ./
COPY --from=builder application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher", "-XX:MaxRAMPercentage=75", "-XX:+UseG1GC"]

对于使用K8的工作,喜欢使用 Helm ,微服务部署很简单,并且具有部署本身,服务,配置
和ServiceMonitor。
最后一个是因为监视使用 kube-prometheus-stack helm chart

微服务头盔图yaml文件是:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.microservice.name }}
  labels:
    app: {{ .Values.microservice.name }}
spec:
  replicas: {{ .Values.microservice.replicas }}
  template:
    metadata:
      name: {{ .Values.microservice.name }}
      labels:
        app: {{ .Values.microservice.name }}
    spec:
      containers:
        - name: {{ .Values.microservice.name }}
          image: {{ .Values.microservice.image }}
          imagePullPolicy: Always
          resources:
            requests:
              memory: {{ .Values.microservice.resources.requests.memory }}
              cpu: {{ .Values.microservice.resources.requests.cpu }}
            limits:
              memory: {{ .Values.microservice.resources.limits.memory }}
              cpu: {{ .Values.microservice.resources.limits.cpu }}
          livenessProbe:
            httpGet:
              port: {{ .Values.microservice.livenessProbe.httpGet.port }}
              path: {{ .Values.microservice.livenessProbe.httpGet.path }}
            initialDelaySeconds: {{ .Values.microservice.livenessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.microservice.livenessProbe.periodSeconds }}
          readinessProbe:
            httpGet:
              port: {{ .Values.microservice.readinessProbe.httpGet.port }}
              path: {{ .Values.microservice.readinessProbe.httpGet.path }}
            initialDelaySeconds: {{ .Values.microservice.readinessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.microservice.readinessProbe.periodSeconds }}
          ports:
            - containerPort: {{ .Values.microservice.ports.http.containerPort }}
              name: {{ .Values.microservice.ports.http.name }}
            - containerPort: {{ .Values.microservice.ports.grpc.containerPort}}
              name: {{ .Values.microservice.ports.grpc.name }}
          env:
            - name: SPRING_APPLICATION_NAME
              value: microservice_k8s
            - name: JAVA_OPTS
              value: "-XX:+UseG1GC -XX:MaxRAMPercentage=75"
            - name: SERVER_PORT
              valueFrom:
                configMapKeyRef:
                  key: server_port
                  name: {{ .Values.microservice.name }}-config-map
            - name: GRPC_SERVER_PORT
              valueFrom:
                configMapKeyRef:
                  key: grpc_server_port
                  name: {{ .Values.microservice.name }}-config-map
            - name: SPRING_ZIPKIN_BASE_URL
              valueFrom:
                configMapKeyRef:
                  key: zipkin_base_url
                  name: {{ .Values.microservice.name }}-config-map
            - name: SPRING_R2DBC_URL
              valueFrom:
                configMapKeyRef:
                  key: r2dbc_url
                  name: {{ .Values.microservice.name }}-config-map
            - name: SPRING_FLYWAY_URL
              valueFrom:
                configMapKeyRef:
                  key: flyway_url
                  name: {{ .Values.microservice.name }}-config-map
      restartPolicy: Always
      terminationGracePeriodSeconds: {{ .Values.microservice.terminationGracePeriodSeconds }}
  selector:
    matchLabels:
      app: {{ .Values.microservice.name }}

---

apiVersion: v1
kind: Service
metadata:
  name: {{ .Values.microservice.name }}-service
  labels:
    app: {{ .Values.microservice.name }}
spec:
  selector:
    app: {{ .Values.microservice.name }}
  ports:
    - port: {{ .Values.microservice.service.httpPort }}
      name: http
      protocol: TCP
      targetPort: http
    - port: {{ .Values.microservice.service.grpcPort }}
      name: grpc
      protocol: TCP
      targetPort: grpc
  type: ClusterIP

---

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  labels:
    release: monitoring
  name: {{ .Values.microservice.name }}-service-monitor
  namespace: default
spec:
  selector:
    matchLabels:
      app: {{ .Values.microservice.name }}
  endpoints:
    - interval: 10s
      port: http
      path: /actuator/prometheus
  namespaceSelector:
    matchNames:
      - default

---

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Values.microservice.name }}-config-map
data:
  server_port: "8080"
  grpc_server_port: "8000"
  zipkin_base_url: zipkin:9411
  r2dbc_url: "r2dbc:postgresql://postgres:5432/bank_accounts"
  flyway_url: "jdbc:postgresql://postgres:5432/bank_accounts"

values.yaml 文件:

microservice:
  name: spring-webflux-grpc-microservice
  image: alexanderbryksin/spring_webflux_grpc_microservice:latest
  replicas: 1
  livenessProbe:
    httpGet:
      port: 8080
      path: /actuator/health/liveness
    initialDelaySeconds: 60
    periodSeconds: 5
  readinessProbe:
    httpGet:
      port: 8080
      path: /actuator/health/readiness
    initialDelaySeconds: 60
    periodSeconds: 5
  ports:
    http:
      name: http
      containerPort: 8080
    grpc:
      name: grpc
      containerPort: 8000
  terminationGracePeriodSeconds: 20
  service:
    httpPort: 8080
    grpcPort: 8000
  resources:
    requests:
      memory: '6000Mi'
      cpu: "3000m"
    limits:
      memory: '6000Mi'
      cpu: "3000m"

Lens

作为用于使用K8的UI工具,个人喜欢使用 Lens

您可以找到 GitHub repository here 的更多详细信息和源代码,
当然,总是在现实世界中,业务逻辑和基础架构代码要复杂得多,我们必须实施更多必要的功能。
我希望这篇文章有用和有益,并乐于收到任何反馈或问题,请随时 contact me email 或任何< strong> messengers :)