spring-boot 2.5.4,nacos 作為配置、服務發現中心,Cloud Native Buildpacks 打包鏡像,GitLab CI/CD
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 插件一起應用後,將產生如下反應:
-
創建
bootJar
任務,執行該任務會生成一個 fat jar。該 jar 包把所有的類文件打包進BOOT-INF/classes
中,把項目依賴的所有 jar 包打包進BOOT-INF/lib
中。 -
配置
assemble
任務,該任務依賴於bootJar
任務,所以執行assemble
任務的時候也會執行bootJar
。 -
配置
jar
任務,該任務可以配置 jar 包的classifier
。配置方式如下,默認情況下 classifier 為空字符串:bootJar { classifier = 'boot' } jar { classifier = '' }
-
創建
bootRun
任務用於運行應用程序。 -
創建
bootArchives
配置,注意這裡是配置,不是任務。當應用maven
插件時會為bootArchives
配置創建uploadBootArchives
任務。bootArchives
默認情況下包含bootJar
或bootWar
任務生成的文件。uploadBootArchives { repositories { mavenDeployer { repository url: '//repo.example.com' } } }
-
創建
developmentOnly
配置。該配置用於管理開發時的依賴,比如org.springframework.boot:spring-boot-devtools
,該依賴僅在開發時使用,無需打進 jar 包中。dependencies { developmentOnly 'org.springframework.boot:spring-boot-devtools' }
-
創建
productionRuntimeClasspath
配置。它等價於runtimeClasspath
中的依賴減去developmentOnly
配置中的依賴。 -
配置
JavaCompile
任務默認使用UTF-8
。 -
配置
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
-
在 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); } }
-
在包
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(); } }
-
添加集成測試
(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()); } }
-
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 配置中心、服務發現
-
在 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' }
-
添加讀取服務配置,在 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_USER
、SERVER_IP
、$REGISTERY_NAME
和 $REGISTRY_PWD
在 Gitlab 中通過超級管理員做了全局配置,即在所有項目中都可以使用。
定義 gitlab CI/CD 變量
CI/CD 變量一共有 4 種定義方式,如下:
- 在
.gitlab-ci.yml
文件中定義
- 在項目中定義
- 在組中定義
- gitlab 全局變量
變量優先級(從高到低)
- 觸發變量、流水線變量、手動流水線變量
- 項目變量
- 組變量
- 全局變量
- 繼承變量
.gitlab-ci.yml
文件中,job 中定義的變量.gitlab-ci.yml
中定義的變量,job 外的變量- 部署變量
- 預定義變量