1. 程式人生 > >使用Helm管理kubernetes原生應用

使用Helm管理kubernetes原生應用

Wed Oct 25, 2017

4600 Words|Read in about 10 Min

完本文後您應該可以自己建立chart,並建立自己的私有chart倉庫。

Helm是一個kubernetes應用的包管理工具,用來管理charts——預先配置好的安裝包資源,有點類似於Ubuntu的APT和CentOS中的yum。

Helm chart是用來封裝kubernetes原生應用程式的yaml檔案,可以在你部署應用的時候自定義應用程式的一些metadata,便與應用程式的分發。

Helm和charts的主要作用:

  • 應用程式封裝
  • 版本管理
  • 依賴檢查
  • 便於應用程式分發

安裝Helm

前提要求

  • Kubernetes1.5以上版本
  • 叢集可訪問到的映象倉庫
  • 執行helm命令的主機可以訪問到kubernetes叢集

安裝步驟

首先需要安裝helm客戶端

curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get > get_helm.sh
chmod 700 get_helm.sh
./get_helm.sh

建立tiller的serviceaccountclusterrolebinding

kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller

然後安裝helm服務端tiller

helm init -i sz-pg-oam-docker-hub-001.tendcloud.com/library/kubernetes-helm-tiller:v2.3.1

我們使用-i指定自己的映象,因為官方的映象因為某些原因無法拉取。

為應用程式設定serviceAccount

kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'

檢查是否安裝成功:

$ kubectl -n kube-system get pods|grep tiller
tiller-deploy-2372561459-f6p0z         1/1       Running   0          1h
$ helm version
Client: &version.Version{SemVer:"v2.3.1", GitCommit:"32562a3040bb5ca690339b9840b6f60f8ce25da4", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.3.1", GitCommit:"32562a3040bb5ca690339b9840b6f60f8ce25da4", GitTreeState:"clean"}

建立自己的chart

我們建立一個名為mychart的chart,看一看chart的檔案結構。

$ helm create mongodb
$ tree mongodb
mongodb
├── Chart.yaml #Chart本身的版本和配置資訊
├── charts #依賴的chart
├── templates #配置模板目錄
│   ├── NOTES.txt #helm提示資訊
│   ├── _helpers.tpl #用於修改kubernetes objcet配置的模板
│   ├── deployment.yaml #kubernetes Deployment object
│   └── service.yaml #kubernetes Serivce
└── values.yaml #kubernetes object configuration

2 directories, 6 files

模板

Templates目錄下是yaml檔案的模板,遵循Go template語法。使用過Hugo的靜態網站生成工具的人應該對此很熟悉。

我們檢視下deployment.yaml檔案的內容。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: {{ template "fullname" . }}
  labels:
    chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    metadata:
      labels:
        app: {{ template "fullname" . }}
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        ports:
        - containerPort: {{ .Values.service.internalPort }}
        livenessProbe:
          httpGet:
            path: /
            port: {{ .Values.service.internalPort }}
        readinessProbe:
          httpGet:
            path: /
            port: {{ .Values.service.internalPort }}
        resources:
{{ toYaml .Values.resources | indent 12 }}

這是該應用的Deployment的yaml配置檔案,其中的雙大括號包擴起來的部分是Go template,其中的Values是在values.yaml檔案中定義的:

# Default values for mychart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
  repository: nginx
  tag: stable
  pullPolicy: IfNotPresent
service:
  name: nginx
  type: ClusterIP
  externalPort: 80
  internalPort: 80
resources:
  limits:
    cpu: 100m
    memory: 128Mi
  requests:
    cpu: 100m
    memory: 128Mi

比如在Deployment.yaml中定義的容器映象image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"其中的:

  • .Values.image.repository就是nginx
  • .Values.image.tag就是stable

以上兩個變數值是在create chart的時候自動生成的預設值。

我們將預設的映象地址和tag改成我們自己的映象sz-pg-oam-docker-hub-001.tendcloud.com/library/nginx:1.9

檢查配置和模板是否有效

當使用kubernetes部署應用的時候實際上講templates渲染成最終的kubernetes能夠識別的yaml格式。

使用helm install --dry-run --debug <chart_dir>命令來驗證chart配置。該輸出中包含了模板的變數配置與最終渲染的yaml檔案。

$ helm install --dry-run --debug mychart
Created tunnel using local port: '58406'
SERVER: "localhost:58406"
CHART PATH: /Users/jimmy/Workspace/github/bitnami/charts/incubator/mean/charts/mychart
NAME:   filled-seahorse
REVISION: 1
RELEASED: Tue Oct 24 18:57:13 2017
CHART: mychart-0.1.0
USER-SUPPLIED VALUES:
{}

COMPUTED VALUES:
image:
  pullPolicy: IfNotPresent
  repository: sz-pg-oam-docker-hub-001.tendcloud.com/library/nginx
  tag: 1.9
replicaCount: 1
resources:
  limits:
    cpu: 100m
    memory: 128Mi
  requests:
    cpu: 100m
    memory: 128Mi
service:
  externalPort: 80
  internalPort: 80
  name: nginx
  type: ClusterIP

HOOKS:
MANIFEST:

---
# Source: mychart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: filled-seahorse-mychart
  labels:
    chart: "mychart-0.1.0"
spec:
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
    name: nginx
  selector:
    app: filled-seahorse-mychart

---
# Source: mychart/templates/deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: filled-seahorse-mychart
  labels:
    chart: "mychart-0.1.0"
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: filled-seahorse-mychart
    spec:
      containers:
      - name: mychart
        image: "sz-pg-oam-docker-hub-001.tendcloud.com/library/nginx:1.9"
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
        livenessProbe:
          httpGet:
            path: /
            port: 80
        readinessProbe:
          httpGet:
            path: /
            port: 80
        resources:
            limits:
              cpu: 100m
              memory: 128Mi
            requests:
              cpu: 100m
              memory: 128Mi

我們可以看到Deployment和Service的名字前半截由兩個隨機的單片語成,最後才是我們在values.yaml中配置的值。

部署到kubernetes

mychart目錄下執行下面的命令將nginx部署到kubernetes叢集上。

helm install .
NAME:   eating-hound
LAST DEPLOYED: Wed Oct 25 14:58:15 2017
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Service
NAME                  CLUSTER-IP     EXTERNAL-IP  PORT(S)  AGE
eating-hound-mychart  10.254.135.68  <none>       80/TCP   0s

==> extensions/v1beta1/Deployment
NAME                  DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
eating-hound-mychart  1        1        1           0          0s


NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace default -l "app=eating-hound-mychart" -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

現在nginx已經部署到kubernetes叢集上,本地執行提示中的命令在本地主機上訪問到nginx例項。

export POD_NAME=$(kubectl get pods --namespace default -l "app=eating-hound-mychart" -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

在本地訪問http://127.0.0.1:8080即可訪問到nginx。

檢視部署的relaese

$ helm list
NAME        	REVISION	UPDATED                 	STATUS  	CHART        	NAMESPACE
eating-hound	1       	Wed Oct 25 14:58:15 2017	DEPLOYED	mychart-0.1.0	default

刪除部署的release

$ helm delete eating-hound
release "eating-hound" deleted

打包分享

我們可以修改Chart.yaml中的helm chart配置資訊,然後使用下列命令將chart打包成一個壓縮檔案。

helm package .

打包出mychart-0.1.0.tgz檔案。

依賴

我們可以在requirement.yaml中定義應用所依賴的chart,例如定義對mariadb的依賴:

dependencies:
- name: mariadb
  version: 0.6.0
  repository: https://kubernetes-charts.storage.googleapis.com

使用helm lint .命令可以檢查依賴和模板配置是否正確。

安裝源

我們在前面安裝chart可以通過HTTP server的方式提供。

$ helm serve
Regenerating index. This may take a moment.
Now serving you on 127.0.0.1:8879

訪問http://localhost:8879可以看到剛剛安裝的chart。

Helm chart源

點選連結即可以下載chart的壓縮包。

注意事項

下面列舉一些常見問題,和在解決這些問題時候的注意事項。

服務依賴管理

所有使用helm部署的應用中如果沒有特別指定chart的名字都會生成一個隨機的Release name,例如romping-frogsexy-newton等,跟啟動docker容器時候容器名字的命名規則相同,而真正的資源物件的名字是在YAML檔案中定義的名字,我們成為App name,兩者連線起來才是資源物件的實際名字:Release name-App name

而使用helm chart部署的包含依賴關係的應用,都會使用同一套Release name,在配置YAML檔案的時候一定要注意在做服務發現時需要配置的服務地址,如果使用環境變數的話,需要像下面這樣配置。

env:
 - name: SERVICE_NAME
   value: "{{ .Release.Name }}-{{ .Values.image.env.SERVICE_NAME }}"

這是使用了Go template的語法。至於{{ .Values.image.env.SERVICE_NAME }}的值是從values.yaml檔案中獲取的,所以需要在values.yaml中增加如下配置:

image:
  env:
    SERVICE_NAME: k8s-app-monitor-test

解決本地chart依賴

在本地當前chart配置的目錄下啟動helm server,我們不指定任何引數,直接使用預設埠啟動。

helm serve

將該repo加入到repo list中。

helm repo add local http://localhost:8879

然後下載依賴到本地。

helm dependency update

這樣所有的chart都會下載到本地的charts目錄下。

設定helm命令自動補全

為了方便helm命令的使用,helm提供了自動補全功能,如果使用zsh請執行:

source <(helm completion zsh)

如果使用bash請執行:

source <(helm completion bash)

部署MEAN測試案例

MEAN是用來構建網站和web應用的免費開源的JavaScript軟體棧,該軟體棧包括MongoDB、Express.js、Angular和Node.js。

下載charts

$ git clone https://github.com/bitnami/charts.git
$ cd charts/incubator/mean
$ helm dep list
NAME   	VERSION	REPOSITORY                                       	STATUS
mongodb	0.4.x  	https://kubernetes-charts.storage.googleapis.com/	missing

缺少mongodb的依賴,需要更新一下chart。

https://kubernetes-charts.storage.googleapis.com/是Google維護的chart庫,訪問該地址可以看到所有的chart列表。

$ helm dep update
Hang tight while we grab the latest from your chart repositories...
...Unable to get an update from the "local" chart repository (http://127.0.0.1:8879/charts):
	Get http://127.0.0.1:8879/charts/index.yaml: dial tcp 127.0.0.1:8879: getsockopt: connection refused
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈Happy Helming!⎈
Saving 1 charts
Downloading mongodb from repo https://kubernetes-charts.storage.googleapis.com/

所有的image都在 values.yaml 檔案中配置。

下載缺失的chart。

$ helm dep build
Hang tight while we grab the latest from your chart repositories...
...Unable to get an update from the "local" chart repository (http://127.0.0.1:8879/charts):
	Get http://127.0.0.1:8879/charts/index.yaml: dial tcp 127.0.0.1:8879: getsockopt: connection refused
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈Happy Helming!⎈
Saving 1 charts
Downloading mongodb from repo https://kubernetes-charts.storage.googleapis.com/

修改mongodb chart配置

將剛才下載的charts/mongodb-0.4.17.tgz給解壓後,修改其中的配置:

  • persistence下的enabled設定為false
  • 將image修改為我們的私有映象:sz-pg-oam-docker-hub-001.tendcloud.com/library/bitnami-mongodb:3.4.9-r1

執行helm install --dry-run --debug .確定模板無誤。

將修改後的mongodb chart打包,在mongodb的目錄下執行:

helm package .

現在再訪問前面啟動的helm server http://localhost:8879將可以在頁面上看到mongodb-0.4.17這個chart。

我們對官方chart配置做了如下修改後推送到了自己的chart倉庫:

  • requirements.yamlrequirements.lock檔案中的repositoryhttp://localhost:8879
  • values.yaml中的storageClass設定為null
  • values.yaml中的Image都改為私有映象
  • repositroy都設定為http://localhost:8879

:因為我們沒有使用PVC所以將所有的關於持久化儲存的配置都設定為false了。

部署MEAN

mean目錄下執行:

helm install .
NAME:   orbiting-platypus
LAST DEPLOYED: Wed Oct 25 16:21:48 2017
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Secret
NAME                       TYPE    DATA  AGE
orbiting-platypus-mongodb  Opaque  2     2s

==> v1/ConfigMap
NAME                    DATA  AGE
orbiting-platypus-mean  1     2s

==> v1/Service
NAME                       CLUSTER-IP      EXTERNAL-IP  PORT(S)    AGE
orbiting-platypus-mongodb  10.254.144.208  <none>       27017/TCP  2s
orbiting-platypus-mean     10.254.165.23   <none>       80/TCP     2s

==> extensions/v1beta1/Deployment
NAME                       DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
orbiting-platypus-mean     1        1        1           0          2s
orbiting-platypus-mongodb  1        1        1           0          2s


NOTES:

Get the URL of your Node app  by running:

  export POD_NAME=$(kubectl get pods --namespace default -l "app=orbiting-platypus-mean" -o jsonpath="{.items[0].metadata.name}")
  echo http://127.0.0.1:8080/
  kubectl port-forward $POD_NAME 8080:80

這樣MEAN軟體棧就部署到你的kuberentes叢集裡面了(預設是在default namespace下)。

驗證檢查

為了驗證MEAN是否安裝成功過,可以使用kubectl get pods檢視pod是否啟動完成,會先啟動mongodb的pod,然後啟動MEAN中的4步init。

訪問Web UI

在Ingress中增加如下配置:

  - host: mean.jimmysong.io
    http:
      paths:
      - backend:
          serviceName: orbiting-platypus-mean
          servicePort: 80
        path: /

然後在頁面中更新ingress:

kubectl repalce -f ingress.yaml

關於Ingress配置請參考:邊緣節點配置

然後在本地的/etc/hosts檔案中增加一條配置:

172.20.0.119 mean.jimmysong.io

:172.20.0.119即邊緣節點的VIP。

因為該頁面需要載入google的angularjs、還有兩個css在國內無法訪問,可以使用curl測試:

curl mean.jimmysong.io

將會返回HTML內容:

<!doctype html>

<!-- ASSIGN OUR ANGULAR MODULE -->
<html ng-app="scotchTodo">

<head>
    <!-- META -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Optimize mobile viewport -->

    <title>Node/Angular Todo App</title>

    <!-- SCROLLS -->
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
    <!-- load bootstrap -->
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css">
    <style>
        html {
            overflow-y: scroll;
        }
        
        body {
            padding-top: 50px;
        }
        
        #todo-list {
            margin-bottom: 30px;
        }
        
        #todo-form {
            margin-bottom: 50px;
        }
    </style>

    <!-- SPELLS -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>
    <!-- load angular -->

    <script src="js/controllers/main.js"></script>
    <!-- load up our controller -->
    <script src="js/services/todos.js"></script>
    <!-- load our todo service -->
    <script src="js/core.js"></script>
    <!-- load our main application -->

</head>
<!-- SET THE CONTROLLER -->

<body ng-controller="mainController">
    <div class="container">

        <!-- HEADER AND TODO COUNT -->
        <div class="jumbotron text-center">
            <h1>I'm a Todo-aholic <span class="label label-info">{{ todos.length }}</span></h1>
        </div>

        <!-- TODO LIST -->
        <div id="todo-list" class="row">
            <div class="col-sm-4 col-sm-offset-4">



                <!-- LOOP OVER THE TODOS IN $scope.todos -->
                <div class="checkbox" ng-repeat="todo in todos">
                    <label>
						<input type="checkbox" ng-click="deleteTodo(todo._id)"> {{ todo.text }}
					</label>
                </div>

                <p class="text-center" ng-show="loading">
                    <span class="fa fa-spinner fa-spin fa-3x"></span>
                </p>

            </div>
        </div>

        <!-- FORM TO CREATE TODOS -->
        <div id="todo-form" class="row">
            <div class="col-sm-8 col-sm-offset-2 text-center">
                <form>
                    <div class="form-group">

                        <!-- BIND THIS VALUE TO formData.text IN ANGULAR -->
                        <input type="text" class="form-control input-lg text-center" placeholder="I want to buy a puppy that will love me forever" ng-model="formData.text">
                    </div>

                    <!-- createToDo() WILL CREATE NEW TODOS -->
                    <button type="submit" class="btn btn-primary btn-lg" ng-click="createTodo()">Add</button>
                </form>
            </div>
        </div>

        <div class="text-center text-muted">
            <p>A demo by <a href="http://scotch.io">Scotch</a>.</p>
            <p>Read the <a href="http://scotch.io/tutorials/javascript/creating-a-single-page-todo-app-with-node-and-angular">tutorial</a>.</p>
        </div>

    </div>

</body>

</html>

TODO應用的Web頁面

:Todo中的文字來自What does the fox say?

測試完成後可以使用下面的命令將mean chart推送的本地chart倉庫中。

在mean目錄下執行:

helm package .

再次重新整理http://localhost:8879將可以看到如下三個chart:

  • mean
    • mean-0.1.3
  • mongodb
    • mongodb-0.4.17
  • mychart
    • mychart-0.1.0

參考

See Also

Wed Oct 25, 2017

4600 Words|Read in about 10 Min