
在我们的实践中,我们经常面临使客户端应用程序适应在Kubernetes上运行的任务。 在进行这些工作时,会出现许多典型的问题。 最近,我们
在将应用程序转移到Kubernetes时的本地文件一文中介绍了其中的
一个 ,另外一个已经与CI / CD进程相关联的文章将在本文中进行介绍。
Helm和werf的任意命令
应用程序不仅是业务逻辑和数据,而且是成功执行更新必须执行的一组任意命令。 例如,这些可以是数据库的迁移,外部资源可用性的“等待者”,某些代码转换器或拆包器,外部服务发现中的注册商-您可以在不同的项目上完成不同的任务。
Kubernetes提供什么解决这些问题?
Kubernetes擅长将容器作为Pod运行,因此标准解决方案是从映像运行命令。 为此,Kubernetes中有一个
Job原语 ,它允许您使用应用程序容器运行pod并监视此pod的完成。
Helm走得更远,建议在部署过程的不同阶段启动Job。 我们谈论的是
Helm挂钩 ,您可以在更新资源清单之前或之后运行Job。 根据我们的经验,这是一项出色的Helm功能,可用于解决部署任务。
但是,无法在Helm中进行卷
展期间获取有关对象状态的最新信息,因此我们使用
werf实用程序,它可以直接在CI系统中监视卷展期间的资源状态,如果未成功,则可以快速诊断故障。
事实证明,Helm和werf的这些有用功能有时是互斥的,但总有出路。 考虑如何监视资源状态并在迁移示例中运行任意命令。
发布前运行迁移
任何数据库应用程序发行版中不可或缺的一部分就是更新数据模式。 通过运行单独的命令来应用迁移的应用程序的标准部署包含以下步骤:
- 代码库更新;
- 开始迁移;
- 将流量切换到应用程序的新版本。
在Kubernetes中,过程应该相同,但根据我们的需要进行了调整:
- 使用新代码启动一个容器,其中可能包含一组新的迁移;
- 在更新应用程序的版本之前,请先执行在其中应用迁移的过程。
当
应用程序的数据库已经在运行并且我们不需要将其作为部署应用程序的发行版的一部分进行部署时
,请考虑使用该选项。 两个钩子适用于应用迁移:
pre-install
-在处理所有模板之后但在Kubernetes中创建资源之前,它可以在应用程序的第一个Helm版本中使用;pre-upgrade
upgrade-在处理模板之后,在Kubernetes中创建资源之前,在更新Helm发行版并运行时(如pre-install
有效。
使用Helm和提到的两个挂钩的工作示例:
--- apiVersion: batch/v1 kind: Job metadata: name: {{ .Chart.Name }}-apply-migrations annotations: "helm.sh/hook": pre-install,pre-upgrade spec: activeDeadlineSeconds: 60 backoffLimit: 0 template: metadata: name: {{ .Chart.Name }}-apply-migrations spec: imagePullSecrets: - name: {{ required ".Values.registry.secret_name required" .Values.registry.secret_name }} containers: - name: job command: ["/usr/bin/php7.2", "artisan", "migrate", "--force"] {{ tuple "backend" . | include "werf_container_image" | indent 8 }} env: {{ tuple "backend" . | include "werf_container_env" | indent 8 }} - name: DB_HOST value: postgres restartPolicy: Never
注意 :上面的YAML模板是在考虑werf细节的基础上创建的。 要使其适应“干净”头盔,就足够了:- 替换
{{ tuple "backend" . | include "werf_container_image" | indent 8 }}
{{ tuple "backend" . | include "werf_container_image" | indent 8 }}
{{ tuple "backend" . | include "werf_container_image" | indent 8 }}
到您需要的容器映像; - 删除行
{{ tuple "backend" . | include "werf_container_env" | indent 8 }}
{{ tuple "backend" . | include "werf_container_env" | indent 8 }}
{{ tuple "backend" . | include "werf_container_env" | indent 8 }}
,它在env
键中指定。
因此,需要将该Helm模板添加到
.helm/templates
目录中,该目录已经包含其余的发行资源。 当
werf deploy --stages-storage :local
,将首先处理所有模板,然后将它们加载到Kubernetes集群中。
在发布过程中开始迁移
上面的选项表示在数据库已经运行的情况下使用迁移。 但是,如果我们需要对应用程序进行分支审核,并且
数据库与该应用程序一起发布在一个发行版中,该怎么办?
注意 :如果将“服务”与包含数据库IP地址的端点一起使用,则在生产环境中部署时可能会遇到类似的问题。在这种情况下,
pre-install
和
pre-upgrade
钩子不适合我们,因为应用程序将尝试将迁移应用到
尚不存在的数据库。 因此,有必要在发布
后进行迁移。
使用Helm时,由于它
不监视应用程序
的状态,因此是可以实现的任务。 在Kubernetes中加载资源后,后钩
总是会触发:
post-install
-在第一个发行版中将所有资源加载到K8中之后;post-upgrade
后-更新发行版时更新K8中的所有资源后。
但是,如上所述,
werf在发布期间
具有资源跟踪系统 。 我将对此进行更详细的介绍:
- 为了进行跟踪,werf使用了kubedog库的功能,我们已经在博客中谈到了这一功能。
- werf中的此功能使我们可以唯一地确定发布状态,并在CI / CD系统的界面中显示成功或不成功完成部署的信息。
- 如果没有收到此信息,就无法谈论发布过程的任何自动化,因为在Kubernetes集群中成功创建资源只是其中一个阶段。 例如,由于配置错误或网络问题,该应用程序可能无法启动,但是要在
helm upgrade
后看到此消息,您将必须执行其他步骤。
现在回到迁移在Helm post-hook上的应用。 我们遇到的问题:
- 在以一种或另一种方式启动之前,许多应用程序会检查数据库中电路的状态。 因此,如果没有新的迁移,该应用程序可能无法启动。
- 由于werf默认情况下会确保所有对象都处于“
Ready
状态,因此后挂钩将不起作用,并且迁移将失败。 - 可以通过附加注释禁用跟踪对象,但是无法获得有关部署结果的可靠信息。
结果,我们得出以下结论:
- 作业是在主要资源之前创建的,因此无需使用Helm挂钩进行迁移 。
- 但是,必须在每个部署上运行带有迁移的作业。 为此, Job必须具有唯一的名称 (随机名称 ):在这种情况下,对于Helm,这是发行版中的一个新对象,每次都会在Kubernetes中创建该对象。
- 通过这样的启动,不必担心Job会随迁移而积累,因为它们都将具有唯一的名称,并且以前的Job会被新版本删除。
- 进行迁移的作业必须具有检查数据库可用性的init容器 -否则我们将删除部署(Job将落在init容器上)。
产生的配置如下所示:
--- apiVersion: batch/v1 kind: Job metadata: name: {{ printf "%s-apply-migrations-%s" .Chart.Name (now | date "2006-01-02-15-04-05") }} spec: activeDeadlineSeconds: 60 backoffLimit: 0 template: metadata: name: {{ printf "%s-apply-migrations-%s" .Chart.Name (now | date "2006-01-02-15-04-05") }} spec: imagePullSecrets: - name: {{ required ".Values.registry.secret_name required" .Values.registry.secret_name }} initContainers: - name: wait-db image: alpine:3.6 ommand: ["/bin/sh", "-c", "while ! nc -z postgres 5432; do sleep 1; done;"] containers: - name: job command: ["/usr/bin/php7.2", "artisan", "migrate", "--force"] {{ tuple "backend" . | include "werf_container_image" | indent 8 }} env: {{ tuple "backend" . | include "werf_container_env" | indent 8 }} - name: DB_HOST value: postgres restartPolicy: Never
注意 :严格来说,最好使用用于检查数据库可用性的init容器。所有部署操作的通用模板示例
但是,在发布期间需要执行的操作可能不止已提到的迁移的启动。 您不仅可以通过钩子的类型来控制Job的执行顺序,还可以通过
为每个钩子
分配权重来控制它们 -通过注释
helm.sh/hook-weight
。 挂钩按权重升序排序,如果权重相同,则按资源名称排序。
有了大量的Jobs,为Jobs创建通用模板并将配置放在
values.yaml
中
values.yaml
。 后者可能看起来像这样:
deploy_jobs: - name: migrate command: '["/usr/bin/php7.2", "artisan", "migrate", "--force"]' activeDeadlineSeconds: 120 when: production: 'pre-install,pre-upgrade' staging: 'pre-install,pre-upgrade' _default: '' - name: cache-clear command: '["/usr/bin/php7.2", "artisan", "responsecache:clear"]' activeDeadlineSeconds: 60 when: _default: 'post-install,post-upgrade'
...,模板本身是这样的:
{{- range $index, $job := .Values.deploy_jobs }} --- apiVersion: batch/v1 kind: Job metadata: name: {{ $.Chart.Name }}-{{ $job.name }} annotations: "helm.sh/hook": {{ pluck $.Values.global.env $job.when | first | default $job.when._default }} "helm.sh/hook-weight": "1{{ $index }}" spec: activeDeadlineSeconds: {{ $job.activeDeadlineSeconds }} backoffLimit: 0 template: metadata: name: {{ $.Chart.Name }}-{{ $job.name }} spec: imagePullSecrets: - name: {{ required "$.Values.registry.secret_name required" $.Values.registry.secret_name }} initContainers: - name: wait-db image: alpine:3.6 ommand: ["/bin/sh", "-c", "while ! nc -z postgres 5432; do sleep 1; done;"] containers: - name: job command: {{ $job.command }} {{ tuple "backend" $ | include "werf_container_image" | indent 8 }} env: {{ tuple "backend" $ | include "werf_container_env" | indent 8 }} - name: DB_HOST value: postgres restartPolicy: Never {{- end }}
这种方法使您可以快速将新命令添加到发布过程中,并使可执行命令列表更加直观。
结论
本文提供了模板示例,这些模板使您能够描述在发布新版本的应用程序期间需要执行的常见操作。 尽管它们是在数十个项目中实施CI / CD流程的经验的结果,但我们并不坚持针对所有任务只有一种正确的解决方案。 如果本文中描述的示例不能满足您项目的需求,我们将很高兴看到注释中的情况有助于补充本材料。
werf开发人员的评论:
将来,werf计划引入用户可配置的资源部署阶段。 在这样的阶段的帮助下,不仅可以描述两种情况,而且可以描述两种情况。
聚苯乙烯
另请参阅我们的博客: