新聞中心
一篇帶給你Helm Charts開發(fā)完整示例
作者:陽明 2022-03-18 07:48:58
云計算
云原生
開源 Ghost 是基于 Node.js 的開源博客平臺。在開發(fā) Helm Chart 包之前我們最需要做的的就是要知道我們自己的應用應該如何使用、如何部署,不然是不可能編寫出對應的 Chart 包的。

專注于為中小企業(yè)提供成都網(wǎng)站建設、網(wǎng)站建設服務,電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)科爾沁右翼中免費做網(wǎng)站提供優(yōu)質的服務。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了千余家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設實現(xiàn)規(guī)模擴充和轉變。
Helm 的使用是比較簡單的,但是要讓我們自己開發(fā)一個 Chart 包還是有不小難度的,主要還是 go template 的
語法規(guī)則不夠人性化,這里我們用一個完整的實例來演示下如何開發(fā)一個 Helm Chart 包。
應用
我們這里以 Ghost 博客應用為例來演示如何開發(fā)一個完整的 Helm Chart 包,Ghost 是基于 Node.js 的開源博客平臺。在開發(fā) Helm Chart 包之前我們最需要做的的就是要知道我們自己的應用應該如何使用、如何部署,不然是不可能編寫出對應的 Chart 包的。
啟動 Ghost 最簡單的方式是直接使用鏡像啟動:
docker run -d --name my-ghost -p 2368:2368 ghost
然后我們就可以通過 http://localhost:2368 訪問 Ghost 博客了。如果我們想要在 Kubernetes 集群中部署兩個副本的 Ghost,可以直接應用下面的資源清單文件即可:
# ghost/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ghost
spec:
selector:
matchLabels:
app: ghost-app
replicas: 2
template:
metadata:
labels:
app: ghost-app
spec:
containers:
- name: ghost-app
image: ghost
ports:
- containerPort: 2368
---
# ghost/service.yaml
apiVersion: v1
kind: Service
metadata:
name: ghost
spec:
type: NodePort
selector:
app: ghost-app
ports:
- protocol: TCP
port: 80
targetPort: 2368
直接通過 kubectl 應用上面的資源對象即可:
kubectl apply -f ghost/deployment.yaml ghost/service.yaml
deployment.apps/ghost created
service/ghost created
kubectl get pod -l app=ghost-app
NAME READY STATUS RESTARTS AGE
ghost-dfd958cc9-4s9b9 1/1 Running 0 2m54s
ghost-dfd958cc9-84kmv 1/1 Running 0 2m54s
kubectl get svc ghost
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ghost NodePort 10.97.227.16080:31950/TCP 3m33s
這樣我們就可以通過 http://:31950 訪問到 Ghost 了:
ghost
看上去要部署 Ghost 是非常簡單的,但是如果我們需要針對不同的環(huán)境進行不同的設置呢?比如我們想將它部署到不同環(huán)境(staging、prod)中去,是不是我們需要一遍又一遍地復制我們的 Kubernetes 資源清單文件,這還只是一個場景,還有很多場景可能需要我們去部署應用,這種方式維護起來是非常困難的,這個時候就可以理由 Helm 來解放我們了。
基礎模板
現(xiàn)在我們開始創(chuàng)建一個新的 Helm Chart 包。直接使用 helm create 命令即可:
helm create my-ghost
Creating my-ghost
tree my-ghost
my-ghost
├── Chart.yaml
├── charts
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── hpa.yaml
│ ├── ingress.yaml
│ ├── service.yaml
│ ├── serviceaccount.yaml
│ └── tests
│ └── test-connection.yaml
└── values.yaml
3 directories, 10 files
該命令會創(chuàng)建一個默認 Helm Chart 包的腳手架,可以刪掉下面的這些使用不到的文件:
templates/tests/test-connection.yaml
templates/serviceaccount.yaml
templates/ingress.yaml
templates/hpa.yaml
templates/NOTES.txt
然后修改 templates/deployment.yaml 模板文件:
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ghost
spec:
selector:
matchLabels:
app: ghost-app
replicas: {{ .Values.replicaCount }}
template:
metadata:
labels:
app: ghost-app
spec:
containers:
- name: ghost-app
image: {{ .Values.image }}
ports:
- containerPort: 2368
env:
- name: NODE_ENV
value: {{ .Values.node_env | default "production" }}
{{- if .Values.url }}
- name: url
value: http://{{ .Values.url }}
{{- end }}
這和我們前面的資源清單文件非常類似,只是將 replicas 的值使用 ` `.`Values`.`replicaCount ` 模板來進行替換了,表示會用 replicaCount 這個 Values 值進行渲染,然后還可以通過設置環(huán)境變量來配置 Ghost,同樣修改 templates/service.yaml 模板文件的內容:
# templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: ghost
spec:
selector:
app: ghost-app
type: {{ .Values.service.type }}
ports:
- protocol: TCP
targetPort: 2368
port: {{ .Values.service.port }}
{{- if (and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePort))) }}
nodePort: {{ .Values.service.nodePort }}
{{- else if eq .Values.service.type "ClusterIP" }}
nodePort: null
{{- end }}
同樣為了能夠兼容多個場景,這里我們允許用戶來定制 Service 的 type,如果是 NodePort 類型則還可以配置 nodePort 的值,不過需要注意這里的判斷,因為有可能即使配置為 NodePort 類型,用戶也可能不會主動提供 nodePort,所以這里我們在模板中做了一個條件判斷:
{{- if (and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePort))) }}
需要 service.type 為 NodePort 或者 LoadBalancer 并且 service.nodePort 不為空的情況下才會渲染 nodePort。
然后最重要的就是要在 values.yaml 文件中提供默認的 Values 值,如下所示是我們提供的默認的 Values 值:
# values.yaml
replicaCount: 1
image: ghost
node_env: production
url: ghost.k8s.local
service:
type: NodePort
port: 80
然后我們可以使用 helm template 命令來渲染我們的模板輸出結果:
helm template --debug my-ghost
install.go:178: [debug] Original chart version: ""
install.go:195: [debug] CHART PATH: /Users/ych/devs/workspace/yidianzhishi/course/k8strain3/content/helm/manifests/my-ghost
---
# Source: my-ghost/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: ghost
spec:
selector:
app: ghost-app
type: NodePort
ports:
- protocol: TCP
targetPort: 2368
port: 80
---
# Source: my-ghost/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ghost
spec:
selector:
matchLabels:
app: ghost-app
replicas: 1
template:
metadata:
labels:
app: ghost-app
spec:
containers:
- name: ghost-app
image: ghost
ports:
- containerPort: 2368
env:
- name: NODE_ENV
value: production
- name: url
value: http://ghost.k8s.local
上面的渲染結果和我們上面的資源清單文件基本上一致了,只是我們現(xiàn)在的靈活性更大了,比如可以控制環(huán)境變量、服務的暴露方式等等。
命名模板
雖然現(xiàn)在我們可以使用 Helm Charts 模板來渲染安裝 Ghost 了,但是上面我們的模板還有很多改進的地方,比如資源對象的名稱我們是固定的,這樣我們就沒辦法在同一個命名空間下面安裝多個應用了,所以一般我們也會根據(jù) Chart 名稱或者 Release 名稱來替換資源對象的名稱。
前面默認創(chuàng)建的模板中包含一個 _helpers.tpl 的文件,該文件中包含一些和名稱、標簽相關的命名模板,我們可以直接使用即可,下面是默認生成的已有的命名模板:
{{/*
Expand the name of the chart.
*/}}
{{- define "my-ghost.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "my-ghost.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "my-ghost.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "my-ghost.labels" -}}
helm.sh/chart: {{ include "my-ghost.chart" . }}
{{ include "my-ghost.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "my-ghost.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-ghost.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
然后我們可以將 Deployment 的名稱和標簽替換掉:
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "my-ghost.fullname" . }}
labels:
{{ include "my-ghost.labels" . | indent 4 }}
spec:
selector:
matchLabels:
{{ include "my-ghost.selectorLabels" . | indent 6 }}
replicas: {{ .Values.replicaCount }}
template:
metadata:
labels:
{{ include "my-ghost.selectorLabels" . | indent 8 }}
spec:
# other spec...
為 Deployment 增加 label 標簽,同樣 labelSelector 中也使用 my-ghost.selectorLabels 這個命名模板進行替換,同樣對 Service 也做相應的改造:
apiVersion: v1
kind: Service
metadata:
name: {{ template "my-ghost.fullname" . }}
labels:
{{ include "my-ghost.labels" . | indent 4 }}
spec:
selector:
{{ include "my-ghost.selectorLabels" . | indent 4 }}
type: {{ .Values.service.type }}
# other spec...
現(xiàn)在我們可以再使用 helm template 渲染驗證結果是否正確:
helm template --debug my-ghost
install.go:178: [debug] Original chart version: ""
install.go:195: [debug] CHART PATH: /Users/ych/devs/workspace/yidianzhishi/course/k8strain3/content/helm/manifests/my-ghost
---
# Source: my-ghost/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: release-name-my-ghost
labels:
helm.sh/chart: my-ghost-0.1.0
app.kubernetes.io/name: my-ghost
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
selector:
app.kubernetes.io/name: my-ghost
app.kubernetes.io/instance: release-name
type: NodePort
ports:
- protocol: TCP
targetPort: 2368
port: 80
---
# Source: my-ghost/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: release-name-my-ghost
labels:
helm.sh/chart: my-ghost-0.1.0
app.kubernetes.io/name: my-ghost
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
selector:
matchLabels:
app.kubernetes.io/name: my-ghost
app.kubernetes.io/instance: release-name
replicas: 1
template:
metadata:
labels:
app.kubernetes.io/name: my-ghost
app.kubernetes.io/instance: release-name
spec:
containers:
- name: ghost-app
image: ghost
ports:
- containerPort: 2368
env:
- name: NODE_ENV
value: production
- name: url
value: http://ghost.k8s.local
版本兼容
由于 Kubernetes 的版本迭代非常快,所以我們在開發(fā) Chart 包的時候有必要考慮到對不同版本的 Kubernetes 進行兼容,最明顯的就是 Ingress 的資源版本。Kubernetes 在 1.19 版本為 Ingress 資源引入了一個新的 API:networking.k8s.io/v1,這與之前的 networking.k8s.io/v1beta1 beta 版本使用方式基本一致,但是和前面的 extensions/v1beta1 這個版本在使用上有很大的不同,資源對象的屬性上有一定的區(qū)別,所以要兼容不同的版本,我們就需要對模板中的 Ingress 對象做兼容處理。
新版本的資源對象格式如下所示:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
service:
name: test
port:
number: 80
而舊版本的資源對象格式如下:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
backend:
serviceName: test
servicePort: 80
現(xiàn)在我們再為 Ghost 添加一個 Ingress 的模板,新建 templates/ingress.yaml 模板文件,先添加一個 v1 版本的 Ingress 模板:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ghost
spec:
ingressClassName: nginx
rules:
- host: ghost.k8s.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ghost
port:
number: 80
然后同樣將名稱和服務名稱這些使用模板參數(shù)進行替換:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ template "my-ghost.fullname" . }}
labels:
{{ include "my-ghost.labels" . | indent 4 }}
spec:
ingressClassName: nginx
rules:
- host: {{ .Values.url }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ template "my-ghost.fullname" . }}
port:
number: {{ .Values.service.port }}
然后接下來我們來兼容下其他的版本格式,這里需要用到 Capabilities 對象,在 Chart 包的 _helpers.tpl 文件中添加幾個用于判斷集群版本或 API 的命名模板:
{{/* Allow KubeVersion to be overridden. */}}
{{- define "my-ghost.kubeVersion" -}}
{{- default .Capabilities.KubeVersion.Version .Values.kubeVersionOverride -}}
{{- end -
{{/* Get Ingress API Version */}}
{{- define "my-ghost.ingress.apiVersion" -}}
{{- if and (.Capabilities.APIVersions.Has "networking.k8s.io/v1") (semverCompare ">= 1.19-0" (include "my-ghost.kubeVersion" .)) -}}
{{- print "networking.k8s.io/v1" -}}
{{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" -}}
{{- print "networking.k8s.io/v1beta1" -}}
{{- else -}}
{{- print "extensions/v1beta1" -}}
{{- end -}}
{{- end -}}
{{/* Check Ingress stability */}}
{{- define "my-ghost.ingress.isStable" -}}
{{- eq (include "my-ghost.ingress.apiVersion" .) "networking.k8s.io/v1" -}}
{{- end -}}
{{/* Check Ingress supports pathType */}}
{{/* pathType was added to networking.k8s.io/v1beta1 in Kubernetes 1.18 */}}
{{- define "my-ghost.ingress.supportsPathType" -}}
{{- or (eq (include "my-ghost.ingress.isStable" .) "true") (and (eq (include "my-ghost.ingress.apiVersion" .) "networking.k8s.io/v1beta1") (semverCompare ">= 1.18-0" (include "my-ghost.kubeVersion" .))) -}}
{{- end -}}
上面我們通過 .Capabilities.APIVersions.Has 來判斷我們應該使用的 APIVersion,如果版本為 networking.k8s.io/v1,則定義為 isStable,此外還根據(jù)版本來判斷是否需要支持 pathType 屬性,然后在 Ingress 對象模板中就可以使用上面定義的命名模板來決定應該使用哪些屬性,如下所示:
{{- if .Values.ingress.enabled }}
{{- $apiIsStable := eq (include "my-ghost.ingress.isStable" .) "true" -}}
{{- $ingressSupportsPathType := eq (include "my-ghost.ingress.supportsPathType" .) "true" -}}
apiVersion: {{ include "my-ghost.ingress.apiVersion" . }}
kind: Ingress
metadata:
name: {{ template "my-ghost.fullname" . }}
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "false"
{{- if and .Values.ingress.ingressClass (not $apiIsStable) }}
kubernetes.io/ingress.class: {{ .Values.ingress.ingressClass }}
{{- end }}
labels:
{{- include "my-ghost.labels" . | nindent 4 }}
spec:
{{- if and .Values.ingress.ingressClass $apiIsStable }}
ingressClassName: {{ .Values.ingress.ingressClass }}
{{- end }}
rules:
{{- if not (empty .Values.url) }}
- host: {{ .Values.url }}
http:
{{- else }}
- http:
{{- end }}
paths:
- path: /
{{- if $ingressSupportsPathType }}
pathType: Prefix
{{- end }}
backend:
{{- if $apiIsStable }}
service:
name: {{ template "my-ghost.fullname" . }}
port:
number: {{ .Values.service.port }}
{{- else }}
serviceName: {{ template "my-ghost.fullname" . }}
servicePort: {{ .Values.service.port }}
{{- end }}
{{- end }}
由于有的場景下面并不需要使用 Ingress 來暴露服務,所以首先我們通過一個 ingress.enabled 屬性來控制是否需要渲染,然后定義了一個 $apiIsStable 變量,來表示當前集群是否是穩(wěn)定版本的 API,然后需要根據(jù)該變量去渲染不同的屬性,比如對于 ingressClass,如果是穩(wěn)定版本的 API 則是通過 spec.ingressClassName 來指定,否則是通過 kubernetes.io/ingress.class 這個 annotations 來指定。然后這里我們在 values.yaml 文件中添加如下所示默認的 Ingress 的配置數(shù)據(jù):
ingress:
enabled: true
ingressClass: nginx
現(xiàn)在我們再次渲染 Helm Chart 模板來驗證資源清單數(shù)據(jù):
helm template --debug my-ghost
install.go:178: [debug] Original chart version: ""
install.go:195: [debug] CHART PATH: /Users/ych/devs/workspace/yidianzhishi/course/k8strain3/content/helm/manifests/my-ghost
---
# Source: my-ghost/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: release-name-my-ghost
labels:
helm.sh/chart: my-ghost-0.1.0
app.kubernetes.io/name: my-ghost
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
selector:
app.kubernetes.io/name: my-ghost
app.kubernetes.io/instance: release-name
type: NodePort
ports:
- protocol: TCP
targetPort: 2368
port: 80
---
# Source: my-ghost/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: release-name-my-ghost
labels:
helm.sh/chart: my-ghost-0.1.0
app.kubernetes.io/name: my-ghost
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
selector:
matchLabels:
app.kubernetes.io/name: my-ghost
app.kubernetes.io/instance: release-name
replicas: 1
template:
metadata:
labels:
app.kubernetes.io/name: my-ghost
app.kubernetes.io/instance: release-name
spec:
containers:
- name: ghost-app
image: ghost
ports:
- containerPort: 2368
env:
- name: NODE_ENV
value: production
- name: url
value: http://ghost.k8s.local
---
# Source: my-ghost/templates/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: release-name-my-ghost
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "false"
labels:
helm.sh/chart: my-ghost-0.1.0
app.kubernetes.io/name: my-ghost
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
ingressClassName: nginx
rules:
- host: ghost.k8s.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: release-name-my-ghost
port:
number: 80
從上面的資源清單可以看出是符合我們的預期要求的,我們可以來安裝測試下結果:
helm upgrade --install my-ghost ./my-ghost -n default
Release "my-ghost" does not exist. Installing it now.
NAME: my-ghost
LAST DEPLOYED: Thu Mar 17 13:11:15 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
helm ls -n default
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
my-ghost default 1 2022-03-17 13:11:15.79828 +0800 CST deployed my-ghost-0.1.0 1.16.0
kubectl get pods -n default 分享文章:一篇帶給你HelmCharts開發(fā)完整示例
標題鏈接:http://www.dlmjj.cn/article/ccsjcpo.html


咨詢
建站咨詢
