在本文中,您將了解在 Kubernetes 上運(yùn)行 Java 應(yīng)用程序的最佳實(shí)踐。大多數(shù)這些建議也適用于其他語(yǔ)言。但是,我正在考慮 Java 特性范圍內(nèi)的所有規(guī)則,并且還展示了可用于基于 JVM 的應(yīng)用程序的解決方案和工具。當(dāng)使用最流行的 Java 框架(如 Spring Boot 或 Quarkus)時(shí),這些 Kubernetes 建議中的一些是設(shè)計(jì)強(qiáng)制的。我將向您展示如何有效地利用它們來(lái)簡(jiǎn)化開發(fā)人員的生活。
1、不要將 Limit 設(shè)置得太低
我們是否應(yīng)該為 Kubernetes 上的 Java 應(yīng)用設(shè)置 limit ?答案似乎顯而易見。有許多工具可以驗(yàn)證您的 Kubernetes YAML 清單,如果您沒有設(shè)置 CPU 或內(nèi)存 limit ,它們肯定會(huì)打印警告。不過,社區(qū)對(duì)此也有一些“熱議”。這是一篇有趣的文章,不建議設(shè)置任何 CPU limit 。這是另一篇文章,作為對(duì)上一篇文章的對(duì)比,他們考慮 CPU limit 。但我們也可以針對(duì)內(nèi)存 limit 開始類似的討論。特別是在 Java 應(yīng)用程序的上下文中。
然而,對(duì)于內(nèi)存管理,這個(gè)命題似乎大不相同。讓我們閱讀另一篇文章——這次是關(guān)于內(nèi)存 limit 和 request 的。簡(jiǎn)而言之,它建議始終設(shè)置內(nèi)存 limit。此外,限制應(yīng)與 request 相同。在 Java 應(yīng)用程序的上下文中,我們可以使用 -Xmx 、 -XX:MaxMetaspaceSize 或 -XX:ReservedCodeCacheSize 等 JVM 參數(shù)限制內(nèi)存也很重要。無(wú)論如何,從 Kubernetes 的角度來(lái)看,pod 接收它 request 的資源。Limit 與它無(wú)關(guān)。
這一切讓我得出了今天的第一個(gè)建議—A—不要將你的 limit 設(shè)置得太低。即使您設(shè)置了 CPU limit ,也不應(yīng)該影響您的應(yīng)用程序。例如,您可能知道,即使您的 Java 應(yīng)用程序在正常工作中不會(huì)消耗太多 CPU,但它需要大量 CPU 才能快速啟動(dòng)。對(duì)于我在 Kubernetes 上連接 MongoDB 的簡(jiǎn)單 Spring Boot 應(yīng)用程序,無(wú)限制和甚至 0.5 核之間的差異是顯著的。通常它在 10 秒以下開始:
將 CPU limit 設(shè)置為 500 millicores ,它開始大約 30 秒:
當(dāng)然,我們可以找到一些例子。但我們也會(huì)在下一節(jié)中討論它們。
2、首先考慮內(nèi)存使用
讓我們只關(guān)注內(nèi)存 limit 。如果您在 Kubernetes 上運(yùn)行 Java 應(yīng)用程序,則有兩個(gè)級(jí)別的最大使用 limit :容器和 JVM。但是,如果您沒有為 JVM 指定任何設(shè)置,也有一些默認(rèn)值。如果您不設(shè)置 -Xmx 參數(shù),JVM 會(huì)將其最大堆大小設(shè)置為可用 RAM 的大約 25%。該值是根據(jù)容器內(nèi)可見的內(nèi)存計(jì)算的。一旦您不在容器級(jí)別設(shè)置 limit ,JVM 將看到節(jié)點(diǎn)的整個(gè)內(nèi)存。
在 Kubernetes 上運(yùn)行應(yīng)用程序之前,您至少應(yīng)該測(cè)量它在預(yù)期負(fù)載下消耗了多少內(nèi)存。幸運(yùn)的是,有一些工具可以優(yōu)化在容器中運(yùn)行的 Java 應(yīng)用程序的內(nèi)存配置。例如,Paketo Buildpacks 帶有內(nèi)置內(nèi)存計(jì)算器,它使用公式 Heap = 總?cè)萜鲀?nèi)存 - Non-Heap - Headroom 計(jì)算 JVM 的 -Xmx 參數(shù)。另一方面,非堆值是使用以下公式計(jì)算的:Non-Heap = Direct Memory + Metaspace + Reserved Code Cache + (Thread Stack * Thread Count) 。
Paketo Buildpacks 目前是構(gòu)建 Spring Boot 應(yīng)用程序的默認(rèn)選項(xiàng)(使用 mvn spring-boot:build-image 命令)。讓我們?yōu)槲覀兊氖纠龖?yīng)用程序嘗試一下。假設(shè)我們將內(nèi)存限制設(shè)置為 512M,它將在 130M 的級(jí)別計(jì)算 -Xmx 。
我的應(yīng)用程序可以嗎?我至少應(yīng)該執(zhí)行一些負(fù)載測(cè)試來(lái)驗(yàn)證我的應(yīng)用程序在高流量下的性能。但再一次 - 不要將 limit 設(shè)置得太低。例如,對(duì)于 1024M 限制, -Xmx 等于 650M。
如您所見,我們使用 JVM 參數(shù)處理內(nèi)存使用情況。它可以防止我們?cè)诘谝还?jié)提到的文章中描述的 OOM kills 。因此,將 request 設(shè)置為與 limit 相同的級(jí)別并沒有太大意義。我建議將其設(shè)置為比正常使用高一點(diǎn)——比方說多 20%。
3、適當(dāng)?shù)?liveness 和 readiness 探針
3.1 介紹
了解 Kubernetes 中的 liveness 和 readiness 探針之間的區(qū)別至關(guān)重要。如果這兩個(gè)探針都沒有仔細(xì)實(shí)施,它們可能會(huì)降低服務(wù)的整體運(yùn)行,例如導(dǎo)致不必要的重啟。第三種類型的探針,啟動(dòng)探針,是 Kubernetes 中一個(gè)相對(duì)較新的特性。它允許我們避免在 liveness 或 readiness 探針上設(shè)置 initialDelaySeconds ,因此如果您的應(yīng)用程序啟動(dòng)需要很長(zhǎng)時(shí)間,它特別有用。有關(guān) Kubernetes 探針的一般和最佳實(shí)踐的更多詳細(xì)信息,我可以推薦那篇非常有趣的文章。
Liveness 探針用于決定是否重啟容器。如果應(yīng)用程序因任何原因不可用,有時(shí)重啟容器是有意義的。另一方面,readiness 探針用于確定容器是否可以處理傳入流量。如果一個(gè) pod 被識(shí)別為未就緒,它將被從負(fù)載平衡中移除。readiness 探針失敗不會(huì)導(dǎo)致 pod 重啟。Web 應(yīng)用程序最典型的 liveness 或 readiness 探針是通過 HTTP 端點(diǎn)實(shí)現(xiàn)的。
由于 liveness 探針的后續(xù)失敗會(huì)導(dǎo)致 pod 重新啟動(dòng),因此它不應(yīng)檢查您的應(yīng)用程序集成的可用性。這些事情應(yīng)該由 readiness 驗(yàn)證。
3.2 配置詳情
好消息是,最流行的 Java 框架(如 Spring Boot 或 Quarkus)提供了兩種 Kubernetes 探針的自動(dòng)配置實(shí)現(xiàn)。他們遵循最佳實(shí)踐,因此我們通常不必了解基礎(chǔ)知識(shí)。但是,在 Spring Boot 中,除了包含 Actuator 模塊之外,您還需要使用以下屬性啟用它們:
management: endpoint: health: probes: enabled:true
由于 Spring Boot Actuator 提供了多個(gè)端點(diǎn)(例如 metric、 trace),因此最好將其公開在與默認(rèn)端口不同的端口(通常為 8080 )。當(dāng)然,同樣的規(guī)則也適用于其他流行的 Java 框架。另一方面,一個(gè)好的做法是檢查您的主要應(yīng)用程序端口——尤其是在 readiness 探針中。
因?yàn)樗x了我們的應(yīng)用程序是否準(zhǔn)備好處理傳入的請(qǐng)求,所以它也應(yīng)該在主端口上監(jiān)聽。它與 liveness probe 看起來(lái)正好相反。如果整個(gè)工作線程池都很忙,我不想重新啟動(dòng)我的應(yīng)用程序。我只是不想在一段時(shí)間內(nèi)收到傳入流量。
我們還可以自定義 Kubernetes 探針的其他方面。假設(shè)我們的應(yīng)用程序連接到外部系統(tǒng),但我們沒有在我們的 readiness 探針中驗(yàn)證該集成。它并不重要,不會(huì)對(duì)我們的運(yùn)營(yíng)狀態(tài)產(chǎn)生直接影響。這是一個(gè)配置,它允許我們?cè)谔结樦袃H包含選定的集成集 (1),并在主服務(wù)器端口上公開 readiness 情況 (2) 。
spring: application: name:sample-spring-boot-on-kubernetes data: mongodb: host:${MONGO_URL} port:27017 username:${MONGO_USERNAME} password:${MONGO_PASSWORD} database:${MONGO_DATABASE} authentication-database:admin management: endpoint.health: show-details:always group: readiness: include:mongo#(1) additional-path:server:/readiness#(2) probes: enabled:true server: port:8081
幾乎沒有任何應(yīng)用可以不依賴外部解決方案(如數(shù)據(jù)庫(kù)、消息代理或其他應(yīng)用程序)。在配置 readiness 探針時(shí),我們應(yīng)該仔細(xì)考慮到該系統(tǒng)的連接設(shè)置。首先你應(yīng)該考慮外部服務(wù)不可用的情況。你將如何處理?我建議將這些超時(shí)減少到較低的值,如下所示。
spring: application: name:sample-spring-kotlin-microservice datasource: url:jdbc//postgres:5432/postgres username:postgres password:postgres123 hikari: connection-timeout:2000 initialization-fail-timeout:0 jpa: database-platform:org.hibernate.dialect.PostgreSQLDialect rabbitmq: host:rabbitmq port:5672 connection-timeout:2000
4、選擇合適的 JDK
如果您已經(jīng)使用 Dockerfile 構(gòu)建了鏡像,那么您可能使用的是來(lái)自 Docker Hub 的官方 OpenJDK 基礎(chǔ)鏡像。然而,目前,鏡像網(wǎng)站上的公告稱它已被正式棄用,所有用戶都應(yīng)該找到合適的替代品。我想這可能會(huì)讓人很困惑,所以你會(huì)在這里找到對(duì)原因的詳細(xì)解釋。
好吧,讓我們考慮一下我們應(yīng)該選擇哪個(gè)備選方案。不同的供應(yīng)商提供多種替代品。如果您正在尋找它們之間的詳細(xì)比較,您應(yīng)該訪問以下站點(diǎn)。17版本推薦使用 Eclipse Temurin。
另一方面,Jib 或 Cloud Native Buildpacks 等最流行的鏡像構(gòu)建工具會(huì)自動(dòng)為您選擇供應(yīng)商。默認(rèn)情況下,Jib 使用 Eclipse Temurin,而 Paketo Buildpacks 使用 Bellsoft Liberica 實(shí)現(xiàn)。當(dāng)然,您可以輕松地覆蓋這些設(shè)置。我認(rèn)為,例如,如果您在與 JDK 提供程序(如 AWS 和 Amazon Corretto)匹配的環(huán)境中運(yùn)行您的應(yīng)用程序,這可能是有意義的。
假設(shè)我們使用 Paketo Buildpacks 和 Skaffold 在 Kubernetes 上部署 Java 應(yīng)用程序。為了將默認(rèn)的 Bellsoft Liberica buildpack 替換為另一個(gè),我們只需要在 buildpacks 部分中逐字設(shè)置它。下面是一個(gè)利用 Amazon Corretto buildpack 的示例。
apiVersion:skaffold/v2beta22 kind:Config metadata: name:sample-spring-boot-on-kubernetes build: artifacts: -image:piomin/sample-spring-boot-on-kubernetes buildpacks: builder:paketobuildpacks/builder:base buildpacks: -paketo-buildpacks/amazon-corretto -paketo-buildpacks/java env: -BP_JVM_VERSION=17
我們還可以使用不同的 JDK 供應(yīng)商輕松測(cè)試我們的應(yīng)用程序的性能。如果您正在尋找此類比較的示例,您可以閱讀我描述此類測(cè)試和結(jié)果的文章。我使用幾個(gè)可用的 Paketo Java 構(gòu)建包測(cè)量了與 Mongo 數(shù)據(jù)庫(kù)交互的 Spring Boot 3 應(yīng)用程序的不同 JDK 性能。
5、考慮遷移到原生編譯
原生編譯是 Java 世界中真正的“游戲規(guī)則改變者”。但我敢打賭,你們中沒有多少人使用它——尤其是在生產(chǎn)中。當(dāng)然,在將現(xiàn)有應(yīng)用程序遷移到本機(jī)編譯的過程中存在(現(xiàn)在仍然存在)許多挑戰(zhàn)。GraalVM 在構(gòu)建期間執(zhí)行的靜態(tài)代碼分析可能會(huì)導(dǎo)致類似 ClassNotFound 或 MethodNotFound 的錯(cuò)誤。為了克服這些挑戰(zhàn),我們需要提供一些提示讓 GraalVM 了解代碼的動(dòng)態(tài)元素。這些提示的數(shù)量通常取決于庫(kù)的數(shù)量和應(yīng)用程序中使用的語(yǔ)言功能的一般數(shù)量。
像 Quarkus 或 Micronaut 這樣的 Java 框架試圖通過設(shè)計(jì)解決與原生編譯相關(guān)的挑戰(zhàn)。例如,他們盡可能避免使用反射。Spring Boot 還通過 Spring Native 項(xiàng)目大大改進(jìn)了原生編譯支持。因此,我在這方面的建議是,如果您要?jiǎng)?chuàng)建一個(gè)新的應(yīng)用程序,請(qǐng)按照為本機(jī)編譯做好準(zhǔn)備的方式進(jìn)行準(zhǔn)備。例如,使用 Quarkus,您可以簡(jiǎn)單地生成一個(gè) Maven 配置,其中包含用于構(gòu)建原生可執(zhí)行文件的專用配置文件。
native native false native
添加后,您可以使用以下命令進(jìn)行本機(jī)構(gòu)建:
$mvncleanpackage-Pnative
然后你可以分析在構(gòu)建過程中是否有任何問題。即使您現(xiàn)在不在生產(chǎn)環(huán)境中運(yùn)行原生應(yīng)用程序(例如您的組織不批準(zhǔn)它),您也應(yīng)該將 GraalVM 編譯作為您接受管道中的一個(gè)步驟。您可以使用最流行的框架輕松地為您的應(yīng)用程序構(gòu)建 Java 原生鏡像。例如,使用 Spring Boot,您只需在 Maven pom.xml 中提供以下配置,如下所示:
org.springframework.boot spring-boot-maven-plugin build-info build-image paketobuildpacks/builder:tiny true --allow-incomplete-classpath
6、正確配置日志記錄
在編寫 Java 應(yīng)用程序時(shí),日志記錄可能不是您首先考慮的事情。然而,在全局范圍內(nèi),它變得非常重要,因?yàn)槲覀冃枰軌蚴占?、存?chǔ)數(shù)據(jù),并最終快速搜索和呈現(xiàn)特定條目。最佳做法是將應(yīng)用程序日志寫入標(biāo)準(zhǔn)輸出 (stdout) 和標(biāo)準(zhǔn)錯(cuò)誤 (stderr) 流。Fluentd 是一種流行的開源日志聚合器,它允許您從 Kubernetes 集群收集日志、處理它們,然后將它們發(fā)送到您選擇的數(shù)據(jù)存儲(chǔ)后端。它與 Kubernetes 部署無(wú)縫集成。
Fluentd 嘗試將數(shù)據(jù)結(jié)構(gòu)化為 JSON 以統(tǒng)一不同來(lái)源和目的地的日志記錄。假設(shè)那樣,最好的方法可能是以這種格式準(zhǔn)備日志。使用 JSON 格式,我們還可以輕松地包含用于標(biāo)記日志的附加字段,然后使用各種條件在可視化工具中輕松搜索它們。
為了將我們的日志格式化為 Fluentd 可讀的 JSON,我們可以在 Maven 依賴項(xiàng)中包含 Logstash Logback 編碼器庫(kù)。
net.logstash.logback logstash-logback-encoder 7.2
然后我們只需要在文件 logback-spring.xml 中為我們的 Spring Boot 應(yīng)用程序設(shè)置一個(gè)默認(rèn)的控制臺(tái)日志 Appender 。
我們是否應(yīng)該避免使用額外的日志 appenders ,而只是將日志打印到標(biāo)準(zhǔn)輸出?根據(jù)我的經(jīng)驗(yàn),答案是——不。您仍然可以使用其他機(jī)制來(lái)發(fā)送日志。特別是如果您使用不止一種工具來(lái)收集組織中的日志——例如 Kubernetes 上的內(nèi)部堆棧和外部的全局堆棧。
就個(gè)人而言,我正在使用一種工具來(lái)幫助我解決性能問題,例如消息代理作為代理。在 Spring Boot 中,我們可以輕松地使用 RabbitMQ。只需包括以下 starter:
org.springframework.boot spring-boot-starter-amqp
然后你需要在 logback-spring.xml 中提供一個(gè)類似的 appender 配置:
{ "time":"%date{ISO8601}", "thread":"%thread", "level":"%level", "class":"%logger{36}", "message":"%message" } ${destination} api-service logs true ex_logstash
7、創(chuàng)建集成測(cè)試
好的,我知道——它與 Kubernetes 沒有直接關(guān)系。但是由于我們使用 Kubernetes 來(lái)管理和編排容器,我們還應(yīng)該對(duì)容器進(jìn)行集成測(cè)試。幸運(yùn)的是,使用 Java 框架,我們可以大大簡(jiǎn)化該過程。
例如,Quarkus 允許我們用 @QuarkusIntegrationTest 注釋測(cè)試。結(jié)合 Quarkus 容器構(gòu)建功能,它是一個(gè)非常強(qiáng)大的解決方案。我們可以針對(duì)包含該應(yīng)用程序的已構(gòu)建鏡像運(yùn)行測(cè)試。首先,讓我們包含 Quarkus Jib 模塊:
io.quarkus quarkus-container-image-jib
然后我們必須通過在 application.properties 文件中將 quarkus.container-image.build 屬性設(shè)置為 true 來(lái)啟用容器構(gòu)建。在測(cè)試類中,我們可以使用 @TestHTTPResource 和 @TestHTTPEndpoint 注解注入測(cè)試服務(wù)器 URL。
然后我們使用 RestClientBuilder 創(chuàng)建一個(gè)客戶端并調(diào)用在容器上啟動(dòng)的服務(wù)。測(cè)試類的名字不是偶然的。為了被自動(dòng)檢測(cè)為集成測(cè)試,它有 IT 后綴。
@QuarkusIntegrationTest publicclassEmployeeControllerIT{ @TestHTTPEndpoint(EmployeeController.class) @TestHTTPResource URLurl; @Test voidadd(){ EmployeeServiceservice=RestClientBuilder.newBuilder() .baseUrl(url) .build(EmployeeService.class); Employeeemployee=newEmployee(1L,1L,"JoshStevens", 23,"Developer"); employee=service.add(employee); assertNotNull(employee.getId()); } @Test publicvoidfindAll(){ EmployeeServiceservice=RestClientBuilder.newBuilder() .baseUrl(url) .build(EmployeeService.class); Setemployees=service.findAll(); assertTrue(employees.size()>=3); } @Test publicvoidfindById(){ EmployeeServiceservice=RestClientBuilder.newBuilder() .baseUrl(url) .build(EmployeeService.class); Employeeemployee=service.findById(1L); assertNotNull(employee.getId()); } }
您可以在我之前關(guān)于使用 Quarkus 進(jìn)行高級(jí)測(cè)試的文章中找到有關(guān)該過程的更多詳細(xì)信息。最終效果如下圖所示。當(dāng)我們?cè)跇?gòu)建期間使用 mvn clean verify 命令運(yùn)行測(cè)試時(shí),我們的測(cè)試在構(gòu)建容器鏡像后執(zhí)行。
該 Quarkus 功能基于 Testcontainers 框架。我們還可以將 Testcontainer 與 Spring Boot 一起使用。這是 Spring REST 應(yīng)用程序及其與 PostgreSQL 數(shù)據(jù)庫(kù)集成的示例測(cè)試。
@SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT) @Testcontainers @TestMethodOrder(MethodOrderer.OrderAnnotation.class) publicclassPersonControllerTests{ @Autowired TestRestTemplaterestTemplate; @Container staticPostgreSQLContainer>postgres= newPostgreSQLContainer<>("postgres:15.1") .withExposedPorts(5432); @DynamicPropertySource staticvoidregisterMySQLProperties(DynamicPropertyRegistryregistry){ registry.add("spring.datasource.url",postgres::getJdbcUrl); registry.add("spring.datasource.username",postgres::getUsername); registry.add("spring.datasource.password",postgres::getPassword); } @Test @Order(1) voidadd(){ Personperson=Instancio.of(Person.class) .ignore(Select.field("id")) .create(); person=restTemplate.postForObject("/persons",person,Person.class); Assertions.assertNotNull(person); Assertions.assertNotNull(person.getId()); } @Test @Order(2) voidupdateAndGet(){ finalIntegerid=1; Personperson=Instancio.of(Person.class) .set(Select.field("id"),id) .create(); restTemplate.put("/persons",person); Personupdated=restTemplate.getForObject("/persons/{id}",Person.class,id); Assertions.assertNotNull(updated); Assertions.assertNotNull(updated.getId()); Assertions.assertEquals(id,updated.getId()); } }
8、最后的想法
我希望這篇文章能幫助您在 Kubernetes 上運(yùn)行 Java 應(yīng)用程序時(shí)避免一些常見的陷阱。將其視為我在類似文章中找到的其他人的建議以及我在該領(lǐng)域的個(gè)人經(jīng)驗(yàn)的總結(jié)。
作者:Piotr
審核編輯:湯梓紅
-
cpu
+關(guān)注
關(guān)注
68文章
10769瀏覽量
210418 -
內(nèi)存
+關(guān)注
關(guān)注
8文章
2942瀏覽量
73726 -
JAVA
+關(guān)注
關(guān)注
19文章
2946瀏覽量
104362 -
容器
+關(guān)注
關(guān)注
0文章
490瀏覽量
22013 -
kubernetes
+關(guān)注
關(guān)注
0文章
223瀏覽量
8675
原文標(biāo)題:Kubernetes 上 Java 應(yīng)用的最佳實(shí)踐
文章出處:【微信號(hào):AndroidPush,微信公眾號(hào):Android編程精選】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論