简化Kubernetes应用部署工具-Helm之Hook

【编者的话】微服务和容器化给复杂应用部署与管理带来了极大的挑战。Helm是目前Kubernetes服务编排领域的唯一开源子项目,做为Kubernetes应用的一个包管理工具,可理解为Kubernetes的apt-get / yum,由Deis 公司发起,该公司已经被微软收购。Helm通过软件打包的形式,支持发布的版本管理和控制,很大程度上简化了Kubernetes应用部署和管理的复杂性。

随着业务容器化与向微服务架构转变,通过分解巨大的单体应用为多个服务的方式,分解了单体应用的复杂性,使每个微服务都可以独立部署和扩展,实现了敏捷开发和快速迭代和部署。但任何事情都有两面性,虽然微服务给我们带来了很多便利,但由于应用被拆分成多个组件,导致服务数量大幅增加,对于Kubernetest编排来说,每个组件有自己的资源文件,并且可以独立的部署与伸缩,这给采用Kubernetes做应用编排带来了诸多挑战:

  1. 管理、编辑与更新大量的K8s配置文件
  2. 部署一个含有大量配置文件的复杂K8s应用
  3. 分享和复用K8s配置和应用
  4. 参数化配置模板支持多个环境
  5. 管理应用的发布:回滚、diff和查看发布历史
  6. 控制一个部署周期中的某一些环节
  7. 发布后的验证

而Helm可以帮助我们解决这些问题。

Helm把Kubernetes资源(比如deployments、services或 ingress等) 打包到一个chart中,而chart被保存到chart仓库。通过chart仓库可用来存储和分享chart。Helm使发布可配置,支持发布应用配置的版本管理,简化了Kubernetes部署应用的版本控制、打包、发布、删除、更新等操作。

本文主要对Helm Hook进行简单的介绍和使用说明。

 

关于Helm的安装、介绍与使用请参考:

简化Kubernetes应用部署工具-Helm安装

简化Kubernetes应用部署工具-Helm简介

简化Kubernetes应用部署工具-Helm之应用部署

简化Kubernetes应用部署工具-Helm之Release配置

 

Helm Hook

简化Kubernetes应用部署工具-Helm之应用部署文章中,展示了采用Helm的一次应用release的生命周期过程,涵盖了发布(helm install)、更新(helm upgrade)、回滚(helm rollback)与删除(helm delete)等部分。为了允许chart开发者在应用release的生命周期中某些关键的时间点上,执行一些操作来更好的服务于release的需求,为此Helm提供了hook机制。

举例说明什么是一个Helm hook,比如ConfigMap或Secret要先于其他chart被加载,或希望在更新/回滚/删除一个release之前能够安全的关闭服务,都可以通过Helm hook实现。

Hook和普通模板文件基本相同,但其可以通过加入一些特殊的注释(annotation)与普通模板文件加以区分,下文会介绍hook的基本格式和用法。

支持Hook类型

Helm提供了如下hook供chart开发者使用:
  • pre-install:在模板文件被渲染之后、而在Kubernetes创建任何资源创建之前执行。
  • post-install:在Kubernetes加载全部的资源之后执行。
  • pre-delete:在Kubernetes删除任何resource之前执行。
  • post-delete:在一个release的全部资源被删除之后执行。
  • pre-upgrade:在模板渲染之后,而在Kubernetes加载任何资源之前执行。
  • post-upgrade:在Kubernetes更新完全部resource之后执行。
  • pre-rollback:在模板被渲染之后、而在Kubernetes执行对全部resource的回滚之前。
  • post-rollback:在Kubernetes的全部resource已经被修改之后执行。

Hook与Release生命周期

上面已经提到,Hook允许chart开发者在release生命周期的一些时间点上可以执行一些操作。以helm install为例,默认情况下其基本生命周期如下:
  1. 用户执行helm install foo
  2. Chart被加载到Helm server-Tiller
  3. 经过一些用户自定义case验证,Tiller用chart中的Values.yaml等渲染foo模板
  4. Tiller加载渲染好的Kubernetes resource到Kubernetes集群
  5. Tiller返回release名称等数据给Helm client
  6. Helm Client退出
如果在install的生命周期内定义pre-install与post-install 2个hook,那么install的生命周期会变成如下序列:
  1. 用户执行helm install foo
  2. Chart被加载到Helm server-Tiller
  3. 经过一些用户自定义case验证,Tiller用chart中的Values.yaml等渲染foo模板
  4. Tiller准备执行pre-install hook(加载hook resource到Kubernetes)
  5. Tiller按hook的权重对hook进行排序,对相同权重的hook按照字母从a-z顺序排序
  6. Tiller按照权重由低到高顺序加载hook
  7. Tiller等待hook状态变为就绪(”Ready”)
  8. Tiller加载resource到Kubernetes
  9. Tiller执行post-install hook(加载hook resource)
  10. Tiller等待hook状态变为就绪
  11. Tiller返回release名称等数据给Helm client
  12. Helm Client退出
所谓hook就绪状态,取决于hook中定义的Kubernetes resource的类型,比如,对于Job类型的resource,就绪状态是job成功构建完成,如果job构建失败,release也失败了。而对于其他类型的resource,一旦resource加载(添加或更新)到Kubernetes,其状态被认为是就绪状态。

Hook权重

前面提到,一个hook可以对应多个resource,例如, secret与config map做为一个pre-install hook.
同时一个resource也可以有多个hook。

  annotations:
    "helm.sh/hook": post-install,post-upgrade

Helm支持一个hook中的resource设置权重,Helm推荐通过对resource设置权重的方式,按权重大小加载resource。对于权重数值可以设为负数,权重的大小由负数到正数顺序。如果未设置权重,resource的加载是顺序执行的,但执行顺序并不会保证(Helm 2.3.0开始,相同权重的执行顺序按照字母a-z顺序执行,但未来版本可能会对相同权重的资源执行顺序有所变化)。

写一个Hook

Hook与普通的Kubernetes声明文件一样,只是在文件metadata中加了特殊的注释(annotations)。但由于Hook文件是模板文件,所以其也具备全部的模板文件特性,包括从内置对象 .Values.Release, and .Template获取渲染值等。

例如下面的模板中,定义了一个Job类型的Kubernetes resource,并定义了一个post-install hook,该hook的用途是当Kubernetes加载全部resource完毕后,按照Values对象设置的值sleepyTime执行sleep操作(如sleepyTime未设置,那么sleep 10秒)。

apiVersion: batch/v1
kind: Job
metadata:
  name: "{{.Release.Name}}"
  labels:
    heritage: {{.Release.Service | quote }}
    release: {{.Release.Name | quote }}
    chart: "{{.Chart.Name}}-{{.Chart.Version}}"
  annotations:
    # This is what defines this resource as a hook. Without this line, the
    # job is considered part of the release.
    "helm.sh/hook": post-install
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": hook-succeeded
spec:
  template:
    metadata:
      name: "{{.Release.Name}}"
      labels:
        heritage: {{.Release.Service | quote }}
        release: {{.Release.Name | quote }}
        chart: "{{.Chart.Name}}-{{.Chart.Version}}"
    spec:
      restartPolicy: Never
      containers:
      - name: post-install-job
        image: "alpine:3.3"
        command: ["/bin/sleep","{{default "10" .Values.sleepyTime}}"]

Hook资源删除

Helm支持2种删除hook resource策略:

  • hook-succeeded
  • hook-failed

当使用"helm.sh/hook-delete-policy" 注释(annoation)时,删除hook resource支持2种策略:"hook-succeeded" 与 "hook-failed"。当删除策略为 "hook-succeeded"时,hook执行成功后,该hook会被Tiller删除。而删除策略为 "hook-failed"时,hook在执行过程中失败后,该hook会被Tiller删除。

Hook resource删除策略举例:

  annotations:
    "helm.sh/hook-delete-policy": hook-succeeded

 

利用Hook处理服务启动顺序依赖

所谓服务依赖指的是启动一个服务,依赖于另外服务。这个时候我们就需要设置服务依赖关系来处理了。可以通过Helm hook来实现服务启动依赖的处理:

举例:serviceA服务依赖serviceB, serviceA的pre-install hook中实现

apiVersion: batch/v1
kind: Job
metadata:
  name: "{{.Release.Name}}"
  labels:
    chart: "{{.Chart.Name}}-{{.Chart.Version}}"
  annotations:
    "helm.sh/hook": pre-install
    "helm.sh/hook-weight": "-5"
spec:
  template:
    metadata:
      name: "{{.Release.Name}}"
      labels:
        chart: "{{.Chart.Name}}-{{.Chart.Version}}"
    spec:
      restartPolicy: Never
      containers:
      - name: pre-install-job
        image: "registry.docker.dev.fwmrm.net/busybox:latest"
        command: ['sh', '-c', "curl --connect-timeout 3 --max-time 5 --retry 10 --retry-delay 5 --retry-max-time 60 --retry-connrefused serviceB:portB/pathB/"]

Helm hook的是一种串行且阻塞式操作(blocking operation),所谓阻塞指的同一个chart,在同一个时刻只有一个hook执行,其他hook以及release的生命周期的其他行为活动(helm install, helm upgrade, helm rollback等)都会被阻塞(block),而串行指的是只有当一个hook成功执行完毕才会执行其他hook。

以上面的pre-install hook的例子说明,pre-install hook的job会先于helm install 执行,且在pre-install hook的job执行过程中,其他的release周期活动已经其他hook都不会被执行。当且仅当serviceB:portB/pathB/在指定时间内服务可达时,pre-install hook才会成功执行,余下的hook以及release其他操作才可以继续执行。

把被依赖的服务是否启动的逻辑判断,放在依赖服务的pre-install hook中,利用Helm hook的是一种串行且阻塞式操作(blocking operation)的特性,如果hook执行失败,那么对应的一次release也就失败了,这就达到了服务启动依赖的处理效果。

 

欢迎转载,请注明作者出处:张夏,FreeWheel Lead Engineer,Kubernetes中文社区