1. 程式人生 > >FreeWheel基於Kubernetes容器雲構建與實踐:應用編排與服務質量保證_Kubernetes中文社群

FreeWheel基於Kubernetes容器雲構建與實踐:應用編排與服務質量保證_Kubernetes中文社群

【編者的話】隨著公司業務不斷髮展以及逐漸向微服務的轉變,我們藉助於Kubernetes容器化解決方案來標準化和簡化應用釋出的整個流程,使原來需要大量人工維護和干預的工作變為自動化。本次內容主要是FreeWheel現階段基於Kubernetes容器化經驗和實踐的總結,目標是提供一個持續、穩定、高效的容器雲平臺。

服務健康檢查與自我恢復

對線上業務來說,保證服務的正常穩定是重中之重,對故障服務的及時處理避免影響業務以及快速恢復一直是開發運維的難點。Kubernetes提供了健康檢查服務,對於檢測到故障服務會被及時自動下線,以及通過重啟服務的方式使服務自動恢復。

主要分享內容:

  1. 如何判斷Container和Service的健康狀態。
  2. 健康檢查失敗的Container和Service,如何自我恢復。
  3. 使用建議。

健康檢查

使用Liveness及Readness探針
  • Liveness探針主要用於判斷Container是否處於執行狀態,比如當服務crash或者死鎖等情況發生時,kubelet會kill掉Container,然後根據其設定的restart policy進行相應操作(可能會在本機重新啟動Container,或者因為設定Kubernetes QoS,本機沒有資源情況下會被分發的其他機器上重新啟動)。
  • Readness探針主要用於判斷服務是否已經正常工作,如果服務沒有載入完成或工作異常,服務所在的Pod的IP地址會從服務的Endpoints中被移除,也就是說,當服務沒有ready時,會將其從服務的load balancer中移除,不會再接受或響應任何請求。

探針處理Handler型別

無論對於Readness或Liveness探針,Handler均支援以下3種類型:ExecAction、TCPSocketAction、HTTPGetAction。每種型別說明與舉例如下:

  • ExecAction:Container內部執行某個具體的命令,例子
  • TCPSocketAction:通過Container的IP、port執行tcp進行檢查, 例子
  • HTTPGetAction:通過Container的IP、port、path,用HTTP Get請求進行檢查,例子
探針檢查結果

探針檢查結果分為3種情況:

  1. 成功(Success):通過檢查。
  2. 失敗(Failure):檢查失敗。
  3. 未知(Unknown):檢查未知,需要人工干預。
健康檢查總結
探針型別          說明                                 通過健康檢查標準 
ExecAction       Container內部執行shell命令            shell命令返回0
TCPSocketAction  通過Container的IP、port執行tcp進行檢查  port是否開啟
HTTPGetAction    通過Container的IP、port、path,       200<=返回值<400
                 用HTTP Get請求進行檢查

服務可用性與自動恢復

  1. 如果服務的健康檢查(readiness)失敗,故障的服務例項從service endpoint中下線,外部請求將不會再轉發到該服務上,一定程度上保證正在提供的服務的正確性,如果服務自我恢復了(比如網路問題),會自動重新加入service endpoint對外提供服務。
  2. 另外,如果設定了Container(liveness)的探針,對故障服務的Container(liveness)的探針同樣會失敗,container會被kill掉,並根據原設定的container重啟策略,系統傾向於在其原所在的機器上重啟該container、或其他機器重新建立一個pod。
  3. 由於上面的機制,整個服務實現了自身可用與自動恢復。

使用建議:

  1. 建議對全部服務同時設定服務(readiness)和Container(liveness)的健康檢查。
  2. 通過TCP對埠檢查形式(TCPSocketAction),僅適用於埠已關閉或程序停止情況。因為即使服務異常,只要埠是開啟狀態,健康檢查仍然是通過的。
  3. 基於第二點,一般建議用ExecAction自定義健康檢查邏輯,或採用HTTP Get請求進行檢查(HTTPGetAction)。
  4. 無論採用哪種型別的探針,一般建議設定檢查服務(readiness)的時間短於檢查Container(liveness)的時間,也可以將檢查服務(readiness)的探針與Container(liveness)的探針設定為一致。目的是故障服務先下線,如果過一段時間還無法自動恢復,那麼根據重啟策略,重啟該Container、或其他機器重新建立一個Pod恢復故障服務。

應用編排與配置管理

微服務和容器化給複雜應用部署與管理帶來了極大的挑戰。Helm是目前Kubernetes服務編排領域的唯一開源子專案,做為Kubernetes應用的一個包管理工具,可理解為Kubernetes的apt-get / yum,由Deis公司發起,該公司已經被微軟收購。Helm通過軟體打包的形式,支援釋出的版本管理和控制,很大程度上簡化了Kubernetes應用部署和管理的複雜性。

隨著業務容器化與向微服務架構轉變,通過分解巨大的單體應用為多個服務的方式,分解了單體應用的複雜性,使每個微服務都可以獨立部署和擴充套件,實現了敏捷開發和快速迭代和部署。但任何事情都有兩面性,雖然微服務給我們帶來了很多便利,但由於應用被拆分成多個元件,導致服務數量大幅增加,對於Kubernetest編排來說,每個元件有自己的資原始檔,並且可以獨立的部署與伸縮,這給採用Kubernetes做應用編排帶來了諸多挑戰:

  1. 管理、編輯與更新大量的Kubernetest配置檔案
  2. 部署一個含有大量配置檔案的複雜Kubernetest應用
  3. 分享和複用Kubernetest配置和應用
  4. 引數化配置模板支援多個環境
  5. 管理應用的釋出:回滾、diff和檢視釋出歷史
  6. 控制一個部署週期中的某一些環節
  7. 釋出後的驗證

而Helm恰好可以幫助我們解決上面問題。

主要分享內容:

  1. Helm的簡介,包括Helm術語、架構、用途等。
  2. 展示Helm做一次release的過程,包括release建立、更新、回滾以及刪除等。
  3. Helm的對release的強大版本管理功能。
  4. 利用Helm/init container處理服務啟動順序依賴。

Helm架構

Helm基本架構如下:

Helm有三個重要概念:

  1. chart:包含了建立Kubernetes的一個應用例項的必要資訊
  2. config:包含了應用釋出配置資訊
  3. release:是一個chart及其配置的一個執行例項
Helm元件

Helm有以下兩個組成部分:

Helm Client是使用者命令列工具,其主要負責如下:

  • 本地chart開發
  • 倉庫管理
  • 與Tiller sever互動
  • 傳送預安裝的chart
  • 查詢release資訊
  • 要求升級或解除安裝已存在的release
  • Tiller Server是一個部署在Kubernetes叢集內部的server,其與Helm client、Kubernetes API server進行互動。

Tiller server主要負責如下:

  • 監聽來自Helm client的請求
  • 通過chart及其配置構建一次釋出
  • 安裝chart到Kubernetes叢集,並跟蹤隨後的釋出
  • 通過與Kubernetes互動升級或解除安裝chart

簡單的說,client管理charts,而server管理髮布release。

Helm實現

Helm client

  • Helm client採用Go語言編寫,採用gRPC協議與Tiller server互動。

Helm server

  • Tiller server也同樣採用Go語言編寫,提供了gRPC server與client進行互動,利用Kubernetes client 庫與Kubernetes進行通訊,當前庫使用了REST+JSON格式。
  • Tiller server 沒有自己的資料庫,目前使用Kubernetes的ConfigMaps儲存相關資訊

說明:配置檔案儘可能使用YAM格式

Helm部署Kubernetes應用

獲取chart

獲取版本為0.2.8的mysql並解壓縮包:

$ helm fetch stable/mysql --version 0.2.8 --untar

利用helm lint命令檢查下載的chart是否存在問題:

$ helm lint mysql
==> Linting mysql
Lint OK
1 chart(s) linted, no failures
建立chart

利用helm create mychart命令建立一個mychart目錄:

$ helm create mychart

生成的mychart的檔案結構如下:

mychart/
|-- charts
|-- Chart.yaml
|-- templates
|   |-- deployment.yaml
|   |-- _helpers.tpl
|   |-- ingress.yaml
|   |-- NOTES.txt
|   `-- service.yaml
`-- values.yaml

2 directories, 7 files

生成chart目錄裡有Chart.yaml,values.yaml 與 NOTES.txt等檔案,下面分別對chart中幾個重要檔案解釋:

Chart.yaml 包含了chart的meta

  • data,描述了Chart名稱、描述資訊與版本。
  • values.yaml:儲存了模板檔案變數。
  • templates/:記錄了全部模板檔案。
  • charts/:依賴chart儲存路徑。
  • NOTES.txt:給出了部署後的資訊,例如如何使用chart、列出預設的設定等等。

chart安裝有以下幾種方式:

  • 指定chart:helm install stable/mariadb
  • 指定打包的chart:helm install ./nginx-1.2.3.tgz
  • 指定打包目錄:helm install ./nginx

覆蓋chart中的預設值,通過指定配置檔案方式:

helm install -f myvalues.yaml ./redis

或者通過–set key=value形式:

$ helm install --set name=prod ./redis

安裝release名稱為mysql例子如下:

$ helm install -n mysql -f mysql/values.yaml --set resources.requests.memory=512Mi mysql

通過helm status檢視release狀態:

$ helm status mysql
LAST DEPLOYED: Tue Sep 12 07:31:49 2017
NAMESPACE: default
STATUS: DEPLOYED

或通過helm list -a檢視全部的release,tag “-a”是檢視全部的release,包括已部署、部署失敗、正在刪除、已刪除release等。

$ helm list -a | grep mysqlmysql           1       Tue Sep 12 07:31:49 2017 DEPLOYED mysql-0.2.8         default
更新release

Helm使用helm upgrade更新已安裝的release:

$ helm upgrade mysql -f mysql/values.yaml --set resources.requests.memory=1024Mi mysql

檢視指定release的歷史部署版本資訊:

$ helm hist  mysql
REVISION    UPDATED                        STATUS           CHART          DESCRIPTION
1           Tue Sep 12 07:31:49 2017       SUPERSEDED       mysql-0.2.8    Install complete
2           Tue Sep 12 07:44:00 2017       DEPLOYED         mysql-0.2.8    Upgrade complete

檢視指定release的歷史版本部署時部分配置資訊,以resources.requests.memory為例,符合檢視部署符合預期:即第一次部署resources.requests.memory設定為512Mi,第二次的升級resources.requests.memory設定為1024Mi:

$ helm get --revision 1 mysql
resources:
requests:
cpu: 100m
memory: 512Mi

$ helm get --revision 2 mysql
resources:
requests:
cpu: 100mw'w
memory: 1024Mi
版本回滾

回滾到第一次的版本:

helm rollback --debug mysql 1
[debug] Created tunnel using local port: '60303'
[debug] SERVER: "localhost:60303"
Rollback was a success! Happy Helming!

檢視mysql release的版本資訊,當前已經回滾到REVISION為1的版本:

$ helm hist mysql
REVISION           UPDATED                    STATUS        CHART          DESCRIPTION
1                  Tue Sep 12 07:31:49 2017   SUPERSEDED    mysql-0.2.8    Install complete
2                  Tue Sep 12 07:44:00 2017   SUPERSEDED    mysql-0.2.8    Upgrade complete
3                  Tue Sep 12 08:50:48 2017   DEPLOYED      mysql-0.2.8    Rollback to 1
刪除chart

利用helm delete命令刪除一個chart:

$ helm delete mysql

即使刪除的chart,其釋出的歷史資訊還是繼續被儲存。

$ helm hist mysql
REVISION      UPDATED                     STATUS         CHART          DESCRIPTION
1             Tue Sep 12 07:31:49 2017    SUPERSEDED     mysql-0.2.8    Install complete
2             Tue Sep 12 07:44:00 2017    SUPERSEDED     mysql-0.2.8    Upgrade complete
3             Tue Sep 12 08:50:48 2017    DELETED        mysql-0.2.8    Deletion complete

可以恢復一個已經刪除的release:

$ helm rollback --debug mysql 2
[debug] Created tunnel using local port: '37413'
[debug] SERVER: "localhost:37413"
Rollback was a success! Happy Helming!

如果希望徹底刪除一個release,可以用如下命令:

$ helm delete --purge mysql
release "mysql" deleted

Helm對release的版本管理

在上面例子中,已經展示了Helm對release的非常強大的版本管理功能,比如通過”helm list -a”檢視有哪些release,通過” helm hist“檢視某一個具體的release釋出過的歷史版本,以及通過” helm get –revision”,檢視某個release的一次歷史版本對應的具體應用配置資訊等。即使已經被刪除的release仍然有記錄,並且通過Helm能夠快速回滾到已刪除release的某個釋出過的歷史版本。Helm的這些版本管理功能,Kubernetes原生並不支援。

使用Helm對Release配置

上面展示了一次Helm釋出的生命週期,包含了chart建立、更新、回滾、刪除等。但這並不是我們使用Helm的唯一原因,我們還需要一個管理配置釋出的工具。

Helm Chart模板採用go模板語言編寫 Go template language,模板的值記錄在values.yaml檔案中。values.yaml檔案中的值可以在安裝chart過程中,通過引數–values YAML_FILE_PATH或–set key1=value1, key2=value2替換。

注意:模板語言中的引用數值型需要注意,不加引號,會被解釋為數值。

檢視上文中建立mychart/templates/configmap.yaml檔案,內容如下:

apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"

一個模板指令由{{}}標記。{{ .Release.Name }}會把release name插入模板。傳入模板的值可被認作是namespaces物件,通過”.”來分隔namespaces元素。

內建物件

物件通過模板引擎傳給模板,有幾種方式在模板內建立新的物件,比如後面要講到的tuple。

Values檔案

上面章節提到Helm模板提供了內建的物件,而Values做為4個內建物件之一,有4種來源:

  • 可通過chart中的values.yaml檔案
  • 對於subchart,父chart中values.yaml檔案
  • helm install 或 helm update 指定 -f flag(helm install -f myvals.yaml ./mychart)
  • 通過–set 引數傳值(例如 helm install –set foo=bar ./mychart)

說明:values.yaml預設會被引用,但其會被父chart中的values.yaml檔案內容覆蓋(overridden),而父chart中的values.yaml檔案會被戶用-f指定的檔案內容覆蓋,而使用者用-f指定的檔案會被–set引數傳的值覆蓋。簡單的說,也就是上面4種Values來源的重要性由上到下的順序,優先順序由低到高。

刪除預設鍵值

如需要刪除鍵值對中刪除預設key,需要將key的值設定為null,helm也會將從覆蓋的鍵值對中將其剔除。

模板功能和Pipeline

前面展示的模板例子中,模板內容基本不去修改,但有一些情況下,我們希望提供給模板的資料對我們更加簡單易用。例如對於 .Values物件注入模板的鍵值可利用模板提供的quote功能引用。

apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ quote .Values.favorite.drink }}
food: {{ quote .Values.favorite.food }}  

Helm 提供了超過60功能函式,其中一些定義在Go模板語言自身: Go template language . 這些函式是 Sprig template library的一部分。

PIPELINES

Pipelines是模板語言的非常強大的特性之一,與Linux/Unix的pipeline類似,pipelines是把模板語言的多個命令通過 (|)的分隔形式,以描述的順序的實現命令聚合功能。

apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ .Values.favorite.drink | repeat 5 | quote }}
food: {{ .Values.favorite.food | upper | quote }}

上面例子中,對.Values.favorite.drink | repeat 5 | quote的值coffee重複了5次後引用,而對.Values.favorite.food先做了一次大寫轉換然後加以引用。類似結果如下:

data:
myvalue: "Hello World"
drink: "coffeecoffeecoffeecoffeecoffee"
food: "PIZZA"

使用 DEFAULT功能

default是模板中經常使用一個功能,該功能是在模板中指定一個預設值。例如下面例子中,如果.Values.favorite.drink沒有被賦值,那麼模板被引用時,tea便為其預設值。

drink: {{ .Values.favorite.drink | default "tea" | quote }} 

操作符和函式

對於Helm模板,類似eq,ne,lt,gt,and,or等操作符已經實現為函式,

控制流(Flow Control)

Helm模板語言提供瞭如下控制結構:

  • if/else 條件語句
  • with 指定範圍
  • range,提供了“for each”-形式的迴圈

除此之外,Helm還提供了一些命名模板的行為:

  • define 模板內部定義一個命名模板
  • template 匯入一個命名模板
  • block declares a special kind of fillable template area

IF/ELSE

條件基本的控制結構如下:

{{ if PIPELINE }}
# Do something
{{ else if OTHER PIPELINE }}
# Do something else
{{ else }}
# Default case
{{ end }}   

pipeline會被認為是false 如果值是下面幾種型別:

  • 布林型別 false
  • 數字 0
  • 空串
  • nil(empty or null)
  • 空集合(map,slice,tuple,dict,array)

空格敏感

Helm模板語言對空格非常敏感,左側有{{-(破折號後面有一個空格)表明空格需要被移除,而右側 -}}表示空格會被消費。

使用with限制引用範圍

通過with與(.) 指定當前範圍到某一個指定的物件,例如下面例子中 .Values.favorites,後面再引用drink與food時,無需再指定Values.favorite。

apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
{{- end }} 

RANGE迴圈

像多數程式語言支援for與foreach等迴圈一樣,Helm模板語言通過支援range操作符遍歷一個集合。

模板變數

模板變數在range的迴圈中非常有用,變數的定義格式為$name ,可通過 :=賦值。舉例如下:

toppings: |-
{{- range $index, $topping := .Values.pizzaToppings }}
  {{ $index }}: {{ $topping }}
{{- end }}  
命名模板

Helm允許我們建立一個命名模板,通過模板名稱,可以在任何地方引用它。一般情況下命名模板會寫在_helpers.tpl 中,並以形式 ({{/* … */}})加上模板說明。比如下面例子中定義了一個my_labels命名模板,並且用 include通過 {{- include “my_labels” }}嵌入到configmap的metadata中。通常 include也可以用 template代替,但是 include在處理YAML檔案的格式輸出上會更勝一籌,另外 include可以包含一個變數的模板{{ include $mytemplate }}。

{{/* Generate basic labels */}}
{{- define "my_labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
{{- end }}  

在configmap.yaml檔案中引用該命名模板:

apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- include "my_labels" . }}
data:
myvalue: "Hello World"
{{- range $key, $val := .Values.favorite }}
{{ $key }}: {{ $val | quote }}
{{- end }}  

需要注意的是,命名模板名稱是全域性的,如果存在多個同名的模板,最後被load到Tiller中的模板會被最終使用。另外模板的命名通常以chart的名字做為字首,比如 {{ define “mychart.labels” }} 或 {{ define “mychart_labels” }}。

設定模板使用範圍

前面的命名模板中,並沒有使用模板的內建物件。下面例子中在命名模板中使用了內建物件:

{{/* Generate basic labels */}}
{{- define "my_labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
chart: {{ .Chart.Name }}
version: {{ .Chart.Version }}
{{- end }}  

在configmap中消費這個命名模板,注意在命名模板呼叫的末尾加上” .”, indent 2表示引用的模板內容向右縮排2格。

apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
labels:
{{ include "mychart_app" . | indent 4 }}
data:
myvalue: "Hello World"
{{- range $key, $val := .Values.favorite }}
{{ $key }}: {{ $val | quote }}
{{- end }}
{{ include "mychart_app" . | indent 2 }}  
模板訪問檔案

上個章節中展示了幾種建立和訪問命名模板的方式,通過命名模板,可以很容易在一個模板中引用另外一個模板。但有的時候,我們還有匯入一個普通檔案內容來渲染模板的需求,而不僅僅是以模板的形式。Helm提供了一個內建 .Files物件。

Helm支援chart中存在一個普通型別的檔案,這些檔案也會被與其他模板檔案一同打包發給給Tiller,但檔案大小目前被限制在1M以內。由於安全原因,一般情況下 templates/的.Files物件不可訪問,另外,chart並不會保留 UNIX mode的資訊,檔案層面的許可權並不會影響對.Files物件的訪問控制。

NOTES.txt檔案

在 chart install 或 chart upgrade的尾部,會打印出使用者幫助資訊,該資訊在 templates/NOTES.txt中定製。Helm並不強制提供使用者幫助資訊,但為了使用者更加方便了解和使用所安裝的應用,強烈建議在templates/NOTES.txt加上使用者幫助資訊。舉例如下:

Thank you for installing {{ .Chart.Name }}.

Your release is named {{ .Release.Name }}.

To learn more about the release, try:

$ helm status {{ .Release.Name }}
$ helm get {{ .Release.Name }}  
SubChart和全域性變數

所謂subchart,指的是一個chart依賴其他chart,被依賴的chart即被稱為subchart。

關於subchart的幾點說明:

  • subchart無需依賴父chart,其是一個完全獨立操作的chart,擁有自己values和模板。
  • subchart沒許可權訪問父chart的values,但父chart可以覆蓋subchart的values。
  • 無論subchart或父chart都可以訪問helm全域性values。
操作subchart

建立subchart

建立subchart的過程與普通chart基本一致,唯一需要注意的是,subchart需要建立在父chart的charts資料夾內,舉例如下:

$ cd mychart/charts
$ helm create mysubchart
Creating mysubchart

為sub chart新增模板和values:

新增 values.yaml 到 mychart/charts/mysubchart :

dessert: cake

在 mychart/charts/mysubchart/templates/configmap.yaml 中建立ConfigMap模板:

apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-cfgmap2
data:
dessert: {{ .Values.dessert }}

subchart可以單獨安裝:

$ helm install --dry-run --debug mychart/charts/mysubchart

父chart的values可以覆蓋子chart,在mychart/values.yaml新增:

mysubchart:
dessert: ice cream

然後執行 helm install –dry-run –debug mychart命令:

# Source: mychart/charts/mysubchart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: unhinged-bee-cfgmap2
data:
dessert: ice cream

從結果可知,子chart的dessert的已由cake被替換為ice cream。

全域性chart values

全部chart,包括子chart都可以訪問全域性chart values。一般全域性chart values記錄在父chart Values.global中。以mychart/values.yaml為例:

mysubchart:
dessert: ice cream

global:
salad: caesar

那麼在mysubchart/templates/configmap.yaml 中,即可以通過 .Values.global.salad 訪問:

apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-cfgmap2
data:
dessert: {{ .Values.dessert }}
salad: {{ .Values.global.salad }} 

父子chart共享模板

Helm中父子chart之間可以共享模板,在任何一個chart中定義的塊,對其他chart均可訪問。

除錯模板

模板在Tiller server端渲染,而非Helm client端。經過Tiller server渲染的模板,最後傳送給Kubernetes API server,而YAML檔案除了格式問題外,可能還有其他多種因為會被Kubernetes API server拒絕。

  • helm lint 驗證chart是否遵循了一些好的實踐
  • helm install –dry-run –debug:chart傳送給Tiller server並用value.yaml中的值來渲染相關模板。但實際上chart並沒有安裝, 只是輸出了渲染後的模板。
  • helm get manifest:檢視Tiller server端已安裝的模板。

Helm Hook

前面展示了採用Helm的一次應用release的生命週期過程,涵蓋了釋出(helm install)、更新(helm upgrade)、回滾(helm rollback)與刪除(helm delete)等部分。為了允許chart開發者在應用release的生命週期中某些關鍵的時間點上,執行一些操作來更好的服務於release的需求,為此Helm提供了hook機制。

舉例說明什麼是一個Helm hook,比如ConfigMap或Secret要先於其他chart被載入,或希望在更新/回滾/刪除一個release之前能夠安全的關閉服務,都可以通過Helm hook實現。

Hook和普通模板檔案基本相同,但其可以通過加入一些特殊的註釋(annotation)與普通模板檔案加以區分,下文會介紹hook的基本格式和用法。

支援Hook型別

Helm提供瞭如下hook供chart開發者使用:

  • pre-install:在模板檔案被渲染之後、而在Kubernetes建立任何資源建立之前執行。
  • post-install:在Kubernetes載入全部的資源之後執行。
  • pre-delete:在Kubernetes刪除任何resource之前執行。
  • post-delete:在一個release的全部資源被刪除之後執行。
  • pre-upgrade:在模板渲染之後,而在Kubernetes載入任何資源之前執行。
  • post-upgrade:在Kubernetes更新完全部resource之後執行。
  • pre-rollback:在模板被渲染之後、而在Kubernetes執行對全部resource的回滾之前。
  • post-rollback:在Kubernetes的全部resource已經被修改之後執行。
Hook與Release生命週期

上面已經提到,Hook允許chart開發者在release生命週期的一些時間點上可以執行一些操作。以helm install為例,預設情況下其基本生命週期如下:

  1. 使用者執行helm install foo
  2. Chart被載入到Helm server-Tiller
  3. 經過一些使用者自定義case驗證,Tiller用chart中的Values.yaml等渲染foo模板
  4. Tiller載入渲染好的Kubernetes resource到Kubernetes叢集
  5. Tiller返回release名稱等資料給Helm client
  6. Helm Client退出

如果在install的生命週期內定義pre-install與post-install 2個hook,那麼install的生命週期會變成如下序列:

  1. 使用者執行helm install foo
  2. Chart被載入到Helm server-Tiller
  3. 經過一些使用者自定義case驗證,Tiller用chart中的Values.yaml等渲染foo模板
  4. Tiller準備執行pre-install hook(載入hook resource到Kubernetes)
  5. Tiller按hook的權重對hook進行排序,對相同權重的hook按照字母從a-z順序排序
  6. Tiller按照權重由低到高順序載入hook
  7. Tiller等待hook狀態變為就緒(”Ready”)
  8. Tiller載入resource到Kubernetes
  9. Tiller執行post-install hook(載入hook resource)
  10. Tiller等待hook狀態變為就緒
  11. Tiller返回release名稱等資料給Helm client
  12. Helm Client退出

所謂hook就緒狀態,取決於hook中定義的Kubernetes resource的型別,比如,對於Job型別的resource,就緒狀態是job成功構建完成,如果job構建失敗,release也失敗了。而對於其他型別的resource,一旦resource載入(新增或更新)到Kubernetes,其狀態被認為是就緒狀態。

Hook權重

前面提到,一個hook可以對應多個resource,例如, secret與config map做為一個pre-install hook。

同時一個resource也可以有多個hook。

annotations:
"helm.sh/hook": post-install,post-upgrade

Helm支援一個hook中的resource設定權重,Helm推薦通過對resource設定權重的方式,按權重大小載入resource。對於權重數值可以設為負數,權重的大小由負數到正數順序。如果未設定權重,resource的載入是順序執行的,但執行順序並不會保證(Helm 2.3.0開始,相同權重的執行順序按照字母a-z順序執行,但未來版本可能會對相同權重的資源執行順序有所變化)。

寫一個Hook

Hook與普通的Kubernetes宣告檔案一樣,只是在檔案metadata中加了特殊的註釋(annotations)。但由於Hook檔案是模板檔案,所以其也具備全部的模板檔案特性,包括從內建物件 .Values,.Release,and .Template獲取渲染值等。

Hook資源刪除

Helm支援2種刪除hook resource策略:

  • hook-succeeded
  • hook-failed

當使用”helm.sh/hook-delete-policy” 註釋(annoation)時,刪除hook resource支援2種策略:”hook-succeeded” 與 “hook-failed”。當刪除策略為 “hook-succeeded”時,hook執行成功後,該hook會被Tiller刪除。而刪除策略為 “hook-failed”時,hook在執行過程中失敗後,該hook會被Tiller刪除。

Hook resource刪除策略舉例:

annotations:
"helm.sh/hook-delete-policy": hook-succeeded
利用Hook處理服務啟動順序依賴

所謂服務依賴指的是啟動一個服務,依賴於另外服務。這個時候我們就需要設定服務依賴關係來處理了。

對於服務啟動的依賴,可以採用2種方式:

  • Init Container
  • Helm hook

理解Init Container

一個Pod中可以有一或多個Init Container。Pod的中多個Init Container啟動順序為yaml檔案中的描述順序,且序列方式啟動,下一個Init/app Container必須等待上一個Init Container完成後方可啟動。例如,Init Container1-> … -> Init Containern -> app Container[1-n]。Init Container1成功啟動並且完成後,後續的Init Container和app Container才可以啟動,如Init Container啟動或執行相關檢查失敗,後續的init Container和應用Container將不會被執行啟動命令。

因此可利用Init Container來判斷app Container中被依賴的服務是否成功啟動。如被依賴的app Container服務啟動失敗,那麼利用Init Container啟動失敗可以阻止後續app Container服務的啟動。

由於Init Container必須要在Pod狀態變為Ready之前完成,所以其不需要readiness探針。另外在資源的requests與limits上與普通Container有細微差別,詳見 Resources,除以上2點外,Init Container與普通Container並無明顯區別。

Init Containers用途

  1. 前文已經提及,由於Init Container必須在app Containers啟動之前完成,所以可利用其特性,用作服務依賴處理。比如某一個服務A,需依賴DB或memcached,那麼可以利用服務A Pod的Init Container判斷db/memcached是否正常提供服務,如果啟動服務失敗或工作異常,設定Init Container啟動失敗,那麼Pod中的服務A就不會被啟動了。
  2. 應用映象因安全原因等沒辦法安裝或執行的工具,可放到Init Container中執行。另外,Init Container獨立於業務服務,與業務無關的工具如SED,Awk,Python,Dig等也可以按需放到Init Container之中。最後,Init Container也可以被授權訪問應用Container無權訪問的內容。

Init Container處理服務依賴應用舉例

serviceA服務依賴serviceB,而serviceB採用上文提及Readness探針的HTTPGetAction Handler。

spec:
initContainers:
- name: init-serviceA
image: registry.docker.dev.fwmrm.net/busybox:latest
command: ['sh', '-c', "curl --connect-timeout 3 --max-time 5 --retry 10 --retry-delay 5 --retry-max-time 60 serviceB:portB/pathB/"]
containers:

Helm hook處理服務依賴

也可以通過Helm hook來實現服務啟動依賴的處理:

舉例:serviceA服務依賴serviceB, serviceA的pre-install hook中實現:

apiVersion: batch/v1
kind: Job
metadata:
name: "{{.Release.Name}}"
labels:
chart: "{{.Chart.Name}}-{{.Chart.Version}}"
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-weight": "-5"
spec:
template:
metadata:
  name: "{{.Release.Name}}"
  labels:
    chart: "{{.Chart.Name}}-{{.Chart.Version}}"
spec:
  restartPolicy: Never
  containers:
  - name: pre-install-job
    image: "registry.docker.dev.fwmrm.net/busybox:latest"
    command: ['sh', '-c', "curl --connect-timeout 3 --max-time 5 --retry 10 --retry-delay 5 --retry-max-time 60 --retry-connrefused serviceB:portB/pathB/"]

Helm hook的是一種序列且阻塞式操作(blocking operation),所謂阻塞指的同一個chart,在同一個時刻只有一個hook執行,其他hook以及release生命週期的其他行為活動(helm install,helm upgrade,helm rollback等)都會被阻塞(block)。而序列指的是隻有當一個hook成功執行完畢後,才會執行其他hook。

以上面的pre-install hook的例子說明,pre-install hook的job會先於helm install執行,且在pre-install hook的job執行過程中,其他的release週期活動已經其他hook都不會被執行。當且僅當serviceB:portB/pathB/在指定時間內服務可達時,pre-install hook才會成功執行,餘下的hook以及release其他操作才可以繼續執行。

簡而言之,把被依賴的服務是否成功啟動的邏輯判斷,放在依賴服務的pre-install hook中,利用Helm hook的序列且阻塞式操作(blocking operation)的特性,如果hook執行失敗,那麼對應的一次release也就失敗了,這就達到了服務啟動依賴的處理效果。

服務質量與資源利用率的均衡

Kubernetes做為目前主流的容器叢集管理平臺,需要整體統籌平臺資源使用情況、公平合理的將資源分配給相關Pod容器使用,並且要保證容器生命週期內有足夠的資源來保證其執行。 與此同時,由於資源發放的獨佔性,即資源已經分配給了某容器,同樣的資源不會在分配給其他容器,對於資源利用率相對較低的容器來說,佔用資源卻沒有實際使用(比如CPU、記憶體)造成了嚴重的資源浪費,Kubernetes需從優先順序與公平性等角度提高資源的利用率。

為了實現資源被有效排程和分配的同時提高資源利用率,Kubernetes針對不同服務質量的預期,通過QoS(Quality of Service)來對Pod進行服務質量管理,提供了個採用requestslimits兩種型別對資源進行分配和使用限制。對於一個Pod來說,服務質量體現在兩個為2個具體的指標: CPU與記憶體。實際過程中,當NODE節點上記憶體資源緊張時,kubernetes會根據預先設定的不同QoS類別進行相應處理。

本部分主要分享內容:

  1. 資源限制的原因。
  2. 資源需求(Requests)和限制( Limits)。
  3. QoS分類以及使用場景。
  4. 使用建議。
設定資源限制的原因

如果未做過節點nodeSelector,親和性(node affinity)或Pod親和、反親和性(pod affinity/anti-affinity)等Pod高階排程策略設定,我們沒有辦法指定服務部署到指定機器上,如此可能會造成cpu或記憶體等密集型的Pod同時分配到相同Node,造成資源競爭。另一方面,如果未對資源進行限制,一些關鍵的服務可能會因為資源競爭因OOM(Out of Memory)等原因被kill掉,或者被限制CPU使用。

資源需求(Requests)和限制( Limits)

對於每一個資源,container可以指定具體的資源需求(requests)和限制(limits),requests申請範圍是0到node節點的最大配置,而limits申請範圍是requests到無限,即0 <= requests <=Node Allocatable,requests <= limits <= Infinity。

對於CPU,如果Pod中服務使用CPU超過設定的limits,Pod不會被kill掉但會被限制。如果沒有設定limits,Pod可以使用全部空閒的cpu資源。
對於記憶體,當一個Pod使用記憶體超過了設定的limits,Pod中container的程序會被kernel因OOM kill掉。當Container因為OOM被kill掉時,系統傾向於在其原所在的機器上重啟該Container或本機或其他重新建立一個Pod。

QoS分類

Kubelet提供QoS服務質量管理,支援系統級別的OOM控制。在Kubernetes中,Pod的QoS級別:GuaranteedBurstable與 Best-Effort。下面對各級別分別進行相應說明:

Guaranteed:Pod中所有容器都必須統一設定limits,並且設定引數都一致,如果有一個容器要設定requests,那麼所有容器都要設定,並設定引數同limits一致,那麼這個Pod的QoS就是Guaranteed級別。

注:如果一個容器只指明limit而未設定request,則request的值等於limit值。

Guaranteed舉例:容器只指明瞭limits而未指明requests

containers:
name: foo
resources:
  limits:
    cpu: 10m
    memory: 1Gi
name: bar
resources:
  limits:
    cpu: 100m
    memory: 100Mi

Burstable:Pod中只要有一個容器的requestslimits的設定不相同,該Pod的QoS即為Burstable。舉例如下:

Container bar沒有指定resources

containers:
name: foo
resources:
  limits:
    cpu: 10m
    memory: 1Gi
  requests:
    cpu: 10m
    memory: 1Gi

name: bar

Best-Effort:如果對於全部的resources來說requestslimits均未設定,該Pod的QoS即為Best-Effort。舉例如下:

containers:
name: foo
resources:
name: bar
resources:
可壓縮資源與不可壓縮資源

Kubernetes根據資源能否伸縮排行分類,劃分為可壓縮資源和不可以壓縮資源2種。CPU資源是目前支援的一種可壓縮資源,而記憶體資源和磁碟資源為目前所支援的不可壓縮資源。

QoS優先順序

3種QoS優先順序從有低到高(從左向右):

Best-Effort pods -> Burstable pods -> Guaranteed pods
資源回收策略

當Kubernetes叢集中某個節點上可用資源比較小時,Kubernetes提供了資源回收策略保證被排程到該節點Pod服務正常執行。當節點上的記憶體或者CPU資源耗盡時,可能會造成該節點上正在執行的Pod服務不穩定。Kubernetes通過kubelet來進行回收策略控制,保證節點上Pod在節點資源比較小時可以穩定執行。

可壓縮資源:CPU

在壓縮資源部分已經提到CPU屬於可壓縮資源,當Pod使用超過設定的limits值,Pod中程序使用cpu會被限制,但不會被kill。

不可壓縮資源:記憶體

Kubernetes通過cgroup給Pod設定QoS級別,當資源不足時先kill優先順序低的Pod,在實際使用過程中,通過OOM分數值來實現,OOM分數值從0-1000。

OOM分數值根據OOM_ADJ引數計算得出,對於Guaranteed級別的Pod,OOM_ADJ引數設定成了-998,對於BestEffort級別的Pod,OOM_ADJ引數設定成了1000,對於Burstable級別的Pod,OOM_ADJ引數取值從2到999。對於Kuberntes保留資源,比如kubelet,docker,OOM_ADJ引數設定成了-999,表示不會被OOM kill掉。OOM_ADJ引數設定的越大,通過OOM_ADJ引數計算出來OOM分數越高,表明該Pod優先順序就越低,當出現資源競爭時會越早被kill掉,對於OOM_ADJ引數是-999的表示kubernetes永遠不會因為OOM而被kill掉。

QoS Pods被kill掉場景與順序

  • Best-Effort 型別的Pods:系統消耗完全部記憶體時,該型別Pods會最先被kill掉。
  • Burstable型別Pods:系統消耗完全部記憶體,且沒有Best-Effort container可以被kill時,該型別Pods會被kill掉。
  • Guaranteed Pods:系統消耗完全部記憶體、且沒有Burstable與Best-Effort container可以被kill,該型別的Pods會被kill掉。

注:如果Pod程序因使用超過預先設定的limites而非Node資源緊張情況,系統傾向於在其原所在的機器上重啟該Container或本機或其他重新建立一個Pod。

使用建議
  • 如果資源充足,可將QoS Pods型別均設定為Guaranteed。用計算資源換業務效能和穩定性,減少排查問題時間和成本。
  • 如果想更好的提高資源利用率,業務服務可以設定為Guaranteed,而其他服務根據重要程度可分別設定為BurstableBest-Effort,例如filebeat。
已知問題

不支援swap,當前的QoS策略假設swap已被禁止。

Q&A

Q:這個東西的本質,是不是類似把kubectl的那一套指令做了封裝呢,使操作簡化?

A:不是的,Helm的定位是Kubernetes應用的包管理工具,是對Kubernetes的補充而不是代替。Helm對Release提供了非常強大的版本管理、配置以及Hook等功能,這些都是原生Kubernetes不具備的。

Q:Helm是一個cli client對吧?tiller有沒有API可以呼叫?

A:是的。tiller目前暫時不提供API呼叫,以Pod形式安裝的Tiller service,採用的是clusterIP,然後helm client使用kubectl proxy連線。

Q:請問生產環境負載均衡和服務發現有什麼好的方案?

A:對於生產環境負載均衡,可以採用HAProxy/Nginx等負載均衡器代替kube-proxy以求更好的轉發效能。
對於Kubernetes服務發現:有2種方式,第一種是環境變數,第二種Kubernetes DNS。推薦用Kubernetes DNS,因為環境變數方式對Pod啟動順序有非常強的依賴,即先啟動的Pod看不到在其之後啟動Pod服務的環境注入資訊。

Q:Kubernetes的三種健康檢查型別exec,tcp,http能在一個容器中同時使用嗎?它們分別的應用場景是什麼?

A:可以的,Kubernetes並沒有對此限制。但一般情況下,一個容器服務不會同時提供TCP和HTTP服務。
ExecAction:Container內部執行某個具體的命令,適用於非tcp或者http服務。
TCPSocketActionp:適用於TCP服務的健康檢查,但TCP探測有個限制是只要TPC埠是開啟狀態,即使服務存在故障,健康檢查檢查也會通過
HTTPGetAction:適用於HTTP服務的健康檢查,但使用前提是服務本身需要提供健康檢查路徑。

Q:使用Helm是否可以不用kubectl了?另外是否支援Windows,支援的話如何配置呢?

A:不是的。Helm是Kubernetes應用的包管理工具,對Kubernetes來說,Helm是對其Release版本管理、配置等功能的補充。
支援Windows,安裝可參考:https://github.com/kubernetes/helm/releases

以上內容根據2017年09月19日晚DockOne社群微信群分享內容整理。 分享人張夏,FreeWheel 主任工程師。研究生畢業於中國科學技術大學,近10年IT領域工作經驗,曾先後供職於IBM與新浪微博等公司。目前主要負責公司基於Kubernetes容器雲平臺建設,致力於Kubernetes容器雲產品化與平臺化。