Azure Devops上模版化K8s部署

Posted LeoLaw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Azure Devops上模版化K8s部署相关的知识,希望对你有一定的参考价值。

在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的时候模版是不一样的,那么下载工件引用的项目就不同了)
  •  

Azure DevOps 在发布期间获取部署代理状态

【中文标题】Azure DevOps 在发布期间获取部署代理状态【英文标题】:Azure DevOps get Deployment Agent status during release 【发布时间】:2019-10-07 14:22:57 【问题描述】:

我正在尝试在发布时获取部署池中代理的状态。 用例是我有 2 台带有共享磁盘的服务器,我希望该版本仅在一台服务器上运行。我有两个基于自定义条件运行的部署组:

eq(variables['DeployGroupSelector'], '1')

在确定 DeployGroupSelector var 值的作业之前运行的作业,本质上是一个 case 语句。

在设置 var 的工作中,我正在尝试联系 Azure DevOps REST API:

$headers = @
    Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"


$url = "https://dev.azure.com/$($organization)/_apis/distributedtask/pools/$($poolId)/agents?api-version=5.1"
$response = Invoke-RestMethod $url -Headers $headers -Verbose
write-host "Output: $response"
$status = ($response.value | where $_.name -eq $($env:primaryPoolName)).status
if($status -eq "online")

    Write-Output("##vso[task.setvariable variable=DeployGroupSelector;]1")

else

    Write-Output("##vso[task.setvariable variable=DeployGroupSelector;]2")

对于包含上述脚本的组,选中“允许脚本访问 OAuth 令牌”框。

当我使用 PAT 在本地运行这个 powershell 时,它会返回数据。当我在 ADO 中运行发布时,它会访问服务,但返回一个空数据集:

2019-10-07T14:16:18.8942915Z VERBOSE: GET https://dev.azure.com/xxxxxx/_apis/distributedtask/pools/13/agents?api-version=5.1 with 0-byte payload
2019-10-07T14:16:19.3235204Z VERBOSE: received 22-byte response of content type application/json
2019-10-07T14:16:19.9626359Z VERBOSE: Content encoding: utf-8
2019-10-07T14:16:19.9835101Z Output: @count=0; value=System.Object[]

我已尝试为“项目集合构建服务帐户”组授予对池和组的读取权限,我什至尝试将其授予用户。我尝试添加构建服务帐户组以发布管理员。我什至尝试使用旧的 url 格式以防万一。

添加了powershell返回的数据图片:

更新:为了进一步排除我如何使用令牌的问题,我向相关任务组添加了第二个 powershell 任务。此脚本会命中 AzDO Rest API 的不同部分(如下)。这成功地得到了响应。所以 OAuth 令牌正在工作,它似乎无法访问整个 API。

$标头 = @ 授权 = "承载 $env:SYSTEM_ACCESSTOKEN"

$url = "https://dev.azure.com/$($organization)/$($project)/_apis/git/repositories?api-version=5.1"
$response = Invoke-RestMethod $url -Headers $headers -Verbose
write-host "Output: $($response)"

回复:Output: @value=System.Object[]; count=10

【问题讨论】:

从哪里获得$pooId? 我去了 UI 中的部署池,选择了池,并从 URI 中获取了 ID。也可以通过rest api查询。 我问是因为还有具有不同 id 的代理队列和代理池的 api。我无法重现您的问题,在我的发布中,我得到了响应中的代理。 您能否在此处上传您的 PowerShell 脚本结果的屏幕截图,其 URL 与发布所执行的 URL 相同? 添加图片.. 【参考方案1】:

由于您使用的是 System_AccessToken 变量,您是否还在代理作业中启用了“允许脚本访问 OAuth 令牌”复选框? https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=classic#systemaccesstoken 此链接显示它在构建中的位置,但您会在发布时的代理作业面板底部找到它。如果未选中,这可能就是您得到空响应的原因。

【讨论】:

是的,我启用了这个。 您是否尝试过传入具有池读取权限的 PAT 以尝试隔离是否是权限问题?我知道有时 AzDO 中的某些 API 需要的权限不仅仅是从发布中传入的 PAT(例如某些分布式任务 API 或扩展 API)。 PAT 在作为发布的一部分执行时似乎不起作用。当我在 powershell 中运行它时它工作正常,但是当我将它作为发布的一部分运行时,我得到一些代表登录页面的 html。 嗯,很奇怪,即使您将 PAT 作为参数传递给脚本,它也不起作用?您是在私有代理还是托管代理中运行它? 它是一个托管代理(Azure Pipelines)【参考方案2】:

遇到了完全相同的问题。被认为是两个选项,与您一直在尝试的相同。

    System.AccessToken 拍拍

问题已通过将 PAT 放入 KeyVault 并将其用作管道中 REST API 调用的基本身份验证令牌来解决。

我的建议是,这似乎是预期的和正确的行为。为什么我会这么认为?从 Azure DevOps 的角度来看,我们的案例组织级别和项目级别有两个级别。您可以通过使用的 URI 注意到差异:

$url = "https://dev.azure.com/$($organization)/_apis/distributedtask/pools/$($poolId)/agents?api-version=5.1"

$url = "https://dev.azure.com/$($organization)/$($project)/_apis/git/repositories?api-version=5.1

从安全的角度来看,让来自较低层的实体(在我们的案例中是项目)访问和操作较高层(在我们的案例中是组织)是一种不好的做法。

作为结论,我想说 SystemToken 和 PAT 在本质上略有不同,一个是专门用于代理的,另一个是用于个人资料的。

【讨论】:

以上是关于Azure Devops上模版化K8s部署的主要内容,如果未能解决你的问题,请参考以下文章

Azure DevOps 中 Dapr项目自动部署流程实践

Azure DevOps 在发布期间获取部署代理状态

Azure Devops (VSTS) 代理池和部署池之间的区别

使用 Azure DevOps 部署具有不同名称的 Service Fabric 服务

如何利用Azure DevOps快速实现自动化构建测试打包及部署

Service Fabric、Azure Devops 部署失败:指定的网络密码不正确