最近,已弃用的 API 一直在破坏大家 Kubernetes 清单。 为什么会这样呢?!? 这是因为我们已经熟悉和喜爱的对象正在迁移到它们的新家。 这并非一蹴而就。 弃用警告已经存在相当多的版本了。 我们都只是太懒了,认为那一天永远不会到来。 但是,它真的来了!
所以,也许这次我们被它抓住了。 但下次我们会做好准备,对吧?!? 是的,上次我们也是这么说的。 但是,如果我们能采取一些措施来确保这种情况不再发生呢?
什么是 Deprek8?
Deprek8 是一组 Open Policy Agent (OPA) 策略,可让您检查存储库中已弃用的 API 版本。 这些策略提供了一种在某些内容正在被弃用或已被弃用时提供警告和错误的方法。 但 Deprek8 只是一组定义要监视什么的策略。 您实际上如何主动使用这些策略来监视弃用呢?
有很多方法和工具可以做到这一点; 一种方法是使用 OPA Deprek8 策略。
什么是 OPA Deprek8 策略?
OPA 是“一个开源的、通用的策略引擎,可以实现统一的、上下文感知的策略执行”。 换句话说,OPA 提供了一种基于策略文件建立和执行一组策略的方法。 这些策略在文件中(或一组文件中)使用 Rego 查询语言 定义。 此用例不一定依赖于 OPA 应用程序,但更具体地说,它使用此查询语言来完成繁重的工作。 通过使用 Rego,您可以检查各种清单是否符合某些标准,然后根据您的定义发出警告或错误。 例如,在 Kubernetes 1.16 中,Deployment 对象不能再从 extensions/v1beta1 apiVersion 提供服务。 因此,在您的 .rego 文件中,您可以添加类似以下内容的代码
_deny = msg {
resources := ["Deployment"]
input.apiVersion == "extensions/v1beta1"
input.kind == resources[_]
msg := sprintf("%s/%s: API extensions/v1beta1 for %s is no longer served by default, use apps/v1 instead.", [input.kind, input.metadata.name, input.kind])
}
这将警告您有一个已弃用的清单,并打印一条类似以下的消息
Deployment/myDeployment:Deployment 的 API extensions/v1beta1 不再默认提供服务,请改用 apps/v1。
太棒了! 这正是您避免旧清单到处乱放所需要的。 但这些只是策略; 您需要一些东西来检查这些策略并将它们付诸行动。
Conftest
这就是 Conftest 发挥作用的地方。 Conftest 是一个实用程序,允许您针对任意数量的配置文件执行 Rego 策略。 根据该存储库,Conftest 目前支持
- YAML
- JSON
- INI
- TOML
- HOCON
- HCL
- CUE
- Dockerfile
- HCL2 (Experimental)
- EDN
- VCL
- XML
它有一些相当严格的默认设置(即,期望策略文件位于特定位置),但如果您有自己喜欢的布局,可以使用适当的标志覆盖它们。 如果您想了解更多关于这些细节的信息,请查阅存储库中的 文档。
例如,您可以使用类似以下的命令在 Conftest 上运行任何策略文件
helm template --set podSecurityPolicy.enabled=true --set server.ingress.enabled=true . | conftest -p mypolicy.rego -
这将从 Helm 模板生成适当的输出,并将其直接管道传输到 Conftest 实用程序。 Conftest 根据 mypolicy.rego 文件中定义的任何策略检查该输出,然后为与这些策略匹配的对象提供任何适当的警告或错误。 当然,您可以替换您选择的任何模板工具,或者您可以将特定文件直接馈送到 Conftest 工具。
现在您有了设置策略并针对配置文件执行策略的工具。 但是,您如何将这两件事联系起来呢? 更好的是:您如何自动化此过程以持续监控代码库,以确保您永远不会落后于弃用线呢?
使用 Git 运行检查
有很多方法和工具可以针对代码运行检查。 通过将类似的步骤添加到您的持续集成 (CI) 工具(例如,Jenkins、Tekton 等),您可以实现相同的目标。 在这个非常基本的用例中,我使用了 GitHub Actions,这是 GitHub 存储库的一项新功能。
GitHub Actions 允许您自动化整个工作流程,因此您不必坐在键盘前将所有这些拼凑在一起。 使用 Actions,您可以通过滚动自己的 Actions(如果您正在做一些自定义的事情),或者在大多数情况下,使用 Marketplace 中已经存在的东西,将任意数量的步骤串联成一个工作流程(或多个工作流程)。 幸运的是,其他人已经提供了 Actions 来完成此示例中您需要做的事情,因此您可以依靠社区的专业知识来将您的工作流程组合在一起。
如上述步骤所述,工作流程看起来像这样
- 检索您需要的 Deprek8 策略并将其存储在某处以供以后使用。
- 使用您在步骤 1 中获取的策略文件,针对适当的文件/图表运行 Conftest。
这归结为哪些内容? 嗯,您真正需要做的就是使用 curl 拉取您的策略文件,然后在指向您的代码后通过 Conftest 运行它,使用 curl 和 Conftest Actions。 由于这些 Actions 已经存在,因此您无需编写任何自定义代码! 而且我相信您可以从名称中看出,它们允许您运行关联的命令,而无需进行任何自定义工作来预处理任何内容或拉取任何二进制文件。
现在您有了需要使用的 Actions,您如何将它们组合在一起呢? 这就是您的工作流程发挥作用的地方。 虽然 Actions 是完成工作的代码片段,但如果没有一种将它们串联起来以便可以通过某些事件触发它们的方法,它们就毫无用处。 GitHub Action 工作流程将如下所示
name: Some Awesome Workflow Name
on: An Event That Triggers Our Workflow
jobs:
awesome-job-name:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: awesome-step-name
uses: someorg/someaction@version
with:
args: some args that I might pass to someaction
现在您有了一个工作流程,它具有多个步骤,可以由特定的 GitHub 事件触发,并且可以传递一组参数(如果这适用于该特定 Action)。 此示例非常基础。 但幸运的是,您尝试组合的工作流程同样简单。 这不应被视为 GitHub Action 的全面示例,因为您可以做更多复杂(和优雅)的事情。 如果您有兴趣了解更多信息,请查看 GitHub Actions 文档。
现在您对工作流程的外观有了一个概念,并且知道您有兴趣使用哪些 Actions,请尝试将两者结合在一起。 对于此示例,您希望确保每次更新代码时,都会检查它以确保它没有使用任何已弃用的 API。
首先,使用一些名称和您想要触发的事件来设置您的工作流程。 为您的工作流程和作业提供一个有用的名称,这将有助于您识别它(以及它所做的事情)。
name: API Deprecation Check
on: pull_request, push
jobs:
deprecation-check:
接下来,您需要告诉您的工作流程,您希望基于对该存储库发生的任何 pull_request 或 push 来触发这些 Actions,因为这是将新代码引入存储库的两个主要事件。 您可以使用 on 关键字来执行此操作。
name: API Deprecation Check
on: pull_request, push
jobs:
deprecation-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
然后,添加您希望这些 Actions 运行的位置以及 Action 如何获取代码。 您可以使用 runs-on 关键字告诉 Action 在哪里运行。 您在这里有几个选项:Windows、Mac 或 Ubuntu。 在大多数情况下,使用 Ubuntu 就可以了,因为您通常会依赖在自己的容器内运行的 Actions(而不是在您在此处定义的基础操作系统上运行)。 同样非常重要的是要理解 Action 默认情况下不会检出代码。 当您需要执行与代码交互的操作时,请确保使用 Action actions/checkout。 当包含此项时,您的代码将在您的 Action 中可用,您可以将其传递到工作流程的下一步。
name: API Deprecation Check
on: pull_request, push
name: API Deprecation Check
jobs:
deprecation-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: curl
uses: wei/curl@master
with:
args: https://raw.githubusercontent.com/naquada/deprek8/master/policy/deprek8.rego > /github/home/deprek8.rego
现在您的代码已检出,您可以开始准备使用它做一些事情了。 如前所述,在您可以检查代码中是否存在弃用之前,您首先需要包含您要检查的策略的文件,因此只需使用 curl Action 检索该文件即可。 这是一个非常简单的 Action,因为它接受您通常传递到 curl 命令中的任何参数。 如果您正在做一些更复杂的事情,您可以在这里传入特定的 HTTP Actions、标头等内容。 但是,在本例中,您只是尝试检索文件,因此您需要传递给 Action 的唯一内容是您要检索的 URL(在本例中,是包含您的原始策略文件的 URL),然后告诉它您要将该文件写入的位置。 在本例中,您将让它写入 /github/home。 为什么? 这是因为此文件系统在步骤之间持久存在,并且允许您在此下一步中使用策略文件。
name: API Deprecation Check
on: pull_request, push
jobs:
deprecation-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: curl
uses: wei/curl@master
with:
args: https://raw.githubusercontent.com/naquada/deprek8/master/policy/deprek8.rego > /github/home/deprek8.rego
- name: Check helm chart for deprecation
uses: instrumenta/conftest-action/helm@master
with:
chart: nginx-test
policy: /github/home/deprek8.rego
现在您有了策略文件,只需通过 conftest 针对代码运行它即可。 与 curl Action 类似,conftest Action 只是期望一系列参数来了解它应该如何针对代码运行。 在上面的示例中,它针对 Helm 图表运行,但可以通过将 uses 值更改为 instrumenta/conftest-action@master 来针对特定文件(或一组文件)运行。 只需指向您的图表在存储库中的路径,然后提供您的策略文件的路径(在上一步中指定)。 一旦您将所有这些组合在一起,您就拥有了一个完整的工作流程。 但是,如果您的 Helm 图表中存在一些错误代码,它会是什么样子呢? 要了解答案,请查看 示例存储库。
在 Nginx Helm 图表中,您会注意到其中一个模板是 statefulset。 您可能还会注意到 StatefulSet 使用的 apiVersion 是 apps/v1beta1。 此 API 已在 Kubernetes 1.16 中弃用,现在托管在 apps/v1 中。 因此,当您的 GitHub Actions 工作流程运行时,它应该检测到此问题并提供类似以下的错误
FAIL - StatefulSetf/web: API apps/v1beta1 is no longer served by default, use apps/v1 instead.
Error: plugin "conftest" exited with error
##[error]Docker run failed with exit code 1
Action 指示存在问题,然后使 Action 的其余部分失败。 如果您有兴趣,可以查看 完整的工作流程。
总结
此工作流程将通过提醒您注意任何潜入代码库的已弃用 API,从而在未来避免一些痛苦。 需要明确的是,这是一种提醒机制。 这不会阻止您将错误的代码合并到您的代码库中。 但是,只要您注意,您应该在合并问题代码之前(或之后不久)完全意识到这一点。
接下来该怎么做? 嗯,有几件事要记住。 目前,Deprek8 已更新至 Kubernetes 1.16。 如果您对更新的版本感兴趣,我相信 Deprek8 很乐意接受您的 pull request。
此方法的另一个缺点是 conftest 和 GitHub Actions 有点受限,因为它们只允许您指向特定文件或一次指向单个图表。 如果您想指向多个清单目录或在存储库中包含多个图表怎么办? 目前,解决此问题的唯一方法是列出您感兴趣的每个文件(在有多个图表的情况下)或在工作流程中包含多个步骤。 其他场景可能会变得有问题,例如其他需要一些自定义逻辑才能将参数和模板文件配对在一起的模板引擎。 但是,一个简单的解决方法是在您的工作流程中添加一个步骤,该步骤拉取 Conftest 以及一个微小的内联脚本来循环遍历其中的一些内容。 我相信有更优雅的解决方案(如果您想出了一个,我相信这些项目非常乐意查看您的 PR)。
无论如何,您现在拥有了一种机制,可以让您在签入代码时睡得更安稳! 而且希望这种方法将帮助您构建更强大的工作流程来保护您的代码。
本文最初发表在 Tyler Auerbeck 的 GitHub 存储库 中,并经许可进行编辑后重新发布。
评论已关闭。