## 1. 前提條件
1. 一個 Kubernetes 集群
2. 決定將哪些安全配置應用于您的安裝(如果有)
3. 安裝和配置 Helm 和 Tiller,集群端服務。
`Helm` 將通過讀取您的 `Kubernetes` 配置文件(通常是`$HOME/.kube/config`)來找出安裝 `Tiller` 的位置。這與`kubectl`使用的文件相同。
要找出 Tiller 將安裝到哪個集群,您可以運行 `kubectl config current-context`或`kubectl cluster-info`。
```bash
$ kubectl config current-context
my-cluster
```
## 2. 功能
- 創建新的 chart
- chart 打包成 tgz 格式
- 上傳 chart 到 chart 倉庫或從倉庫中下載 chart
- 在Kubernetes集群中安裝或卸載 chart
- 管理用Helm安裝的 chart 的發布周期
## 3. 概念
Helm 有三個重要概念:
- `chart`:包含了創建Kubernetes的一個應用實例的必要信息
- `config`:包含了應用發布配置信息
- `release`:是一個 chart 及其配置的一個運行實例
## 4. Helm組件
Helm 有以下兩個組成部分:

`Helm Client` 是用戶命令行工具,其主要負責如下:
- 本地 `chart` 開發
- 倉庫管理
- List item
- 與 `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
## 5. 安裝
- [官方安裝](https://v2.helm.sh/docs/using_helm/#installing-helm)
### 5.1 安裝客戶端
我們可以在[Helm Realese](https://github.com/helm/helm/releases)頁面下載二進制文件,這里下載的`v2.10.0`版本,解壓后將可執行文件helm拷貝到`/usr/local/bin`目錄下即可,這樣Helm客戶端就在這臺機器上安裝完成了。
現在我們可以使用Helm命令查看版本了,會提示無法連接到服務端Tiller:
```bash
$ helm version
version.BuildInfo{Version:"v2.10.0", GitCommit:"d506314abfb5d21419df8c7e7e68012379db2354", GitTreeState:"clean", GoVersion:"go1.16.5"}
```
要安裝 `Helm` 的服務端程序,我們需要使用到kubectl工具,所以先確保kubectl工具能夠正常的訪問 kubernetes 集群的apiserver
### 5.2 安裝服務端
Helm 的服務器部分 `Tiller` 通常在您的 Kubernetes 集群內部運行。但對于開發,它也可以在本地運行,并配置為與遠程 Kubernetes 集群通信。
安裝`tiller`到集群中的最簡單方法就是運行 `helm init`. 這將驗證helm的本地環境是否正確設置(并在必要時進行設置)。然后它將連接到kubectl默認情況下連接到的任何集群( `kubectl config view`)。連接后,它將安裝tiller到 `kube-system`命名空間中。
```bash
$ helm init
```
由于 Helm 默認會去`gcr.io`拉取鏡像,所以如果你當前執行的機器沒有配置科學上網的話可以實現下面的命令代替:
```bash
$ helm init --upgrade --tiller-image cnych/tiller:v2.10.0
$HELM_HOME has been configured at /root/.helm.
Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.
Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
To prevent this, run `helm init` with the --tiller-tls-verify flag.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!
```
如果一直卡住或者報 google api 之類的錯誤,可以使用下面的命令進行初始化:
```bash
$ helm init --upgrade --tiller-image cnych/tiller:v2.10.0 --stable-repo-url https://cnych.github.io/kube-charts-mirror/
```
這個命令會把默認的 google 的倉庫地址替換成我同步的一個鏡像地址。
如果在安裝過程中遇到了一些其他問題,比如初始化的時候出現了如下錯誤:
```bash
E0125 14:03:19.093131 56246 portforward.go:331] an error occurred forwarding 55943 -> 44134: error forwarding port 44134 to pod d01941068c9dfea1c9e46127578994d1cf8bc34c971ff109dc6faa4c05043a6e, uid : unable to do port forwarding: socat not found.
2018/01/25 14:03:19 (0xc420476210) (0xc4203ae1e0) Stream removed, broadcasting: 3
2018/01/25 14:03:19 (0xc4203ae1e0) (3) Writing data frame
2018/01/25 14:03:19 (0xc420476210) (0xc4200c3900) Create stream
2018/01/25 14:03:19 (0xc420476210) (0xc4200c3900) Stream added, broadcasting: 5
Error: cannot connect to Tiller
```
解決方案:在節點上安裝`socat`可以解決
```bash
$ sudo yum install -y socat
```
Helm 服務端正常安裝完成后,Tiller默認被部署在kubernetes集群的kube-system命名空間下:
```bash
$ kubectl get pod -n kube-system -l app=helm
NAME READY STATUS RESTARTS AGE
tiller-deploy-86b844d8c6-44fpq 1/1 Running 0 7m
```
此時,我們查看 Helm 版本就都正常了:
```bash
$ helm version
Client: &version.Version{SemVer:"v2.10.0", GitCommit:"9ad53aac42165a5fadc6c87be0dea6b115f93090", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.10.0", GitCommit:"9ad53aac42165a5fadc6c87be0dea6b115f93090", GitTreeState:"clean"}
```
另外一個值得注意的問題是RBAC,我們的 kubernetes 集群是1.10.0版本的,默認開啟了RBAC訪問控制,所以我們需要為Tiller創建一個`ServiceAccount`,讓他擁有執行的權限,詳細內容可以查看 Helm 文檔中的[Role-based Access Control](https://helm.sh/docs/intro/)。 創建`rbac.yaml`文件:
```bash
apiVersion: v1
kind: ServiceAccount
metadata:
name: tiller
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: tiller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: tiller
namespace: kube-system
```
然后使用kubectl創建:
```bash
$ kubectl create -f rbac-config.yaml
serviceaccount "tiller" created
clusterrolebinding.rbac.authorization.k8s.io "tiller" created
```
創建了`tiller`的 `ServceAccount` 后還沒完,因為我們的 Tiller 之前已經就部署成功了,而且是沒有指定 `ServiceAccount` 的,所以我們需要給 Tiller 打上一個 `ServiceAccount` 的補丁:
```bash
$ kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'
```
上面這一步非常重要,不然后面在使用 Helm 的過程中可能出現`Error: no available release name found`的錯誤信息。
至此, Helm客戶端和服務端都配置完成了,接下來我們看看如何使用吧。
## 6. helm init
### 6.1 --history-max
`--history-max`建議在 `helm init` 上進行設置,因為如果未按最大限制清除,則 helm 歷史記錄中的配置映射和其他對象的數量可能會增加。如果沒有設置最大歷史記錄,歷史記錄將無限期保存,留下大量記錄供掌舵和分蘗維護。
```bash
$ helm init --history-max 200
```
### 6.2 --node-selectors
`--node-selectors`標志允許我們指定調度 Tiller pod 所需的節點標簽
```bash
$ helm init --node-selectors "beta.kubernetes.io/os"="linux"
```
已安裝的部署清單將包含我們的節點選擇器標簽。
```bash
...
spec:
template:
spec:
nodeSelector:
beta.kubernetes.io/os: linux
...
```
### 6.3 --override
`--override`允許您指定 Tiller 部署清單的屬性。與`--set` 中其他地方使用的命令不同, `helm init --override`操作最終清單的指定屬性(沒有“值”文件)。因此,您可以為部署清單中的任何有效屬性指定任何有效值。
- **覆蓋注釋**
```bash
helm init --override metadata.annotations."deployment\.kubernetes\.io/revision"="1"
```
輸出:
```bash
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
...
```
- **覆蓋親和力**
```bash
helm init --override "spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight"="1" --override "spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[0].key"="e2e-az-name"
```
指定的屬性組合到“`preferredDuringSchedulingIgnoredDuringExecution`”屬性的第一個列表項中。
```bash
...
spec:
strategy: {}
template:
...
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: e2e-az-name
operator: ""
weight: 1
...
```
### 6.4 --output
該`--output`標志允許我們跳過 Tiller 部署清單的安裝,只需將部署清單以 JSON 或 YAML 格式輸出到標準輸出。然后可以使用類似工具修改輸出jq 并使用kubectl.
在下面的示例中,我們helm init使用--output json標志執行。
```bash
helm init --output json
```
跳過 Tiller 安裝,清單以 JSON 格式輸出到標準輸出。
```bash
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "helm",
"name": "tiller"
},
"name": "tiller-deploy",
"namespace": "kube-system"
},
...
```
### 6.5 其他參數
- 使用`--canary-image`標志安裝金絲雀版本 : `helm init --canary-image`
- 安裝特定的鏡像(版本) `--tiller-image`
- 安裝到特定集群 `--kube-context`
- 安裝到特定的命名空間中 `--tiller-namespace`
- 使用服務帳戶安裝 Tiller `--service-account`(對于啟用 RBAC 的集群)
- 在不安裝服務帳戶的情況下安裝 Tiller `--automount-service-account false`
## 7. 部署示例
我們現在了嘗試創建一個 Chart:
```bash
$ helm create hello-helm
Creating hello-helm
$ tree hello-helm
hello-helm
├── charts
├── Chart.yaml
├── templates
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── ingress.yaml
│ ├── NOTES.txt
│ └── service.yaml
└── values.yaml
2 directories, 7 files
```
我們通過查看`templates`目錄下面的`deployment.yaml`文件可以看出默認創建的 Chart 是一個 `nginx` 服務,具體的每個文件是干什么用的,我們可以前往 [Helm 官方文檔](https://v2.helm.sh/docs/developing_charts/#charts)進行查看,后面會和大家詳細講解的。比如這里我們來安裝 `1.7.9` 這個版本的 `nginx`,則我們更改 `value.yaml` 文件下面的 `image tag` 即可,將默認的 stable 更改為 1.7.9,為了測試方便,我們把 Service 的類型也改成 NodePort
```bash
...
image:
repository: nginx
tag: 1.7.9
pullPolicy: IfNotPresent
nameOverride: ""
fullnameOverride: ""
service:
type: NodePort
port: 80
...
```
現在我們來嘗試安裝下這個 Chart :
```bash
$ helm install ./hello-helm
NAME: iced-ferret
LAST DEPLOYED: Thu Aug 30 23:39:45 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
iced-ferret-hello-helm ClusterIP 10.100.118.77 <none> 80/TCP 0s
==> v1beta2/Deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
iced-ferret-hello-helm 1 0 0 0 0s
==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
iced-ferret-hello-helm-58cb69d5bb-s9f2m 0/1 Pending 0 0s
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace default -l "app=hello-helm,release=iced-ferret" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80
$ kubectl get pods -l app=hello-helm
NAME READY STATUS RESTARTS AGE
iced-ferret-hello-helm-58cb69d5bb-s9f2m 1/1 Running 0 2m
$ kubectl get svc -l app=hello-helm
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
iced-ferret-hello-helm NodePort 10.104.127.141 <none> 80:31236/TCP 3m
```
等到 Pod 創建完成后,我們可以根據創建的 `Service` 的 `NodePort` 來訪問該服務了,然后在瀏覽器中打開`http://k8s.haimaxy.com:31236`就可以正常的訪問我們剛剛部署的 nginx 應用了。

查看release:
```bash
$ helm list
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
winning-zebra 1 Thu Aug 30 23:50:29 2018 DEPLOYED hello-helm-0.1.0 1.0 default
$ helm ls
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
wintering-rodent 1 Thu Oct 18 15:06:58 2018 DEPLOYED mysql-0.10.1 5.7.14 default
```
打包chart:
```handlebars
$ helm package hello-helm
Successfully packaged chart and saved it to: /root/course/kubeadm/helm/hello-helm-0.1.0.tgz
```
然后我們就可以將打包的tgz文件分發到任意的服務器上,通過`helm fetch`就可以獲取到該 Chart 了。
然后我們就可以將打包的tgz文件分發到任意的服務器上,通過helm fetch就可以獲取到該 Chart 了。
刪除release:
```bash
$ helm delete winning-zebra
release "winning-zebra" deleted
```
然后我們看到kubernetes集群上的該 nginx 服務也已經被刪除了。
```bash
$ kubectl get pods -l app=hello-helm
No resources found.
```
------------
參考資料:
- [陽明](https://www.qikqiak.com/k8s-book/docs/43.Helm%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8.html)
- [官方文檔(含有更多細節)](https://v2.helm.sh/docs/using_helm/#initialize-helm-and-install-tiller)
- [kubernetes 快速學習手冊](https://ghostwritten.blog.csdn.net/article/details/108562082)