使用Spring Boot和GraalVM在Knative上構建微服務 - piotr
在本文中,您將學習如何在 Knative 上執行相互通訊的 Spring Boot 微服務。我還將向您展示如何使用 GraalVM 準備 Spring Boot 應用程式的本機映像。然後我們將使用 Skaffold 和 Jib Maven 外掛在 Kubernetes 上執行它。
在 Knative 上,您可以執行任何型別的應用程式——不僅僅是一個函式。在這篇文章中,當我寫“微服務”時,其實我在思考的是服務到服務的通訊。
原始碼
如果您想自己嘗試一下,可以隨時檢視我的原始碼。為此,您需要克隆我的 GitHub 儲存庫。
作為本文中的微服務示例,我使用了兩個應用程式callme-service和caller-service. 它們都公開了一個端點,該端點列印了應用程式 pod 的名稱。caller-service應用程式還呼叫應用程式公開的端點callme-service。
在 Kubernetes 上,這兩個應用程式都將部署為多個修訂版的 Knative 服務。我們還將使用 Knative 路由在這些修訂中分配流量。下面可見的圖片說明了我們示例系統的架構。
1.準備Spring Boot微服務
我們有兩個簡單的 Spring Boot 應用程式,它們公開一個 REST 端點、健康檢查和執行記憶體 H2 資料庫。我們使用 Hibernate 和 Lombok。因此,我們需要在 Maven 中包含以下依賴項列表pom.xml。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> </dependency> |
每次我們呼叫ping端點時,它都會建立一個事件並將其儲存在 H2 資料庫中。REST 端點返回 Kubernetes 內的 pod 和名稱空間的名稱以及事件的 id。該方法在我們對叢集的手動測試中很有用。
@RestController @RequestMapping("/callme") public class CallmeController { @Value("${spring.application.name}") private String appName; @Value("${POD_NAME}") private String podName; @Value("${POD_NAMESPACE}") private String podNamespace; @Autowired private CallmeRepository repository; @GetMapping("/ping") public String ping() { Callme c = repository.save(new Callme(new Date(), podName)); return appName + "(id=" + c.getId() + "): " + podName + " in " + podNamespace; } } |
這是我們的模型類 - Callme。應用程式中的模型類caller-service非常相似。
@Entity @Getter @Setter @NoArgsConstructor @RequiredArgsConstructor public class Callme { @Id @GeneratedValue private Integer id; @Temporal(TemporalType.TIMESTAMP) @NonNull private Date addDate; @NonNull private String podName; } |
另外,讓我們看一下ping. CallerController稍後我們將在討論通訊和跟蹤時對其進行修改。現在,重要的是要了解此方法還呼叫 ping 暴露的方法callme-service並返回整個響應。
@GetMapping("/ping") public String ping() { Caller c = repository.save(new Caller(new Date(), podName)); String callme = callme(); return appName + "(id=" + c.getId() + "): " + podName + " in " + podNamespace + " is calling " + callme; } |
2. 使用 GraalVM 準備 Spring Boot 原生映象
Spring Native 支援使用 GraalVM 本機編譯器將 Spring 應用程式編譯為本機可執行檔案。有關此專案的更多詳細資訊,您可以參考其文件。這是我們應用程式的主要類。
@SpringBootApplication
public class CallmeApplication {
public static void main(String[] args) {
SpringApplication.run(CallmeApplication.class, args);
}
}
Hibernate 在執行時做了很多動態的事情。因此,我們需要讓 Hibernate 在構建時增強應用程式中的實體。我們需要將以下 Maven 外掛新增到我們的構建中。
<plugin>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>${hibernate.version}</version>
<executions>
<execution>
<configuration>
<failOnError>true</failOnError>
<enableLazyInitialization>true</enableLazyInitialization>
<enableDirtyTracking>true</enableDirtyTracking>
<enableExtendedEnhancement>false</enableExtendedEnhancement>
</configuration>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>
在本文中,我使用的是 Spring Native 的最新版本——0.9.0。由於 Spring Native 正在積極開發中,後續版本之間會有較大的變化。如果您將其與其他基於早期版本的文章進行比較,我們不必禁用proxyBeansMethods、排除SpringDataWebAutoConfiguration、新增spring-context-indexer到依賴項或建立hibernate.properties。涼爽的!我也可以使用 Buildpacks 來構建原生映象。
所以,現在我們只需要新增以下依賴項。
<dependency> <groupId>org.springframework.experimental</groupId> <artifactId>spring-native</artifactId> <version>0.9.0</version> </dependency> |
Spring AOT 外掛執行提高本機影像相容性和佔用空間所需的提前轉換。
<plugin> <groupId>org.springframework.experimental</groupId> <artifactId>spring-aot-maven-plugin</artifactId> <version>${spring.native.version}</version> <executions> <execution> <id>test-generate</id> <goals> <goal>test-generate</goal> </goals> </execution> <execution> <id>generate</id> <goals> <goal>generate</goal> </goals> </execution> </executions> </plugin> |
3. 使用 Buildpacks 在 Knative 上執行原生映象
使用 Builpacks 建立原生映象是我們的主要選擇。雖然它需要一個 Docker 守護程式,但它在每個作業系統上都能正常工作。但是,我們需要使用最新的穩定版 Spring Boot。在這種情況下,它是2.4.3。您也可以在 Maven pom.xml 中使用spring-boot-maven-plugin. 由於我們需要在 Kubernetes 上一步構建和部署應用程式,因此我更喜歡在 Skaffold 中進行配置。我們paketobuildpacks/builder:tiny用作構建器影像。還需要使用BP_BOOT_NATIVE_IMAGE環境變數啟用本機構建選項。
apiVersion: skaffold/v2beta11 kind: Config metadata: name: callme-service build: artifacts: - image: piomin/callme-service buildpacks: builder: paketobuildpacks/builder:tiny env: - BP_BOOT_NATIVE_IMAGE=true deploy: kubectl: manifests: - k8s/ksvc.yaml |
Skaffold 配置是指我們的 KnativeService清單。這是非常不典型的,因為我們需要將 pod 和名稱空間名稱注入到容器中。我們還允許每個 pod 最多有 10 個併發請求。如果超過,Knative 會擴大一些正在執行的例項。
apiVersion: serving.knative.dev/v1 kind: Service metadata: name: callme-service spec: template: spec: containerConcurrency: 10 containers: - name: callme image: piomin/callme-service ports: - containerPort: 8080 env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace |
預設情況下,Knative 不允許使用 KubernetesfieldRef功能。為了啟用它,我們需要更新名稱空間knative-features ConfigMap中的knative-serving。所需的屬性名稱是kubernetes.podspec-fieldref。
kind: ConfigMap apiVersion: v1 metadata: annotations: namespace: knative-serving labels: serving.knative.dev/release: v0.16.0 data: kubernetes.podspec-fieldref: enabled |
最後,我們可以使用以下命令在 Knative 上構建和部署 Spring Boot 微服務。
$ skaffold run
4. 使用 Jib 在 Knative 上執行原生映象
與我之前關於 Knative 的文章一樣,我們將使用 Skaffold 和 Jib 在 Kubernetes 上構建和執行我們的應用程式。幸運的是,Jib Maven Plugin 已經引入了對 GraalVM “native images”的支援。Jib GraalVM Native Image Extension 希望 能夠完成生成“原生影像”( 目標)native-image-maven-plugin 的繁重工作 。native-image:native-image然後擴充套件只是簡單地將二進位制檔案複製到容器映像中並將其設定為可執行檔案。
當然,與 Java 位元組碼不同,本機映像不可移植,而是特定於平臺的。Native Image Maven Plugin 不支援交叉編譯,因此 native-image 應該構建在與執行時架構相同的作業系統上。由於我在 Ubuntu 20.10 上構建了我的應用程式的 GraalVM 映像,因此我應該使用相同的基礎 Docker 映像來執行容器化微服務。在這種情況下,我選擇了映象ubuntu:20.10,如下所示。
<plugin> <groupId>com.google.cloud.tools</groupId> <artifactId>jib-maven-plugin</artifactId> <version>2.8.0</version> <dependencies> <dependency> <groupId>com.google.cloud.tools</groupId> <artifactId>jib-native-image-extension-maven</artifactId> <version>0.1.0</version> </dependency> </dependencies> <configuration> <from> <image>ubuntu:20.10</image> </from> <pluginExtensions> <pluginExtension> <implementation>com.google.cloud.tools.jib.maven.extension.nativeimage.JibNativeImageExtension</implementation> </pluginExtension> </pluginExtensions> </configuration> </plugin> |
如果你使用 Jib Maven 外掛,你首先需要構建一個原生映象。為了構建應用程式的本機映像,我們還需要包含一個native-image-maven-plugin. 你需要使用 GraalVM JDK 構建我們的應用程式。
<plugin> <groupId>org.graalvm.nativeimage</groupId> <artifactId>native-image-maven-plugin</artifactId> <version>21.0.0.2</version> <executions> <execution> <goals> <goal>native-image</goal> </goals> <phase>package</phase> </execution> </executions> </plugin> |
因此,本節的最後一步只是執行 Maven 構建。在我的配置中,native-image-maven-plugin需要在native-image配置檔案下啟用一個。
$ mvn clean package -Pnative-image
Skaffold 的配置是典型的。我們只需要啟用 Jib 作為構建工具。
apiVersion: skaffold/v2beta11 kind: Config metadata: name: callme-service build: artifacts: - image: piomin/callme-service jib: {} deploy: kubectl: manifests: - k8s/ksvc.yaml |
最後,我們可以使用以下命令在 Knative 上構建和部署 Spring Boot 微服務。
$ skaffold run
5. Knative 上微服務之間的通訊
我在 Knative 上部署了每個應用程式的兩個修訂版。只是為了比較,部署應用程式的第一個版本是使用 OpenJDK 編譯的。只有最新版本基於 GraalVM 原生映象。因此,我們可以比較兩個版本的啟動時間。
讓我們看一下部署我們應用程式的兩個版本後的修訂列表。流量分為 60% 到最新版本,40% 到每個應用程式的先前版本。
在底層,Knative 建立了 KubernetesServices和多個Deployments. Deployment每個 Knative總是有一個Revision。此外,有多種服務,但始終其中一項服務是針對所有修訂版的。那Service是一種ExternalName服務型別。假設您仍想在多個修訂版之間拆分流量,您應該在通訊中準確使用該服務。服務的名稱是callme-service。但是,我們應該使用帶有名稱空間名稱和svc.cluster.local字尾的 FQDN 名稱。
我們可以使用 SpringRestTemplate來呼叫callme-service. 為了保證對整個請求路徑的跟蹤,我們需要在後續呼叫之間傳播 Zipkin 標頭。對於通訊,我們將使用具有完全限定的內部域名 ( callme-service.serverless.svc.cluster.local) 的服務,如前所述。
@RestController @RequestMapping("/caller") public class CallerController { private RestTemplate restTemplate; CallerController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @Value("${spring.application.name}") private String appName; @Value("${POD_NAME}") private String podName; @Value("${POD_NAMESPACE}") private String podNamespace; @Autowired private CallerRepository repository; @GetMapping("/ping") public String ping(@RequestHeader HttpHeaders headers) { Caller c = repository.save(new Caller(new Date(), podName)); String callme = callme(headers); return appName + "(id=" + c.getId() + "): " + podName + " in " + podNamespace + " is calling " + callme; } private String callme(HttpHeaders headers) { MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); Set<String> headerNames = headers.keySet(); headerNames.forEach(it -> map.put(it, headers.get(it))); HttpEntity httpEntity = new HttpEntity(map); ResponseEntity<String> entity = restTemplate .exchange("http://callme-service.serverless.svc.cluster.local/callme/ping", HttpMethod.GET, httpEntity, String.class); return entity.getBody(); } } |
為了測試我們的微服務之間的通訊,我們只需要caller-service通過 Knative呼叫Route。
GET /caller/ping讓我們對呼叫者服務端點執行一些測試呼叫。我們應該使用 URL http://caller-service-serverless.apps.cluster-d556.d556.sandbox262.opentlc.com/caller/ping。
在第一次請求 caller-service 時呼叫最新版本的 callme-service(用 GraalVM 編譯)。在第三個請求中,它與舊版本的 callme-service(使用 OpenJDK 編譯)進行通訊。讓我們比較同一應用程式的這兩個版本的啟動時間。
使用 GraalVM,我們有0.3s而不是5.9s。我們還應該記住,我們的應用程式會啟動一個記憶體中的嵌入式 H2 資料庫。
6. 使用 Jaeger 配置跟蹤
為了啟用 Knative 的跟蹤,我們需要更新名稱空間knative-tracing ConfigMap中的knative-serving。當然,我們首先需要在我們的叢集中安裝 Jaeger。
apiVersion: operator.knative.dev/v1alpha1 kind: KnativeServing metadata: name: knative-tracing namespace: knative-serving spec: sample-rate: "1" backend: zipkin zipkin-endpoint: http://jaeger-collector.knative-serving.svc.cluster.local:9411/api/v2/spans debug: "false" |
你也可以使用 Helm chart 來安裝 Jaeger。使用此選項,您需要執行以下 Helm 命令。
$ helm repo add jaegertracing https://jaegertracing.github.io/helm-charts $ helm install jaeger jaegertracing/jaeger |
Knative 會自動建立 Zipkin span headers。我們唯一的目標是在caller-service和callme-service應用程式之間傳播 HTTP 標頭。在我的配置中,Knative 向 Jaeger 傳送 100% 的跟蹤資訊。讓我們看一下GET /caller/pingKnative 微服務網格中端點的一些跟蹤。
我們還可以檢視每個請求的詳細檢視。
結論
在 Knative 上執行微服務時,需要考慮幾件重要的事情。我專注於與通訊和跟蹤相關的方面。我還展示了 Spring Boot 不必在幾秒鐘內啟動。使用 GraalVM,它可以在幾毫秒內啟動,因此您絕對可以將其視為無伺服器框架。
相關文章
- 使用Kafka Streams和Spring Boot微服務中的分散式事務 - PiotrKafkaSpring Boot微服務分散式
- 使用Knative和Tekton在Kubernetes上釋出金絲雀版本 - Piotr
- 使用Spring Cloud Kubernetes基於Kubernetes、Spring Boot和Docker構建微服務架構 - MoriohCloudSpring BootDocker微服務架構
- 通過Spring Boot,Spring Cloud Gateway構建基於Consul叢集的微服務案例演示 – Piotr的TechBlogSpring BootCloudGateway微服務
- 透過Spring Boot,Spring Cloud Gateway構建基於Consul叢集的微服務案例演示 – Piotr的TechBlogSpring BootCloudGateway微服務
- 在Kubernetes上使用Spring Boot實現Hazelcast分散式快取 – PiotrSpring BootAST分散式快取
- 使用Java和Spring WebFlux構建響應式微服務JavaSpringWebUX微服務
- 構建Spring Boot應用的微服務服務動態路由Spring Boot微服務路由
- 使用Spring Boot和GraphQL構建靈活的API服務Spring BootAPI
- 基於Spring Boot和Spring Cloud實現微服務架構Spring BootCloud微服務架構
- Spring boot 2.1.9 + Dubbo 2.7.3 + Nacos 1.1.4 構建微服務系統Spring Boot微服務
- 使用Spring Boot 2.0快速構建服務元件Spring Boot元件
- 構建Spring Boot應用的微服務服務監控與告警Spring Boot微服務
- 使用Golang和MongoDB構建微服務GolangMongoDB微服務
- spring boot構建restful服務Spring BootREST
- 學習使用Spring Boot和Spring Cloud建立微服務架構的5本書 - hackernoonSpring BootCloud微服務架構
- spring cloud + spring boot + springmvc+mybatis微服務雲架構CloudSpring BootSpringMVCMyBatis微服務架構
- Spring Cloud Spring Boot mybatis分散式微服務雲架構CloudSpring BootMyBatis分散式微服務架構
- 使用Spring Boot實現微服務架構的開源專案Spring Boot微服務架構
- Spring Cloud構建微服務架構-spring cloud服務監控中心SpringCloud微服務架構
- Spring Cloud分散式微服務雲架構構建SpringCloud分散式微服務架構
- 在GraalVM中部署執行Spring Boot應用 - Indrek OtsLVMSpring Boot
- Spring Cloud構建微服務架構-服務閘道器SpringCloud微服務架構
- Spring Cloud構建微服務架構-Hystrix服務降級SpringCloud微服務架構
- 使用Spring Boot和Kafka Streams實現基於SAGA模式的分散式事務原始碼教程 - PiotrSpring BootKafka模式分散式原始碼
- 在 Kubernetes 上使用Spring Boot+ActiveMQSpring BootMQ
- 在國外是如何用Spring Boot、Spring Cloud、Docker實現微服務系統架構Spring BootCloudDocker微服務架構
- 使用JBang構建Spring Boot Rest API教程Spring BootRESTAPI
- 使用Springdoc OpenAPI替代SpringFox提供微服務API文件 – PiotrSpringAPI微服務
- spring cloud + spring boot + springmvc+mybatis分散式微服務雲架構CloudSpring BootSpringMVCMyBatis分散式微服務架構
- 如何使用訊息佇列、Spring Boot和Kubernetes擴充套件微服務佇列Spring Boot套件微服務
- 在spring boot中整合微服務閘道器係統Spring Cloud ZuulSpring Boot微服務CloudZuul
- 微服務架構:構建PHP微服務生態微服務架構PHP
- Laravel 5 使用 Grpc 構建的微服務LaravelRPC微服務
- 使用Intellij中的Spring Initializr來快速構建Spring Boot工程IntelliJSpring Boot
- Spring Boot中使用JPA構建動態查詢Spring Boot
- 使用Spring Boot、Spring State Machine Framework和Zookeeper構建分散式狀態機 - ÖzdinçÇelikelSpring BootMacFramework分散式
- Spring Cloud構建微服務架構—服務閘道器過濾器SpringCloud微服務架構過濾器