在2022年我们终于完成了主要业务系统上K8s的计划,在这里总结下我们上K8s时候的模版工程。
前提条件
本文不讨论K8s是什么,什么是容器化,为什么需要容器化,什么是微服务等这些基础内容,这些到处说的烂大街了。此类内容有兴趣可以看看微软系的介绍:
本文假设你已经能对你应用进行docker打包,并正确推到docker仓库里。本文假设你docker打包是利用azure DevOps pipeline进行且有使用buildId作为tag。
这里主要讨论的是面向批量项目上K8s的时候使用的发布模版,更多偏向于“管理”特性,不会过多解释类似K8s等的相关概念。
为什么需要模版化部署
如今,微服务或容器化逐渐成为主流,而使用K8s基本上已成为每个有志之士的应用现代化目标。然而,容器本身虽然在本地调试中比较方便,但一旦作为正式应用部署到远程环境中,常常会出现各种问题。这些问题主要源于Dev和Ops之间的边界,因此,DevOps的重要性日益凸显。
运维提供了K8s环境,就认为自己的工作完成了;而开发只提供了在本地运行的容器应用,也认为自己的工作完成了。
然而,我注意到有一些人直接在各个环境中编写yaml文件并将其推送到线上。这是否意味着测试环境和线上环境是相同的?环境变量是否有统一控制机制?机密信息是否得到了适当处理?资源分配如何确定?HPA的配置如何确定?等等。
因此,我认为解决自动化版本发布的模板化问题是使用K8s的重要问题。我们需要有一个基础的模版,里面配置好了大多数预制情况,然后根据有限的参数,对个别变量标记的地方进行替换。
我们自己在进行该项目的时候也查询了不少资料,许多资料更多讨论的是每一个步骤的细节,如果是以有限的项目为目标的话,怎么干都行,但是如果是批量项目要上的话,那么“统一管理”,“模版化部署”我认为还是很重要的。
最终实现的效果就是我需要发布一个k8s的应用的时候,只需要填写少数有关变量即可,如下图
如果有什么需要修改的基础配置,只需要修改模版即可
如何设计模版
首先找到helm挺符合要求的,然后我们就基于helm作为模版进行操作。
注意:使用helm的话要么需要你任务步骤里需要添加helm installer步骤,要么你预先在你的build agent里安装helm,然后通过demand标记使其能正确分配到有helm的agent里,我们使用的后者,所以我们步骤里没有helm的安装步骤,如果不进行此操作可能会导致bake步骤的失败。
我们的项目模版可以参考github里的地址:
https://github.com/virtualcca/k8s-template
其中为了区分本身helm的变量与法,我把需要进行一些替换操作的使用[[xxx]]标记,需要对这些自己处理下操作后理论上就可以拿来用或者视情况调整下values.yaml的值。
我们的模版主要根据以下几个参数来进行区分和替换:
- prodType:区分环境(demo或者prod线上等)
- deploymentType:给线上资源划分几个SKU,我们自己定义的分别是small/normal/large/work。默认用normal,后续监控资源长期很小的调整到small,核心的站点用large,有一些处理消息队列的我们称之为work的使用work的sku
- appType:区分app类型,默认都是app(应用),另外有效取值是dapr和function(我们有dapr的应用,另外也有将Azure Function进行本地化部署,所以将这些需要单独标记出来)
- ingress:我们归类上就分了2个,对内,和对外,对外的需要支持https
同时我们模版里默认引用了一个叫default-conf的configmap配置,里面设置了一定要有的一些默认配置信息,比如时区要是东八区这种。
另外说到模版的话应用也是有一定程度需要配合,我们通过https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks 项目给我们每个站点都增加了健康检查(通过我们内部的基础底层框架)
分别定义了/heartbeat和/healtcheck,分别映射到Liveness和readiness,站点上一般会把这些映射到自己核心依赖的数据库检查(select 1),如果短暂错误先下线避免脏数据(readiness),如果持续性错误则尝试重启或者漂移到别的Node重启(liveness)
如何使用Azure DevOps实现模版化部署
注:后续会使用到一个叫“任务组”的功能,详情可以参考这里对任务组的解释 https://learn.microsoft.com/zh-cn/azure/devops/pipelines/library/task-groups?view=azure-devops
步骤1:创建仓库并存入模版
首先需要在Azure Devops中创建一个Azure Repo,并将需要用于部署的Kubernetes模版存入该仓库。
步骤2:创建管道实现自动打包
接下来需要创建一个管道,使用一个简单的yaml做为管道实现模版的变化自动打包。
可以参考Github仓库中的https://github.com/virtualcca/k8s-template/blob/main/azure-pipeline.yaml
这样每次模版变动后就会产生Artifacts,可用于后续正式发布流程使用。
步骤3:处理K8s发布有关的操作
在发布里,随便新建一个发布,添加3个”部署到Kubernetes”的任务。
在“操作”里,分别从上到下选择create secret, bake, deploy
步骤4:上面3个步骤相关信息填写好
主要是Kubernetes服务链接,命名空间,Docker注册表服务连接等
注意create secret里的机密类型选择dockerRegistry,机密名称随意但是后续deploy里有个ImagePullSecrets里面也要填写一样的名称
bake步骤里的“重写”可以把模版里暴露的相关配置在这里填写上,格式如同 key1:value1的格式,一行一个配置
步骤5:选中刚才3个任务,组成任务组
步骤6: 将任务组里的变量参数化
重新review下刚才填写的东西,不少是重复的,比如命名空间3个步骤里都有,包括bake步骤里重写的信息,类似这种我们应该统一变量控制。
具体操作是将这些信息改为 $(变量名) 。
但是改了后会发觉引用这个任务组里是没有出现可配置的变量信息(主要体现在bake步骤的“重写”里的那一部分)。
此时需要使用一个bash步骤相当于强制告诉Azure Devops我有这些变量。
然后你任务组里就会有这些变量名称,稍微完善下注释和为了便捷性提供下大多数情况的默认值
步骤6: 将bake渲染的yaml给deploy步骤使用
在bake步骤后新建一个bash步骤,填入
echo "##vso[task.setvariable variable=mainfest]$KUBERNETESMANIFEST2_MANIFESTSBUNDLE"
然后deploy里的“清单”填写$(mainfest)
注:在任务组里不能直接使用bake通过“输出变量”的方式使用,这里使用了一种曲线救国的办法。
步骤9: 解决一个mainfest变量问题
到这里K8s步骤核心的任务组基本配置完毕,我们保存,然后新建一个发布定义,引用这个任务组,我们来使用它吧。
然后使用的时候会发现莫名其妙多了一个叫mainfest的变量需要你输入,而且还是必填项,不知道的你随便输入一点东西比如几个空格,然后会发觉无法使用。
解决该问题需要对之前创建的任务组进行导出下载,然后在其中寻找inputs节点的name为mainfest的变量,并将其删除。之后再导入即可。
不过这个操作方法挺繁琐的,主要是导入后它不是“更新”你原来有的,而是“新建”了一个任务组,导致如果你有引用原来那个任务组的话,都要重新操作配置,由于这个原因,所以建议K8s的那3个步骤作为一个单独的任务组,把该暴露的变量配置都暴露出来,然后通过另外的任务组去引用这个,另外的任务组在去执行上面说的下载工件等操作(因为这个会有扩展的,比如我们后续用的Azure Function的部署步骤,下载的工件就是不一样的)
整个调整后的导出的模版可以参考github里的我们的这个版本: https://github.com/virtualcca/k8s-template/blob/main/K8S-Internal.json
步骤10:拆分任务组
为避免繁琐的操作,建议将K8s的三个步骤作为一个单独的任务组,将该暴露的变量配置都暴露出来,然后通过另一个任务组去引用该任务组并执行下载工件等操作。
最终得到的任务组可以用于快速的K8s批量化部署模版。
步骤11: 新建最终版本任务组
新建一个新的任务组,在第一个步骤里使用DownloadBuildArtifacts步骤,以下载Artifacts的形式获取到我们最前面的模版。
最后
最终新构建的任务组就是片头图里的那个,以后要部署K8s的应用只需要简单的使用这个任务组并填写有限几个必填变量,即可完成应用线上部署需要,最大程度简化流程并且也能实现流程统一化
未来如果有什么需要调整的,也能基于模版的形式统一调整
流程解析
- 在Azure Repo里存储我们的yaml模版,便于统一的配置管理,模版变更后会自动打包工件(使其每次发布用的工件都是最新的)。
- 通过任务组的形式组织各个步骤,使得配置发布的人只需要引用一个步骤,在填写相关有限的变量配置即可,最大程度简化发布的配置过程。
- 整个步骤流程是 下载工件->创建凭据(create secret)->烘焙(bake)->部署(deploy)
- 后续可以通过修改相关模版配合使用其他的方法形式(比如配合Azure Function的时候模版是不一样的,那么下载工件引用的项目就不同了)