惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - Zhang_Xiang

代码是 AI 写的,生产事故谁背锅? AI Agent 走出 Demo 幻觉的唯一解药:Harness Engineering 从 page、page_size 到游标:深入解析C端产品的两种主流分页技术 Apache Kafka 的基本概念 Apache Kafka 移除 ZK Proposals webRTC demo Spring Authorization Server(AS)从 Mysql 中读取客户端、用户 Java 对象实现 Serializable 的原因 Spring Data JPA 使用 Spring Authorization Server 实现授权中心 OAuth 2.1 框架 Spring Security dapr 本地环境升级 BuildPack 打包 如何拆分大型单体系统为微服务 高可用 Keycloak,K8s Keycloak 13 自定义用户身份认证流程(User Storage SPI) - Zhang_Xiang OAuth 2.0、OIDC 讲不清楚? Mokito 单元测试与 Spring-Boot 集成测试 关于 JMeter 5.4.1 的一点记录
spring-boot 2.5.4,nacos 作为配置、服务发现中心,Cloud Native Buildpacks 打包镜像,GitLab CI/CD
Zhang_Xiang · 2021-08-23 · via 博客园 - Zhang_Xiang

spring-boot 2.5.4,nacos 作为配置、服务发现中心,Cloud Native Buildpacks 打包镜像,GitLab CI/CD

本文主要介绍 Java 通过 Cloud Native Buildpacks 打包镜像,通过 Gitlab 配置 CI/CD。以及使用 nacos 作为配置中心,使用 grpc 作为 RPC 框架。

前置条件:

  • JDK 版本:1.8
  • gradle 版本:7.1
  • spring-boot 版本:2.5.4
  • nacos 版本:1.3.1
  • GitLab 配置

spring-boot gradle 插件

spring-boot gradle 插件在 gradle 中提供 spring-boot 支持。该插件可以打 jar 或者 war 包。

plugins {
	id 'org.springframework.boot' version '2.5.4'
}

新建一个 gradle 项目,该项目在只引用 id 'org.springframework.boot' version '2.5.4' 插件的情况下,gralde 任务分布完全没有变化,如下图所示。

引入 java 插件

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.5.4'
}

但当引入 java 插件后,情况就大大不同了,可见,spring-boot 插件和 java 插件一起应用后,将产生如下反应:

  1. 创建bootJar任务,执行该任务会生成一个 fat jar。该 jar 包把所有的类文件打包进 BOOT-INF/classes 中,把项目依赖的所有 jar 包打包进 BOOT-INF/lib 中。

  2. 配置 assemble 任务,该任务依赖于 bootJar 任务,所以执行 assemble 任务的时候也会执行 bootJar

  3. 配置 jar 任务,该任务可以配置 jar 包的 classifier。配置方式如下,默认情况下 classifier 为空字符串:

    bootJar {
        classifier = 'boot'
    }
    
    jar {
        classifier = ''
    }
    
  4. 创建 bootBuildImage 任务,该任务可以使用 CNB 打包 OCI 镜像。后面会详细介绍如何使用 CNB。

  5. 创建 bootRun 任务用于运行应用程序。

  6. 创建 bootArchives 配置,注意这里是配置,不是任务。当应用 maven 插件时会为 bootArchives 配置创建 uploadBootArchives 任务。bootArchives 默认情况下包含 bootJarbootWar 任务生成的文件。

    uploadBootArchives {
        repositories {
            mavenDeployer {
                repository url: 'https://repo.example.com'
            }
        }
    }
    
  7. 创建 developmentOnly 配置。该配置用于管理开发时的依赖,比如 org.springframework.boot:spring-boot-devtools,该依赖仅在开发时使用,无需打进 jar 包中。

    dependencies {
        developmentOnly 'org.springframework.boot:spring-boot-devtools'
    }
    
  8. 创建 productionRuntimeClasspath 配置。它等价于 runtimeClasspath 中的依赖减去 developmentOnly 配置中的依赖。

  9. 配置 JavaCompile 任务默认使用 UTF-8

  10. 配置 JavaCompile 任务使用 -parameters 配置编译器参数。

引入 io.spring.dependency-management 插件

引入该插件后,将自动管理依赖版本。

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.5.4'
    id "io.spring.dependency-management" version "1.0.11.RELEASE"
}

group 'com.toy'
version '1.0.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
}

引入 grpc 框架

基于本示例使用 nacos 作为服务发现中心,本示例将使用 net.devh:grpc-spring-boot-starter 依赖作为框架。

工程结构

目前为止,我们介绍了 java 项目中引入 spring gradle 所需的插件,以及各个组件的作用。接下来我们介绍如何引入 grpc,以及引入 grpc 后,我们的工程结构。

改造后工程结构总体如下:

protobuf

用于保存 proto 文件,以及发布 proto 文件,当客户端引用时,保证 jar 包最小。build.gradle 文件内容如下:

plugins {
    id 'java'
    id 'idea'
    id 'com.google.protobuf' version '0.8.17' //google proto 插件
    id 'maven-publish'
}

group 'com.toy'
version '1.0.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    //用于生成 java 类
    compileOnly 'io.grpc:grpc-protobuf:1.39.0'
    compileOnly 'io.grpc:grpc-stub:1.39.0'
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.17.3"
    }
    plugins {
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.39.0'
        }
    }

    generateProtoTasks {
        all()*.plugins {
            grpc {
            }
        }
    }
}

publishing {
    publications {
        proto_package(MavenPublication) {
        }
    }
    repositories {
        maven {
            allowInsecureProtocol = true
            url '你的 Maven 仓库地址'
            credentials {
                username = 'Maven 账号'
                password = 'Maven 密码'
            }
        }
    }
}

生成的 Java 类路径为 $projectName/build/.. 如下所示,生成的所有 class 文件位于 proto 文件夹下:

rpc

  1. 在 rpc 项目中添加启动类 ToyApplication,内容如下:

    package com.toy.rpc;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * @author Zhang_Xiang
     * @since 2021/8/20 15:34:58
     */
    @SpringBootApplication(scanBasePackages = {"com.toy.*"})
    public class ToyApplication {
        public static void main(String[] args) {
            SpringApplication.run(ToyApplication.class, args);
        }
    }
    
  2. 在包 com.toy.rpc.impl 中添加 HelloImpl 文件,内容如下:

    package com.toy.rpc.impl;
    
    import com.toy.proto.GreeterGrpc;
    import com.toy.proto.HelloReply;
    import com.toy.proto.HelloRequest;
    import io.grpc.stub.StreamObserver;
    import net.devh.boot.grpc.server.service.GrpcService;
    
    /**
     * @author Zhang_Xiang
     * @since 2021/8/20 15:35:56
     */
    @GrpcService
    public class HelloImpl extends GreeterGrpc.GreeterImplBase {
    
        @Override
        public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
            HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + request.getName()).build();
            responseObserver.onNext(reply);
            responseObserver.onCompleted();
        }
    }
    
  3. 添加集成测试

    (1)添加集成测试配置

    package com.toy.config;
    
    import net.devh.boot.grpc.client.autoconfigure.GrpcClientAutoConfiguration;
    import net.devh.boot.grpc.server.autoconfigure.GrpcServerAutoConfiguration;
    import net.devh.boot.grpc.server.autoconfigure.GrpcServerFactoryAutoConfiguration;
    import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
    import org.springframework.boot.test.context.TestConfiguration;
    
    /**
    * @author Zhang_Xiang
    * @since 2021/8/12 16:26:25
    */
    @TestConfiguration
    @ImportAutoConfiguration({
            GrpcServerAutoConfiguration.class, // Create required server beans
            GrpcServerFactoryAutoConfiguration.class, // Select server implementation
            GrpcClientAutoConfiguration.class}) // Support @GrpcClient annotation
    public class IntegrationTestConfigurations {
    
    }
    

    (2)添加测试类

    package com.toy;
    
    import com.toy.config.IntegrationTestConfigurations;
    import com.toy.proto.GreeterGrpc;
    import com.toy.proto.HelloReply;
    import com.toy.proto.HelloRequest;
    import net.devh.boot.grpc.client.inject.GrpcClient;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.annotation.DirtiesContext;
    import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
    
    import static org.junit.jupiter.api.Assertions.assertEquals;
    
    /**
    * @author Zhang_Xiang
    * @since 2021/8/20 16:02:41
    */
    @SpringBootTest(properties = {
            "grpc.server.inProcessName=test", // Enable inProcess server
            "grpc.server.port=-1", // Disable external server
            "grpc.client.inProcess.address=in-process:test" // Configure the client to connect to the inProcess server
    })
    @SpringJUnitConfig(classes = {IntegrationTestConfigurations.class})
    @DirtiesContext
    public class HelloServerTest {
    
        @GrpcClient("inProcess")
        private GreeterGrpc.GreeterBlockingStub blockingStub;
    
        @Test
        @DirtiesContext
        public void sayHello_replyMessage() {
            HelloReply reply = blockingStub.sayHello(HelloRequest.newBuilder().setName("Zhang").build());
            assertEquals("Hello Zhang", reply.getMessage());
        }
    }
    
    
  4. build.gradle

    plugins {
        id 'java'
        id 'idea'
        id 'org.springframework.boot' version '2.5.4'
        id "io.spring.dependency-management" version "1.0.11.RELEASE"
    }
    
    group 'com.toy'
    version '1.0.0-SNAPSHOT'
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        implementation platform('io.grpc:grpc-bom:1.39.0') //使所有 protobuf 插件的版本保持一致
        implementation 'net.devh:grpc-spring-boot-starter:2.12.0.RELEASE'
        developmentOnly 'org.springframework.boot:spring-boot-devtools'
    
        implementation project(':protobuf') //引入 protobuf 项目
    
        testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
        testImplementation 'io.grpc:grpc-testing'
        testImplementation('org.springframework.boot:spring-boot-starter-test')
    }
    
    bootBuildImage {
        imageName = "harbor.xxx.com/rpc/${project.name}:${project.version}"
        publish = true
        docker {
        publishRegistry {
                username = "admin"
                password = "admin"
                url = "harbor.xxx.com"
            }
        }
    }
    
    test {
        useJUnitPlatform()
    }
    

至此,整个 grpc 项目基础结构完成。

添加 nacos 配置中心、服务发现

  1. 在 rpc 项目 build.gradle 文件中引入读取 nacos 配置的 jar 包和注册服务到 nacos 中的 jar 包。

    dependencies{
        implementation 'org.springframework.boot:spring-boot-starter-web' //用于注册服务
        //添加此引用的原因是为了解决 spring boot 2.5.4 无法读取 nacos 配置的问题
        implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap:3.0.3'
        implementation 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2021.1'
        implementation 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config:2021.1'
    }
    
  2. 添加读取服务配置,在 rpc 项目中添加 bootstrap.propertise,内容如下:

    spring.profiles.active=dev
    spring.application.name=toy
    

    添加 bootstrap-dev.properties,内容如下:

    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    spring.cloud.nacos.config.namespace=52f2f610-46f6-4c57-a089-44072099adde
    spring.cloud.nacos.config.file-extension=yaml
    spring.cloud.nacos.config.group=DEFAULT_GROUP
    spring.cloud.nacos.discovery.namespace=52f2f610-46f6-4c57-a089-44072099adde
    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
    

至此,完成了服务端通过 nacos 读取配置,并且把服务端注册到 nacos 中。

gitlab CI/CD

在根项目目录下添加 .gitlab-ci.yml 文件。当 gitlab 安装了 runner 后,将自动触发 CI/CD,内容如下:

variables:
  CONTAINER_NAME: toy
  IMAGE_VERSION: 1.0.0
  IMAGE_TAG: harbor.xxx.com/toy/rpc
  PORT: 10086

stages:
  - test
  - publishJar
  - bootBuildImage //spring-boot 从 2.3.0 版本以后引入了 BootBuildImage 任务。
  - deploy

test:
  stage: test
  script:
    - gradle clean
    - gradle rpc:test

publishProtoBuf:
  stage: publishJar
  script:
    - gradle protobuf:publish

bootBuildImage:
  stage: bootBuildImage
  script:
    - gradle rpc:bootBuildImage

deployDev:
  stage: deploy
  script:
    - ssh $SERVER_USER@$SERVER_IP "docker login --username=$REGISTERY_NAME --password=$REGISTRY_PWD harbor.xxx.com; docker pull $IMAGE_TAG:$IMAGE_VERSION;"
    - ssh $SERVER_USER@$SERVER_IP "docker container rm -f $CONTAINER_NAME || true"
    - ssh $SERVER_USER@$SERVER_IP "docker run -d -p $PORT:$PORT -e JAVA_OPTS='-Xms512m -Xmx512m -Xss256K'  --net=host --name $CONTAINER_NAME $IMAGE_TAG:$IMAGE_VERSION"
  when: manual

这几个步骤什么意思呢?

  • 定义项目级别的变量
  • 定义了 4 个步骤,其中每个步骤中的任务又是可以并行的
    • test:运行项目中的单元测试(项目中没有写单元测试)、集成测试
    • publishJar:发布项目中 protobuf 项目到私有 maven 仓库中
    • bootBuildImage:打包镜像,并根据配置发布到镜像仓库中,这里打包过程需要详细说明
    • deploy:部署镜像到远程服务器中,在此步骤中配置了 when:manual,意思是手动触发此步骤

注意: 这里 SERVER_USERSERVER_IP$REGISTERY_NAME$REGISTRY_PWD 在 Gitlab 中通过超级管理员做了全局配置,即在所有项目中都可以使用。

定义 gitlab CI/CD 变量

CI/CD 变量一共有 4 种定义方式,如下:

  1. .gitlab-ci.yml 文件中定义
  2. 在项目中定义
  3. 在组中定义
  4. gitlab 全局变量

变量优先级(从高到低)

  1. 触发变量、流水线变量、手动流水线变量
  2. 项目变量
  3. 组变量
  4. 全局变量
  5. 继承变量
  6. .gitlab-ci.yml 文件中,job 中定义的变量
  7. .gitlab-ci.yml 中定义的变量,job 外的变量
  8. 部署变量
  9. 预定义变量

源码地址