歡迎光臨
每天分享高質量文章

五大Kubernetes最佳實踐

在最近的一次Weave用戶組在線會議WOUG[1]上兩個工程師做了Kubernetes相關的分享。
谷歌雲的開發者佈道師Sandeep Dinesh(@SandeepDinesh)做了一個演講,給大家列舉了在Kubernetes上運行應用的最佳實踐清單;Jordan Pellizzari(@jpellizzari),是來自Weaveworks的工程師,隨後也做了一個分享,內容是在他們使用Kubernetes開發運行SaaS Weave Cloud兩年之後學到的經驗教訓。


Kubernetes最佳實踐

這篇演講中的最佳實踐來源於Sandeep和團隊進行的關於在Kubernetes上以多種不同方式運行同一任務的討論。他們把討論的結果總結為一個最佳實踐的清單。
這些最佳實踐被分成以下類別:
  1. 構建容器

  2. 容器內部

  3. 部署

  4. 服務

  5. 應用架構


1、構建容器

不要信任任意的基礎鏡像
不幸的是我們看到這個錯誤一直在發生, Pradeep說到。人們從DockerHub上隨便拉一個某人做的基礎鏡像——這麼做的理由僅僅是第一眼看過去這個鏡像裡面打包有他們需要的包——接著他們就把這個隨便選的鏡像推到生產環境中。
這麼做是非常錯誤的:你使用的代碼可能有很多漏洞,bug,錯誤版本,或者本身就被人有意把惡意軟體打包進去——只是你不知道罷了。
要減輕這種風險,你可以使用靜態分析工具,比如CoreOS’ Clair[2]或者Banyon Collector[3]來對容器進行漏洞掃描。
保持基礎鏡像儘量小
基於最簡潔的可用基礎鏡像,然後基於它構建軟體包,這樣你就知道鏡像裡面到底有哪些東西。
越小的基礎鏡像開銷也越小。你的應用可能只要5M, 但是如果你盲目的隨便找一個鏡像,比如Node.js, 它裡面就包括了額外500M你根本要不到的庫檔案。
使用小鏡像的其它優勢有:
  • 快速構建

  • 節約儲存

  • 拉去鏡像更快

  • 更小的潛在攻擊面

使用構建器樣式
這種樣式對靜態語言特別有用,編譯類似Go,C++或者Typescript for Node.js這些語言時。
在這種樣式里你有一個構建容器,裡面打包有編譯器,依賴包,以及單元測試。 代碼通過第一步之後產出構建的artifacts,這包括所有的檔案,bundles等。然後再通過一個運行時容器,包括有監控和除錯工具等。
到最後, 你的Dockerfile裡面將會只包含你的基礎鏡像以及運行時環境容器。

2、容器內部

在容器的內部使用非root用戶
如果你在容器內使用root來更新包,那麼你要把用戶改成非root用戶。
原因很簡單,如果你的容器有後門被人利用了而且你還沒把它的用戶改成root之外的,那麼一個簡單的容器逃離將會導致你整個主機的root權限都被利用。但是如果你改成了非root用戶,黑客就沒那麼容易得到root用戶的權限了。
做為最佳實踐,你要對你的基礎設施加多層外殼保護。
在Kubernetes裡面你可以通過設置安全背景關係[4]runAsNonRoot: true來實現,這樣會對整個集群cluster來生效。
檔案系統只讀
這一個最佳實踐通過設置readOnlyFileSystem: true來實現。
每個容器裡面跑一個行程
你當然可以在一個容器裡面跑多個行程,但是推薦跑一個。這是由編排器的工作方式決定的。Kubernetes基於一個行程是否健康來管理容器。如果你在一個容器裡面有20個行程,它如何知道容器是否健康呢?
不要使用 Restart on Failure, 而應當 Crash Cleanly
Kubernetes會重新啟動失敗的容器,因此你應該乾凈的做崩潰退出(給出一個錯誤碼),這樣Kubernetes就可以不用你的人工干預來成功重起了。
日誌打到標準輸出和標準錯誤輸出(stdout & stderr)
Kubernetes預設會監聽這些管道,然後將輸出傳到日誌服務上面去。在谷歌雲上可以直接用StackDriver日誌系統。
3、部署

使用record選項來使回滾更方便
在取用一個yaml檔案時,請使用–record選項:
kubectl apply -f deployment.yaml --record
帶了這個選項之後,每次升級的時都會儲存到部署的日誌裡面,這樣就提供了回滾一個變更的能力。

多使用描述性的標簽label
因為標簽可以是任意的鍵值對,其表達力非常強。參考下圖,以有名字為’Nifty‘的應用部署到四個容器裡面。 通過選擇BE標簽你可以挑選出後端容器。

使用sidecar來做代理、監視器等
有時候你需要一組行程跟其它某個行程通訊。但是你又不希望把它們所有的都放進一個容器裡面(前面提到的一個容器跑一個行程), 你希望的是把相關的行程都放到一個Pod裡面。
常見情況是你需要運行行程依賴的一個代理或者監視器,比如你的行程依賴一個資料庫, 而你不希望把資料庫的密碼硬編碼進每個容器裡面,這個時會你可以把密碼放到一個代理程式裡面當作sidecar,由它來管理資料庫連接:

不要使用sidecar來做啟動引導
儘管sidecar在處理集群內外的請求時非常有用,Sandeep不推薦使用它做啟動。再過去,引導啟動(bootstraping)是唯一選項,但是現在Kubernetes有了“init 容器”。
當容器裡面的一個行程依賴於其它的一個微服務時, 你可以使用init容器來等到行程啟動以後再啟動你的容器。這可以避免當行程和微服務不同步時產生的很多錯誤。
基本原則就是: 使用sidecar來處理總是發生的事件,而用init容器來處理一次性的事件。
不要使用:latest或者無標簽
這個原則是很明顯的而且大家基本都這麼在用。如果你不給你的容器加標簽,那麼它會總是拉最新的,這個“最新的”並不能保證包括你認為它應該有的那些更新。
善用readiness、liveness探針
使用探針可以讓Kubernetes知道節點是否正常,以此決定是否把流量發給它。預設情況下Kubernetes檢查行程是否在運行。但是通過使用探針, 你可以在預設行為下加上你自己的邏輯。

4、服務

不要使用type: Loadbalancer
每次你在部署檔案裡面加一個公有雲提供商的loadbalancer(負載均衡器)的時候,它都會創建一個。 它確實是高可用,高速度,但是它也有經濟成本。
使用Ingress來代替,同樣可以實現通過一個end point來負載均衡多個服務。這種方式不但更簡單,而且更經濟。當然這個策略只有你提供http和web服務時有用,對於普通的TCP/UDP應用就沒用了。

Type: Nodeport可能已經夠用了
這個更多是個人喜好,並不是所有人都推薦。NodePort把你的應用通過一個VM的特定端口暴露到外網上。 問題就是它沒有像負載均衡器那樣有高可用。比如極端情況,VM掛了你的服務也掛了。
使用靜態IP, 它們免費!
在谷歌雲上很簡單,只需要為你的ingress來創建全域性IP。類似的對你的負載均衡器可以使用Regional IP。這樣當你的服務down了之後你不必擔心IP會變。
將外部服務映射到內部
Kubernetes提供的這個功能不是所有人都知道。如果您需要群集外部的服務,您可以做的是使用ExternalName型別的服務。這樣你就可以通過名字來呼叫這個服務,Kubernetes manager會把請求傳遞給它,就好像它在集群之中一樣。Kubernetes對待這個服務就好像它在同一個內網裡面,即使實際上它不在。
5、應用架構

使用Helm Charts
Helm基本上就是打包Kubernetes應用配置的倉庫。如果你要部署一個MongoDB, 存在一個預先配置好的Helm chart,包括了它所有的依賴,你可以十分容易的把它部署到集群中。
很多流行的軟體/組件都有寫好了的Helm charts, 你可以直接用,省掉大量的時間和精力。
所有下游的依賴是不可靠的
你的應用應該有邏輯和錯誤信息負責審計你不能控制的所有依賴。Sandeep建議說你可以使用Istio或者Linkerd這樣的服務網格來做下游管理。
使用Weave Cloud
集群是很難可視化管理的。 使用Weave Cloud[5]可以幫你監視集群內的情況和跟蹤依賴。
確保你的微服務不要太“微小”
你需要的是邏輯組件,而不是每個單獨的功能/函式都變成一個微服務。
使用命名空間來分離集群
例如, 你可以在同一個集群裡面創建prod、dev、test這樣不同的命名空間,同時可以對不同的命名空間分配資源, 這樣萬一某個行程有問題也不會用盡所有的集群資源。
基於角色的訪問控制RBAC
實施時當的訪問控制來限制訪問量, 這也是最佳的安全實踐。
從運行Weave Cloud生產環境學到的教訓

接下來Jordan Pellizzari做了一個演講,題目是在過去兩年我們在Kubernetes上開發運行Weave Cloud學到的經驗。 我們當前運行在AWS EC2上, 總共有72個Kubernetes部署運行在13個主機和150個容器裡面。我們所有的持續性儲存儲存在S3,DynamoDB或者RDS裡面, 我們並不在容器裡面儲存狀態信息。 關於我們如何搭建基礎設施的細節可以參看這篇文件[6]。
挑戰1:對基礎設施做版本控制
在Weaveworks我們把所有的基礎架構儲存在Git中, 如果我們要對基礎設施做變更,要像代碼一樣提Pull request。我們把這稱為GitOps,也寫了多篇博文。你可以從這篇讀起: GitOps – Pull Request支撐的運維[7]。
在Weave, 所有的Terraform腳本,Ansible以及Kubernetes YAML檔案都被儲存在Git裡面做版本控制。
把基礎架構放在Git裡面是一個最佳實踐,這有多個原因:
  • 發佈可以很方便的回滾

  • 對誰做了什麼修改有追蹤審計

  • 災難恢復相當簡單

問題: 當生產與版本控制不一致時該怎麼辦?
除了把所有內容儲存在Git中之外,我們也有一個流程會檢查生產集群中運行的狀態與版本控制中的內容差異。如果檢查到有不同,就會給我們的Slack頻道發一個報警。
我們使用一個叫Kube-Diff[8]的開源工具來檢查不同。
挑戰2:自動化的持續交付
自動化你的CI/CD流水線,避免手工的Kubernetes部署。因為我們一天內做多次部署,這種方式節約了團隊的時間也避免了手工容易發生錯誤的步驟。在Weaveworks,開發人員只需要做一個Git push,然後Weave Cloud會做以下的事情:
  • 打過標簽的代碼通過CircleCI的測試然後構建一個新的容器鏡像,推送這個新的鏡像到倉庫中。

  • Weave Cloud的“Deploy Automator‘檢測到新鏡像,從庫中拉取新鏡像然後在配置庫裡面更新對應的YAML檔案。

  • Deploy Synchronizer會檢測到集群需要更改in了,然後它會從配置庫裡面拉更新的配置清單,最後將新的鏡像部署到集群中。

GitOps流水線

這裡有一篇稍長的文章[9],我們認為的構建自動化CI/CD流水線的最佳實踐都在裡面描述了。


總結

Sandeep Dinesh做了一個關於創建、部署、運行應用到Kubernetes裡面的五個最佳實踐的深度分享。隨後Jordan Pellizzari做了Weave如何在kubernetes中管理SaaS產品Weave Cloud和經驗教訓的分享。