1. 程式人生 > >將Java 應用容器化改造並遷移到Kubernetes 平臺

將Java 應用容器化改造並遷移到Kubernetes 平臺

為了能夠適應容器雲平臺的管理模式和管理理念,應用系統需要完成容器化的改造過程。對於新開發的應用,建議直接基於微服務架構進行容器化的應用開發;對於已經執行多年的傳統應用系統,也應該逐步將其改造成能夠部署到容器雲平臺上的容器化應用。本文針對傳統的Java 應用,對如何將應用進行容器化改造和遷移到Kubernetes 平臺上進行說明。

要將傳統Java 應用改造遷移到Kubernetes 平臺上執行,通常要經過以下幾個步驟。

(1)進行應用程式碼改造,要考慮配置檔案、多例項部署下的分散式架構問題,並對程式程式碼和架構做出相應的改造。

(2)進行容器化改造,選擇合適的基礎映象並打包生成新的應用映象,使得應用能以容器方式部署、執行。

(3)進行Kubernetes 建模與部署,採用合適的Kubernetes 資源物件建模Java 應用,最終釋出到Kubernetes 平臺上實現應用的自動化運維。

接下來以一個傳統的Java 應用改造遷移過程為例,來說明上述步驟中的細節。

1 Java 應用的容器化改造遷移

我們的目標是搭建一個簡單的學員分數管理系統(Study Application),應用介面與架構如下圖。

Study Application 是一個典型的J2EE 系統,為了方便理解,並沒有採用額外的框架技術,而是採用了MySQL 資料庫,將JSP 作為Web 頁面,並通過JDBC 進行資料庫操作,整個系統以標準方式部署在Tomcat 的webapp 目錄下。

下圖所示是Study Application的目錄結構與說明。

下面是在index.jsp 中訪問資料庫的關鍵程式碼, 資料庫連線的配置資訊被放在jdbc.properties 屬性檔案中,便於在不同的環境下修改:

    Class.forName("com.mysql.jdbc.Driver");
    java.util.Properties pps = new java.util.Properties();
    pps.load(new java.io.FileInputStream("jdbc.properties"));
    String ip=pps.getProperty("mysql_ip");
    String user=pps.getProperty("user");
    String password=pps.getProperty("password");
    System.out.println("Connecting to database...");
    conn =
java.sql.DriverManager.getConnection("jdbc:mysql://"+ip+":3306"+"?useUnicode=true&characterEncoding=UTF-8", user,password)
        stmt = conn.createStatement();
        String sql = "show databases like 'HPE_APP'";
        rs =stmt.executeQuery(sql);

我們知道,應用在以容器化執行以後,是不建議進入容器裡修改配置檔案的(在多例項情況下很難保持配置檔案同步更新),因此,需要修改從jdbc.properties 屬性檔案中獲取資料庫連線的以上程式碼,根據容器環境的要求,將其改為從環境變數中獲取,改造後的程式碼如下:

    String ip=System.getenv("mysql_ip");
    String user=System.getenv("user");
    String password=System.getenv("password");

改造後的程式碼基本達到了容器化的要求,但對於一個完整的應用來說,由於還存在使用者Session 會話保持的問題,因此還需要實現分散式的Session 會話機制,才能做到多例項部署,此時可以考慮採用Spring Session 框架來改造、升級我們的單體應用。對於大部分RESTful 服務,由於不需要會話保持功能,因此可以直接多副本部署,多個例項可以同時提供服務。

2 Java 應用的容器映象構建

接下來,我們需要將自己的Java 應用打包為Docker 映象,以容器方式啟動並提供服務。在打包映象時,需要注意以下幾個關鍵問題。

(1)需要注意基礎映象的選擇問題。選擇基礎映象的兩個原則:標準化與精簡化。儘可能選擇Docker 官方釋出的基礎映象,這些基礎映象通常符合標準化與精簡化這兩個目標。比如,它們都有Dockerfile 原始檔,我們可以獲知此映象是如何製作的,並可以在此基礎上實現諸如軟體版本、效能優化、日誌及安全等方面的特殊定製,然後打包為公司級別的內部標準映象,供各個專案使用。

(2)需要注意業務程序的啟動方式。與在物理機上將自己的程式放到後臺執行的方式不同,在容器化時,我們需要將自己的業務程序放到前臺執行。這樣一來,當業務程序由於某種原因而停止時,容器也隨之銷燬,我們就能及時觀察到這種嚴重故障,並做出相應的行動來恢復系統。目前有一些系統在容器化的過程中採用了supervisord 這樣的工具,將業務的主程序和輔助程序放到後臺啟動,並交給supervisord 監管,這種做法雖然在一定程度上也能實現自動重啟故障程序的目標,但它將問題隱藏得更深,即使業務程序由於特殊故障始終無法重啟成功,運維人員也發現不了問題,因此不建議採用這種方式啟動業務程序。

(3)需要注意程式的日誌輸出問題。在物理機上執行業務程序時,我們通常會把程式日誌輸出到指定的檔案中,以便更好地排查故障。但在容器化以後,我們需要改變這種做法,將程式的日誌直接輸出在容器的螢幕上(或者說控制檯Console 上),此時Docker 會將這些輸出日誌存放到容器之外的特定檔案中,第三方的日誌收集工具(例如Elasticsearch)就可以方便採集這些日誌並實現集中化的日誌搜尋和分析功能。此外,Docker 也提供了統一的log 命令來檢視容器的日誌,這推進了系統運維的標準化。Java 中常用的Log4j 及Slf4j日誌框架都支援把日誌輸出到控制檯的配置方式,在打包應用時,需要對日誌的配置檔案做出相應的修改。

(4)需要注意檔案操作的問題。當業務程序執行在物理機上時,它看到的檔案系統就是物理機的檔案系統;但當業務程序執行在容器中時,它所訪問的檔案系統就是一種特殊的、被隔離的、分層模式的虛擬檔案系統,在這種情況下,頻繁進行I/O 操作的效能比較低。為了解決這個問題,容器可以使用Volume 將頻繁進行操作的目錄對映到容器外部(通常是物理機上);同時,Volume 也是容器與外部交換檔案的重要工具,因此在製作映象和執行容器時,需要考慮Volume 對映的問題,對於在程式執行過程中產生的大量臨時檔案和被頻繁讀寫的檔案,或者在需要跟外界交換檔案時,可以選擇掛載Volume。

下圖是Study Application 打包映象的示意圖及對應的Dockerfile 原始碼。

Study Application 的映象繼承了tomcat:9-alpine 這個官方的基礎映象,這個映象基於Alpine Linux,如果對比一下,我們會發現,基於alpine 的映象不到5MB,而基於Ubuntu或CentOS 的映象都在100MB 以上。此外,從Study Application 的Dockerfile 來看,製作Java 型別應用的Docker 映象是很方便的一件事,通常只需幾行程式碼。

3 在Kubernetes 上建模與部署

在應用容器化後,就可以在Kubernetes 上建模與部署了,在建模的過程中,我們需要考慮一些關鍵問題,這些問題及其答案如下。

(1)將業務程序建模為Pod 還是RC?

對於這個問題,最重要的判斷依據是該程序提供的是有狀態服務還是無狀態服務。對於無狀態服務,比如大多數REST 介面的服務,通常是可以在任意節點上啟動並提供服務的,例如我們這裡的Web 應用程式就符合無狀態服務。但對於有狀態服務,比如MySQL服務,我們通常不能這麼做,因為它依賴本地儲存的資料庫檔案。對於有狀態服務,我們通常只能將業務程序建模為Pod,這是因為RC 控制的Pod 例項可以從一臺節點飄到另一臺節點上,如果我們能夠通過共享儲存解決Pod 的狀態問題,則也可以把某些有狀態服務的程序建模為RC,這種做法與StatefulSet 很類似。

(2)我們是否需要在Pod 的基礎上,繼續建模對應的Service?

這主要取決於此Pod 是否會被其他業務程序(或終端使用者)所訪問,對於不會被其他業務程序所訪問的Pod,我們無須建模對應的Service。實際上,在一個分散式系統中,大多數程序都會被建模為Service 並對應一個微服務,如果某個服務還需要被終端使用者訪問,則往往還需要“匯出”外網訪問地址,比如NodePort 埠。對於無須外部訪問的Service,還可以考慮建模為Headless Service,在這種情況下,該Service 不會分配一個虛擬的ClusterIP,通訊效率更高。

(3)是否需要考慮應用的資料儲存問題?

如果只是本機儲存,則可以直接使用Kubernetes Volume 資源物件;如果希望有遠端儲存功能,則可以考慮使用PV(Persistent Volume)。這樣一來,不管Pod 被排程到哪臺機器,都可以繼續訪問原來的儲存資料。如果希望系統自動管理共享儲存的空間,則可以考慮建模對應的StorageClass。

(4)是否需要考慮應用的配置問題?

我們知道,在幾乎所有應用開發中,都會涉及配置檔案的管理問題,比如StudyApplication 中的資料庫配置資訊,常見的網際網路應用還有快取中介軟體、訊息佇列、全文檢索等一系列中介軟體的配置檔案。而在分散式情況下,釋出在多個節點上的Pod 副本都需要訪問同一份配置檔案,這也加大了配置管理的難度,為此業內的一些大公司專門開發了自己的一套配置管理中心,如360 的Qcon、百度的Disconf 等,但這些解決方案都比較複雜而且有侵入性。Kubernetes 則提供了無侵入的更簡單的方案,這就是ConfigMap,我們可以把任意數量的配置檔案放入ConfigMap 中,實現集中化管理,然後通過環境變數的方式將配置資料傳遞到Pod 裡,或者通過Volume 方式掛載到Pod 內。

在Study Application 中,Web 應用在Kubernetes 上的建模如圖6-4 所示。我們通過定義一個RC 來控制Web 的Pod 例項,資料庫連線資訊則通過環境變數傳遞到Pod 裡,然後定義一個Service,並且通過NodePort 方式暴露到叢集外供使用者訪問,即可完成這個Java應用的容器化改造工作。

本文選自《Kubernetes權威指南:企業級容器雲實戰》一書,電子工業出版社9月出版。本書通過全新的視角,針對容器雲領域現下的熱點和技術難點,給出了基於Kubernetes的企業級容器雲落地指南,為企業傳統IT轉型和業務上雲提供助力。