Kubernetes官方java客戶端之八:fluent style

程式設計師欣宸發表於2021-01-14

歡迎訪問我的GitHub

https://github.com/zq2599/blog_demos

內容:所有原創文章分類彙總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等;

概覽

  1. 本文是《Kubernetes官方java客戶端》系列的第八篇,以下提到的java客戶端都是指client-jar.jar;
  2. 前文《Kubernetes官方java客戶端之七:patch操作》涉及的知識點、程式碼、操作都太多了,對作者和讀者都是莫大的折磨,到了本篇我們們輕鬆一下,寫幾段簡單流暢的程式碼,瞭解java客戶端對fluent style程式設計的支援,並且編碼完成後的驗證操作也很簡單;

關於fluent styel

  1. 也稱為fluid coding, fluent programming,是一種增強程式碼可讀性的風格,使得閱讀程式碼時更加自然流暢,特點是函式返回有關型別,使得多個函式的呼叫前後連結起來。
  2. 關於fluent style可以參考Martin Flowler於2005年發表的文章,地址是:https://martinfowler.com/bliki/FluentInterface.html ,使用fluent style前後的程式碼對比如下圖所示:

3.

原始碼下載

  1. 如果您不想編碼,可以在GitHub下載所有原始碼,地址和連結資訊如下表所示(https://github.com/zq2599/blog_demos):
名稱 連結 備註
專案主頁 https://github.com/zq2599/blog_demos 該專案在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該專案原始碼的倉庫地址,https協議
git倉庫地址(ssh) git@github.com:zq2599/blog_demos.git 該專案原始碼的倉庫地址,ssh協議
  1. 這個git專案中有多個資料夾,本章的應用在kubernetesclient資料夾下,如下圖紅框所示:
    在這裡插入圖片描述

實戰步驟概述

  1. 在父工程kubernetesclient下面新建名為fluent的子工程;
  2. fluent工程中只有一個類FluentStyleApplication,啟動的main方法以及fluent style的程式碼都在此類中;
  3. FluentStyleApplication.java提供四個web介面,功能分別是:新建namespace、新建deployment、新建service、刪除前面三個介面新建的所有資源;
  4. fluent工程編碼完成後,不需要做成映象部署在kubernetes環境內部,而是作為一個普通SpringBoot應用找個java環境啟動即可,與《Kubernetes官方java客戶端之三:外部應用 》一文的部署和啟動一致;
  5. 依次呼叫每個介面,驗證功能是否符合預期;

編碼

  1. 在父工程kubernetesclient下面新建名為fluent的maven子工程,pom.xml內容如下,需要注意的是排除掉spring-boot-starter-json,原因請參考《Kubernetes官方java客戶端之二:序列化和反序列化問題 》
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.bolingcavalry</groupId>
        <artifactId>kubernetesclient</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <groupId>com.bolingcavalry</groupId>
    <artifactId>fluent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>fluent</name>
    <description>Demo project for fluent style</description>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-json</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>io.kubernetes</groupId>
            <artifactId>client-java</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.0.RELEASE</version>
            </plugin>
        </plugins>
    </build>

</project>
  1. 新建FluentStyleApplication.java,首先,該類作為啟動類,要有main方法:
public static void main(String[] args) {
  SpringApplication.run(FluentStyleApplication.class, args);
}
  1. 定義常量NAMESPACE作為本次實戰的namespace:
private final static String NAMESPACE = "fluent";
  1. 用@PostConstruct註解修飾setDefaultApiClient方法,令其在例項化時執行一次,裡面做了一些全域性性的初始化設定,注意kubeConfigPath變數對應的config檔案路徑要正確:
/**
     * 預設的全域性設定
     * @return
     * @throws Exception
     */
    @PostConstruct
    private void setDefaultApiClient() throws Exception {
        // 存放K8S的config檔案的全路徑
        String kubeConfigPath = "/Users/zhaoqin/temp/202007/05/config";
        // 以config作為入參建立的client物件,可以訪問到K8S的API Server
        ApiClient client = ClientBuilder
                .kubeconfig(KubeConfig.loadKubeConfig(new FileReader(kubeConfigPath)))
                .build();

        // 會列印和API Server之間請求響應的詳細內容,生產環境慎用
        client.setDebugging(true);

        // 建立操作類
        Configuration.setDefaultApiClient(client);
    }
  1. 接下來是建立namespace的web服務,如下所示,由於namespace在kubernetes的apiVersion是v1,因此建立的是V1Namespace例項:
 @RequestMapping(value = "/fluent/createnamespace")
    public V1Namespace createnamespace() throws Exception {

        V1Namespace v1Namespace = new V1NamespaceBuilder()
                .withNewMetadata()
                .withName(NAMESPACE)
                .addToLabels("label1", "aaa")
                .addToLabels("label2", "bbb")
                .endMetadata()
                .build();

        return new CoreV1Api().createNamespace(v1Namespace, null, null, null);
    }
  1. 為了更清晰的展現fluent style效果,將上述程式碼與建立namespace的yaml檔案內容放在一起對比,如下圖所示,可見對照著yaml檔案就能將程式碼寫出來:
    在這裡插入圖片描述

  2. 接下來是建立service的程式碼,為了便於和yaml對應起來,程式碼中特意加了縮排:

 @RequestMapping(value = "/fluent/createservice")
    public V1Service createservice() throws Exception {
        V1Service v1Service = new V1ServiceBuilder()
                // meta設定
                .withNewMetadata()
                    .withName("nginx")
                .endMetadata()

                // spec設定
                .withNewSpec()
                    .withType("NodePort")
                    .addToPorts(new V1ServicePort().port(80).nodePort(30103))
                    .addToSelector("name", "nginx")
                .endSpec()
                .build();

        return new CoreV1Api().createNamespacedService(NAMESPACE, v1Service, null, null, null);
    }
  1. 建立deployment的程式碼如下,因為內容較多所以相對複雜一些,請注意,由於deployment在kubernetes的apiVersion是extensions/v1beta1,因此建立的是ExtensionsV1beta1Deployment例項:
 @RequestMapping(value = "/fluent/createdeployment")
    public ExtensionsV1beta1Deployment createdeployment() throws Exception {
        ExtensionsV1beta1Deployment v1Deployment = new ExtensionsV1beta1DeploymentBuilder()
                // meta設定
                .withNewMetadata()
                    .withName("nginx")
                .endMetadata()

                // spec設定
                .withNewSpec()
                    .withReplicas(1)
                    // spec的templat
                    .withNewTemplate()
                        // template的meta
                        .withNewMetadata()
                            .addToLabels("name", "nginx")
                        .endMetadata()

                        // template的spec
                        .withNewSpec()
                            .addNewContainer()
                                .withName("nginx")
                                .withImage("nginx:1.18.0")
                                .addToPorts(new V1ContainerPort().containerPort(80))
                            .endContainer()
                        .endSpec()

                    .endTemplate()
                .endSpec()
                .build();

        return new ExtensionsV1beta1Api().createNamespacedDeployment(NAMESPACE, v1Deployment, null, null, null);
    } 
  1. 從上述程式碼可見,withXXXendXXX是成對出現的,請在程式設計的時候注意不要遺漏了endXXX,建議在寫withXXX的同時就把endXXX寫上;
  2. 最後一個方法是清理所有資源的,前面建立的deployment、service、namespace都在此一次性清理掉,實際操作中發現了一個尷尬的情況:刪除deployment和namespace時,傳送到API Server的刪除請求都收到的操作成功的響應,但kubernetes客戶端在反序列化響應內容時丟擲異常(日誌中顯示了詳細情況),鄙人能力有限暫未找到解決之道,因此只能用try catch來避免整個方法丟擲異常,好在kubernetes實際上已經刪除成功了,影響不大:
    @RequestMapping(value = "/fluent/clear")
    public String clear() throws Exception {

        // 刪除deployment
        try {
            new ExtensionsV1beta1Api().deleteNamespacedDeployment("nginx", NAMESPACE, null, null, null, null, null, null);
        } catch (Exception e)
        {
            log.error("delete deployment error", e);
        }

        CoreV1Api coreV1Api = new CoreV1Api();

        // 刪除service
        coreV1Api.deleteNamespacedService("nginx", NAMESPACE, null, null, null, null, null, null);

        // 刪除namespace
        try {
            coreV1Api.deleteNamespace(NAMESPACE, null, null, null, null, null, null);
        } catch (Exception e)
        {
            log.error("delete namespace error", e);
        }

        return "clear finish, " + new Date();
    }
  1. 編碼已完成,啟動fluent工程,接下來開始驗證功能是否正常;

驗證

  1. 將fluent工程直接在IEDA環境啟動;

  2. 瀏覽器訪問:http://localhost:8080/fluent/createnamespace ,頁面會展示API Server返回的完整namespace資訊:
    在這裡插入圖片描述

  3. 瀏覽器訪問:http://localhost:8080/fluent/createservice ,頁面會展示API Server返回的完整service資訊:
    在這裡插入圖片描述

  4. 瀏覽器訪問:http://localhost:8080/fluent/createdeployment ,頁面會展示API Server返回的完整deployment資訊:
    在這裡插入圖片描述

  5. 驗證前面幾個介面建立的服務是否可用,我這裡kubernetes的IP地址是192.168.50.135,因此訪問:http://192.168.50.135:30103 ,可以正常顯示nginx首頁:

在這裡插入圖片描述

  1. SSH登入kubernetes伺服器檢視,通過kubernetes的java客戶端建立的資源都正常:
    在這裡插入圖片描述

  2. 驗證完成後,瀏覽器訪問:http://localhost:8080/fluent/clear ,即可清理掉前面三個介面建立的資源;

  • 至此,基於fluent style呼叫java客戶端的實戰就完成了,希望您能熟練使用此風格的API呼叫,使得編碼變得更加輕鬆流暢,順便預告一下,下一篇繼續做一些簡單輕鬆的操作,目標是熟悉java客戶端的常用操作;

你不孤單,欣宸原創一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 資料庫+中介軟體系列
  6. DevOps系列

歡迎關注公眾號:程式設計師欣宸

微信搜尋「程式設計師欣宸」,我是欣宸,期待與您一同暢遊Java世界...
https://github.com/zq2599/blog_demos

相關文章