一、引言
傳統(tǒng)后端Java & Spring單體架構在向微服務架構升級的過程中,我們引入了Spring Cloud(Netflex、Alibaba等)框架,Spring Cloud幫我們解決了分布式服務的注冊發(fā)現、服務的動態(tài)配置等等。以目前國內比較流行的Spring Cloud Alibaba為例,我們需要在我們的代碼中集成Spring Cloud Alibaba的相關依賴,然后單獨部署Nacos服務端、Seata服務端等。我們的確借助Spring Cloud框架完成了微服務架構下的絕大部分功能,但是代價就是我們的代碼不得不依賴Spring Cloud框架,后續(xù)隨著Java版本、Spring版本的升級,我們的代碼也需要不斷去適配新版本的Spring Cloud框架,同時我們還需要維護和升級服務注冊中心、配置中心等的部署,給開發(fā)和運維人員都帶來不小的壓力。
隨著云原生時代的到來,容器與K8S已成為PasS平臺的事實標準。
云計算
指托管在外部數據中心并按使用量付費提供給用戶的軟件基礎設施。公司不必為昂貴的服務器付費并進行維護。相反,他們可以使用云提供商提供的按需云原生服務,例如存儲、數據庫和分析。
云原生
是在云計算環(huán)境中構建、部署和管理現代應用程序的軟件方法。CNCF 將不可變基礎設施、微服務、聲明式 API、容器和服務網格列為云原生架構的核心技術。
云計算 vs 云原生
云計算是云供應商按需提供的資源、基礎設施和工具。而 云原生是一種使用云計算模型構建和運行軟件程序的方法。
云原生應用程序開發(fā)
描述了開發(fā)人員如何以及在何處構建和部署云原生應用程序。開發(fā)人員采用特定的軟件實踐來縮短軟件交付時間,并提供滿足不斷變化的用戶期望的準確功能。一些常見的云原生開發(fā)實踐包括CI、CD、Devops、Serverless。
容器(如Docker)
為更輕量的虛擬化技術,將應用打包成容器,能夠保持多環(huán)境運行的一致性,快速部署遷移。K8S 可以理解為負責集群節(jié)點編排、容器編排的平臺,管理集群中的部署節(jié)點、容器的部署與調度編排、容器間的訪問路由編排等。
Service Mesh服務網格(如Istio)
可以簡單理解為K8S容器管理平臺之上的微服務管理平臺,不侵入代碼(跨編程語言),通過Sidecar(伴生容器)的形式將原本微服務框架中的基礎功能(服務注冊發(fā)現、服務路由、流量分發(fā)、熔斷、限流、監(jiān)控、安全等)提取到基礎設施層,但額外引入的Sidecar會增加集群的資源損耗、請求延時等。
Serverless無服務器
無服務器計算是一種云原生模式,云提供商完全管理底層服務器基礎設施。開發(fā)人員之所以使用無服務器計算,是因為云基礎設施會自動擴展和配置以滿足應用程序要求。開發(fā)人員只需為應用程序使用的資源付費。當應用程序停止運行時,無服務器架構會自動移除計算資源。
參考:https://aws.amazon.com/cn/what-is/cloud-native/
我們在將后端微服務架構向云原生遷移的過程中,也出現了如下幾種方式。
二、方式1:在K8S上部署Spring Cloud Alibaba
有的團隊之前使用過Spring Cloud Alibaba,所以在向云原生K8S平臺遷移的時候,保持原代碼不變,直接在K8S平臺上部署了一套Nacos服務端等組件,然后將應用發(fā)布到K8S平臺,應用依舊使用Nacos進行服務注冊發(fā)現和配置管理。
注: 通常會將Nacos也部署到K8S集群內,
若Nacos部署到K8S集群外,集群外IP地址空間和K8S集群內IP地址空間不通,會導致Nacos與K8S集群內應用間無法進行通信。
三、方式2:在K8S上部署Spring Cloud K8S
有的團隊調研發(fā)現了Spring Cloud K8S,可以將Spring Cloud框架和K8S平臺進行融合,所以在代碼中集成Spring Cloud K8S框架,Spring Cloud K8S同樣包括了DiscoveryClient和Config兩大模塊,分別實現了Spring Cloud的服務發(fā)現、配置管理等接口。集成Spring Cloud K8S框架的應用通過查詢K8S API獲取服務的Endpoint、獲取對應的ConfigMap,所以應用部署到K8S時,還需賦予應用查詢K8S API的K8S平臺相關權限,K8S相關權限定義可參見Spring Cloud K8S官網示例。
集成Spring Cloud K8S,除了代碼端需要依賴Spring Cloud K8S框架,還需要在K8S平臺定義:
- Service、Deployment - 用于獲得服務的注冊信息
- ConfigMap - 用來指定應用的配置信息
同時還需要區(qū)分本地開發(fā)環(huán)境(無K8S)和線上運行環(huán)境(有K8S)的服務間調用方式(URL或服務發(fā)現)、配置讀取(本地配置或ConfigMap配置及其優(yōu)先級),通常會在代碼中bootstrap.yaml默認關閉Spring Cloud K8S(默認開發(fā)環(huán)境禁用Spring Cloud K8S),
# bootstrap.yaml
spring:
cloud:
kubernetes:
enabled: false
然后在線上K8S環(huán)境通過環(huán)境變量開啟Spring Cloud K8S:
# deployment.yaml
...
env:
- name: SPRING_CLOUD_KUBENETERS_ENABLED
value: "true"
...
Spring Cloud K8S、Spring Cloud Alibaba等Spring Cloud方案,都需要侵入代碼,都有一定的學習成本,且后續(xù)升級還需要各種適配,還需要考慮本地和線上運行環(huán)境的不同。Spring Cloud K8S方案相較于Spring Cloud Alibaba,不需要部署單獨的中間件(如Nacos),充分利用了K8S平臺本身提供的Service、ConfigMap等特性,減少了單獨維護Spring Cloud各方案本身提供的服務注冊中心、配置中心(如Nacos、Eureka等)的工作量,但如果有分布式事務、熔斷限流等需求,還是要在代碼層面解決,目前K8S平臺暫不提供相關功能。
3.1 第1次優(yōu)化:移除Spring Cloud K8S DiscoveryClient
前文提到過Spring Cloud K8S包括DiscoveryClient和Config兩大模塊,分別實現了Spring Cloud的服務發(fā)現、配置管理接口。由于K8S本身支持Service概念,所以可以直接借助K8S平臺本身基于Service的服務注冊發(fā)現機制,將代碼中的Spring Cloud K8S Discovery Client完全移除。如此在本地開發(fā)環(huán)境通過應用URL進行相互調用,在線上K8S環(huán)境同樣通過URL(對應K8S Service)進行調用,二者都是通過直接指定URL進行調用(行為一致),在線上K8S環(huán)境通過指定Service URL(如:http://serviceName.namespace:8080),底層的服務注冊發(fā)現、服務轉發(fā)等均有K8S平臺來完成,代碼端從此不再需要關注服務注冊發(fā)現、負載均衡(由K8S底層代理模式決定)等問題。
此種方式下,代碼端僅依賴Spring Cloud K8S Config模塊,本地開發(fā)和線上環(huán)境均通過URL進行服務間相互調用,充分利用了K8S平臺本身的Service機制。
四、方式3:在K8S上部署SpringBoot應用
之前通過集成Spring Cloud K8S框架,貌似讓我們的微服務架構更傾向于云原生了,但是開發(fā)者需要同時對Spring Cloud、Spring Cloud K8S、K8S平臺本身都有所了解,即便前文提到的移除Spring Cloud K8S DiscoveryClient的方式,開發(fā)者還是需要關注K8S上ConfigMap中的配置格式及配置優(yōu)先級、本地與線上運行環(huán)境行為不一致等問題,給開發(fā)者帶來了較大的學習成本和不確定性。
既然我們已經利用K8S Service替代了原來的Spring Cloud K8S DiscoveryClient模塊,那我們是否也可以利用K8S平臺的某些特性替代Spring Cloud K8S Config模塊呢?如此我們的代碼端完全不依賴Spring Cloud K8S框架,在線上K8S環(huán)境充分利用K8S平臺的特性,從而使我們的代碼端與K8S徹底解耦。
4.1 第2次優(yōu)化:移除Spring Cloud K8S Config
我們的后端Java應用(SpringBoot)通常都是以jar包形式運行,jar包內的配置文件即為內置的配置文件,通常也是我們開發(fā)環(huán)境代碼端的配置,在線上運行環(huán)境時我們需要一種方式來替換jar包內原有的配置,如之前使用Spring Cloud框架時代碼端會集成相應的配置中心(如Spring Cloud Alibaba依賴Nacos),又或者集成單獨的配置管理中心(如Apollo等),此種方式的代價就是侵入代碼,且需要依賴獨立的配置管理中心。
回到外部配置覆蓋jar內配置這個問題本身,SpringBoot自身提供了自定義config/目錄的方案,config/目錄需位于執(zhí)行java啟動命令的工作目錄下,且config/目錄內的配置優(yōu)先級高于jar內配置的優(yōu)先級,此種方式不需要依賴獨立的配置中心。
而在K8S平臺中ConfigMap本身就是用于管理配置的,并支持掛載到容器中的指定目錄,ConfigMap中的內容發(fā)生改變時,也會近實時地更新容器內掛載的文件內容。如此在K8S環(huán)境中通過ConfigMap定義線上環(huán)境的配置,然后通過掛載到應用運行容器的config/目錄下,即可達到K8S平臺上的外部配置ConfigMap替換jar包內配置的效果。此種方式充分利用了SpringBoot自身的外部配置特性,對應用代碼沒有任何改動,也無需集成配置中心組件(如Spring Cloud K8S Config、Spring Cloud Alibaba Config等),而在線上利用K8S ConfigMap掛載到容器目錄的方式完成了外部配置的替換。具體K8S Deployment掛載ConfigMap到容器的腳本示例如下:
# 應用配置描述文件
kind: ConfigMap
apiVersion: v1
metadata:
name: app-atom
data:
application.yaml: |-
osmium:
# atom相關屬性
atom:
props:
name: myName
age: 30
other: otherValue
---
# 應用部署描述文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-atom
# ...
spec:
# ...
spec:
# 聲明卷
volumes:
# 聲明ConfigMap卷
- name: app-config
configMap:
# 對應K8s ConfigMap名稱
name: app-atom
containers:
- name: app-atom
image: $DOCKER_REGISTRY/$DOCKER_NAMESPACE/$APP_NAME_ATOM:$IMAGE_TAG
# ...
# 掛載ConfigMap配置文件到/config目錄下(config目錄需位于執(zhí)行java啟動命令的工作目錄下)
volumeMounts:
- name: app-config
mountPath: /config/
# ...
綜上,完整的K8S腳本Deployment、Service、ConfigMap定義如下圖:
K8S各資源間的關系如下圖:
4.2 支持配置自動刷新
使用config/目錄定義外部配置文件這種方式,在config/目錄下的配置文件發(fā)生改變時,默認應用端是不支持動態(tài)刷新的。若想支持外部配置的動態(tài)刷新,可通過集成spring-cloud-context組件,該組件結合spring-boot-starter-actuator組件,在外部配文件發(fā)生變更后,可手動調用POST /actuator/refresh
端點觸發(fā)配置的動態(tài)刷新,此方式會對Spring Environment
、標記@ConfigurationProperties的屬性類
及標記@RefreshScope(包含@Value屬性)的類
進行動態(tài)更新,從而達到刷新配置的效果。若想支持在外部配置發(fā)生變更時自動刷新應用配置,可通過監(jiān)聽config/目錄下的配置文件是否發(fā)生變更,若發(fā)生變更則調用Spring Cloud Context的配置刷新方法ContextRefresher.refresh()
以刷新應用的配置。此種集成Spring Cloud Context及創(chuàng)建config/目錄以支持配置動態(tài)刷新完全是Spring框架自身的特性,僅依賴文件系統(tǒng),不依賴K8S平臺,即便后續(xù)切換到不同PasS平臺也完全沒有關系。監(jiān)聽配置目錄及觸發(fā)配置變更的核心代碼如下:
/**
* 配置監(jiān)聽屬性
*
* @author luohq
* @date 2023-04-13 15:44
*/
@Data
@ConfigurationProperties(prefix = ConfigMonitorProps.PREFIX)
public class ConfigMonitorProps {
/**
* 配置前綴
*/
public static final String PREFIX = "osmium.config.monitor";
/**
* 是否啟用配置監(jiān)聽
*/
private Boolean enabled = true;
/**
* 外部配置文件所在的文件夾<br/>
* 注:推薦將配置文件統(tǒng)一放到單獨的config文件下,且位于運行java命令的工作目錄下,如此Spring會默認讀取config文件夾下的配置文件,且config文件下的配置優(yōu)先級高于jar包內(classpath)配置文件
*/
private String dirPath;
}
---
/**
* 配置監(jiān)聽、通知服務
*
* @author luohq
* @date 2023-04-13
*/
public class WatchNotifyService {
private static final Logger log = LoggerFactory.getLogger(WatchNotifyService.class);
private ConfigMonitorProps configMonitorProps;
private ContextRefresher contextRefresher;
public WatchNotifyService(ConfigMonitorProps configMonitorProps, ContextRefresher contextRefresher) {
this.configMonitorProps = configMonitorProps;
this.contextRefresher = contextRefresher;
}
@PostConstruct
void initStartWatch() {
new Thread(() -> {
startWatch();
}, "CONFIG-WATCHING-THREAD").start();
}
/**
* 啟動配置文件夾監(jiān)聽
*/
public void startWatch() {
try {
if (!StringUtils.hasText(this.configMonitorProps.getDirPath())) {
log.warn("Stop Config Directory Watching because the dir-path is blank!");
return;
}
//監(jiān)聽目錄
Path path = Paths.get(this.configMonitorProps.getDirPath());
if (!Files.isDirectory(path)) {
log.warn("Stop Config Directory Watching because the dir-path: {} is not a directory!", this.configMonitorProps.getDirPath());
return;
}
log.info("Start Config Directory Watching in dir: '{}'", this.configMonitorProps.getDirPath());
//創(chuàng)建一個文件系統(tǒng)的監(jiān)聽服務
WatchService watchService = FileSystems.getDefault().newWatchService();
//為該文件夾注冊監(jiān)聽,監(jiān)聽新建、修改、刪除事件。只能為文件夾(目錄)注冊監(jiān)聽,不能為單個文件注冊監(jiān)聽
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
//編寫事件處理
while (true) {
//拉取一個WatchKey。當觸發(fā)監(jiān)聽的事件時,就會產生一個WatchKey,此WatchKey封裝了事件信息。
WatchKey watchKey = watchService.take();
//使用循環(huán)是因為這一個WatchKey中可能有多個文件變化了,比如Ctrl+A全選,然后刪除,只觸發(fā)了一個WatchKey,但有多個文件變化了
for (WatchEvent event : watchKey.pollEvents()) {
log.info("WATCHING {}/{} - {}", this.configMonitorProps.getDirPath(), event.context(), event.kind());
}
//刷新配置
this.refreshConfig();
//雖然是while()循環(huán),但WatchKey和ByteBuffer一樣,使用完要重置狀態(tài),才能繼續(xù)用。
//如果不重置,WatchKey使用一次過后就不能再使用,即只能監(jiān)聽到一次文件變化。
watchKey.reset();
}
} catch (Throwable ex) {
log.error("Config Directory Watching Exception!", ex);
}
}
/**
* 刷新配置
*
* @return 被刷新的屬性名
*/
public Set<String> refreshConfig() {
log.info("START REFRESH CONTEXT CONFIG");
//更新Spring Cloud配置
Set<String> refreshKeys = this.contextRefresher.refresh();
return refreshKeys;
}
}
綜上,在代碼中我們僅僅只是搭建了一個SpringBoot應用,無需依賴各種Spring Cloud實現方案,應用間的相互調用使用URL,而外部配置的替換遵循SpringBoot自帶的config/目錄形式,而在線上K8S環(huán)境則通過K8S Service支持應用服務的注冊發(fā)現,通過K8S ConfigMap掛載config/目錄的形式支持外部配置的替換。此種方式也是筆者推崇的Spring應用向K8S遷移的方式,此種方式使得代碼端更清爽,不必依賴繁重的Spring Cloud框架,也不用考慮后續(xù)Spring Cloud框架的升級,開發(fā)人員可以更專注于業(yè)務功能的實現,而微服務架構相關的服務注冊發(fā)現、配置管理都可以交由K8S平臺完成,做到了代碼端即不依賴微服務框架(Spring Cloud)、也不依賴于線上K8S環(huán)境,而線上K8S平臺完全以非侵入的方式來完成微服務框架的工作。此種方式中,K8S平臺可以很好的解決服務注冊發(fā)現、服務負載均衡、外部配置管理的問題,但如果有分布式事務、熔斷、限流、服務追蹤等需求,還是要在代碼層面解決,目前K8S平臺暫不提供相關功能。
此種方式中,我們依賴K8S平臺做了很多功能,可能有的人會顧慮架構的實現過多依賴于K8S,強綁定到了K8S平臺。K8S作為目前云原生時代的PasS平臺事實上的標準,即便后續(xù)出現了替代品,那想必能替代K8S的必定強于K8S,K8S能做到的它應該都能做到甚至做的更多、更好。并且此種方式中我們并沒有像Spring Cloud K8S強耦合K8S平臺專有的API,而是已非侵入代碼的方式、URL、文件目錄等形式,保證了代碼端可以在完全不依賴K8S平臺的環(huán)境下運行,做到了沒有強綁定K8S平臺,但是K8S平臺又能為我們的應用架構錦上添花,即便后續(xù)切換到了不同PaaS平臺,也可以利用相似的機制保證應用的正常運行。
五、關于3種方式的選擇
回到最開始的方式1:在K8S上部署Spring Cloud Alibaba
,我們需要:
- 在代碼端維護Spring Cloud Alibaba相關依賴并且后續(xù)每次的升級都要小心謹慎
- 通過單獨部署Nacos來完成服務注冊發(fā)現、配置管理,上線后還需在K8S環(huán)境部署Nacos
- 開發(fā)人員在代碼端反復調試以確認是否正確集成Spring Cloud Alibaba
若采用方式2:在K8S上部署Spring Cloud K8S
,我們需要:
- 在代碼端維護Spring Cloud K8S相關依賴并且后續(xù)每次的升級都要小心謹慎
- 本地開發(fā)環(huán)境禁用Spring Cloud K8S,線上K8S環(huán)境需要開啟Spring Cloud K8S
- 無需單獨部署Nacos,上線后由K8S平臺負責服務的注冊發(fā)現、外部配置管理
- 開發(fā)人員需要同時對Spring Cloud K8S、K8S平臺本身都有所了解,且開發(fā)人員需在線上K8S環(huán)境反復調試以確認是否正確集成Spring Cloud K8S(使用Spring Cloud K8S框架需要在K8S環(huán)境中進行調試,而對于開發(fā)人員來說部署K8S集群遠比部署一個Nacos服務端要困難的多)
若采用方式3:在K8S上部署SpringBoot應用
,我們需要:
- 在代碼端僅僅維護一個SpringBoot應用,無需依賴Spring Cloud框架的各種實現依賴,代碼端更清爽
- 代碼端無需關注服務的注冊發(fā)現、線上環(huán)境的配置替換,開發(fā)人員可以更多的關注核心業(yè)務的實現
- 線上K8S環(huán)境完全以非侵入的方式完成服務的注冊發(fā)現(Service)、配置替換(ConfigMap),而應用部署到線上K8S由架構師團隊、運維團隊統(tǒng)一制定規(guī)則,不與代碼相耦合。
綜合考量,以上3種方式中:
- 最推薦
方式3:在K8S上部署SpringBoot應用
,由K8S平臺負責服務注冊發(fā)現、服務間負載均衡、外部配置管理 - 堅決不推薦
方式2:在K8S上部署Spring Cloud K8S
,不要給自己找麻煩,夾在Spring Cloud和K8S之間進退兩難,建議直接切換到方式3:在K8S上部署SpringBoot應用
- 若已采用了
方式1:在K8S上部署Spring Cloud Alibaba
,同樣建議切換到方式3:在K8S上部署SpringBoot應用
,做減法遠比做加法更容易,切換后會發(fā)現負擔更小了,神清氣爽…
六、方式4:擁抱Service Mesh
K8S平臺能幫我們以不侵入代碼的方式解決微服務架構下的服務注冊發(fā)現、服務負載均衡、外部配置管理的問題,但是微服務架構下還包括許多其他的需求,例如熔斷、限流、服務追蹤等,在K8S平臺之上我們還可以選擇另一種方式:服務網格(Istio)。
Service Mesh服務網格(如Istio) 可以簡單理解為K8S容器管理平臺之上的微服務管理平臺,不侵入代碼(跨編程語言),通過Sidecar(伴生容器)的形式將原本微服務框架中的基礎功能(服務注冊發(fā)現、服務路由、流量分發(fā)、熔斷、限流、監(jiān)控、安全等)提取到基礎設施層,但額外引入的Sidecar會增加集群的資源損耗、請求延時等。
比如我之前經歷過的一次架構決策,當時整個團隊剛剛從Spring升級到SpringBoot生態(tài),我們并沒有選擇Spring Cloud框架,而是直接跨過了Spring Cloud,選擇了K8S + ServiceMesh Istio的架構方案。
之所以選擇K8s + Istio,主要出于以下幾個方面考慮:
- Service Mesh不侵入代碼,對我們的代碼改動量最小(僅在需要支持分布式服務追蹤功能時改造Http調用工具 - 透傳追蹤請求頭)
- Service Mesh - Istio可以滿足我們對服務注冊發(fā)現、服務多版本路由、Http接口級別的熔斷、服務追蹤、更細粒度的負載均衡策略等的基礎需求
- 我們的開發(fā)團隊不需要大規(guī)模修改之前的代碼(無需糾結集成Spring Cloud框架及后續(xù)框架的升級),可以更多的關注核心業(yè)務功能的開發(fā)
- Istio可以滿足我們灰度發(fā)布的需求(如根據用戶ID分發(fā)流量到不同版本的服務)
- 除了后端Java技術棧,我們還有前端NodeJs技術棧、Python技術棧,同樣可以借助Istio實現跨開發(fā)語言的微服務架構的基礎功能
- 擁有富有經驗的運維和架構團隊
整個遷移過程對開發(fā)團隊幾乎無感,由運維團隊負責K8S的搭建與運維,然后由架構團隊、運維團隊協作完成Devops流水線編排、Istio服務編排規(guī)則的制定。整個遷移過程歷時將近3個月,擁有極陡峭的學習曲線,對運維與架構團隊也是提出了一定的挑戰(zhàn),好在最終順利完成遷移工作。此次遷移過程可以理解為一次擁抱未來的嘗試,雖并不是當下最穩(wěn)妥的方式,但對于開發(fā)團隊來說是成本相對較低的方式。文章來源:http://www.zghlxwxcb.cn/news/detail-635468.html
七、關于Devops
之前見過在代碼中通過Maven插件做Docker鏡像構建、K8S部署的,此種方式使得我們代碼構建的生命周期和Devops功能相耦合,并且其對構建環(huán)境也是有特定要求的(如依賴docker命令、kubectl命令等),可能還會在代碼庫中暴露相關憑證信息(如Harbor賬號密碼、kubeconfig配置等),開發(fā)者很難在本地開發(fā)環(huán)境對其進行調試,且增加了維護成本。還有就是通過自定義sh腳本的方式,此種方式雖不與代碼構建的生命周期相綁定,但同樣存在對特定執(zhí)行環(huán)境的要求、暴露相關憑證的風險,同時不具備行業(yè)共識性,同樣的腳本換到新團隊肯定是要重新進行測試、評估以確認其可行性的??梢詤⒁娭暗乃悸?,我們已經盡可能將原來耦合在代碼里的微服務架構的功能都轉移到K8S Pass平臺,那我們是否也可以將耦合在代碼中的Devops功能也抽取到云原生時代所推崇的Devops平臺呢?筆者是推薦將Devops相關的功能都交由專用的Devops平臺來做的,例如使用Jenkins平臺,所有的憑證(Docker憑證、K8s憑證、Gitlab憑證)都在Jenkins平臺進行管理,可由Devops工程師在相應代碼庫中(或者獨立的代碼庫中)建立單獨的文件夾維護Jenkins流水線Pipeline腳本、Dockerfile、K8S部署腳本等,此種方式可以將我們的代碼和Devops解耦,所有和Devops相關的流程、運行環(huán)境都交給標準的Devops平臺來完成,如此也有助于在團隊內部形成通用的Devops構建流程,達成共識,形成復用。文章來源地址http://www.zghlxwxcb.cn/news/detail-635468.html
到了這里,關于后端SpringBoot應用向云原生K8S平臺遷移的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!