本文從應用開發人員的角度,總結一下 Kubernetes 的業務開發中的最佳實踐,本文不會展示任何相關的 kubelet 命令以及 yaml 格式文件,通過盡可能精簡篇幅來提高讀者閱讀體驗和效率。

Kubernetes 通過 Namespace 將資源進行邏輯上的隔離,默認情況下集群中有三個命名空間:default、kube-public 和 kube-system,
通常情況下,有一個非生產的 Kubernetes 集群用于特定環境 (開發、測試、集成),如果出于成本和業務服務獨立性考慮,將生產和非生產環境混合到一個集群中,
則應該建立新的 Namespace 來進行業務服務區分與隔離,例如常見的 Namespace 是 test 和 prod。
此外,建立新的 Namespace 之后,應該為不同的 Namespace 來配置不同的資源配額和資源限制。例如給測試環境分配較少的資源額度,給生產環境分配較多的資源額度, 這樣即使在測試環境對某個服務進行壓測,也不會影響到生產環境。

Kubernetes 中支持使用 RBAC 為集群中的用戶和組定義細粒度的訪問控制規則,例如可以將無狀態應用、有狀態應用、路由管理的功能開放給開發人員,將命名空間、資源配額與限制的功能開放給運維人員。
將 Pod 視為獨立的機器,并將單個應用或者多個緊密耦合的應用放入單個 Pod 中。
對于多層應用,應該分散放到多個 Pod 中,典型的如 Web 應用,應該將前端頁面、后端接口、緩存、數據庫分別放入不同的 Pod 中。此外,不要將應用部署到單獨的 Pod 中,在保持應用可以水平擴展的前提下:
如何判定單個 Pod 中是否應該放入多個容器?這里給出幾個判斷條件:
組合大于繼承
這條軟件設計原則針對 Pod 依然適用,大多數情況下,單個應用容器都是最佳實踐,對于某些多應用容器場景,可以通過將輔助容器中的應用使用子進程的方式運行,這樣就可以將多個容器應用集成到一個容器應用中。
每個容器中的應用都應該遵循單一職責,只負責一個功能或執行一項任務。如果應用內部發生不可恢復的錯誤,應該讓它崩潰并自動退出,Kubernetes 會自動重啟該容器。
容器共享主機的內核,因此隔離程度無法到達虛擬機級別,安全的策略就是禁止使用 root 身份運行應用。
應該盡可能縮小鏡像體積,因為當集群自動擴容添加新節點時,新節點必須下載容器鏡像。容器的鏡像體積越小,節點下載速度越快,應用的啟動速度也就越快。
如果 CRI 容器使用的是 Docker,可以參考之前的 Docker 最佳實踐[1]。
如果所在企業沒有自己的基礎設施團隊,建議鏡像倉庫和云服務器的提供商保持一致,這樣保證鏡像倉庫高可用的同時,可以節省帶寬費用并提升鏡像拉取速度 (因為走的是內網)。
網上大多數文章會提到 不要使用 latest 作為鏡像標簽,筆者不是特別認同,還是需要具體情況具體分析,筆者的建議是:
git commit 哈希值作為標簽的鏡像 (回滾時專用鏡像)| 維度 | 名稱 | 值 |
|---|---|---|
| 發布版本 | release | "stable", "canary" |
| 發布環境 | environment | "dev", "qa", "production" |
| 應用類型 | tier | "frontend", "backend", "cache" |
對于無狀態應用,直接使用 Deployment 部署。

下面主要說下幾個重要的參數。
Pod 的副本數量。
指定滾動更新過程中不可用的 Pod 數量上限,數值可以是絕對數字,也可以是百分比 (最終會轉換成數字),例如 Pod 數量為 10, maxUnavailable 值的 30%, 那么在滾動升級過程中,始終保證可用的 Pod 數量為 7 。
指定可以創建超出 replicas 的 Pod 數量,數值可以是絕對數字,也可以是百分比 (最終會轉換成數字),例如 Pod 數量為 10, maxSurge 值為 30%, 那么在滾動升級過程中,始終 Pod 最大數量不超過 13 。
在滾動升級期間設置合理的 maxUnavailable 和 maxSurge 值,在資源消耗和保證服務可用性之間取得平衡。
指定新創建的 Pod 的最小就緒時間,只有超過這個時間 Pod 才會被認為可用,使用該參數更好掌握升級部署過程,避免 Pod 中的進程啟動后立即接受流量導致錯誤,同時減緩滾動升級的速率。
通常情況下需要根據業務場景計算出應用的初始化時間上限,同時還要考慮到應用的就緒時間條件,通過兩者的綜合考量,將 minReadySeconds 值設置地盡量高一些。
默認情況下, 10 分鐘內無法完成滾動升級將被判定為失敗,對于大多數無狀態應用來說這個時間太長了,可以更新 spec.progressDeadlineSeconds 來自定義超時時間。
Deployment 的默認策略是 RollingUpdate, 也就是滾動升級。
此外還可以將策略設置為 Recreate, Recreate 策略首先終止當前版本中的所有 Pod,然后創建并啟動新 Pod, 兩個過程之間會有一定的服務不可用空窗期, 該策略的應用場景是: 無法接受應用在滾動升級策略中同時存在的兩個版本,并且可以接受一定的服務不可用空窗期 (例如金融相關業務),可以在業務低峰期采用這種策略,并在用戶界面給出對應的維護公告。
將所有由 systemd 管理的進程使用 DaemonSet 管理。
針對不同的環境單獨配置 ConfigMap, 可以將多個小的配置文件合并到一個大的配置文件中,參考 這個示例[2]。
熱更新 ConfigMap 會導致應用重新加載新的配置,但不會觸發應用程序重啟。

確保采用 Secret 的方式存儲敏感數據,禁止使用環境變量、自定義加密方式等毫無意義的奇技淫巧。同時限制特定容器集合才能訪問 Secret, 讀取 Secret 之后保持只讀狀態,避免以明文存儲 Secret 數據或將 Secret 傳輸給第三方。
注意: Secrets 默認情況下采用 base64 編碼存儲非加密的形式存儲在 etcd 中,需要配置為靜態加密方式。
正常情況下,Job 會在 Pod 中運行直到完成,但是,如果節點發生故障,或者當 Pod 在運行時被驅逐出節點時,調度器會將 Job 放在一個新的節點上重新運行。所以 Job 要保證冪等性 (例如創建數據表時的 IF NOT EXISTS 判斷語句),并且在狀態為失敗時自動停止,不再重新啟動 (將 Restart Policy 配置為 Never)。對于涉及到網絡請求的 Job, 筆者的建議是 將請求重試等策略放在應用代碼中,而不是通過配置 Job 的 Restart Policy 來完成。
比較重要的兩個聲明字段:
將周期性的自動化任務通過 CronJob 來執行,例如數據備份、回歸測試、日志分析、運營數據郵件、已有 Cron 任務的遷移等。
注意: 因為工作節點本身受到資源使用和調度的限制,所以可能發生 Job 運行時間延時的情況 (和在應用層使用的定時任務組件延時情況類似),如果 Job 本身有較高的時間敏感度, 可以通過指定 startingDeadlineSeconds 字段來指定 Job 運行截止日期,這樣的話,如果 Job 運行時會檢測,如果運行時間大于截止日期,Job 將不再運行并且直接標記狀態為失敗。
此外,不應該將 Job 的運行時間作為業務時間序列屬性,對于多副本運行的 Job 要保證冪等性。
不管是傳統的 Linux Cron 任務,還是通過守護進程 + 定時器組件模式,亦或是 Kubernetes 中的 Job, 這些都只是具體的運行時載體,最重要的是保證業務代碼的健壯性。
將 Pod 內應用日志輸出到文件或標準輸出,然后通過日志采集組件,最后將日志數據發送到自建日志倉庫或云服務商提供的日志聚合服務。
大多數情況下,使用云服務商提供的默認配置就可以滿足需求,如果需要自定義網絡策略,可以參考 官方文檔[3]。
監控 Kubernetes 集群對于確保應用程序的運行狀況和性能至關重要,使用 Prometheus、Grafana 等工具或云服務商提供的原生監控解決方案,來收集和分析集群中的指標, 例如 CPU 使用率、內存使用率和網絡流量等,同時設置警報和主動通知方式,保證集群出現任何問題時收到通知。
集群伸縮可以自動添加或者刪除工作節點,需要主要的是: 如果遇到業務在某個時刻流量突增的場景,則最好提前手動添加節點,畢竟新節點從啟動到加入到集群并開始接收流量也需要時間,提前增加節點可以避免突增的流量造成負載過大甚至宕機。
新的版本除了引入新功能之外,還包括漏洞和安全修復,可以定期升級到最新的穩定版本,享受官方升級帶來的紅利。擔心已有組件和新版本不兼容?主流云服務商都有兼容性自動檢測功能,直接使用即可。
隨著微服務架構的普及,實現一個簡單的服務都需要對云原生中涉及到的分布式技術棧和容器編排基礎知識有較深入的理解。因此,開發人員必須精通現代編程語言來實現業務功能,并且同時精通云原生技術來解決非功能性需求。
