使用Helm管理kubernetes原生應用

原文地址:使用Helm管理kubernetes原生應用

瀏覽原文,格式效果更佳,本文已歸檔到 kubernetes-handbook

讀完本文後您應該可以自己創建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.shnchmod 700 get_helm.shn./get_helm.shn

創建tiller的serviceaccount和clusterrolebinding

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

然後安裝helm服務端tiller

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

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

為應用程序設置serviceAccount:

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

檢查是否安裝成功:

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

創建自己的chart

我們創建一個名為mychart的chart,看一看chart的文件結構。

$ helm create mongodbn$ tree mongodbnmongodbn├── Chart.yaml #Chart本身的版本和配置信息n├── charts #依賴的chartn├── templates #配置模板目錄n│ ├── NOTES.txt #helm提示信息n│ ├── _helpers.tpl #用於修改kubernetes objcet配置的模板n│ ├── deployment.yaml #kubernetes Deployment objectn│ └── service.yaml #kubernetes Serivcen└── values.yaml #kubernetes object configurationnn2 directories, 6 filesn

模板

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

我們查看下deployment.yaml文件的內容。

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

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

# Default values for mychart.n# This is a YAML-formatted file.n# Declare variables to be passed into your templates.nreplicaCount: 1nimage:n repository: nginxn tag: stablen pullPolicy: IfNotPresentnservice:n name: nginxn type: ClusterIPn externalPort: 80n internalPort: 80nresources:n limits:n cpu: 100mn memory: 128Min requests:n cpu: 100mn memory: 128Min

比如在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

檢查配置和模板是否有效

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

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

$ helm install --dry-run --debug mychartnCreated tunnel using local port: 58406nSERVER: "localhost:58406"nCHART PATH: /Users/jimmy/Workspace/github/bitnami/charts/incubator/mean/charts/mychartnNAME: filled-seahorsenREVISION: 1nRELEASED: Tue Oct 24 18:57:13 2017nCHART: mychart-0.1.0nUSER-SUPPLIED VALUES:n{}nnCOMPUTED VALUES:nimage:n pullPolicy: IfNotPresentn repository: sz-pg-oam-docker-hub-001.tendcloud.com/library/nginxn tag: 1.9nreplicaCount: 1nresources:n limits:n cpu: 100mn memory: 128Min requests:n cpu: 100mn memory: 128Minservice:n externalPort: 80n internalPort: 80n name: nginxn type: ClusterIPnnHOOKS:nMANIFEST:nn---n# Source: mychart/templates/service.yamlnapiVersion: v1nkind: Servicenmetadata:n name: filled-seahorse-mychartn labels:n chart: "mychart-0.1.0"nspec:n type: ClusterIPn ports:n - port: 80n targetPort: 80n protocol: TCPn name: nginxn selector:n app: filled-seahorse-mychartnn---n# Source: mychart/templates/deployment.yamlnapiVersion: extensions/v1beta1nkind: Deploymentnmetadata:n name: filled-seahorse-mychartn labels:n chart: "mychart-0.1.0"nspec:n replicas: 1n template:n metadata:n labels:n app: filled-seahorse-mychartn spec:n containers:n - name: mychartn image: "sz-pg-oam-docker-hub-001.tendcloud.com/library/nginx:1.9"n imagePullPolicy: IfNotPresentn ports:n - containerPort: 80n livenessProbe:n httpGet:n path: /n port: 80n readinessProbe:n httpGet:n path: /n port: 80n resources:n limits:n cpu: 100mn memory: 128Min requests:n cpu: 100mn memory: 128Min

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

部署到kubernetes

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

helm install .nNAME: eating-houndnLAST DEPLOYED: Wed Oct 25 14:58:15 2017nNAMESPACE: defaultnSTATUS: DEPLOYEDnnRESOURCES:n==> v1/ServicenNAME CLUSTER-IP EXTERNAL-IP PORT(S) AGEneating-hound-mychart 10.254.135.68 <none> 80/TCP 0snn==> extensions/v1beta1/DeploymentnNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGEneating-hound-mychart 1 1 1 0 0snnnNOTES:n1. Get the application URL by running these commands:n export POD_NAME=$(kubectl get pods --namespace default -l "app=eating-hound-mychart" -o jsonpath="{.items[0].metadata.name}")n echo "Visit http://127.0.0.1:8080 to use your application"n kubectl port-forward $POD_NAME 8080:80n

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

export POD_NAME=$(kubectl get pods --namespace default -l "app=eating-hound-mychart" -o jsonpath="{.items[0].metadata.name}")necho "Visit http://127.0.0.1:8080 to use your application"nkubectl port-forward $POD_NAME 8080:80n

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

查看部署的relaese

$ helm listnNAME REVISION UPDATED STATUS CHART NAMESPACEneating-hound 1 Wed Oct 25 14:58:15 2017 DEPLOYED mychart-0.1.0 defaultn

刪除部署的release

$ helm delete eating-houndnrelease "eating-hound" deletedn

打包分享

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

helm package .n

打包出mychart-0.1.0.tgz文件。

依賴

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

dependencies:n- name: mariadbn version: 0.6.0n repository: https://kubernetes-charts.storage.googleapis.comn

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

安裝源

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

$ helm servenRegenerating index. This may take a moment.nNow serving you on 127.0.0.1:8879n

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

點擊鏈接即可以下載chart的壓縮包。

部署MEAN測試案例

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

下載charts

$ git clone https://github.com/bitnami/charts.gitn$ cd charts/incubator/meann$ helm dep listnNAME VERSION REPOSITORY STATUSnmongodb 0.4.x https://kubernetes-charts.storage.googleapis.com/ missingn

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

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

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

所有的image都在 values.yaml 文件中配置。

下載缺失的chart。

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

修改mongodb chart配置

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

  • 將persistence下的enabled設置為false
  • 將image修改為我們的私有鏡像:sz-pg-oam-docker-hub-001.tendcloud.com

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

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

helm package .n

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

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

  • requirements.yaml和requirements.lock文件中的repository為http://localhost:8879
  • 將values.yaml中的storageClass設置為null
  • 將values.yaml中的Image都改為私有鏡像
  • repositroy都設置為http://localhost:8879

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

部署MEAN

在mean目錄下執行:

helm install .nNAME: orbiting-platypusnLAST DEPLOYED: Wed Oct 25 16:21:48 2017nNAMESPACE: defaultnSTATUS: DEPLOYEDnnRESOURCES:n==> v1/SecretnNAME TYPE DATA AGEnorbiting-platypus-mongodb Opaque 2 2snn==> v1/ConfigMapnNAME DATA AGEnorbiting-platypus-mean 1 2snn==> v1/ServicenNAME CLUSTER-IP EXTERNAL-IP PORT(S) AGEnorbiting-platypus-mongodb 10.254.144.208 <none> 27017/TCP 2snorbiting-platypus-mean 10.254.165.23 <none> 80/TCP 2snn==> extensions/v1beta1/DeploymentnNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGEnorbiting-platypus-mean 1 1 1 0 2snorbiting-platypus-mongodb 1 1 1 0 2snnnNOTES:nnGet the URL of your Node app by running:nn export POD_NAME=$(kubectl get pods --namespace default -l "app=orbiting-platypus-mean" -o jsonpath="{.items[0].metadata.name}")n echo http://127.0.0.1:8080/n kubectl port-forward $POD_NAME 8080:80n

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

驗證檢查

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

訪問Web UI

在Ingress中增加如下配置:

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

然後在頁面中更新ingress:

kubectl repalce -f ingress.yamln

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

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

172.20.0.119 mean.jimmysong.ion

:172.20.0.119即邊緣節點的VIP。

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

curl mean.jimmysong.ion

將會返回HTML內容:

<!doctype html>nn<!-- ASSIGN OUR ANGULAR MODULE -->n<html ng-app="scotchTodo">nn<head>n <!-- META -->n <meta charset="utf-8">n <meta name="viewport" content="width_=device-width, initial-scale=1">n <!-- Optimize mobile viewport -->nn <title>Node/Angular Todo App</title>nn <!-- SCROLLS -->n <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">n <!-- load bootstrap -->n <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css">n <style>n html {n overflow-y: scroll;n }nn body {n padding-top: 50px;n }nn #todo-list {n margin-bottom: 30px;n }nn #todo-form {n margin-bottom: 50px;n }n </style>nn <!-- SPELLS -->n <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>n <!-- load angular -->nn <script src="js/controllers/main.js"></script>n <!-- load up our controller -->n <script src="js/services/todos.js"></script>n <!-- load our todo service -->n <script src="js/core.js"></script>n <!-- load our main application -->nn</head>n<!-- SET THE CONTROLLER -->nn<body ng-controller="mainController">n <div class="container">nn <!-- HEADER AND TODO COUNT -->n <div class="jumbotron text-center">n <h1>Im a Todo-aholic <span class="label label-info">{{ todos.length }}</span></h1>n </div>nn <!-- TODO LIST -->n <div id="todo-list" class="row">n <div class="col-sm-4 col-sm-offset-4">nnnn <!-- LOOP OVER THE TODOS IN $scope.todos -->n <div class="checkbox" ng-repeat="todo in todos">n <label>n <input type="checkbox" ng-click="deleteTodo(todo._id)"> {{ todo.text }}n </label>n </div>nn <p class="text-center" ng-show="loading">n <span class="fa fa-spinner fa-spin fa-3x"></span>n </p>nn </div>n </div>nn <!-- FORM TO CREATE TODOS -->n <div id="todo-form" class="row">n <div class="col-sm-8 col-sm-offset-2 text-center">n <form>n <div class="form-group">nn <!-- BIND THIS VALUE TO formData.text IN ANGULAR -->n <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">n </div>nn <!-- createToDo() WILL CREATE NEW TODOS -->n <button type="submit" class="btn btn-primary btn-lg" ng-click="createTodo()">Add</button>n </form>n </div>n </div>nn <div class="text-center text-muted">n <p>A demo by <a href="http://scotch.io">Scotch</a>.</p>n <p>Read the <a href="http://scotch.io/tutorials/javascript/creating-a-single-page-todo-app-with-node-and-angular">tutorial</a>.</p>n </div>nn </div>nn</body>nn</html>n

訪問 http://mean.jimmysong.io 可以看到如下界面,我在其中添加幾條todo:

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

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

在mean目錄下執行:

helm package .n

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

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

參考

  • Deploy, Scale And Upgrade An Application On Kubernetes With Helm
  • Helm charts
  • Go template
  • Helm docs
  • How To Create Your First Helm Chart

推薦閱讀:

TAG:Kubernetes | cloudnative |