天天看點

從應用開發角度認識 K8S

從應用開發角度認識 K8S

雲原生應用

我們正經曆從單體應用轉向分布式微服務架構應用的技術趨勢。分布式微服務架構作為越來越多的軟體開發設計模式,以領域設計模型來指導業務需求的抽象與封裝。對業務的實體抽象還是邊界劃分,會以微服務架構作為落地點,形成微服務叢集。并實施運作在雲原生編排平台。

從應用開發角度認識 K8S

雲原生應用結構 from Kubernetes-Patterns

雲原生應用的基石是幹淨整潔,業務邏輯相對單一,并與其他領域對象獨立的代碼實作。這一階段保證業務品質的主要是程式設計基本功,以及高覆寫率的自動化測試能力。

  • 領域設計驅動是近年微服務技術熱潮下的主流設計模式,主要解決的問題是如何拆解一個複雜的業務場景需求到多個微服務單元。領域設計驅動是微服務架構的設計模式,微服務架構是基于領域設計模式的實作方式。一個微服務可以對應一個領域對象,也可以是一個領域服務。
  • 分布式微服務架構實作的雲原生應用具有高可用,彈性伸縮,容忍失敗以及健康自省等特點。它使得我們處理日益增長的業務需求的能力從開發程式設計的複雜性逐漸轉移到了資源整合,操作與管理的複雜性上。
  • 微服務是單一的,運作在一個單程序中的簡單應用。容器技術恰好能夠提供這種隔離封裝,将一個簡單的微服務以Dockfile模版方式标準化,可無差别得運作在分布式叢集的任意資源節點。
  • K8S作為目前最流行的雲原生平台架構,對于一組微服務的互動,持續化資料的存儲,或者實施多個具有依賴關系的微服務運作,以及容量規劃等問題,能夠提供一套自動化的系統性解決方案。

用OOP方式解讀K8S

對于應用開發者,面向對象模式想必了然于胸。OOP設計了一套對一個邏輯對象的生命周期管理的方法論,類比OOP思路,筆者接下來詳細介紹一些K8S核心資源對象以及應用方式。

從應用開發角度認識 K8S

OOP vs K8S from Kubernetes-Patterns

建構/部署保持隔離性Pod/Deployment

Image

容器鏡像類比OOP的類,定義了一個子產品的全部屬性與功能,提供了唯一暴露在外的API調用方式以及參數集合,對應着一個獨立完整的釋出周期,就像容器的設計藍圖。這種靜态定義方式,可以定義并初始化容器進行,使其在任意環境任意時刻行為完全一緻。一個容器鏡像對應着一個微服務,屬于開發團隊的産物。

Container

容器類比OOP的對象,是容器鏡像的運作态。一個容器是一個容器鏡像的運作程序,而一個容器鏡像則可以在任意時刻任意環境下建立任何數量容器。

Pod

  • Java應用開發者都知道基于Springboot-MVC架構的Java應用部署時隻需提供一個Jar包,Jar包内部源碼被編譯後不可再改變。Pod是雲原生編排平台的資源排程部署的最小單元。Pod和容器的關系類似Java的Jar包和對象,應用開發者傳遞的容器鏡像通過Pod在K8S叢集上部署并排程,容器則是Pod的内部資源對象,K8S無法感覺也無法幹預。
  • K8S設計Pod為部署排程的最小單元,是因為Pod實作了内部一組容器在存儲空間,網絡空間及程序空間可共享該Pod資源,類似一個虛拟機上同時運作多個程序。容器間通信類似單節點程序間通信。Pod 的設計,就是要讓它裡面的容器盡可能多地共享 Linux Namespace,僅保留必要的隔離和限制能力。
  • Pod類似OOP的Module,即邏輯緊密的對象集合通常屬于一個獨立子產品。Pod的定義示例如下:
apiVersion: v1
kind: Pod
metadata:
  name: index-helm-57677c549-lgww5
  namespace: bss-dev
spec:
  containers:
    - command:
        - java
        - '-jar'
        - /home/demo/app.jar
      env:
        - name: aliyun_logs_release_tags
          value: revision=3f44253.20201030-1039
      image: 'registry-vpc.cn-shanghai-finance-1.aliyuncs.com/XXX/XXX.XXX:latest'
      imagePullPolicy: Always           
  • Pod->spec下包含了一個或多個容器模版定義。PodYAML提供了很多屬性,有興趣深入的讀者可以參考學習 https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/api/core/v1/types.go
  • Pod->spec有一個很特殊的容器模版定義,關鍵詞為initContainer,顧名思義是做初始化的容器。初始化容器必須先于應用容器啟動執行完畢,并且隻能執行成功。
  • Pod->spec除了定義了容器模版,還定義容器間共享的存儲資源Volume挂載方式,和影響Pod資源排程的節點選擇器标簽,親和性以及容忍性等。

NameSpace

  • 命名空間是一個組的概念,為叢集資源提供了邏輯劃分的能力。這種使用方式類似OOP的Package。當項目變大,時常有同名的類或者對象,為了區分會以packge為路徑字首,定義和引用相應對象。K8S叢集中常常運作着數百個應用服務,也會出現同名資源的情況。命名空間可以在K8S上實作對一組資源對象隔離與權限管理。
  • 命名空間最常用的場景是在一個K8S叢集區分開發環境和測試環境。命名空間也可以提供多租戶運作環境,或者為了應用運作具有隔離性,為某個應用部署命名一個單獨的命名空間。
  • 命名空間雖然提供了資源範圍邏輯劃分的能力,但是并沒有真正隔離一個叢集内部Pod之間的通信,即屬于同一叢集内的多個命名空間下的Pod仍能在叢集内互相通信。如果需要做到命名空間之間的完全隔離,可以采用NetworkPolicy實作。

Deployment

  • 在K8S運作的應用服務更新就是建立一個新版本Pod,并銷毀舊版本Pod的過程,由Deployment資源對象定義。微服務架構應用的數量成百上千,如果手動部署,則會引入人為錯誤并且部署操作很容易成為整個系統的瓶頸。K8S将部署Pod全部操作定義在Deployment資源對象,由平台自動化地部署Pod。
  • Deployment資源對象除了要定義部署Pod是什麼樣的以外,還會定義預期部署狀态。比如,預期部署Pod數量,或在哪個節點上部署。
  • 部署一個新版本要麼生成新版本Pod,待健康檢查等确認新Pod可對外提供服務,再将舊版本Pod銷毀,最終達到預期部署狀态;要麼就銷毀原有舊版本Pod,再生成新版本Pod,直到達到預期部署狀态。第一種方式為Rolling Update,好處是部署時沒有任何downtime,壞處是會存在多個版本同時提供服務,導緻服務狀态不一緻。第二種方式為Recreate,好處是在任意時刻都不會存在多個版本的應用服務,壞處則是部署時存在downtime。K8S預設的部署政策為Rolling Update。
  • Deployment通過ReplicaSet控制器建立和管理Pod,確定Pod能如預期運作成功,并且滿足Deployment的部署定義,比如開啟幾個副本,滿足部署政策等。ReplicaSet解耦了部署與Pod運作,Pod處于Running狀态并不能代表應用部署成功。Deployment對Pod副本數量以及應用容器的Health Probe做了設定,以確定程序啟動以及應用運作成功。隻有當建立ReplicaSet所屬的Pod運作成功,并且副本數量達到Deployment設定ReplicaSet的數量,舊的ReplicaSet管理的Pod不再承接任何負載或被銷毀時,Deployment達到預期部署狀态,才能代表部署成功。

構造器InitContainer

  • 初始化在OOP中,比如Java類定義,是構造器。封裝了對象使用之前必要的初始化操作。初始化容器InitContainer,InitContainer是Pod級别的初始化,同理,是Pod->spec的一類特殊的容器模版定義,隔離了主應用容器程序與初始化操作,確定初始化操作能夠在應用容器啟動之前完成。
  • 在容器級别也可以完成初始化,利用容器鏡像模版Dockerfile->ENTERPOINT定義。容器級别的初始化影響範圍是該容器鏡像定義内部,而Pod級别初始化操作InitContainer是對Pod内的全部容器組定義。一般情況下,容器初始化更多是Devops關心,與應用開發者工作相關性不大。InitContainer可以将初始化容器模版定義從研發周期上隔離。
  • Pod級别初始化操作InitContainer的好處是可以統一設定通路共享Volume的通路權限;在應用服務啟動之前準備好依賴的元件或者資料;驗證應用服務運作的依賴運作健康等。在應用服務主容器程序啟動之前,確定前置條件準備完備,進而確定應用服務能夠運作成功。
  • 處于通路控制等安全性考慮,一般不建議在應用容器鏡像定義裡開放Pod共享資源的通路權限。我們盡量将Pod的共享資源管理操作留給編排平台設定與控制,與應用服務本身隔離。最大化地確定應用服務本身不帶有平台依賴屬性。是以,InitContainer也提升了微服務應用開發的安全性。
  • 一個Pod模版可定義多個InitContainer以及多個應用容器。K8S確定InitContainer按定義順序依次執行初始化操作,在應用容器啟動前執行完畢,而應用容器啟動是并行的。
  • InitContainer與一般的應用容器基本相同,但是,InitContainer一般為Completed,不會存在Failure終止狀态,因為當InitContainer執行失敗會直接導緻Pod重新開機,重新開始執行InitContainer。

組合模式Sidecar

  • 在前文我們把容器鏡像和容器類比OOP的類和對象,因為容器鏡像定義了一個職責單一的應用微服務。那如果在應服務運作時,我們需要擴充或添加一些旁路操作,此時,類似OOP的組合設計模式,我們其實可以直接整合另一個容器鏡像定義到同一個Pod,這就是Sidecar。
  • Sidecar保證了應用容器的職責單一,同時,也能在Pod級别為其添加更新資料,配置檔案,靜态資源或者采集日志資料這種能夠獨立複用的旁路操作集合。Sidecar能夠通過組合多個職責單一的容器,提供一個功能完備,具備上線能力的應用微服務,同時,確定開發團隊隻用考慮業務應用功能本身。
  • Sidecar與InitContainer是兩種不同的容器定義。首先InitContainer定義的是Pod級别的初始化操作集合,必須在所有應用容器啟動之前執行完畢,具有嚴格的執行順序。Sidecar與應用容器執行順序沒有嚴格控制,兩者通常是同時運作在一個Pod内,共享Pod資源,共同完成Pod暴露的服務能力。

配置管理ConfigMap/Secret

  • 應用開發的12原則中有一條是在環境中存儲配置。配置資訊與應用隔離,可以通過環境變量來存儲應用的配置資訊。環境變量具有全局性,可将其在應用程序運作時加載。當配置資訊較大時,利用環境變量傳遞配置資訊就不是什麼好辦法了。Java-Springboot應用提供了Profile檔案,記錄和儲存應用相關的配置資訊,開發者可以依據環境區分不同的Profile檔案。
  • 在K8S裡配置管理ConfigMap和Secret同時支援環境變量Key/Value形式和應用配置Profile檔案形式。環境變量的Key一般是全大寫字母字母表示;應用配置Profile檔案是小寫字母表示額檔案名。
  • K8S提供Secret資源對象來配置敏感資料。比如,資料庫連結的使用者名,密碼等。
  • ConfigMap與Secret對象通過Volume挂載到Pod裡,是以該配置資訊被Pod内的容器組共享。ConfigMap與Secret的資料存儲上限為1MB,故當應用配置檔案過大,可考慮使用InitContainer初始化一個配置管理容器在同一個Pod下。在應用容器啟動前,傳遞應用配置到應用指定挂載目錄,進而更新應用配置資訊。

異步/并發執行Job/Cronjob

  • 應用開發過程中,常會面對批處理任務/定時任務需求。目前流行的應用架構,比如Java的Spring-Batch或者Python的Celery都可以實作異步任務/定時任務。但是這種應用級别的實作方法,在雲原生中,會使應用服務實作的很重,比如異步任務通常要求所屬應用滿足高可用,資源彈性伸縮以及故障自愈。這些特性都是K8S平台天然自帶的,可以考慮将應用的異步任務實作委托給K8S的Job/Cronjob控制器對象。
  • K8S的Job控制器對象,類似Deployment,是建立與管理Pod生命周期的一種實作方式。與Deployment不同的是,Job控制的Pod是運作結束就終止,即Pod的終态為Completed。Job的Pod預設不會直接銷毀,主要目的是提供檢視任務運作日志結果。
  • K8S的Cronjob控制器對象,顧名思義,在Job對象之上組合了定時觸發事件邏輯。主要使用的場景包括但不限于檔案傳輸,發送郵件或者短信通知,以及備份與定時清理過期備份等。

結語

筆者整理了一部分K8S基礎知識點的初衷是為了審視一下K8S這個龐大的技術棧裡開發者掌握和使用K8S所要了解的最小知識點集合。筆者相信未來的應用都是建立在雲之上,是以不論是哪個角色,都得掌握必要的K8S知識點才能流暢地開啟雲原生開發之旅。

以上很多内容都是筆者在學習

https://time.geekbang.org/column/intro/116

以及Kubernetes-patterns

https://developers.redhat.com/blog/2020/05/11/top-10-must-know-kubernetes-design-patterns/

時的心得和讀書體會,受益于大師們對K8S技術棧多種角度的解讀與梳理。

最後這次比較系統的學習梳理的契機也源于團隊對于K8S技術的重視,特别感謝團隊上司的重視與支援。