• 使用Helm管理kubernetes应用
    • 安装Helm
    • 创建自己的chart
      • 模板
      • 检查配置和模板是否有效
      • 部署到kubernetes
      • 打包分享
      • 依赖
      • 安装源
    • 注意事项
      • 服务依赖管理
      • 解决本地chart依赖
      • 设置helm命令自动补全
    • 部署MEAN测试案例
    • 参考

    使用Helm管理kubernetes应用

    读完本文后您应该可以自己创建chart,并创建自己的私有chart仓库。

    Helm是一个kubernetes应用的包管理工具,用来管理charts——预先配置好的安装包资源,有点类似于Ubuntu的APT和CentOS中的yum。

    Helm chart是用来封装kubernetes原生应用程序的yaml文件,可以在你部署应用的时候自定义应用程序的一些metadata,便与应用程序的分发。

    Helm和charts的主要作用:

    • 应用程序封装
    • 版本管理
    • 依赖检查
    • 便于应用程序分发

    安装Helm

    前提要求

    • Kubernetes1.5以上版本
    • 集群可访问到的镜像仓库
    • 执行helm命令的主机可以访问到kubernetes集群

    安装步骤

    首先需要安装helm客户端

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

    创建tiller的serviceaccountclusterrolebinding

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

    然后安装helm服务端tiller,目前最新版 v2.14.2,若无法访问 gcr.io,可以使用阿里云镜像,如:

    1. helm init --upgrade -i registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.12.2 --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

    我们使用-i指定自己的镜像,因为官方的镜像因为某些原因无法拉取,官方镜像地址是:gcr.io/kubernetes-helm/tiller:v2.14.2,使用helm version可查看helm客户端版本。

    为应用程序设置serviceAccount

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

    检查是否安装成功:

    1. $ kubectl -n kube-system get pods|grep tiller
    2. tiller-deploy-2372561459-f6p0z 1/1 Running 0 1h
    3. $ helm version
    4. Client: &version.Version{SemVer:"v2.14.2", GitCommit:"a8b13cc5ab6a7dbef0a58f5061bcc7c0c61598e7", GitTreeState:"clean"}
    5. Server: &version.Version{SemVer:"v2.12.2", GitCommit:"7d2b0c73d734f6586ed222a567c5d103fed435be", GitTreeState:"clean"}

    创建自己的chart

    我们创建一个名为mychart的chart,看一看chart的文件结构。

    1. $ helm create mongodb
    2. $ tree mongodb
    3. mongodb
    4. ├── Chart.yaml #Chart本身的版本和配置信息
    5. ├── charts #依赖的chart
    6. ├── templates #配置模板目录
    7. ├── NOTES.txt #helm提示信息
    8. ├── _helpers.tpl #用于修改kubernetes objcet配置的模板
    9. ├── deployment.yaml #kubernetes Deployment object
    10. └── service.yaml #kubernetes Serivce
    11. └── values.yaml #kubernetes object configuration
    12. 2 directories, 6 files

    模板

    Templates目录下是yaml文件的模板,遵循Go template语法。使用过Hugo的静态网站生成工具的人应该对此很熟悉。

    我们查看下deployment.yaml文件的内容。

    1. apiVersion: extensions/v1beta1
    2. kind: Deployment
    3. metadata:
    4. name: {{ template "fullname" . }}
    5. labels:
    6. chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
    7. spec:
    8. replicas: {{ .Values.replicaCount }}
    9. template:
    10. metadata:
    11. labels:
    12. app: {{ template "fullname" . }}
    13. spec:
    14. containers:
    15. - name: {{ .Chart.Name }}
    16. image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
    17. imagePullPolicy: {{ .Values.image.pullPolicy }}
    18. ports:
    19. - containerPort: {{ .Values.service.internalPort }}
    20. livenessProbe:
    21. httpGet:
    22. path: /
    23. port: {{ .Values.service.internalPort }}
    24. readinessProbe:
    25. httpGet:
    26. path: /
    27. port: {{ .Values.service.internalPort }}
    28. resources:
    29. {{ toyaml .Values.resources | indent 12 }}

    这是该应用的Deployment的yaml配置文件,其中的双大括号包扩起来的部分是Go template,其中的Values是在values.yaml文件中定义的:

    1. # Default values for mychart.
    2. # This is a yaml-formatted file.
    3. # Declare variables to be passed into your templates.
    4. replicaCount: 1
    5. image:
    6. repository: nginx
    7. tag: stable
    8. pullPolicy: IfNotPresent
    9. service:
    10. name: nginx
    11. type: ClusterIP
    12. externalPort: 80
    13. internalPort: 80
    14. resources:
    15. limits:
    16. cpu: 100m
    17. memory: 128Mi
    18. requests:
    19. cpu: 100m
    20. memory: 128Mi

    比如在Deployment.yaml中定义的容器镜像image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"其中的:

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

    以上两个变量值是在create chart的时候自动生成的默认值。

    我们将默认的镜像地址和tag改成我们自己的镜像harbor-001.jimmysong.io/library/nginx:1.9

    检查配置和模板是否有效

    当使用kubernetes部署应用的时候实际上讲templates渲染成最终的kubernetes能够识别的yaml格式。

    使用helm install --dry-run --debug <chart_dir>命令来验证chart配置。该输出中包含了模板的变量配置与最终渲染的yaml文件。

    1. $ helm install --dry-run --debug mychart
    2. Created tunnel using local port: '58406'
    3. SERVER: "localhost:58406"
    4. CHART PATH: /Users/jimmy/Workspace/github/bitnami/charts/incubator/mean/charts/mychart
    5. NAME: filled-seahorse
    6. REVISION: 1
    7. RELEASED: Tue Oct 24 18:57:13 2017
    8. CHART: mychart-0.1.0
    9. USER-SUPPLIED VALUES:
    10. {}
    11. COMPUTED VALUES:
    12. image:
    13. pullPolicy: IfNotPresent
    14. repository: harbor-001.jimmysong.io/library/nginx
    15. tag: 1.9
    16. replicaCount: 1
    17. resources:
    18. limits:
    19. cpu: 100m
    20. memory: 128Mi
    21. requests:
    22. cpu: 100m
    23. memory: 128Mi
    24. service:
    25. externalPort: 80
    26. internalPort: 80
    27. name: nginx
    28. type: ClusterIP
    29. HOOKS:
    30. MANIFEST:
    31. ---
    32. # Source: mychart/templates/service.yaml
    33. apiVersion: v1
    34. kind: Service
    35. metadata:
    36. name: filled-seahorse-mychart
    37. labels:
    38. chart: "mychart-0.1.0"
    39. spec:
    40. type: ClusterIP
    41. ports:
    42. - port: 80
    43. targetPort: 80
    44. protocol: TCP
    45. name: nginx
    46. selector:
    47. app: filled-seahorse-mychart
    48. ---
    49. # Source: mychart/templates/deployment.yaml
    50. apiVersion: extensions/v1beta1
    51. kind: Deployment
    52. metadata:
    53. name: filled-seahorse-mychart
    54. labels:
    55. chart: "mychart-0.1.0"
    56. spec:
    57. replicas: 1
    58. template:
    59. metadata:
    60. labels:
    61. app: filled-seahorse-mychart
    62. spec:
    63. containers:
    64. - name: mychart
    65. image: "harbor-001.jimmysong.io/library/nginx:1.9"
    66. imagePullPolicy: IfNotPresent
    67. ports:
    68. - containerPort: 80
    69. livenessProbe:
    70. httpGet:
    71. path: /
    72. port: 80
    73. readinessProbe:
    74. httpGet:
    75. path: /
    76. port: 80
    77. resources:
    78. limits:
    79. cpu: 100m
    80. memory: 128Mi
    81. requests:
    82. cpu: 100m
    83. memory: 128Mi

    我们可以看到Deployment和Service的名字前半截由两个随机的单词组成,最后才是我们在values.yaml中配置的值。

    部署到kubernetes

    mychart目录下执行下面的命令将nginx部署到kubernetes集群上。

    1. helm install .
    2. NAME: eating-hound
    3. LAST DEPLOYED: Wed Oct 25 14:58:15 2017
    4. NAMESPACE: default
    5. STATUS: DEPLOYED
    6. RESOURCES:
    7. ==> v1/Service
    8. NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
    9. eating-hound-mychart 10.254.135.68 <none> 80/TCP 0s
    10. ==> extensions/v1beta1/Deployment
    11. NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
    12. eating-hound-mychart 1 1 1 0 0s
    13. NOTES:
    14. 1. Get the application URL by running these commands:
    15. export POD_NAME=$(kubectl get pods --namespace default -l "app=eating-hound-mychart" -o jsonpath="{.items[0].metadata.name}")
    16. echo "Visit http://127.0.0.1:8080 to use your application"
    17. kubectl port-forward $POD_NAME 8080:80

    现在nginx已经部署到kubernetes集群上,本地执行提示中的命令在本地主机上访问到nginx实例。

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

    在本地访问http://127.0.0.1:8080即可访问到nginx。

    查看部署的relaese

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

    删除部署的release

    1. $ helm delete eating-hound
    2. release "eating-hound" deleted

    打包分享

    我们可以修改Chart.yaml中的helm chart配置信息,然后使用下列命令将chart打包成一个压缩文件。

    1. helm package .

    打包出mychart-0.1.0.tgz文件。

    依赖

    我们可以在requirements.yaml中定义应用所依赖的chart,例如定义对mariadb的依赖:

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

    使用helm lint .命令可以检查依赖和模板配置是否正确。

    安装源

    使用第三方chat库

    • 添加fabric8库
    1. $ helm repo add fabric8 https://fabric8.io/helm
    • 搜索fabric8提供的工具(主要就是fabric8-platform工具包,包含了CI、CD的全套工具)
    1. $ helm search fabric8

    我们在前面安装chart可以通过HTTP server的方式提供。

    1. $ helm serve
    2. Regenerating index. This may take a moment.
    3. 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文件的时候一定要注意在做服务发现时需要配置的服务地址,如果使用环境变量的话,需要像下面这样配置。

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

    这是使用了Go template的语法。至于{{ .Values.image.env.SERVICE_NAME }}的值是从values.yaml文件中获取的,所以需要在values.yaml中增加如下配置:

    1. image:
    2. env:
    3. SERVICE_NAME: k8s-app-monitor-test

    解决本地chart依赖

    在本地当前chart配置的目录下启动helm server,我们不指定任何参数,直接使用默认端口启动。

    1. helm serve

    将该repo加入到repo list中。

    1. helm repo add local http://localhost:8879

    在浏览器中访问http://localhost:8879可以看到所有本地的chart。

    然后下载依赖到本地。

    1. helm dependency update

    这样所有的chart都会下载到本地的charts目录下。

    设置helm命令自动补全

    为了方便helm命令的使用,helm提供了自动补全功能,如果使用zsh请执行:

    1. source <(helm completion zsh)

    如果使用bash请执行:

    1. source <(helm completion bash)

    部署MEAN测试案例

    MEAN是用来构建网站和web应用的免费开源的JavaScript软件栈,该软件栈包括MongoDB、Express.js、Angular和Node.js。

    下载charts

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

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

    https://kubernetes-charts.storage.googleapis.com/是Google维护的chart库,访问该地址可以看到所有的chart列表。

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

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

    下载缺失的chart。

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

    修改mongodb chart配置

    将刚才下载的charts/mongodb-0.4.17.tgz给解压后,修改其中的配置:

    • persistence下的enabled设置为false
    • 将image修改为我们的私有镜像:harbor-001.jimmysong.io/library/bitnami-mongodb:3.4.9-r1

    执行helm install --dry-run --debug .确定模板无误。

    将修改后的mongodb chart打包,在mongodb的目录下执行:

    1. 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目录下执行:

    1. helm install .
    2. NAME: orbiting-platypus
    3. LAST DEPLOYED: Wed Oct 25 16:21:48 2017
    4. NAMESPACE: default
    5. STATUS: DEPLOYED
    6. RESOURCES:
    7. ==> v1/Secret
    8. NAME TYPE DATA AGE
    9. orbiting-platypus-mongodb Opaque 2 2s
    10. ==> v1/ConfigMap
    11. NAME DATA AGE
    12. orbiting-platypus-mean 1 2s
    13. ==> v1/Service
    14. NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
    15. orbiting-platypus-mongodb 10.254.144.208 <none> 27017/TCP 2s
    16. orbiting-platypus-mean 10.254.165.23 <none> 80/TCP 2s
    17. ==> extensions/v1beta1/Deployment
    18. NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
    19. orbiting-platypus-mean 1 1 1 0 2s
    20. orbiting-platypus-mongodb 1 1 1 0 2s
    21. NOTES:
    22. Get the URL of your Node app by running:
    23. export POD_NAME=$(kubectl get pods --namespace default -l "app=orbiting-platypus-mean" -o jsonpath="{.items[0].metadata.name}")
    24. echo http://127.0.0.1:8080/
    25. kubectl port-forward $POD_NAME 8080:80

    这样MEAN软件栈就部署到你的kuberentes集群里面了(默认是在default namespace下)。

    验证检查

    为了验证MEAN是否安装成功过,可以使用kubectl get pods查看pod是否启动完成,会先启动mongodb的pod,然后启动MEAN中的4步init。

    访问Web UI

    在Ingress中增加如下配置:

    1. - host: mean.jimmysong.io
    2. http:
    3. paths:
    4. - backend:
    5. serviceName: orbiting-platypus-mean
    6. servicePort: 80
    7. path: /

    然后在页面中更新ingress:

    1. kubectl repalce -f ingress.yaml

    关于Ingress配置请参考:边缘节点配置

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

    1. 172.20.0.119 mean.jimmysong.io

    :172.20.0.119即边缘节点的VIP。

    因为该页面需要加载google的angularjs、还有两个css在国内无法访问,可以使用curl测试:

    1. curl mean.jimmysong.io

    将会返回HTML内容:

    1. <!doctype html>
    2. <!-- ASSIGN OUR ANGULAR MODULE -->
    3. <html ng-app="scotchTodo">
    4. <head>
    5. <!-- META -->
    6. <meta charset="utf-8">
    7. <meta name="viewport" content="width=device-width, initial-scale=1">
    8. <!-- Optimize mobile viewport -->
    9. <title>Node/Angular Todo App</title>
    10. <!-- SCROLLS -->
    11. <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
    12. <!-- load bootstrap -->
    13. <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css">
    14. <style>
    15. html {
    16. overflow-y: scroll;
    17. }
    18. body {
    19. padding-top: 50px;
    20. }
    21. #todo-list {
    22. margin-bottom: 30px;
    23. }
    24. #todo-form {
    25. margin-bottom: 50px;
    26. }
    27. </style>
    28. <!-- SPELLS -->
    29. <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>
    30. <!-- load angular -->
    31. <script src="js/controllers/main.js"></script>
    32. <!-- load up our controller -->
    33. <script src="js/services/todos.js"></script>
    34. <!-- load our todo service -->
    35. <script src="js/core.js"></script>
    36. <!-- load our main application -->
    37. </head>
    38. <!-- SET THE CONTROLLER -->
    39. <body ng-controller="mainController">
    40. <div class="container">
    41. <!-- HEADER AND TODO COUNT -->
    42. <div class="jumbotron text-center">
    43. <h1>I'm a Todo-aholic <span class="label label-info">{{ todos.length }}</span></h1>
    44. </div>
    45. <!-- TODO LIST -->
    46. <div id="todo-list" class="row">
    47. <div class="col-sm-4 col-sm-offset-4">
    48. <!-- LOOP OVER THE TODOS IN $scope.todos -->
    49. <div class="checkbox" ng-repeat="todo in todos">
    50. <label>
    51. <input type="checkbox" ng-click="deleteTodo(todo._id)"> {{ todo.text }}
    52. </label>
    53. </div>
    54. <p class="text-center" ng-show="loading">
    55. <span class="fa fa-spinner fa-spin fa-3x"></span>
    56. </p>
    57. </div>
    58. </div>
    59. <!-- FORM TO CREATE TODOS -->
    60. <div id="todo-form" class="row">
    61. <div class="col-sm-8 col-sm-offset-2 text-center">
    62. <form>
    63. <div class="form-group">
    64. <!-- BIND THIS VALUE TO formData.text IN ANGULAR -->
    65. <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">
    66. </div>
    67. <!-- createToDo() WILL CREATE NEW TODOS -->
    68. <button type="submit" class="btn btn-primary btn-lg" ng-click="createTodo()">Add</button>
    69. </form>
    70. </div>
    71. </div>
    72. <div class="text-center text-muted">
    73. <p>A demo by <a href="http://scotch.io">Scotch</a>.</p>
    74. <p>Read the <a href="http://scotch.io/tutorials/javascript/creating-a-single-page-todo-app-with-node-and-angular">tutorial</a>.</p>
    75. </div>
    76. </div>
    77. </body>
    78. </html>

    访问 http://mean.jimmysong.io 可以看到如下界面,我在其中添加几条todo:

    TODO应用的Web页面

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

    测试完成后可以使用下面的命令将mean chart推送的本地chart仓库中。

    在mean目录下执行:

    1. helm package .

    再次刷新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
    • How To Create Your First Helm Chart
    • Speed deployment on Kubernetes with Helm Chart – Quick yaml example from scratch