1. 程式人生 > >用Helm3構建多層微服務

用Helm3構建多層微服務

Helm是一款非常流行的k8s包管理工具。以前就一直想用它,但看到它產生的檔案比k8s要複雜許多,就一直猶豫,不知道它的好處能不能抵消掉它的複雜度。但如果不用,而是用Kubectl來進行調式真的很麻煩。正好最近Helm3正式版出來了,比原來的Helm2簡單了不少,就決定還是試用一下。結果證明確實很複雜,它的好處和壞處大致相當。有了它確實能大大簡化對k8s的調式,但也需要花費比較多的時間來學習,而且產生的配置檔案要複雜許多。但是事實是現在沒有什麼很方便的幫助調式k8s的工具,在沒有更好的方案之前,我還是建議用它,只是前期需要花些功夫學習和掌握它。

Helm3和Helm2的語法差不太多,只是使用起來更方便,不用安裝Tiller。一個比較明顯的變化是不再需要“requirements.yaml”, 依賴關係是直接在“chart.yaml”中定義。有關Helm3和Helm2的區別,詳情請參見CHANGES SINCE HELM 2。

網上有不少講述Helm的文章,但大部分都是主要講解安裝和舉一個簡單的例子。但Helm使用起來還是比較複雜的,一定要有一個複雜的例子才能把它的功能講清楚,裡面有不少設計方面的問題需要思考。我剛開始接觸的時候就覺得頭緒繁多,不知從哪下手。本文就通過一個相對複雜的例子來講解用Helm3來設計配置檔案的思路,使上手更容易。

這裡不講Helm3的安裝,它比較很容易。也不講解Helm的基本語法,你可以自己去看其他文件。即使你不懂Helm,應該也能猜出七八成,剩下的就要讀文件了(Charts)。Helm的語法還是比較複雜的,要想搞懂可能要花一兩天時間。

本文假設你對helm有一個大概的瞭解,想要構建一個複雜的微服務,但有不知如何下手;或者你想了解一下構建Helm的最佳實踐,那就請你繼續讀下去。

Helm檔案結構

chart裡一個很重要的概念就是模板(template),它就是Go語言模板,它是裡面加入了程式設計邏輯的k8s檔案。這些模板檔案在使用時都要先進行模板解析,把其中的程式邏輯轉化成對應的編碼,最終生成k8s配置檔案。

以上就是Helm自動生成的chart目錄結構,在Helm裡每個專案叫一個chart,它由下面幾個組成部分:

  • "Chart.yaml":存有這個chart的基本資訊,
  • "values.yaml":定義模板中要用到的常量。
  • “template”目錄:裡面存有全部的模板檔案,其中最重要的是“deployment.yaml”和“service.yaml”,分別是部署和服務檔案. "helpers.tpl"用來定義變數,"ingress.yaml"和"serviceaccount.yaml"分別是對外介面和服務賬戶,這裡暫時沒用, “NOTES.txt”是註釋檔案。
  • “charts”目錄: 存有這個chart依賴的所有子chart。

Helm的基本元素

Helm有四個基本元素,值,常量,變數和共享常量(這個後面會講)

值(literal)

Helm在k8s的基礎之上增加了模板功能,使k8s的配置檔案更加靈活。裡面的主要概念就是模板(Template),也就是在k8s的配置檔案裡增加了常量和變數以及程式設計邏輯。如果你不用這些新增功能,那麼就是普通的YAML檔案(k8s配置檔案),裡面用到的基本元素就是值。

常量

節點定位(Node Anchor):

如果你想複用重複的值,能把它定義成常量嗎?YAML有一個功能叫節點定位(Node Anchor),類似於定義一個常量,然後引用。但它有一些限制,定義的必須是一個節點,因此不如真正的常量靈活。
例如如下檔案中,用“&”定義了一個常量“&k8sdemoDatabaseService”,然後用“*k8sdemoDatabaseService”引用它。

global:
  k8sdemoDatabaseService: &k8sdemoDatabaseService k8sdemo-database-service
  mysqlHost: *k8sdemoDatabaseService

這時,“k8sdemoDatabaseService:”是YAML檔案節點的鍵名,“&k8sdemoDatabaseService”是節點定位的名字,相當於常量名,“k8sdemo-database-service”是YAML節點的鍵值。在上述程式碼中,k8sdemoDatabaseService和mysqlHost的值都是“k8sdemo-database-service”。

有關節點定位(Node Anchor)的詳細內容,請參見 YAML。

常量:

由於節點定位的侷限性,Helm引入了真正的常量,也就是在"values.yaml"裡定義的內容,它可以定義是任何東西,不只限於節點。

在"values.yaml"裡定義常量:

replicaCount: 1

在部署模板裡引用:

replicas: {{ .Values.replicaCount }}

那麼什麼時候用常量,什麼時候用值(Literal)呢?如果一個值在模板中出現多次,就要定義常量,避免重複。例如“accessModes”,既要在儲存卷裡出現,又要在儲存卷申請裡出現。另外如果值有可能變化(不論是隨部署環境變化,還是隨時間變化),那麼就定義成常量,這樣在修改時就只用改"values.yaml",而不必修改模板檔案。例如“replicas”的值(也就是叢集的個數)是可能變化的,就要定義成常量。在模板裡可以引用常量的,但在"values.yaml"裡不行,因為它只是普通YAML檔案,沒有模板解析功能,因此不支援常量,這裡就只能用節點定位(來代替常量)。

有關Helm常量的詳細內容,請參見 Use placeholders in yaml和Use YAML with variables。

變數

節點定位的功能是有限的,例如你想利用已有的節點定位,對它進行轉換,定義一個新的節點定位,這在"values.yaml"裡就不行了。
例如你已有節點定位“name”,你想在這個基礎上定義一個新的節點定位“serviceName”,這個"values.yaml"就不支援了,你必須要用模板。

如下所示,這在"values.yaml"裡是不支援的。

name: &name k8sdemo-backend
serviceName:*name-service

這就引出了變數的概念,但它只能在模板裡才行。 換句話說,模板既支援常量,也支援變數。但如果把變數的定義邏輯放在Helm每個模板裡,就顯得很亂。因此一般的做法是把這些邏輯放在一個單獨的模板檔案裡,這個就是前面講到的"_helpers.tpl"檔案。當你需要對常量進行轉換,生成新的常量,你就在定義變數,這部分程式碼就放在"_helpers.tpl"裡。

下面就是"_helpers.tpl"中定義"k8sdemo.name"的程式碼。

{{- define "k8sdemo.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

在以上這些元素中,常量(也就是在"values.yaml"中定義的)是最靈活的,能用它時儘量用它。而且因為它是定義在普通YAML檔案中("values.yaml"),應用程式可以直接訪問它,這樣可以實現應用程式和k8s之間的資料共享。但如果你需要對常量進行程式設計轉換,那就沒辦法了,只能定義變數,把它放在"_helpers.tpl"中。

ConfigMap和Secret

在k8s中ConfigMap和Secret是用來儲存共享配置引數和保密引數的,但在Helm中,由於有了上面講的Helm基本元素,它們完全可以代替ConfigMap的功能,因此ConfigMap就不需要了,但Secret還是需要的,因為要儲存加密資訊。下面會講解。

有關ConfigMap的設計侷限性,請參見把應用程式遷移到k8s需要修改什麼?

Chart設計

現在我們就用一個具體的例子來展示Helm的chart設計。這個例子是一個微服務應用程式,它共有三層: 前端,後端和資料庫,只有這樣才能讓Helm的一些設計問題付出水面,如果只有一層的話,就太簡單了,沒有參考價值。

在k8s中,每一層就是一個單獨的服務,它裡面有各種配置檔案。Helm的優勢是把這些不同的服務組成一個Chart來共同管理和調式,方便了許多。

上面就是最終的chart目錄結構圖。“chart”是總目錄,裡面有三個子目錄“k8sdemo”,“k8sdemo-backend”,“k8sdemo-database”, 每一個對應一個服務,每個服務都是一個獨立的chart,能單獨調式部署,chart之間也可以有依賴關係。其中“k8sdemo”是父chart,同時也是前端服務,它的“charts”目錄裡有它依賴的另外兩個服務。“k8sdemo-backend”是後端服務,“k8sdemo-database”是資料庫服務。

處理Chart的依賴關係有兩種方式:

  1. 嵌入式:就是直接把依賴的chart放在“charts”子目錄裡,這樣子chart是父chart的一部分。它是一種緊耦合的關係,好處是比較簡單,但不夠靈活。
  2. 依賴匯入式:就是各個chart是並列關係,各自單獨除錯部署,互相獨立,需要合併時再把子chart匯入父chart裡,它是一種鬆耦合的關係,好處是比較靈活,但設計更復雜。 在這種結構下,各個chart可以單獨工作也可以聯合工作,不過你需要更好的設計。

這裡採用的是依賴匯入式方式,主要原因是我原來認為嵌入式需要一起除錯,複雜度太高,如果你覺得這不是問題,這也是個不錯的辦法。用依賴匯入式方式,可以單獨除錯各個chart,簡單了很多。後來發現其實採用嵌入式也可以單獨除錯子chart,只是父chart不能單獨除錯而已。

除錯順序:

當你採用依賴匯入式方式時,除錯順序關係不大,因為各個chart是各自獨立的,可以單獨除錯。舉個例子,雖然“k8sdemo-backend”需要“k8sdemo-database”才能正常執行,但當沒有資料庫服務時,你的程式也可以執行,只不過輸出的是錯誤資訊,但這並不影響你除錯chart。

我先除錯“k8sdemo”,它雖然依賴另外兩個chart,但沒有它們也能單獨工作。然後再除錯“k8sdemo-backend”和“k8sdemo-database”,最後再把它們匯入到“k8sdemo”中去再進行聯調。

調式“k8sdemo”

它的除錯是最容易的,由於它裡面沒有真正的前端程式碼,只要把Nginx除錯成功了就可以了。只要在生成的檔案基礎上做些修改就行了。

鍵入如下命令建立chart,其中“k8sdemo”是chart的名字,這個名字很重要,服務的名字和label都是由它產生的。

helm create k8sdemo

這之後,系統會自動建立前面講到的chart目錄結構。讓後就是對已經生成的檔案進行修改。

修改"values.yaml":
以下是"values.yaml"主要修改的地方

image:
  repository: nginx:1.17.6
  pullPolicy: Never

imagePullSecrets: []
nameOverride: "k8sdemo"
fullnameOverride: "k8sdemo"

service:
  type: NodePort
  port: 80
  nodePort: 31080

另外,由於"ingress.yaml"和"serviceaccount.yaml"暫時沒用,就把它們都設成了“false”

ingress:
  enabled: false

serviceAccount:
  # Specifies whether a service account should be created
  create: false

修改"service.yaml":

apiVersion: v1
kind: Service
metadata:
  name: {{ include "k8sdemo.fullname" . }}
  labels:
    {{- include "k8sdemo.labels" . | nindent 4 }}
spec:
  type: {{.Values.service.type}}
  ports:
    - port: {{.Values.service.port}}
      nodePort: {{.Values.service.nodePort}}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    {{- include "k8sdemo.selectorLabels" . | nindent 4 }}

修改"deployment.yaml":

。。。
containers:
  - name: {{ .Chart.Name }}
    securityContext:
      {{- toYaml .Values.securityContext | nindent 12 }}
    image: {{ .Values.image.repository }}
    imagePullPolicy: {{ .Values.image.pullPolicy }}
。。。

以上都是簡單的修改,不涉及到設計問題。由於篇幅的關係,這裡沒有列出全部原始碼,如果有興趣請在本文末尾找到原始碼地址。

共享常量

在進行下面的除錯之前,先要講一個重要概念。 前面介紹Helm的基本元素時講的都是在一個chart裡共享值,如果要在不同chart之間共享值(例如k8s服務名,資料庫使用者名稱和埠),那麼這些還不夠,你需要共享常量. 通常情況下子chart和父chart之間的常量是不能共享的,如果需要共享,需要有一種特殊的方法來定義常量,這就是共享常量。它必須是定義在父chart中。

共享常量

例如,你在“k8sdemo”的“values.yaml”加入下面程式碼,注意節點的名字必須是子chart名(例如“k8sdemo-backend”)

k8sdemo-backend:
  replicaCount: 2
k8sdemo-database:
  replicaCount: 2

在“k8sdemo”的模板裡就可以通過“{{ .Values.k8sdemo-backend.replicaCount }}” 來訪問。當Helm發現節點名是子chart名時,它會自動拷貝這個常量到子chart的“values.yaml”中,因此,在“k8sdemo-backend”中,你也可以通過“{{ .Values.replicaCount }}” 來訪問這個常量。注意這裡並沒有包含子chart名(“k8sdemo-backend”),而是隻有常量名,因為子chart名只是一個標識,而不是常量名的一部分。

全域性常量

共享常量只能把常量共享給一個字chart,如果你需要多個子chart之間共享,就需要建立全域性常量,它用“global”來標識,下面是示例。

在“k8sdemo-backend”的"values.yaml"中定義:

global:
  k8sdemoDatabaseService: &k8sdemoDatabaseService k8sdemo-database-service
  mysqlUserName: dbuser
  mysqlUserPassword: dbuser
  mysqlPort: 3306
  mysqlHost: *k8sdemoDatabaseService
  mysqlDatabase: service_config

在“k8sdemo-backend”的“deployment.yaml”中引用。

env:
  - name: MYSQL_USER_NAME
    value: {{ .Values.global.mysqlUserName }}
  - name: MYSQL_USER_PASSWORD
    value: {{ .Values.global.mysqlUserPassword }}
  - name: MYSQL_HOST
    value: {{ .Values.global.mysqlHost }}
  - name: MYSQL_PORT
    value: "{{ .Values.global.mysqlPort }}"
  - name: MYSQL_DATABASE
    value: {{ .Values.global.mysqlDatabase }}

在“k8sdemo-database”的"values.yaml"中定義:

global:
  k8sdemoDatabaseService: k8sdemo-database-service
  mysqlUserName: dbuser
  mysqlUserPassword: dbuser
  mysqlRootPassword: root
  mysqlDatabase: service_config

在“k8sdemo-database”的“deployment.yaml”中引用。

env:
  - name: MYSQL_ROOT_PASSWORD
    value: {{ .Values.global.mysqlRootPassword }}
  - name: MYSQL_USER_NAME
    value: {{ .Values.global.mysqlUserName }}
  - name: MYSQL_USER_PASSWORD
    value: {{ .Values.global.mysqlUserPassword }}
  - name: MYSQL_DATABASE
    value: {{ .Values.global.mysqlDatabase }}

當把“k8sdemo-backend”和“k8sdemo-database”匯入"k8sdemo"後進行聯調時, 就要把上面提到的全域性常量寫入"k8sdemo"的"values.yaml"檔案中,這樣就能讓各個子chart共享這些常量。如下所示:

global:
  k8sdemoBackendService: k8sdemo-backend-service
  k8sdemoDatabaseService: &k8sdemoDatabaseService k8sdemo-database-service
  mysqlUserName: dbuser
  mysqlUserPassword: dbuser
  mysqlRootPassword: root
  mysqlPort: 3306
  mysqlHost: *k8sdemoDatabaseService
  mysqlDatabase: service_config

如果父chart和子chart有重複的全域性常量,這時父chart("k8sdemo")的全域性常量值就會覆蓋子chart的全域性常量。

它的使用原則就是如果只是子chart獨有的常量就在子chart的"values.yaml"中定義,如果是共享的常量就在父chart中定義。但如果採用的是依賴匯入方式,由於子chart也要單獨除錯,這時你在子chart裡也要定義這些全域性常量。這樣在進行chart總除錯時,就會使用父chart的中的值。

詳情請參見 Subcharts and Global Values。

除錯“k8sdemo-backend”

“k8sdemo-backend”的chart需要取(與“k8ssdemo”)不同的名字,
建立:

helm create k8sdemo-backend

上面就是“k8sdemo-backend”的目錄圖。由於它需要建持久卷,因此這裡增加了兩個檔案“persistentvolume.yaml”和“persistentvolumeclaim.yaml” ( 不是自動生成的)。

值得一提的是k8s物件的命名。一般情況下,如果不需要對其進行引用,用chart的全名就行了。例如部署的名稱,如下所示。

name: {{ include "k8sdemo.fullname" . }}

如果是服務名(Service Name),它需要在應用程式和k8s之間共享,也需要在父chart和子chart之間共享,這時最好單獨定義一個全域性共享常量。

在“values.yaml”中定義:

global:
  k8sdemoBackendService: k8sdemo-backend-service

在“service.yaml”中引用:

name: {{.Values.global.k8sdemoBackendService}}

除錯“k8sdemo-database”

它的除錯方式與“k8sdemo-backend”大同小異,就不詳細講解了。

聯合除錯:

上面各個chart都單獨除錯成功之後,就要把它們合在一起進行聯合除錯。
在“k8sdemo”(父chart)中加入依賴關係(Chart.yaml)。

dependencies:
  - name: k8sdemo-backend
    repository: file://../k8sdemo-backend
    version: 0.1.0
  - name: k8sdemo-database
    repository: file://../k8sdemo-database
    version: 0.1.0

這裡為了簡單起見,沒有用到chart庫(Chart Repository),使用了本地目錄。這裡的“file://”是針對chart的根的相對路徑,“file://..”就是“k8sdemo”的上級目錄。

詳情請參見How to refer to a helm chart in the same repository。

修改全域性常量("values.yaml"):

global:
  k8sdemoBackendService: k8sdemo-backend-service
  k8sdemoDatabaseService: &k8sdemoDatabaseService k8sdemo-database-service
  mysqlUserName: dbuser
  mysqlUserPassword: dbuser
  mysqlRootPassword: root
  mysqlPort: 3306
  mysqlHost: *k8sdemoDatabaseService
  mysqlDatabase: service_config

只有需要在chart之間共享的常量才需要在父chart裡的"values.yaml"定義,其餘的在各自子chart裡的"values.yaml"定義就可以了。

鍵入如下命令“helm dependency update k8sdemo”,更新依賴關係

~ # vagrant@ubuntu-xenial:~/jfeng45/k8sdemo/script/kubernetes/chart$ helm dependency update k8sdemo
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈Happy Helming!⎈
Saving 2 charts
Deleting outdated charts

完成之後,生成的圖如下所示,這時在“charts”目錄下就匯入了新的依賴關係“k8sdemo-backend”和“k8sdemo-database”的chart。

有一點需要注意的是,單獨除錯和聯合除錯時,生成的k8s配置檔案大部分都是一樣的,但有一個地方不同

下面是聯合除錯時“k8sdemo-database”的部署檔案,最後一行“app.kubernetes.io/instance: ”的值是“k8sdemo”。

# Source: k8sdemo/charts/k8sdemo-database/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: k8sdemo-database
  labels:
    helm.sh/chart: k8sdemo-database-0.1.0
    app.kubernetes.io/name: k8sdemo-database
    app.kubernetes.io/instance: k8sdemo
。。。

下面是單獨除錯時“k8sdemo-database”的部署檔案,最後一行“app.kubernetes.io/instance: ”的值是“”k8sdemo-database”。

# Source: k8sdemo/charts/k8sdemo-database/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: k8sdemo-database
  labels:
    helm.sh/chart: k8sdemo-database-0.1.0
    app.kubernetes.io/name: k8sdemo-database
    app.kubernetes.io/instance: k8sdemo-database
。。。

因為“instance”的名字是“{{ .Release.Name }}”,而單獨除錯和聯合除錯時給的“release”名字不同。而其他的值都是由配置檔案決定的,因此不會有意外。

安裝k8sdemo:

vagrant@ubuntu-xenial:~/jfeng45/k8sdemo/script/kubernetes/chart$ helm upgrade k8sdemo ./k8sdemo
Release "k8sdemo" has been upgraded. Happy Helming!
NAME: k8sdemo
LAST DEPLOYED: Fri Nov 29 01:28:55 2019
NAMESPACE: default
STATUS: deployed
REVISION: 2
NOTES:
1. Get the application URL by running these commands:
  export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services k8sdemo)
  export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT

獲取Pod名稱:

vagrant@ubuntu-xenial:~/jfeng45/k8sdemo/script/kubernetes/chart$ kubectl get pod
NAME                                          READY   STATUS    RESTARTS   AGE
k8sdemo-74cb7b997c-pgcj4                      1/1     Running   0          33s
k8sdemo-backend-5cd9d79856-dqlmz              1/1     Running   0          33s
k8sdemo-database-85855485c6-jtksb             1/1     Running   0          33s
k8sdemo-jenkins-deployment-675dd574cb-r57sb   1/1     Running   3          23d

執行程式進行測設:

vagrant@ubuntu-xenial:~/jfeng45/k8sdemo/script/kubernetes/chart$ kubectl exec -ti k8sdemo-backend-5cd9d79856-dqlmz -- /bin/sh
~ # ./main.exe
time="2019-11-27T07:03:03Z" level=debug msg="connect to database "
time="2019-11-27T07:03:03Z" level=debug msg="dataSourceName:dbuser:dbuser@tcp(k8sdemo-database-service:3306)/service_config?charset=utf8"
time="2019-11-27T07:03:03Z" level=debug msg="FindAll()"
time="2019-11-27T07:03:03Z" level=debug msg="created=2019-10-21"
time="2019-11-27T07:03:03Z" level=debug msg="find user:{1 Tony IT 2019-10-21}"
time="2019-11-27T07:03:03Z" level=debug msg="find user list:[{1 Tony IT 2019-10-21}]"
time="2019-11-27T07:03:03Z" level=debug msg="user lst:[{1 Tony IT 2019-10-21}]"
~ #

其他問題:

由於篇幅有限,本文不可能把所有的問題都講清楚,還有兩個比較重要的問題,這裡簡單的提一下。

1.Secret:
本文用的都是明碼,如果需要加密的話有兩種方式,一種是 helm-secrets,另一種是Vault,請閱讀相關文件。

2.為不同環境設定不同的常量:
本文只建立了針對一種環境的檔案 ,如果你需要針對不同環境(例如DEV,QA,PROD)配置不同的引數的話,你可以在“k8sdemo”的chart裡給不同的環境建立不同的"values.yaml",例如“values-dev.yaml”給DEV環境。但在子chart裡,就不能這樣做,因為系統要求"values.yaml"。這時,你可以在父chart的“values-dev.yaml”裡為不同的子chart建立常量,這樣這些常量就能覆蓋子chart裡定義的常量。

在“values-dev.yaml”加入下面程式碼。

k8sdemo-backend:
    replicaCount: 2
k8sdemo-database:
    replicaCount: 2

鍵入如下命令試執行:

vagrant@ubuntu-xenial:~$ cd /home/vagrant/jfeng45/k8sdemo/script/kubernetes/chart
vagrant@ubuntu-xenial:~/jfeng45/k8sdemo/script/kubernetes/chart$ helm install --dry-run --values ./k8sdemo/values-dev.yaml --debug k8sdemo ./k8sdemo

檢視結果,子chart中的相應引數已被覆蓋。

詳情請參閱How to set environment related values.yaml in Helm subcharts?

常見錯誤:

在除錯過程中還是遇到了不少問題,但大多數都是與語法有關的問題,因為Helm和k8s都用的是YAML檔案,而它對檔案格式有著嚴格的要求,如果不滿足要求就會報錯。幸好它報錯時包含了錯誤程式碼行號,這樣查詢起來比較容易。

  1. Pod的狀態是CrashLoopBackOff

它的症狀是在用“helm install --dry-run --debug”除錯時沒有問題,但正式執行時出了問題,用下面命令檢查,Pod的狀態是“CrashLoopBackOff”。

vagrant@ubuntu-xenial:~$ kubectl get pod
NAME                                           READY   STATUS             RESTARTS   AGE
k8sdemo-74cb7b997c-gn5v2                       1/1     Running            1          47h
k8sdemo-backend-6cdbb96964-tb2xd               0/1     CrashLoopBackOff   129        9h
k8sdemo-database-deployment-578fc88c88-mm6x8   1/1     Running            12         37d
k8sdemo-jenkins-deployment-675dd574cb-r57sb    1/1     Running            3          19d

這個問題我以前除錯k8s時也碰到過,主要是與Docker映象有關,但這次明明映象是 好的。試了很多組合,最後終於發現是自動生成的程式碼出了問題。
在“deployment.yaml”裡有下面程式碼,這是Helm自動生成用來測試部署的。

livenessProbe:
  httpGet:
    path: /
    port: http
readinessProbe:
  httpGet:
    path: /
    port: http

把它去掉之後就沒有問題了。而且它只在特定的chart(“k8sedemo-backend”)裡會出錯,在“k8sdemo”裡就沒有問題。我現在也不是特別清楚問題在哪,只是把它暫時刪除掉了。

  1. 持久卷未能繫結到持久卷申請

它的症狀是宿主機的持久卷未能繫結到持久卷申請,導致持久卷申請又另外建立了一個持久卷。你用“kubectl get pv”就能看到新建立的持久卷,但實際上它是不必要的,只要把持久卷申請繫結到已有的PV上就行了。這個錯誤並不是每次都發生,而是隨機的。大部分時間繫結正確,少數時候繫結錯誤。我開始想是不是因為執行k8s檔案的順序問題,但k8s檔案是按照檔案類別(kind)來執行的,按理來說順序應該是正確的。再有一個可能就是時間延遲,因為建立持久卷需要時間,而如果持久卷申請沒有檢測到這個持久卷,那麼它就會另外建立一個。如果真是這樣的話,就要在建立時設定一個延遲。但它暫時來講對我影響不大,因此就偷了一下懶,以後有時間再來除錯。

原始碼庫

完整原始碼的github連結:
k8sdemo

索引:

  1. CHANGES SINCE HELM 2
  2. Charts
  3. YAML
  4. Use placeholders in yaml
  5. Use YAML with variables
  6. 把應用程式遷移到k8s需要修改什麼?
  7. Subcharts and Global Values
  8. How to refer to a helm chart in the same repository
  9. helm-secrets
  10. Vault
  11. How to set environment related values.yaml in Helm subcharts?