原文:
annas-archive.org/md5/ec641f51414ac10506c7857d2f65c8f4译者:飞龙
第五章:集成、交付和部署 — 自动化无处不在
第二章 包括了一个平台的参考架构,突出了诸如 开发者体验、 自动化与编排和 可观察性平面等层次。 第三章 从一个不同的角度结束了该参考架构,采用自上而下的方法,从 目的、 用户界面、 核心平台组件、 平台作为产品以及 成功的 KPI等方面进行了阐述。
大多数平台的构建目的是为了使开发团队能够更容易地发布软件,而无需处理构建、部署、测试、验证、安全、操作、发布或扩展软件的复杂性。 在本章中,我们将深入探讨平台的各个层次和组件,以便理解如何集中化并自动化发布软件所需的专业知识,并将其提供为 自助服务。
在本章结束时,我们将学习如何为软件制品定义端到端的发布过程,将 持续集成/持续部署 (CI/CD)过程与制品生命周期阶段对齐,使用 CI、CD 和持续发布工具自动化这些阶段,将这些工具集成到现有流程中,观察自动化效果,并通过 IDP 实现扩展。
因此,我们将在本章中覆盖以下主要内容:
-
介绍 持续 X
-
GitOps:从推动到拉取期望状态
-
理解容器和制品注册表的重要性,作为 入口点
-
定义发布过程 和管理
-
实现可持续的 CI/CD 流程用于 DevOps — 应用生命周期编排
-
内部开发者平台 (IDP) — 平台中的自动化巨兽
持续 X 介绍
如果这是你第一次听说 关于 持续集成 (CI) 或 持续交付 (CD),那么我们建议你查看一些关于这些基本概念的优秀文献。 Jez Humble 是 https://continuousdelivery.com/ 的维护者,并且是 《持续交付》 一书的共同作者, Dave Farley。如果你需要这个主题的速成课程,请查看他们的资料。 也有一些录制的讲座,提供了很好的概述,比如题为 持续交付听起来很棒,但它行不通 这里: https://vimeo.com/193849732。
为了确保我们有相同的理解,让我们快速回顾一下构建模块是什么以及为什么它在 软件交付中很重要。
持续 X 的高层次定义
自动化软件交付的 基本基础是正确的 配置管理 ,包括构建、部署、验证、操作和扩展我们系统所需的所有资产:代码、测试、部署和基础设施定义、依赖关系、可观测性、所有权等。 将所有这些资产放入版本控制中,可以生成可重复且可靠的输出,提供审计能力,允许我们回滚破坏性更改,并提供灾难 恢复能力。
Git,或任何 类型的 Git 版本,今天最有可能在软件组织中用于版本控制。 根据你使用的 Git 解决方案,你将看到额外的内建功能,如跨团队协作(问题跟踪、解决合并冲突等)、自动化(提交检查、交付流水线等)或报告(效率或 DORA 指标)。
现在我们已经建立了基础,让我们深入探讨 持续 X。
CI
持续集成 是 一种强调频繁和自动化地将代码变更集成到共享代码库中的实践。 当多个开发人员在同一个代码库上工作时,定期将这些变更合并非常重要,以验证代码是否能良好集成,并生成可以部署到环境中的工件(如容器镜像或二进制文件)。 持续集成的关键方面包括 以下内容:
-
自动化构建:代码 提交到版本控制系统时,会触发一个自动化构建过程,该过程会编译代码、运行测试,并且 生成工件
-
测试自动化:单元测试、集成测试以及其他检查会在构建 过程中执行 如果有任何测试失败,构建会被标记为失败 。
-
反馈回路:这为 开发人员提供了快速反馈,以便快速修复问题,从而提高整体代码质量 和 稳定性
持续测试和可观察性的验证
尽管持续集成已经强调了 自动化单元测试和集成测试的重要性,但我们要强调的是,在生命周期早期进行更多自动化测试和验证,将带来更好的质量和稳定性。 假设构建的软件提供了 REST API 或 UI 接口,应该针对这些接口进行基本验证,以确保功能的准确性(例如,API 是否返回正确的响应,使用无效参数时是否返回正确的 HTTP 状态码,API 调用是否有超时,或是否返回任何 HTTP 错误?)
可观察性 对验证系统健康至关重要 ,并提供了更快排查问题所需的数据。 作为持续验证 过程的一部分,我们必须验证已测试的构建是否生成有效且预期的可观察性数据。 我们应该验证是否生成了所有预期的指标、日志或跟踪数据,并且在运行这些基本的单元测试、集成测试或 API 测试后,是否没有明显的异常或离群值。
我们一直在强调, 可观察性必须是现代软件的非功能性要求。 这就是为什么持续集成应该验证是否生成了预期的数据。 如果没有,那么这就像是一个失败的单元或集成测试,你应该将构建 标记为失败!
对于 Financial One ACME 及其关键财务服务,我们应验证 以下内容:
-
API 是否能正确验证调用者的访问控制(例如,不能查询 其他用户的财务数据)?
-
API 是否不会记录任何机密数据,如信用卡号、用户名 或令牌?
-
API 是否能正确生成失败尝试的指标,以便在生产中用于警报潜在的 黑客攻击?
持续交付
在持续交付网站上定义(https://continuousdelivery.com/),“持续交付是将所有类型的变更——包括新特性、配置更改、错误修复和实验——以安全、快速且可持续的方式投入生产,或交到用户手中的能力。”目标是消除 部署中的失败恐惧。 与其进行不频繁的大规模发布,部署应该成为常规操作,持续发生。 此外,采用新的部署模式,如蓝绿部署、金丝雀部署或特性标记,可以进一步降低风险。 这些将在关于部署 与发布的章节中进一步讨论!
持续交付的一些方面如下:
-
自动化部署:来自 CI 的新 工件与其他更改捆绑,并自动部署。 部署定义是声明式的并且受版本控制,因此允许在任何环境中以更可预测、可重复和低风险的方式进行更新。 环境。
-
部署流水线:与 CI 相比,流水线允许更高级别的测试和部署验证。 在此,执行性能、安全性、可扩展性、韧性和用户体验等测试。 这不仅验证单一工件,而是整个部署 变更集!
-
质量门控与晋升:在部署流水线的末尾,所有测试结果充当质量门控 ,然后将变更晋升到下一个环境:从开发 到 质量保证 (QA),从 QA 到暂存,从暂存 到生产。
-
回滚与向前滚动:如果生产环境中的质量门槛未通过,可以通过回滚至先前版本控制的部署配置来触发回滚。 另一种策略是向前滚动,意味着问题被修复, 并且由于自动化部署,修复可以迅速部署,以避免 需要回滚。
持续部署 – 将部署与发布解耦
CD 以自动化和快速的方式部署更改。 然而,仍然存在更改导致失败的风险,需要回滚或向前滚动,正如 前面所解释的那样。
持续部署 更进一步,采用了新的部署模式,倾向于将更改的部署与向最终用户发布新功能集分离。 目前已经成熟的模式如下:
-
蓝绿部署:新的 部署(通常标记为 蓝色)将与现有部署(通常标记为 绿色)并行进行。 通过负载均衡器,可以将流量切换到蓝色。 如果蓝色出现问题,流量可以切换回仍在运行的实例,从而避免了回滚,并最小化了对 最终用户的影响。 如果一切顺利,绿色将变为蓝色,直到下次部署 来临。
-
金丝雀部署:类似于蓝绿部署,但在更精细的层次上。 这是新版本与旧版本并行部署的做法。 首先,它会部署到一小部分用户或一部分流量。 如果一切正常,分阶段的推出将继续,直到所有用户流量都使用新版本。 如果在阶段过程中出现问题,流量将切换回旧版本。
-
特性标志:与旧版本和新版本的负载均衡并排部署不同,特性标志 允许开发者“隐藏”新代码 在开关/切换后面。 在部署期间,新版本会覆盖旧版本,但不会执行隐藏的新代码。 通过细粒度的配置,可以为单个用户、用户类型、地理区域或任何其他服务消费者属性启用特性。 如果某个特性有问题,只需进行一次运行时配置更改,代码就会 重新变为非活动状态。
将部署与发布解耦,使团队能够更好地控制新功能的推出,从而最小化风险。 还有更多关于实现细节以及挑战的内容,但超出了本书的范围。 如果你有兴趣了解更多,可以查看 OpenFeature [1],一个 云原生计算基金会 (CNCF)孵化的项目。 OpenFeature 提供了一种标准的、特性标志管理、供应商无关的方式供开发者实现特性标志。 围绕它的社区也有很多关于渐进式交付的最佳实践,其中包括前面讨论的所有 模式 。
基础设施的持续 X
持续 X 不仅仅与应用程序代码或配置相关。 相同的概念应适用于任何基础设施定义。 作为平台,我们将需要某些基础设施组件。 无论是虚拟机、Kubernetes 节点、负载均衡器、域名系统 (DNS)、文件存储、数据库、虚拟网络、无服务器计算,还是其他任何允许我们运行核心平台以及将通过我们的 平台自服务部署、操作和管理的应用程序的组件。
就像应用程序代码一样,我们希望将基础设施需求配置为代码,进行版本控制,并应用相同的 CI 和持续测试、验证、 和交付。
GitOps 也是近几年出现的一个术语,它专注于自动化根据声明性定义的期望状态,通过 Git 进行版本控制来配置基础设施的过程。 我们将在本章的后续部分更详细地介绍 GitOps。 首先, 让我们从 基础设施即代码 **(IaC)**开始,讨论一些基础知识。
IaC
有许多不同的工具可以实现 IaC ,而且你很可能已经在你的组织中使用了一个或多个工具:Terraform、Ansible、Puppet、Chef、CloudFormation 以及 AWS 云开发工具包 (CDK)仅举几个例子。
这是一个非常简单的 Terraform 代码片段,用于创建一个类型为 c5.large的 EC2 实例:
resource "aws_instance" "finoneacme_demoserver" {
ami = "ami-01e815680a0bbe597"
instance_type = "c5.large"
tags = {
Name = "IaCTFExample"
}
}
这里有一个使用 Ansible 创建 AWS S3 存储桶的例子:
- name: provisioning S3 Bucket using Ansible playbook
hosts: localhost
connection: local
gather_facts: false
tags: provisioning
tasks:
- name: create S3 bucket
S3_bucket:
name: finoneacme_bucket_dev
region: us-east-1
versioning: yes
tags:
name: bucketenv
type: dev
IaC 使我们能够定义我们期望的 IaC 状态。 这段代码可以进行版本控制,类似于应用程序代码,一旦部署,就会实现期望的基础设施配置。 与应用程序代码类似,我们可以使用 以下内容:
-
CI:用来验证我们所有的 IaC 是否有效。 IaC 工具通常具有“干运行”功能,可以验证是否没有错误,所有配置文件是否没有冲突或 依赖问题。
-
测试和部署验证:在 IaC 部署后,我们可以验证我们是否真正达到了期望的状态(例如,确保 EC2 实例已启动并运行,S3 存储桶可以 访问,等等)。
-
回滚或恢复:IaC 让我们可以回滚更改或恢复到先前的版本,因为一切 都被版本控制!
关于 IaC 的更多细节,包括版本控制策略(IaC 的存储位置),作者建议参考现有的书籍和博客,了解 该主题。
Crossplane – 平台和应用的 IaC
基础设施即代码(IaC)不仅仅局限于核心平台服务,它还适用于我们允许开发团队通过平台自服务部署的应用程序。 一个新的应用程序可能需要文件存储、数据库和公共 DNS,或需要部署第三方解决方案;这取决于其虚拟机,而该虚拟机可以从部署的应用程序访问,这些应用程序可能会驻留在 K8s 上。
你可以为 Terraform、Ansible 和 CDK 提供模板,开发人员可以将其添加到自己的代码库中,然后作为其应用程序持续 X 的一部分进行应用。
在云原生领域中,出现了一种工具来覆盖应用程序和基础设施编排,它就是 CNCF 项目 Crossplane [2]。除了提供许多不同的云供应商或甚至 Terraform 的提供程序外,它还提供了 Compositions。 Compositions 是一个用于创建多个托管资源作为单一对象的模板。 这使得平台团队能够为常见的应用程序架构定义这样的模板,然后让应用程序团队仅使用该模板来实例化正确的基础设施并部署 应用程序。
我们在 第二章 中讨论了一个自服务使用案例——自动化性能测试环境的配置。 我们可以定义一个组成模板,使开发团队能够像使用下面示例中的模板一样轻松使用它: 这个例子:
apiVersion: composites.financialone.acme/v1alpha1
kind: PerformanceTestCluster
metadata:
name: ptest-devteam1
spec:
clusterSize: "medium"
targetApp:
repoUrl: "https://financialone.acme/our-app-repo"
targetRevision: "2.5.1"
chart: "ourapp"
loadProfile: "spike-load"
observability: true
notifySlackOnReady: "#devteam1"
leaseTime: "12h"
这个 Composition 的定义是 PerformanceTestCluster 由平台工程团队与那些知道如何安装负载测试和可观察性工具的专家共同创建。 在前面的例子中,一个新的中型 K8s 集群将被配置,所请求的应用程序将使用引用的 Helm chart 进行安装,捕获可观察性数据(例如,配置 Prometheus 和日志抓取),并且将部署负载测试工具,以便能够运行尖峰负载场景。 一旦一切准备就绪,带有环境详情的 Slack 通知将发送给团队。 最后但同样重要的是,该环境将在 12 小时后根据强制性 leaseTime 字段的规定自动关闭。
前面的例子已经展示了将 IaC 集成到我们的持续 X 工作中时的强大功能!
Continuous X 作为我们平台中的系统关键组件
我们可以从许多不同的工具中选择来实施 Continuous X:Jenkins、GitLab、Tekton、Argo CD、Flux、Keptn、Crossplane、Selenium 和 k6,仅举几例。 无论我们选择哪种工具,这些工具需要始终可用、具备弹性并且安全,因为它们是我们平台的支柱。 这些工具与任何支撑我们业务的软件同样重要。 想想 Financial One ACME。 如果开发人员需要发布一个修复,解决其金融软件中的关键生产问题,他们需要 Continuous X 能够完美运行。
为了确保在需要时这些组件能够可用,我们需要应用与我们业务关键应用相同的最佳实践:
-
默认安全:如果攻击者能够进入我们的 Continuous X 工具包,他们将打开进入我们平台管理的任何系统的大门。 由于这一关键性, 第七章 完全致力于构建安全和 合规的产品。
-
测试我们所做的每一个更改:假设我们使用 GitLab 作为我们的 Git 和 CI 工具之一。 我们必须对 GitLab 的部署配置进行版本控制,并通过相同的 Continuous X 流程运行,以验证每个新版本或配置更改。 如果必要,我们将回滚或前进,以防更新 引发问题!
-
部署高可用性:遵循这些工具的部署指南,以确保高可用性。 如果我们有全球分布的团队,我们需要确保将某些组件尽可能部署到离终端用户最近的地方。 另外,考虑零停机时间的升级选项并 遵循这些选项。
-
观察每个组件:每个工具都提供某种类型的遥测数据,用以指示健康状态。 例如,Argo CD 会暴露 Prometheus 指标,用于工作队列长度、Git 请求和同步活动。 这些数据能指示 Argo CD 是否仍然能够正常工作。 持续增加的工作队列深度表明 Argo CD 无法跟上所有请求,需要 进行排查。
-
服务级协议(SLAs)和故障警报:我们——平台团队——必须在最终用户报告问题之前就知道存在问题。 这就是为什么我们需要为每个组件设置 SLAs,并在系统未按预期工作时配置适当的警报。 实现这一点的最简单方法是设置针对每个工具的关键 API 端点的合成检查(例如,通过每五分钟运行一次的合成检查来验证 Jenkins UI 是否响应,这为我们提供了一个提前警告信号,以防 Jenkins 在其他人注意到之前就开始出现问题)。
以下是一个示例仪表板,突出显示了工具的关键健康指标,例如 Argo CD。 同样的原则必须应用于构成我们核心平台能力的所有其他工具!
图 5.1:监控并观察平台中每个工具的情况
平台组件与您的关键应用程序同样至关重要
平台的核心将围绕将更改部署到各种环境展开。 所有支持这些用例的工具——尤其是那些用于连续 X 的工具——需要具有高度可用性、弹性和安全性。 确保将相同的工程最佳实践应用于平台的所有组件!
现在我们已经回顾了连续 X(集成、测试、验证、交付、部署和发布)的核心概念,并讨论了所有配置(代码、部署配置、基础设施、可观测性等)必须遵循相同的原则,接下来是时候探讨如何利用这些概念为通过平台实现自主服务的团队提供支持。
GitOps – 从推送到拉取期望状态的转变
CI/CD 已经存在多年。 《持续交付》一书最初发布于 2010 年——容器(通过 Docker 推广,从 2013 年开始)和容器编排平台(如 Kubernetes,从 2014 年开始)出现之前的几年。
快速推进到 2024 年,当本书首次发布时;我们生活在一个以下情况的世界: 现状是:
-
Git 是事实的源头 。 它包含了所有的内容作为代码:源代码、测试、基础设施和部署定义、可观测性、所有权等。
-
CI/CD 是构建、测试并 打包容器 / 开放容器倡议 (OCI) 镜像 来自源代码 Git 仓库,并将它们发布到工件注册中心(例如,Harbor)。
-
GitOps 持续尝试应用部署 Git 仓库(例如 Helm 图表)中声明的最新期望状态,并将任何额外的配置(例如,观测性)推送到 相应的工具中。
上述描述并不是适用于所有情况的统一模型。 GitOps 在每个组织中的实施方式都会有所不同。 如果你搜索 什么是 GitOps?,你会找到许多不同的变体,如 以下内容:
-
分离 CI 和 CD:CI 负责发布容器,CD 负责发布打包后的工件,如 Helm 图表
-
单一 Git:将一切作为代码(源代码、测试、部署、观测性等)存储在单一的 Git 仓库中
-
推送 GitOps:通过流水线或工作流推送期望的状态,而不是通过 GitOps 操作员 将更改拉入目标环境
在接下来的章节中,我们将重点介绍一种偏向于 拉取 (将配置拉入目标环境 ) 模型,而不是 推送 (从外部工具推送配置到目标环境 ) 模型,该模型可以使用 CNCF 工具(如 Flux 或 Argo CD)实现,如下图所示,该图取自 Codefresh 学习中心的 GitOps:https://codefresh.io/learn/gitops/。
图 5.2:GitOps 流程基础,如 Argo CD 或 Flux 等 GitOps 工具所推广的那样
是否选择使用自动化流水线或工作流推送更改到目标 Kubernetes 环境是由你决定的! 为了让决策更容易,我们将深入探讨每个阶段的更多细节,并了解 在每个阶段中应该遵循的一些最佳实践!
阶段 1 – 从源代码到容器镜像
迁移到 GitOps 并不 改变我们如何从 Git 中版本控制的源代码构建应用程序。 在构建新工件之前,一个好的做法是自动化依赖检查和更新。 例如, Renovate Bot *[3]*工具可以与 Git 集成,并在发现过时的依赖、第三方库或其他依赖项时创建 Pull Requests。 这些工具非常有用。
一旦我们在 Git 中更新了代码, CI 就有了和以前一样的任务:它创建一个工件,最有可能是一个容器镜像,并将其推送到容器注册表! 这个 CI 可以根据需要触发,或者在某些分支的提交、Pull Requests,或者任何其他合理的触发条件下触发。 这是对组织来说非常重要的!
有很多不同的工具可以完成这项工作:Jenkins、GitLab、Azure DevOps、GitHub Actions、Bitbucket Pipelines 等等。 也有各种容器注册表选项。 稍后我们将在本书中讨论容器注册表及其重要性,它们和我们平台的所有组件一样,都是我们 未来平台成功的关键!
一旦 CI 成功编译源代码、执行单元测试,并对代码进行任何额外的质量检查后,便可以将二进制文件打包成容器镜像。 构建容器镜像有很多最佳实践;从只将单个服务打包到容器中、谨慎使用公共基础镜像,到构建尽可能小的镜像。 遵循所有这些规则将提高容器镜像从 CI 到生产环境的成功率。 这里有两个显而易见但非常重要的实践,我们希望在本章提到(更多信息,请参阅之前提到的书籍):
-
finoneacme/fund-transfer:2.34.3,其中finoneacme/fund-transfer是名称,2.34.3是标签。 在你构建镜像时,标签由你决定,但你应该遵循一致的命名规则。 行业中有两种常见的做法:-
MAJOR.MINOR.PATCH。每个次要或修补版本号必须是向后兼容的更改。 结合版本名称latest,这是指向最新可用版本的默认值,你可以方便地访问特定的镜像(例如,X.Y.Z)。 它还允许选择特定次版本的最新修补版本(例如,X.Y)或某个主版本的最新次版本和修补版本(例如,X)。 -
git commit哈希作为版本,而不是跟踪语义版本。 提交哈希 在镜像上也立即告诉我们是哪个源代码提交负责生成了这个容器镜像。 对于我们的镜像,这可能意味着它被标记为 这样:financeoneacme/fund-transfer:d85aaef。
-
-
log4j漏洞。 这就是为什么安全性必须超越 CI 流程中的静态检查,并贯穿整个工件的生命周期。 这一点将在 第七章中进一步探讨 ,该章节深入探讨了构建和运营 安全产品。
现在我们已经将容器镜像上传到我们的 容器注册表中,我们可以在 部署定义中使用它。
第 2 阶段 – 从容器镜像到富含元数据的部署工件
容器镜像现在需要找到进入部署定义的路径。 看 Kubernetes,我们需要一个清单文件来定义我们的部署定义,之后可以将其应用到我们的 K8s 集群。
在 第三章中,我们强调了将 元数据(所有权、可观察性级别、通知)添加到我们的部署文件中,将有利于许多自助服务用例,例如 作为自助服务请求日志 的用例。 因此,除了我们的源代码,我们还需要对我们的部署定义进行版本控制,并且应强制执行最小元数据集。 这使我们的平台自助服务用例(例如,谁部署了哪些服务,属于哪个应用程序)以及遵循一般基础设施最佳 实践(例如,定义请求和资源限制)成为可能,正如你在以下清单中看到的:
mainfest.yaml(Kubernetes 部署文件)
apiVersion: apps/v1
kind: Deployment
metadata:
name: fund-transfer-service
spec:
…
template:
metadata:
annotations:
# team ownership
owner.team: dev-team-backend
app.kubernetes.io/name: fund-transfer-service
# app-context
app.kubernetes.io/part-of: backend-services
app.kubernetes.io/version: d85aaef
# log level
observability.logs.level: info
# log source
observability.logs.location: stdout
…
spec:
containers:
- name: fund-transfer
image: "financeoneacme/fund-transfer:d85aaef"
imagePullPolicy: IfNotPresent
resources:
# specify limits
limits:
memory: "200Mi"
cpu: "2"
requests:
memory: "100Mi"
cpu: "2"
…
部署定义可以是一个清单文件或一组清单文件,因为我们可能还需要额外的 K8s 资源,例如服务或 Ingress 定义。 与我们将清单文件组织在与服务源代码相邻的子文件夹中的做法不同,我们可以选择使用模板化或 打包框架和工具,如 Kustomize [5] 或 Helm [6]。
考虑如何组织和版本控制所有“作为代码”的文件是非常重要的。 有几种策略,每种策略都有优缺点,并且都有很好的文档支持。 如果想深入了解,作者建议阅读关于仓库和目录结构的不同模式。 以下部分只是对你在深入研究这一主题时必须知道的内容的简要概述!
单一仓库与多仓库
在设计 Git 仓库结构时,你有两个常见的选择,业界称之为单一仓库(monorepo) 或多仓库(polyrepo);一个 Git 仓库或多个由团队 或职能划分的仓库:
图 5.3:单一仓库与多仓库——两种模式的优缺点
-
单一仓库:在单一仓库模式中,所有配置文件(代码、基础设施、可观察性等)都存储在一个 Git 仓库中。 这种模式适用于每一个潜在的环境(开发、测试、预发布、生产),也就是说,所有配置都在同一个 Git 仓库中,通过文件夹来区分。
单一仓库的好处是所有内容都集中在一个地方。 缺点是,这个仓库最终会变得非常庞大,当像 ArgoCD 或 Flux 这样的工具需要不断扫描整个仓库以检查变化时,可能会引发性能问题。 它还使得应用程序和基础设施拥有者之间的关注点分离变得更加困难。
-
多仓库:在多仓库模式中,我们有多个仓库,这使得分离关注点变得更加容易。 这些可以是按团队划分的仓库(例如,应用团队 A、B、C),按环境划分的仓库(开发、测试、生产等),按租户划分的仓库(在多租户系统中),或者按组织边界划分的仓库(如应用团队、平台团队等)。
好处在于更好的关注点分离。 缺点是,最终我们会管理大量的 Git 仓库,这使得在所有配置文件中确保一致性和整体有效性变得更加困难。这些配置文件分布在多个仓库中,组成了整个配置。
目录结构 – 遵循 DRY 原则
无论是单一仓库还是多仓库,每个 Git 仓库都需要有良好的目录结构。 通常,我们会看到它反映了组织结构,将开发团队与管理基础设施或平台的团队分开。 一个好的实践是遵循 不重复自己(DRY) [7] 原则。 这个理念的核心是找到最佳结构,以避免在不同位置复制/粘贴常见的 YAML 设置。 这就是像 Helm 和 Kustomize 这样的工具提供帮助的地方。
以下结构示例灵感来源于一篇来自 Red Hat [8] 的 GitOps 博客,展示了平台管理员的配置和应用团队使用像 Kustomize 这样的模板工具的配置。 该结构可以用于一个单一的 monorepo,通过文件夹进行分离,或者按照 polyrepo 模式 进行配置。
| 平台团队 | 开发团队 |
|---|
| ├── bootstrap``│ ├── base``│ └── overlays``│ └── default``├── cluster-config``│``**├──** gitops-controller **│ ├── identity-provider
│ └── image-scanner
└── components
**├──** applicationsets
**├── applications
**└──** argocdproj**** | └── deploy``├── base``└── overlays``├── dev``├── prod``└── stage |
表 5.1:Kustomize 等工具可能使用的目录结构
现在我们已经了解了组织仓库和目录结构的不同方式,接下来需要学习新容器镜像如何从 第一阶段 进入我们仓库中的部署定义!
用新容器镜像版本更新清单
最后一步是 第二阶段 将 CI 构建出的新镜像版本推广到我们的部署文件中。 这可能是更新部署清单,如前面的示例所示,或者在使用 values.yaml 文件时更新 Helm 图表。
由于这些配置文件是版本控制的,存储在 Git 仓库中(无论是单一仓库还是多个仓库,都没有关系),我们应该遵循常规的 Git 工作流,创建一个 Pull Request 来推广镜像的更新。 我们可以通过以下两种方式自动创建该 Pull Request:
-
CI 流水线中的步骤:作为 CI 的最后一步,可以打开一个 Pull Request,将新镜像标签推广到相应的部署定义仓库和正确的目录结构(例如,如果这是一个新构建的镜像,则更新开发覆盖目录中的值)
-
容器注册表中的 Webhook:当注册表接收到 CI 的新镜像时,它可以触发一个 Webhook,允许 我们在部署 Git 仓库中创建相同的 Pull Request
以下是我们在前面示例中看到的 Kubernetes 部署 的更新版本信息。 你可以将其与之前示例中的值进行对比:
apiVersion: apps/v1
kind: Deployment
metadata:
name: fund-transfer-service
spec:
…
template:
metadata:
annotations:
owner.team: dev-team-backend
app.kubernetes.io/name: fund-transfer-service
app.kubernetes.io/part-of: backend-services
app.kubernetes.io/version: a3456bc
observability.logs.level: info
observability.logs.location: stdout
…
spec:
containers:
- name: fund-transfer
image: "financeoneacme/fund-transfer:a3456bc"
…
现在,我们知道了由 CI 构建的新镜像是如何进入我们各自仓库中的部署定义的。 我们之前已经看到过 GitOps 操作符在本章中的图像。 现在,是时候深入了解 GitOps 操作符如何将我们在 Git 中的目标状态应用到我们的 目标集群了!
第三阶段 – GitOps – 保持你期望的部署状态
现在我们已经将所有内容作为代码存储在 Git 中,是时候讨论如何将这些配置应用到目标环境了。 一种方法是使用与 CI 相同的推送模型。 我们可以在 Git 仓库发生更改时触发交付管道,然后将最新版本的清单或 Helm 图表应用到目标环境。 然而,在本章中,我们专注并倾向于使用拉取模型,这由 GitOps 操作员或 GitOps 代理实现,例如 Argo CD、Flux 以及一些 商业产品。
GitOps 操作员简介 – 将 Git 同步到 K8s
GitOps 操作员 持续调和并确保我们在仓库中声明的期望状态与目标环境中实际运行的状态匹配。 如果 Git 中的状态与目标环境中的状态不匹配,操作员会检测到一个 不同步 的情况。 这种情况可能发生在 Git 配置更改时(例如,新的镜像可用并导致清单更新)。 也可能发生在有人修改了 K8s 中的配置时(例如,通过手动更新或使用不同的自动化工具)。 以下是一个高层次的概述,展示了 GitOps 操作员的角色: GitOps 操作员:
图 5.4:GitOps 操作员将 Git 中的期望状态与 K8s 中的实际状态同步
根据 GitOps 操作员工具的不同,调和过程中的配置选项也会有所不同。 值得查看云原生生态系统中两个重要工具的文档: Argo CD [9] 和 Flux [10]。另外,请熟悉配置元素,因为一些工具有项目和应用程序的概念,而其他工具则只有 源的概念。
理解调和 – 保持 K8s 与 Git 的同步
从本质上来说,GitOps 操作会从源头(如 Git 仓库、Git 仓库中的文件夹、OCI 仓库、S3 存储桶等)获取项目或应用的期望状态,并与目标 K8s 集群上当前运行的状态进行比较。 这两个状态可能是 同步的 或 不同步的。当状态是 不同步时,GitOps 操作有不同的选项来同步这些状态——即:将当前状态调整为匹配 期望状态:
-
手动同步:通过 命令行接口 CLI (CLI) 或者 用户界面(UI),可以触发 GitOps 操作来同步这 两个状态。
-
自动同步:一旦 系统出现不同步,GitOps 操作会尝试 自动同步。
-
同步计划/窗口:可能会有一些时候你希望或者不希望同步发生。 这时,同步计划或同步窗口就发挥作用了,它们可以阻止或允许同步 发生。
-
同步失败:有时 GitOps 操作可能无法应用期望的状态。 这可能是由于配置文件错误(例如,引用了无效的镜像)。 也可能是因为基础设施问题(例如,K8s 的资源不足)。 还可能是由于工具之间的冲突(例如,自动扩展工具,如 HPA/KEDA,正在更改副本或资源限制)。 重要的是要意识到这种状态并正确处理。 请参阅最佳实践部分以获取一些 额外的建议!
现在我们了解了同步的基本知识,接下来让我们看看不同的 GitOps 操作 部署 模式吧!
GitOps 操作模式 – 单一模式、中心辐射模式和多对多模式
最简单的版本 – 也是许多人开始时可能会使用的模型 – 是 monorepo 方法,GitOps 操作从单一目标环境中拉取,如我们在 图 5*.3* 中看到的两种模式。 虽然单一目标是一个常见模式,特别是当你刚开始使用 GitOps 时,我们还有其他模式需要 快速介绍一下。
GitOps 也可以设置为一个中心 GitOps 操作员,它将 Git 中声明的期望状态保持一致,并通过推送这些更改与多个目标环境进行同步。 这被称为 集线器-辐射模型 hub-and-spoke model。另一种选择是 多对多模型,其中每个目标环境都有自己的 GitOps 操作员,它会不断地将期望状态与其自身集群中的状态进行同步,如下图所示:
图 5.5:集线器-辐射模式和多对多 GitOps 操作模式
现在我们已经讨论了 GitOps 中的主要阶段,让我们总结一下,并快速看一下几个 最佳 实践。
GitOps 最佳实践
这个列表并不完整,但应该能为你在定义 GitOps 过程时提供一个良好的起点:
-
将配置与源代码库/文件夹分离:建议将实际的源代码与部署定义分开。 可以放在不同的仓库中,或者在同一仓库中放在不同的文件夹里。 为什么要这么做? 这是为了清晰地分离关注点和访问权限。 这使得 Git 触发的操作变得更加简便,因为在部署配置文件中的更改应该触发与源代码文件中不同的操作。 如果 CI 修改相同的仓库,它避免了可能的无限循环变化! 仓库越小,GitOps 工具扫描所有文件以确定期望状态所需的工作就越少。
-
正确的同步设置 – 拉取与 Webhook:GitOps 工具提供不同的同步设置,用于扫描源系统(如 Git)和调度触发同步。 对于扫描,了解默认的拉取频率非常重要(例如,默认情况下,Argo CD 每三分钟拉取一次所有 Git 仓库)。 Argo CD 和 Flux 还可以配置为接收来自 Git 系统的 Webhook,这样就将拉取机制替换为推送机制! 这非常重要,因为随着源系统(Git、工件仓库、S3 存储桶等)数量的增加,从 GitOps 工具到这些系统的 API 调用次数也会增加。 因此,最好监控 GitOps 工具到外部系统的调用次数,以便在行为发生剧烈变化时及时收到警报。 行为的变化可能是由于不小心更改了默认设置的配置。 大多数工具提供 Prometheus 或 OpenTelemetry 指标,可以通过你的 可观测性工具进行观察!
作者见过由于 GitOps 工具在同步过程中产生过多负载,导致 API 限制甚至使 Git 系统崩溃的配置! 同步!
-
并非每个配置都必须在清单中:由于 GitOps 跟踪期望状态与实际状态,因此重要的是将一些可能由不同工具管理的配置排除在清单之外。 以副本为例。 如果你使用 HPA 或 KEDA 等工具来自动扩展 Pod,则不希望在清单中设置静态的副本数。 这会导致 GitOps 工具在 HPA/KEDA 进行任何更改时检测到不同步状态。 因此,导致两个自动化工具彼此竞争。 彼此竞争。
-
GitOps 通知以处理同步状态:GitOps 工具在同步状态变化时提供通知。 这通常发生在 GitOps 检测到不同步状态时,完成同步时,或当同步失败时出现问题。 在这些情况下,收到通知非常重要,因为你需要确保能够处理同步失败或在最新更新成功同步时将信息反馈给开发团队。 团队。 已 同步。
为了接收通知,GitOps 工具会创建 Kubernetes 事件,你可以将这些事件导入到你的可观测性解决方案中,并对其进行反应/警报。 GitOps 工具通常还提供某种类型的本地通知功能,当发生特定事件时,可以触发外部工具。 例如,Flux 提供了警报功能,而 Argo CD 提供了通知的概念。 这两者都允许你在某些需要关注的事件发生时,发送 Slack 消息或触发其他外部工具!
GitOps——从推送到拉取的转变
GitOps 将 Git 的力量从应用代码扩展到一切皆代码。 虽然 CI/CD 仍然侧重于构建工件,但 GitOps 提供了一种优雅的方式,将所需的部署状态拉取到软件开发生命周期中的任何目标环境。 系统的变更只能通过 Git 完成,借助变更可追溯性、回滚到先前版本的功能,并通过 Pull Requests 强制执行审查流程。
现在我们已经覆盖了 GitOps 的基础内容,我们应该看看它如何在构建现代平台中对我们有所帮助。 作为平台团队,我们可以通过在 CI/CD 中使用自动化以及容器注册表来集中执行最佳实践(版本控制、策略等),从而减少错误变更请求的可能性。 通过 Git,每个变更和部署都可以追溯到 Git 提交,这使得故障排除变得更加容易,同时它还提供了额外的自服务功能(例如,当你的变更已经同步到目标环境时,通知开发团队,或通过 git commit 哈希值 通知他们最新版本存在问题)。
现在,让我们花些时间讲解容器注册表,因为没有它们,我们将无法发布或分发开发团队利用平台作为自服务所生成的任何镜像。 一个自服务平台。
理解容器和工件注册表作为入口点的重要性
容器和工件注册表值得单独成章,因为它们是现代云原生平台的核心构建块之一。 然而,我们将尽量提供相关知识,以帮助你跟随本书后续章节的内容。
我们区分公有注册表和 私有注册表:
-
公共注册中心 通常由个人或小团队使用,这些用户希望尽可能快速地启动并运行他们的注册中心。 然而,某些时候,值得考虑 私有注册中心。
-
私有注册中心 提供 几个关键功能,如高效的镜像存储、漏洞扫描、将镜像复制到其他注册中心、在拉取镜像时强制执行访问控制以及通知其他工具有关更新的内容,最终目标是使镜像能够快速、轻松地被需要它们的环境部署。
虽然私有容器注册中心通常仅在内部访问,用于推送新的构建并由我们的 GitOps 工具拉取,但我们也可以将注册中心开放给 公众。这里的 公众指的是 允许第三方供应商将他们最新的镜像或部署构件推送到此公共端点。 随着组织依赖于第三方软件——想想你自己部署的任何现成软件产品——我们可以在这些软件部署到内部系统之前,利用相同的漏洞扫描、复制和访问控制过程。 例如,金融公司 ACME 可能允许他们的第三方供应商为开发票务系统推送新版本到该公共端点。 一旦扫描并验证完毕,这些版本就可以部署到运行所有开发工具的内部 K8s 集群中。
以下图示展示了容器注册中心如何集成到端到端交付过程中,该过程从推送一个新的构件(第三方或 CI/CD)开始,直到该新构件被部署到目标环境:
图 5.6: 容器注册中心 – 我们平台的心跳
虽然从不同的开源和商业注册中心供应商那里可以获得大量深入的信息,但我们想简要概述一下注册中心如何融入我们的平台工程架构、为什么某些概念很重要,以及我们如何最好地将容器注册中心作为易于使用的自助服务提供给终端用户!
从容器到构件注册中心
在深入探讨 过程之前,我们需要快速指出,容器注册表——尽管名字有这种暗示——并不局限于容器镜像。 大多数容器注册表通常支持 OCI [11] 镜像标准。 在过去的几年中,容器注册表扩展了对非容器工件的支持,如 Helm Charts、压缩版的 Manifests 或基于 Kustomize 的模板。 这种扩展还带来了工件注册表名称的变化,因为这些工具管理的是一般的工件,而不仅仅是 容器镜像。
但这还不是全部。 工件注册表、开源软件或 SaaS (即 软件即服务) 服务,通常还具备其他功能,如访问控制、区域复制、审计日志、策略执行、安全扫描、通知等 更多功能。
构建和推送工件到注册表
上传 (推送) 和 下载 (拉取) 镜像可以使用 Docker(或其他工具,如 Podman)命令来完成。 在此之前,你需要先对注册表进行身份验证(无论是私有还是公共注册表,例如 Docker Hub)。 一旦认证通过,推送和拉取就非常简单,如以下代码所示:
通过 docker 命令与容器注册表交互
export REGISTRY=registry.finone.acme
# Authenticate
docker login -u YOURUSER -p YOURPASSWORD $REGISTRY
# Build an image
docker build -t $REGISTRY/financeoneacme/fund-transfer:1.2.3 . # Push an image
docker push $REGISTRY/financeoneacme/fund-transfer:1.2.3
# Pull an image
docker pull $REGISTRY/financeoneacme/fund-transfer:1.2.3
在 CI 过程中构建新镜像时,遵循最佳实践来处理镜像标签和元数据非常重要。 因此,OCI 也在其镜像规范中列出了建议的注释 [12] ,如 created, authors, url,以及 documentation (所有前缀为 org.opencontainers.image.)。
除了 API 接口外,注册表通常还提供 一个 用户界面 (UI),使得查看已上传的镜像、它们占用的空间以及根据注册表的管理功能提供所有这些功能的配置方面(例如,创建项目、管理用户、指定策略、配置webhooks 等)变得更加容易。
管理已上传的工件
注册表通常 管理项目中的工件。 项目可以是注册表中的私有或公共,意味着公共项目中的工件可以被任何能够访问注册表的人访问,而私有项目只能由授权用户访问。 这引出了用户管理和访问控制,可以在项目级别定义,也可以输入访问日志。 注册表会为每次推送和拉取创建日志,以便有良好的审计追踪。 CNCF Harbor 是一个非常流行的容器注册表,它还提供了关于所有这些功能的良好文档。 我们建议你阅读公开的文档 [13] 以了解所有这些功能。 需要记住的关键是如何将你的镜像组织到项目中,以及你将谁授予访问权限。 如果我们还希望允许外部第三方将他们的镜像推送到我们的注册表中,可以为他们创建特定的用户,允许他们上传到相应的容器项目 !
漏洞扫描
第七章 更详细地讨论了安全性,但在这里需要提到的是,像工件注册表这样的中央组件,每个工件都必须通过它,是进行静态漏洞检查的完美场所。 注册表通常提供开箱即用的 扫描功能,或者允许根据工件类型集成额外的工具。 这些工具将在上传新镜像时调用,以扫描镜像的到达,或者按计划进行扫描,以确保镜像被扫描并在持续的基础上重新扫描。
订阅注册表中工件生命周期事件
一个 artifact 通常会经历 从初始上传(推送)、安全扫描、复制到其他仓库、下载(拉取)到最后被删除的不同生命周期阶段,直到它不再需要为止。 以 Harbor 为例,我们也可以使用 Webhook 订阅这些生命周期阶段。 这为许多有趣的使用案例提供了可能,例如 以下几点:
-
安全:通知安全团队关于 新的漏洞
-
存储:清理旧镜像以防存储配额 达到限制
-
部署:从 CI/CD 或 第三方供应商上传的新镜像
-
审计:跟踪谁在拉取 哪些 artifact
作为参考,以下是可以与 Webhook 一起使用的完整生命周期事件列表: artifact deleted, artifact pulled, artifact pushed, chart deleted, chart downloaded, chart uploaded, quota exceeded, quote near threshold, replication finished, scanning failed, 和 scanning finished。
保持性和不可变性
仓库可能会迅速 膨胀—如果没有良好的清理策略, 可能会导致存储成本高昂。 因此,建议清理不再需要的旧镜像。 因此,一些仓库提供开箱即用的保留策略,您可以定义匹配特定规则的镜像以及它们将保留多长时间。 一旦镜像超过保留期,它们将 被删除。
另一个使用案例是, 默认情况下,每个人都可以上传一个新镜像,且具有相同的 镜像标签,导致之前的镜像版本没有标签。 为了防止这种情况,某些仓库提供不可变性规则,禁止标签 从 现有镜像中移除!
监控我们的仓库
由于注册表是我们平台的核心 ,我们需要确保它们保持健康。 如果注册表停止处理推送或拉取请求,无法执行漏洞检查,无法复制到其他注册表或发送通知,那么我们就会遇到问题! 这意味着关键更新无法及时进入目标环境。 这可能意味着一个安全漏洞——尽管通过新镜像修复——无法得到修复,因为新镜像无法传送到受感染的环境。 这也可能意味着我们无法在承诺的时间内 发布新特性。
为了观察我们的注册表健康状况,我们可以监控它们的各种健康指标。 在自托管的注册表中,这些指标通常通过 Prometheus 或自定义的 REST API 暴露。 在 SaaS 托管的注册表中,这些指标通常通过供应商的监控服务 API 暴露,例如 AWS CloudWatch。
一个好的参考是 Harbor,这是我们之前提到的 CNCF 项目。 它通过 Prometheus [14],暴露了许多重要的指标,包括项目和项目内仓库的数量、使用的存储、执行和排队的任务数量,以及 Harbor API 的性能指标(例如,推送或拉取工件需要多长时间)。
图 5.7:监控我们的注册表,及早识别潜在问题
Harbor 也是 OpenTelemetry 的早期采纳者,这是一个 CNCF 项目,最初为云原生社区引入了分布式追踪的标准。 Harbor 提供了 一个 OpenTelemetry 导出器 [15],生成带有更详细信息的追踪数据,既可用于健康监测,也可用于故障排除!
注册表——我们所有工件的核心中心
容器或工件注册表 是所有构建和部署的软件的核心 中心。 它 提供了一种集中管理、扫描、分发和执行访问控制的方式,确保所有软件在部署前进行管理。 因此,它必须被视为一个高度关键的组件,必须进行监控以确保可用性和弹性,同时本身必须是安全的!
现在我们已经讨论了我们的工件注册表的重要性,让我们继续看看所有这些组件是如何与我们的 发布流程 集成的。
定义发布流程和管理
我们已经涵盖了构建和部署软件所需的所有基本模块。 CI 构建新的容器镜像或工件,并将其推送到注册表。 我们知道注册表具有扫描漏洞、复制到其他注册表、通知其他工具的能力,以及强制 访问控制。
我们还了解了 GitOps 及其通过拉取方法确保目标环境中定义的期望部署状态(清单文件、Helm 图表、Kustomize 等)。
我们现在讨论的是如何定义和执行完整的端到端发布流程,以及如何管理从初始创建、初始部署、推广到其他阶段,以及更新到新版本,直到软件不再 需要为止!
下图突显了将新镜像推送并复制到其他注册表只是问题的一部分。 镜像的新版本还需要在管理单一或多个 Git 仓库中的部署定义中进行引用。 在拥有多个阶段(开发、QA、生产)和在每个阶段拥有多个区域或集群时,我们还需要在每个阶段和 每个区域/集群之间推广该新版本!
图 5.8:发布流程 – 从初始推送到部署和推广,从阶段到阶段
在上图中, values.yaml 代表 Helm 图表的值。 当然,这只是简化版,因为我们之前讨论过的值会有更多(例如所有权、应用上下文、日志级别、 git commit 哈希、容器 注册表等)。
现在,让我们看看如何完成这些部署更新,各个阶段之间应该发生什么,我们有哪些推出选项,以及如何最好地跟踪实时库存,了解是谁在何时部署了哪些内容!
将部署更新到新版本
有 多种方法可以更新 values.yaml 文件,用于初始开发阶段,并且它如何被推广到下一个阶段。 根据组织使用的工具和成熟度或流程,这可以通过不同的方式实现。 选项如下: 如下:
-
作为 CI 一部分更新:一旦 CI 将新镜像发布到注册表,它可以打开一个 Pull Request,更新第一个阶段仓库中的版本。
-
通过注册表 Webhook 更新:当一个新镜像上传到注册表时,可以使用 Webhook 打开一个 Pull Request。 这种方法类似于让 CI 来完成。 然而,它解耦了这个过程。 这也能够独立于使用哪个工具(例如,CI)或哪个创建者(例如,第三方供应商)推送新 镜像版本。
-
计划更新:每小时、每天早上 8 点,或者任何其他时间表都可以用来创建一个 Pull Request,拉取注册表中的最新版本信息。 这提供了持续更新和按计划批量更改的功能。 按计划进行。
-
手动 Pull Requests:作为自动化 Pull Requests 的前奏,或强制 强制手动批准,版本更新也可以 通过手动完成。
批量变更以应对依赖关系
当处理可以独立部署的简单应用程序或微服务时,更新单个文件可能已经足够。 通常,我们必须将多个变更合并为一个变更请求。 这通常发生在变更依赖于其他组件的变更时。 例如,我们的 Finance One ACME 可能会出现新版本的 fund-transfer 服务也需要新版本的 account-info 服务,而这个服务又需要基础设施更改。 你可以看到,它很容易变得复杂,而且完全自动化变得更加困难。 自动化变得更加困难。
这些变更很难自动化,通常会回退到某个发布管理团队,由他们手动解决这些 依赖关系。
还有其他解决方法。 一种方法是使用包管理器,例如 Helm,其中 Helm 图表可以 包含部署整个应用所需的所有配置。 Helm 图表 还可以成为上传到 注册表的工件。
我们在前面的章节中看到的另一种方法是使用像 Crossplane 这样的工具,它 提供了基础设施即代码(IaC)和应用程序即代码。 以下是使用组合来部署一个包含多个组件的应用程序的示例,组件包括业务逻辑、缓存、入口和 数据库:
apiVersion: composites.financialone.acme/v1alpha1
kind: FinancialBackend
metadata:
name: tenantABC-eu-west
spec:
service-versions:
fund-transfer: 2.34.3
account-info: 1.17.0
redis-cache:
version: 7.4.2
name: transfer-cache
size: medium
database:
size: large
name: accounts-db
region: "eu-west"
ingress:
url: "https://tenantABC-eu-west.financialone.acme"
正如我们所看到的,有多种方法可以解决这个应用程序 依赖性问题。
当我们面临跨应用程序依赖或依赖于独立部署和更新的共享服务时,这将变得更加棘手。 遵循最佳实践来定义这些服务之间良好而清晰的 API,并确保主要版本之间的向后兼容性,将使这变得更加容易。 要了解更多信息,可以查看现有的文献或 CNCF TAG 应用交付 [16] 在管理 应用程序和部署依赖方面所做的工作。
部署前和部署后的检查
一旦我们准备好并提交了所有的部署配置更改到 Git,我们就可以准备将其部署到目标集群。 如本章前面所解释的,我们可以选择使用推送模型(例如,交付管道部署我们的更改)或拉取模型(例如,GitOps 操作员将 Git 中的最新版本同步到 目标集群)。
无论是哪种方法,我们都希望进行一些部署前和部署后的检查,以回答如下问题:
-
部署前:我们准备好部署 该更改了吗?
-
所有的 外部 依赖是否已准备好?
-
所有新的镜像是否已成功扫描并且没有 漏洞?
-
目标环境中是否没有正在进行的维护或部署隔离?
-
所有需要批准部署的人 都批准了吗?
-
-
部署后:部署 是否成功?
-
更新的服务是否可用并成功 处理请求?
-
系统是否满足所有功能性和 非功能性要求?
-
所有服务是否符合它们的 SLA 并且 服务级别 目标 (SLOs)?
-
是否每个人都已被通知 关于部署的情况?
-
该部署是否可以升级到 下一阶段?
-
预部署检查可以通过多种方式实现。 其中一些可以作为 Pull Request 流程的一部分来实现(例如,当所有预部署检查未完成时,Pull Request 不能合并)。 它们也可以在部署管道中进行验证,或者作为 GitOps 工具中的预同步钩子来实现。
后部署检查可以在部署完成后通过部署管道或 GitOps 工具中的后同步钩子来实现。
一个能够在 Kubernetes 上独立于部署方式实现预部署和后部署检查的工具是 CNCF 项目 Keptn。 Keptn 可以在预部署检查未成功时停止 Kubernetes 部署。 Keptn 还可以执行后部署检查,例如执行测试、评估 SLOs,或 通知团队。
要了解更多关于 Keptn 的信息,请查看 文档 [17]。
部署通知
一旦我们得知新的 部署是否成功或失败,并完成了部署后的检查,我们可以利用这些信息,通知那些能够从中受益的人或工具。 无论是使用像 Keptn 这样的工具、GitOps 工具的通知,还是通过管道来实现,以下是一些发送 此类信息的示例:
-
一个聊天:在他们的 Slack 渠道中通知开发团队,他们的最新部署要么已准备好,要么已经未通过 检查
-
一个票证:通过更新 Pull Request 或 Jira 票证,告知关于 部署状态
-
一个状态页面:保持部署状态页面的更新,显示版本、环境和健康信息 直到最新
-
一个可观察性后端:将部署事件发送到你的 可观察性后端
部署事件到可观察性后端
可观察性工具不仅跟踪度量、日志和追踪信息,还可以跟踪诸如 部署或 配置变更事件等。 许多可观察性工具(如Dynatrace, Datadog, New Relic等)可以利用这些事件并将它们与 应用行为的变化进行关联。 这可以显著提高事件响应,因为它可以 与特定的 部署变更相关联。
阶段之间的晋升
假设我们在开发中有一个新的 版本成功通过了所有后部署检查,那么这些更改是如何从开发环境推广到 QA,然后再进入生产环境的呢? 每个更改是否都必须推广到生产环境,还是有其他更好的策略? 新的发布是否会一次性推出到所有生产环境,还是有更好的策略 呢?
以下是我们在过去与之合作的组织中看到的一些策略。 对于所有这些策略,意味着创建一个 Pull Request,将新的部署定义从较低阶段的 Git 位置推送到较高阶段的 Git 位置:
-
自动化:通常出现在开发环境和 QA 环境之间。 每当后部署检查成功时,它可以触发一个自动化的 Pull Request。 这确保了所有通过基本检查的开发更改能够迅速推送到一个用于更 全面测试的环境中。
-
计划性:例如,每天一次,将开发或 QA 中的最新版本推广到一个特殊的测试环境(例如,性能测试环境)。 这确保团队每天都能获得他们更新在过去 24 小时内的性能行为变化反馈。
-
受控/手动:通常越接近生产环境,这种情况越常见。 那些成功通过 QA 和性能测试的更改将被标记为可以安全推送到更高级别的环境中。 实际的晋升通常是手动进行的,而 Pull Request 本身可能已经自动创建(但不会自动批准),例如,从那些成功通过性能 测试阶段的版本中创建!
-
生产环境中的多阶段部署:当有多个生产集群时,通常的做法是先将更改部署到一个集群。 然后,验证一切正常后,再继续发布到其他集群。 第一个集群可能仅供内部使用,或者仅供那些知道他们会首先接收到更新的用户(例如,亲朋好友或早期访问小组成员)。 当在多个区域或多个 SaaS 服务商中部署时,建议定义清晰的 顺序(例如,先在欧洲部署,再在 美国发布)。
在我们不同的生产环境中使用多个质量门控阶段和分阶段发布策略的 目标是:减少 部署失败的风险!
蓝绿部署、金丝雀发布和功能标志
虽然使用多个 阶段进行发布前和发布后检查已经 降低了风险,但我们还可以为每个单独的部署做更多的工作: 渐进式交付 策略,如 蓝绿部署、金丝雀发布或功能标志。 我们已经在 持续部署—将部署与 发布解耦 章节中详细讨论了这些内容。
在发布过程中,这是一个重要话题,因为存在几个 悬而未决的问题:
-
谁将接收到新版本? 是某个特定百分比的用户,还是 某个特定群体?
-
如何衡量和验证发布 是否成功?
-
谁负责做出前进或 回滚决策?
就像通过发布后检查验证部署一样,我们也可以通过渐进式交付进行相同的操作。 开源 工具,如 Argo Rollouts [18] 和 Flagger [19],或商业工具提供自动化分析,比较渐进式发布各阶段之间的差异。 通常通过查询可观察性平台的数据来实现;例如,使用 Prometheus,验证新版本是否在关键服务级别指标(如请求失败率、响应时间、内存和 CPU 消耗)上与现有版本没有差异。 有关更多详情,建议查看相应工具的文档,这些工具用于 渐进式发布。
发布清单
自动化部署和发布过程的价值主张在于,工程团队可以更频繁地将更新发布到各个目标环境中。 我们越是通过平台简化这种自动化的可用性,团队就会更频繁地发布 更多版本。
发布清单可以帮助 我们跟踪当前在哪些环境中、由哪些团队发布了哪些版本。 从平台工程的角度来看,我们可以强制执行对这些信息的一致定义:版本、环境和所有权。 如果一切都以代码配置,这意味着这些信息可以在 Git 中获取。 当使用 Kubernetes 作为目标平台时,我们还可以将这些信息作为注释添加到我们的 Kubernetes 对象(部署、 Pod、Ingress)上。
在之前的示例中,我们已经强调了一些标准的 K8s 注释。 这里是一个 部署定义的片段:
apiVersion: apps/v1
kind: Deployment
metadata:
name: fund-transfer-service
spec:
…
template:
metadata:
annotations:
owner.team: team-us
app.kubernetes.io/name: fund-transfer
app.kubernetes.io/part-of: backend-tenant-2
app.kubernetes.io/version: 1.5.1
在 K8s 中,我们 可以简单地查询每个 K8s 集群的 K8s API,以获取这些标签下所有部署的概览。 然后我们可以获得如下概览 :
| 集群 | 发布 | 部分 | 所有者 |
|---|---|---|---|
dev | fund-transfer:1.5.1 | backend | team-at |
dev | account-info:1.17.2 | backend | team-ae |
qa | fund-transfer:1.4.9 | backend | team-at |
qa | account-info:1.17.1 | backend | team-ae |
prod-eu | fund-transfer:1.4.7 | backend-tenant-1 | team-de |
prod-eu | account-info:1.17.1 | backend-tenant-1 | team-de |
prod-us | fund-transfer:1.4.6 | backend-tenant-2 | team-us |
prod-us | account-info:1.17.0 | backend-tenant-2 | team-us |
表 5.2:基于部署工件元数据的发布清单
可观察性工具通常提供此功能,因为它们已经拉取 K8s 的 API 来观察集群的健康状况、事件和对象,包括这些元数据。
另一种方法是直接从 Git 解析这些信息。像 Backstage 这样的工具正是做的这个,它们通过这些信息提供一个易于搜索的软件目录。
发布管理——从启动到任务控制
对于许多组织来说,发布过程的结束标志是将新版本部署到生产环境中,随后运维团队接管所有保持软件运行的工作。这两个阶段通常也被称为启动和任务控制,这两个术语来源于 NASA 如何管理其太空任务。
多年前,DevOps 运动受到了许多著名例子的推动,例如 AWS 平台,它推广了*你构建它,你运行它!*的方法。这意味着开发者的责任不仅仅是构建一个工件,然后将其扔到所谓的“运维墙”那边。开发者必须对他们的代码从开发到生产的全过程负全责并拥有所有权。他们需要处理事件和更新,直到他们的代码最终被淘汰。
当我们看今天复杂的环境时,拥有每一个方面(代码和基础设施)是非常困难的。通过平台工程,我们尝试通过提供自服务来减少复杂性,并给团队提供更多控制权,使他们能够拥有自己软件的端到端生命周期。
在构建未来平台时,我们的重点必须是提供自服务功能来编排整个工件的生命周期。实际上,将整个应用程序的生命周期编排成一个单一的工件,通常只是应用程序的一部分。生命周期编排包括构建、部署和发布,但也包括生产支持所需的所有用例。这包括通过扩展性实现的弹性、事件管理、获取正确的可观察数据以进行故障排除,以及自动化交付以推动修复和更新。
在接下来的部分,我们将深入探讨生命周期编排,如何通过使我们的生命周期可观察来提高透明度,以及事件驱动模型如何帮助减少管道和编排代码的复杂性。
实现可持续的 CI/CD for DevOps – 应用生命周期编排
从构建,到发布,再到推广、部署、发布、修复。 这是需要自动化的很多任务,用来协调我们工件和应用程序的整个生命周期。 我们在那些负责端到端的团队中看到的挑战是,大多数这些脚本处理的是 以下内容:
-
触发某个(硬编码)工具
-
在某个(硬编码)环境中执行
-
然后,等待工具完成 它的工作
-
解析特定的 结果格式
-
根据解析结果,决定接下来做什么 (硬编码)
这些脚本代码往往会被复制粘贴到不同的项目中,稍作修改并适配。 对单个脚本进行修复或调整很难推广到所有其他变种,因为没有简单的跟踪这些脚本的方法。 这导致了高昂的维护成本,使得改变这些脚本中使用的过程或工具变得不灵活,并且浪费了工程师用于做自己 常规工作的时间。
使用管道脚本的强大功能没有问题。 许多管道自动化工具还提供了抽象、通过库实现的代码复用以及其他特性,以减少代码重复并增加可重用性。 因此,根据您使用的工具,确保遵循这些 最佳实践!
存在一种适合现代云原生应用及 云原生环境的不同方法: 生命周期 事件驱动编排!
在接下来的部分,我们将深入探讨这一方法,因为它提供了大量的灵活性和集中式可观察性,并将导致更可持续的 CI/CD 自动化 以及运维!
工件生命周期事件可观察性
事件驱动编排的基础是生命周期事件,如在 第三章中讨论的那些。它涉及观察工件的完整生命周期:从代码的初始 Git 提交、容器镜像构建并推送到注册表,再到每个阶段的发布,直到工件更新或退休,包含所有的中间步骤。
到目前为止,在本章中,我们讨论了许多生命周期步骤,并且强调了如何提取其中一些生命周期 事件:
-
CI/CD 流水线 可以在它们 构建 或 部署
-
工件注册表 在容器被 推送, 拉取, 或 扫描
-
PreSync,Sync,PostSync, 和SyncFailed -
Git 工作流可以用于在代码 更改 或 被 提升
-
像 Keptn 这样的工具提供了每个部署的前后事件,以及 复杂应用程序的事件
-
容器 平台,如 Kubernetes,暴露了有关 部署健康
CDEvents [20],一个来自连续交付基金会的项目,扩展了 CloudEvents 规范,后者已经是一个成熟的 CNCF 项目,并且在生态系统中得到了广泛的应用。 CDEvents 最初的目标是为构建、测试和部署的所有生命周期阶段标准化事件。 最近,它已扩展到覆盖运营中的部署生命周期阶段,如 生产事故。
这个想法是,遵循这些事件标准的工具更容易与生态系统中的所有其他工具集成。 不需要管理和维护工具之间的硬编码集成,这些工具通过开放标准的 API 和事件进行通信。 工具可以触发这些事件;例如,Jenkins 创建了一个新工件,其他工具可以订阅这些事件(例如,GitLab 可以订阅并触发一个工作流来扫描并发布容器)。 这个生命周期阶段也会生成一个 标准化事件。
所有事件都有最基本的属性集,用于识别阶段、工件以及涉及的工具,此外,还有必填的附加属性(例如,初始 git 提交,版本,环境,和 责任团队)
当所有这些事件被发送到一个中央事件中心时,它就能实现对工件生命周期的完全事件驱动编排。 工件的生命周期。
例如,如果你决定通知开发团队有关其 Slack 中的新部署,你可以轻松地订阅部署事件,并将该事件转发到 Slack。 如果你更改聊天工具为其他工具,只需更改该事件订阅。 无需查找所有管道中当前发送通知的代码,因为这通过简单的事件 订阅变更完成。
总的来说,在所有工具和阶段中使用一组定义良好的生命周期事件,使我们的平台 工程方法具备了许多能力:
-
可追溯性:我们可以追踪每个工件从初始创建到生命周期结束的全过程。 这让我们能够看到工件的位置(发布库存)、它们卡住的位置,以及谁负责将工件放入 某个环境。
-
度量:我们可以衡量多少工件在生命周期中流动以及花费了多长时间。 这是报告 DORA 效率指标的基础!
-
互操作性:我们可以轻松地集成新工具或替换它们,只要它们都遵循相同的标准(例如,切换通知工具只是更改一个 事件订阅)的问题。
-
灵活性:像替换工具一样,我们可以通过让额外的工具在特定事件上添加工作,轻松地调整我们的交付流程(例如,在某些环境中为部署添加强制性的安全扫描,可以通过安全 扫描工具的事件订阅实现)。
让我们快速回顾一下与创建和维护冗长复杂的自动化脚本相比,这种事件驱动系统的构建块。 过程 逻辑。
与事件一起工作
第一步是我们使用的工具必须在工件生命周期中发出这些事件。 观察 CDEvents(它扩展了 CloudEvents),在生态系统中已经有一些工具提供了开箱即用的支持,例如 Jenkins、Tekton、Keptn、Tracetest、Spinnaker 等。
那些尚未集成的工具,可以使用现有的 SDK 轻松发布这些事件。 以下是一个使用 Python 的代码示例,它创建了一个带有元数据的管道运行完成事件(cdevents.new_pipelinerun_finished_event),标识管道、制品或所有者:
import cdevents
event = cdevents.new_pipelinerun_finished_event(
context_id="git-abcdef1231",
context_source="jenkins",
context_timestamp=datetime.datetime.now(),
subject_id="pipeline_job123",
custom_data={
"owner": "dev-team-backend",
"part-of": "backend-services",
"name" : "fund-transfer-service"
},
subject_source="build",
custom_data_content_type="application/json",
pipeline_name="backendBuildPipeline",
url="https://finone.acme/ci/job123",
)
# Create a CloudEvent from the CDEvent
cloudevent = cdevents.to_cloudevent(event)
# Creates the HTTP request representation of the CloudEvent in structured content mode
headers, body = to_structured(event)
# POST it to the event bus, data store
requests.post("<some-url>", data=body, headers=headers)
前面的 Python 代码示例创建了一个新的 pipelinerun_finished_event,表示管道执行完成。 附加的上下文数据指示了哪个管道以及它何时构建,它允许我们提供附加的元数据,例如所有权、制品或该管道属于哪个应用程序。
无论你是使用 CDEvents 标准提案,还是发送你自己的生命周期事件, 基于 CloudEvents 是个不错的主意,因为该项目已经有许多行业集成,可以发布或消费 CloudEvents,Knative 就是 其中一个例子!
通过订阅事件来进行协调
一旦我们的所有工具都 发布标准化事件,我们就可以通过让我们希望参与该过程的工具订阅它们想要 执行的事件,从而更容易地协调我们的制品生命周期过程。
我们在本章之前讨论过相同的概念,当时我们谈到使用工具(如 Argo CD 或 Harbor)的 webhook 功能来在新制品可用时或新部署成功同步时执行操作。
标准事件模型的好处是,工具不再需要订阅特定工具的特定 webhook(例如,Argo CD 的 webhook)。 相反,我们可以订阅一个接收所有涉及工具的标准化生命周期事件的中央事件中心,如下图所示:
图 5.9:所有工具都发布并订阅标准化的生命周期事件
将一切 基于事件标准,消除了点对点工具集成或管道脚本中硬编码的工具集成需求。 举个例子,我们可以简单地订阅 dev.cdevents.service.published 事件,获取关于该服务的详细信息并将其转发到我们的 聊天工具。
如果今天该工具是 Slack,将来我们决定切换到另一个工具时,我们只需更改 该订阅。
另一个用例是将不同的工具作为流程的一部分,在不同的环境中处于活动状态。 由于这些标准化的事件包含大量的元数据(例如,某个服务被部署到哪个环境),我们可以订阅某个特定环境的事件。 以下是展示 一些示例的表格:
| 源工具 | 事件属性 | 被订阅者 | 操作 |
|---|---|---|---|
| Argo | service.deployed``staging``fund-transfer:2.3``team-backend | K6 当 environment == "``staging" | 执行 简单负载 |
| . | Slack 当 所有者 == team-backend | 向团队的后端 Slack 频道发送通知 | |
| OTel Collector | 收集所有事件并转发到 可观测性后端 |
表 5.3:来自 Argo 的相同事件可以被不同的工具订阅,以执行不同的操作
一些工具已经提供了 开箱即用的支持,能够订阅 CloudEvent 源或提供一个可以消费 CloudEvent 的 API 端点。 对于其他工具,则需要构建一个轻量级的集成层,该层订阅这些事件并将其转发给目标工具。 也可以通过支持 CloudEvents 的事件总线系统来实现。
分析事件
现在我们知道了事件是如何 发送的,以及其他工具如何订阅这些事件,我们可以讨论如何利用它们来分析我们的生命周期过程 实际上是如何运作的。
定义良好的 事件拥有时间戳、生命周期阶段定义(=事件类型)以及一些上下文信息(工件、环境、所有者),可以用于分析以下问题: 例如:
-
在某个 特定环境中发生了多少次部署?
-
一个特定应用程序或租户的部署次数是多少? 或者租户的部署情况如何?
-
一个团队的活跃程度如何,基于 所有权信息?
-
有多少工件从开发环境 到达生产环境?
-
哪些工件需要很长时间,并且在到达生产环境的过程中被阻塞了? 到生产环境的路径上在哪里出现了阻塞?
-
是否有某些制品比其他制品更容易引发安全漏洞或生产问题?
-
在流程中,哪些工具消耗了大部分 时间?
-
有哪些工具通常是导致端到端流程缓慢的原因?
我们可能还有更多的问题,可以通过分析 这些事件来解答。
我们如何分析这些事件? 你可以将所有这些事件流式传输到数据库或你的可观察性平台。 在 图 5*.9*中,我们包含了 OpenTelemetry,因为事件可以直接被摄取并转发到你的可观察性后台,并 在那里进行分析。
通过事件可观察性为 CI/CD 带来透明性
将所有事件与所有元数据以及明确定义的表示生命周期阶段的类型放在一个地方,使我们能够获得关于集成、交付和运维过程的很多透明性。 这些数据使我们能够优化流程,从而实现 更高的可持续性。
为 CI/CD 和运维构建自动化通常会导致大量自定义代码,需要在所有被复制的项目中进行维护。 我们在这一节中学到的是,转向事件驱动的方法来管理制品生命周期,可以解决很多复杂性问题,这些问题通常隐藏在自定义脚本或硬编码的 工具对工具集成中。
在 第九章中,我们将讨论如何通过做出正确的 架构决策来减少我们平台组件中的技术债务。
IDP – 平台中的自动化克拉肯
到目前为止,在这一章中 我们已经学到了许多关于自动化端到端构建、交付、部署和发布过程的基本构建模块。 我们讨论了通过 GitOps 部署期望状态的新方法,其中期望状态是从目标环境中拉取的,而不是从外部工具(如 管道)推送过来。
我们讨论了从第一次提交到将软件发布给最终用户的端到端发布流程。 最后,我们讨论了将事件驱动方法应用于编排我们的工件生命周期,这提供了一个集中的事件中心,使所有发生的事情更加透明和可观察。 这还使我们更灵活,因为我们可以将工具集成和流程定义从流水线或 bash 脚本转移到事件订阅和 事件驱动工作流中,从而消除复杂性。
在这最后一部分,我们想简要了解哪些概念可以通过您可能已经拥有的现有工具实施,哪些新方法存在以解决我们讨论的一些挑战,以及您可能希望查看的一些新工具,这些工具在过去几年中已经出现 - 无论是开源还是商业 - 都可以减轻一些工作 的负担。
我们将通过将自己置于我们的用户、开发人员或开发团队的鞋子中来实现这一目标,因为他们将需要适应新的工作方式。
为更轻松的启动提供黄金路径模板!
我们尝试推行许多新实践,例如确保部署的正确元数据(版本、应用上下文、所有权等),或者在每个构建流水线中包含安全漏洞检查。 在平台工程社区中,这些也被称为 黄金路径。 为了确保新项目团队能够轻松遵循这些实践,我们需要使它们易于获取 和采纳!
最简单且最有影响力的方法是提供软件或存储库模板。 这些是形式为清单文件、流水线、自动化脚本等的模板,开发人员可以在模板存储库中找到,然后应用到 他们的项目中。
虽然这种方法有效,但并不强迫工程师真正使用这些模板;此外,这是一个额外的手动步骤,也可能导致错误。
使这变得更加简单和自动化的一种方法是提供一个 CLI 或 UI 来初始化新的或更新现有的 git 存储库,使用最佳实践模板。 Backstage,这是一个由 Spotify 捐赠给 CNCF 的 项目。
Backstage 软件模板 功能 旨在将 Golden Path 模板作为每个开发人员构建新软件组件时的入口点。 模板可以由了解如何正确配置流水线、启用自动化测试和部署以及强制 安全检查的专家定义。
一旦定义了模板,它们就可以通过易于使用的向导进行访问,提示开发人员提供一些关键输入数据,例如他们实现的服务类型、所有权信息、可观察性或安全性的要求等 - 所有这些输入都将影响使用来自 模板的文件和配置创建新存储库或更新现有存储库。
要了解更多关于模板的信息,请查看 Backstage 网站上的详细文档和示例。 Backstage 网站 [21]。
通过 Crossplane 的抽象
另一种简化和强制最佳实践的方法是在定义您的 应用程序或服务时提供额外的抽象层。 在 K8s 中,我们必须定义我们的部署、服务、Ingress, 持久卷索赔 (PVCs),以及在需要部署依赖服务时更多的软件组件,如数据库、缓存或其他所需的 软件组件。
在 IaC 的早期部分,我们介绍了 CNCF 项目 Crossplane。 Crossplane 通过代码编排 同时管理基础设施和应用程序部署,并提供了所谓的 复合体的概念。在这里,我们不会花更多时间,因为我们之前已经提供了几个示例,展示了如何使用复合体来提供性能测试环境以及定义金融后端应用程序类型,开发人员只需指定要部署的服务版本即可。 以下是复合体定义的前两行。 在 Crossplane – 用于平台和 应用程序的 IaC 部分中查看其余内容:
apiVersion: composites.financialone.acme/v1alpha1
kind: FinancialBackend
在提供抽象时,使开发人员了解这些抽象的重要性至关重要。 这可以通过提供教育材料或简单地通过与前面讨论的相同模板方法(如使用 Backstage 等工具)来实现。
一切 Git-flow 驱动
嗯,Git 作为我们的事实来源应该不是什么惊讶的事情——我们在本章早期就已经确立了这一点。 然而,大多数 Git 解决方案提供了额外的功能,我们可以利用这些功能来加强标准和流程(例如,GitHub 工作流)。 工作流可以根据计划触发,也可以作为 Git 驱动过程中的许多不同事件的一部分(例如, 推送, 拉取 请求, 发布)。
这使我们能够在构建和推送工件之前也强制执行我们的标准;例如,验证我们期望每次部署时必需的元数据文件(例如,所有者信息)。 我们还可以使用它自动进行代码扫描并生成得分卡,或者我们可以用它来验证所有依赖项是否安全且没有已知的 安全漏洞。
根据选择的 Git 工具,你通常会找到一个市场或最佳实践目录,列出可以为特定类型的项目执行的工作流和操作。 确保你熟悉所有基于你的 工具选择可能实现的功能。
软件目录
一旦我们使开发人员能够构建更多遵循所有流程的软件,我们希望会看到大量新服务的开发。 这些是其他开发人员也需要了解的服务,以避免开发团队构建重复的服务,并鼓励开发人员在现有服务 和 API 的基础上构建更多功能。
一个 软件目录 提供所有可用服务和 API 的概述,并理想情况下还提供一些文档,这是我们 追求的目标。
由于 Git 是事实来源,我们可以直接从 Git 中提取大部分信息。 根据我们选择的 Git 解决方案,软件目录可能已经是提供的一部分。 然而,软件目录中还有更多服务和 API,组织可以拥有并基于它们进行开发(例如,部署在本地的外部 API 或第三方软件)。
Backstage,这个 也提供了前面讨论过的模板功能的工具,还附带一个软件目录。 它通过解析 Git 仓库中的特定元数据文件获取数据,但也允许外部数据源提供实体信息。 以下插图来自 Backstage 博客,展示了 Spotify 在 Backstage 中的软件目录的样子: 样子:
图 5.10:从 Git 中提取的实体元数据的软件目录
从前面的截图中我们可以看到,软件目录是了解一个组织内有哪些软件组件、它们是什么类型、谁拥有它们、在哪里可以找到源代码以及 其他信息的强大工具。
像 Backstage 这样的工具 并不是完整的 IDP;然而,它们代表了一个门户——一个图形化界面——可以访问大多数 IDP 用户相关的数据。 IDP。
虽然 Backstage 是一个选项,但市场上还有许多其他选择。 从自家开发到其他开源或商业工具,如 Cortex、Humanitec、Port, 或 Kratix。
总结
在这一章中,我们了解了从最初创建到投入生产的整个过程中的底层自动化和流程。 对于现代平台,采用 GitOps 方法,尤其是通过拉取而非推送的方式,应当是一个重要的考虑因素。 我们学习了 Git 作为真实来源,以及将业务逻辑作为容器或 OCI 合规的镜像放入我们的 目标环境中的方式。
随着组织的成长,推行良好的流程和最佳实践变得尤为重要。 为了确保执行效果,它需要易于访问,并且应当作为自助服务提供端到端的支持,以免影响工程师们的创造力流动。 工程师的创造力。
这也引出了下一章的主题。 在 第六章中,我们深入探讨了专注于自助服务功能的重要性,这些功能真正解决了目标用户——我们的开发人员——的需求。 我们将讨论如何将这些黄金路径的最佳实践引入我们的平台,从而显著改善开发人员完成工作的方式。
进一步阅读
-
[1] OpenFeature – https://openfeature.dev/
-
[2] Crossplane –
www.crossplane.io/ -
[3] Renovate Bot –
github.com/renovatebot/renovate -
[4] 语义化版本控制 –
semver.org/ -
[5] Kustomize –
kustomize.io/ -
[6] Helm –
helm.sh/ -
[7] 务实程序员 – DRY 原则 –
media.pragprog.com/titles/tpp20/dry.pdf -
[8] 如何设置 GitOps 目录结构 –
developers.redhat.com/articles/2022/09/07/how-set-your-gitops-directory-structure#directory_structures -
[9] Argo CD –
argo-cd.readthedocs.io/en/stable/ -
[10] Flux –
fluxcd.io/flux/ -
[11] 开放容器倡议 –
opencontainers.org/ -
[12] 建议的容器注解 –
github.com/opencontainers/image-spec/blob/main/annotations.md -
[13] Harbor –
goharbor.io/ -
[14] Harbor Prometheus 指标 –
goharbor.io/docs/2.10.0/administration/metrics/ -
[15] Harbor 分布式追踪 –
goharbor.io/docs/2.10.0/administration/distributed-tracing/ -
[16] CNCF TAG 应用交付 –
tag-app-delivery.cncf.io/ -
[17] Keptn –
keptn.sh/ -
[18] Argo Rollouts –
argoproj.github.io/rollouts/ -
[19] Flagger –
flagger.app/ -
[20] CDEvents –
github.com/cdevents -
[21] Backstage –
backstage.io/
第六章:为开发人员及其自助服务构建
没有可访问功能的平台不是一个平台。 作为规则,自助服务应该包含在内,并且必须提高可访问性,以增加价值。 在构建平台的过程中,通常没有区分基础设施层和应用层。 内部开发者门户(IDPs)的集成为此带来了新的复杂性,它解决了一些需求 同时也留下了一些未解答的问题。 将社区引入你的平台并对他们的贡献持开放态度是很重要的。
在本章中,我们将回顾一些概念,并分享一些最佳实践的思路。 到本章结束时,你应该了解如何构建一个具有韧性、灵活性并能够满足用户需求的平台。 他们所处的位置。
在本章中,我们将涵盖以下 主要主题:
-
软件开发与平台开发——避免 混淆
-
减少 认知负荷
-
自助服务 开发者门户
-
落地、扩展和集成 你的 IDP
-
平台可观察性的架构考虑 在平台中
-
为社区开放你的平台 并促进合作
技术要求
在本章中,将会有一些技术示例,包括 .yaml 文件和命令。 虽然你不需要设置集群来跟随本章内容,但如果设置集群,可能会加深你的理解。 我们使用了以下技术来开发我们的示例 和解释:
-
kind – 测试版本为 kind
v0.22.0 go1.20.13 -
我们使用本指南设置了一个三节点的 集群: https://kind.sigs.k8s.io/docs/user/quick-start/#configuring-your-kind-cluster
-
Docker(建议使用 Docker 无根设置) 推荐使用
-
The
kubectl命令行工具 -
一个 GitHub 仓库
代码示例可以在 Chapter06 文件夹内找到 这里: https://github.com/PacktPublishing/Platform-Engineering-for-Architects。
在本章中,我们将进行一些小型教程。 尽管并非每个代码片段都需要在 Kubernetes 集群中运行,但建议设置一个本地的 Kind 集群,其中至少包含一个控制平面节点和三个工作节点,以便充分体验教程的价值。 Kind 集群的配置可以在 GitHub 上的章节仓库中找到。
软件与平台开发——避免混淆
在我们迈向 平台开发的过程中,我们必须明确区分平台开发和其他形式的 软件开发。 请记住,平台的目的是为了使开发和运维团队能够进行开发和运营;它并不是客户直接体验的东西,尽管客户确实 间接受益。
平台的价值在于将构建和交付应用程序所需的所有工具、服务和应用程序统一在一起,呈现在用户面前。 简而言之,平台是开发者用来将软件应用程序交付给最终用户的一系列服务。 你可以 使用现有的软件应用程序,因此也可以在不编写 任何代码的情况下开发 一个平台。
那么,如何开发一个平台,如何开发软件呢? 它们在哪些地方交集,在哪些地方不同?
平台生命周期与软件生命周期
在 许多 方面,平台的生命周期看起来与任何 软件开发生命周期 类似 (SDLC) (SDLC)。
图 6.1:一个 SDLC
在前面的章节中,我们讨论了强大规划阶段的好处,并将平台视为产品的处理方式。 虽然在此过程中避免过度设计平台很重要,但你仍然会最终得到一个具有多方面特性的系统。 因此,由于平台的相对规模和复杂性,平台的发布可能不像按下按钮并让你的更改在一些服务器或集群中传播那样简单。 这在很大程度上是因为平台内部有大量的动态组件。 例如,创建一个新的集成以交付 软件材料清单 (SBOM)将比在现有的策略引擎中调整策略以使其 更具限制性对用户造成的干扰更小。
如果我们看看 一个典型平台的结构及其 用户考量,理解平台生命周期如何与 软件应用程序 有所不同会更加容易。
图 6.2:IDP 组件和功能区域示例
在这个平台示例中,预生产环境位于左侧,生产环境则位于右侧,客户面向的应用程序将在其中部署(应用程序着陆区)。 无论是单一 Kubernetes 集群、多集群还是多架构,平台是一个紧密结合的整体,包含这两种范式。 平台应该将架构从用户中抽象出来,让他们能够 独立工作。
在这里,DevOps 工程师关心的是应用程序如何构建和发布。 诸如渐进式发布、DORA 指标(https://dora.dev/)以及其他 DevOps 实践是平台可能需要支持的内容。 同样,如果您的组织在云原生之路上已经走得很远,那么与生产应用程序操作相关的数据将是站点可靠性工程师关心的内容。 还有其他相关的考量因素,比如安全检查、基础设施即代码以及文档。 这些方面的每一项都可以在平台内单独控制,并对所有或部分 角色可用。
图 6*.2* 假设有专门的角色在工作;然而,如果没有专门的 DevOps 团队,那么开发人员和质量工程师很可能会在这方面分担职责。 这个整体概念仍然适用于即便是小型组织,在这些组织中,一个人可能需要同时承担多个角色的用户故事。 即使是理论上划分的角色,也应该有助于在进行平台生命周期管理时提供帮助。
平台 不仅仅是软件;它还是与工作负载协同调优的过程,以确保其对用户的性能。 虽然用户需求确实会影响平台的大小、范围和功能,但它应该尽可能设计得不偏不倚,以确保一个用户的黄金路径不会优先于 另一个用户。
可靠性与可维护性
一个平台的 可靠性 和 可服务性 是开发者在 组织中使用该平台的最大因素。 但这些因素到底意味着什么呢? 这些概念各自到底意味着什么呢?
从本质上讲,可靠性 涵盖了平台的整体可用性以及用户与平台成功互动的能力。 平台的可靠性应假设已经在平台中实现了多租户(多个用户之间完全隔离)。 这是因为这个概念仍然可以在不需要的地方应用,但你的平台仍然支持具有各自需求的多个用户。 尽管多租户对于内部工具而言并不是直观的做法,但高度监管的团队可能更愿意在一个隔离的环境中工作,以确保没有安全性和合规性违规的风险。 即使所有用户都是内部客户,他们仍然不应该暴露于或受到其他 用户工作负载的影响。
为了实现平台的可靠性,你应该利用调优和策略,帮助你确保工作负载隔离,并保持平台的完整性。 在 Kubernetes 环境中,一个节点可以是虚拟机或真实硬件。 与任何机器一样,每个节点都有分配给它的固定内存和 CPU。 然而,与标准虚拟机不同,当你超额分配 CPU 时,节点可以使用原本属于其他节点的 CPU。 这一点对内存不适用,虽然内存也可以被过度订阅,且 内存不足 (OOM) 会 导致节点重启。 这样可以通过保证在 CPU 周期可用时,资源能够使用这些 CPU 周期,从而降低整体资源需求;然而,另一方面,由于软件错误导致的 CPU 占用过多的恶劣进程,可能永远无法将 CPU 周期返回给节点池,以便再次使用。 其他因素,例如 Pod 优先级、请求、限制和 Pod 中断预算,会影响调度器如何决定在哪个地方以及如何调度 Pods,是否会驱逐已有的工作负载。 有关这些主题的更多信息,重要的是定期查看 Kubernetes 文档,因为新特性和最佳实践不断涌现,而该项目也在持续 发展(https://kubernetes.io/docs/home/)。
虽然这些设置的最终调优和编排需要 由平台工程团队来决定,但我们通常建议,这些调优应该成为规划阶段的一部分。 在平台生命周期内,你应该持续不断地进行评估。 你应该在平台的生命周期中持续重新评估。 平台。
平台的可服务性类似于可靠性,不同之处在于可服务性更多关注的是需要利用平台的用户的黄金路径。 为了使平台具有可服务性,它需要满足用户的需求。 与自助服务不同,自助服务是用户在不依赖管理团队的情况下,能够合理完成所有合理操作的能力,可服务性则从整体上看待所有用户需求,并在用户所在的位置满足这些需求。 可靠性可以视为可服务性的衡量标准,但满足用户需求的目标比单纯的可靠性更加全面。 可服务性是平台产品思维的核心, 平台的核心。
类似于关键用户旅程, 黄金路径映射了系统用户的更关键使用场景。 一般来说,这是用户可以采取的步骤序列,并期望达到 预期结果。
对于利用 IDP 的应用程序开发者来说,黄金路径可能是这样的:
图 6.3:使用 IDP 的黄金路径示例
开发者推送提交后, 持续集成 (CI) 系统 自动执行其魔法。 应用程序或应用程序的升级 进入生产环境后,它会将数据发送回日志记录和可观察性工具,供开发者利用,从而使他们能够获得关于应用程序生产状态 和性能的洞察。
结论
考虑到这一点,我们现在可以看一下,平台工程的 SDLC 与一个平台运行的应用程序开发之间的分歧。 在平台的黄金路径中,用户不再是与一个完成所有工作的服务或不透明系统交互,而是与由多个服务组成的系统交互。 这些服务中的一些或全部可能是自家研发的,但更可能是这些服务是内部工具与开源技术的混合,共同协作,构建出我们所说的 平台。
由于平台由多个组件构成,你还可以基于每个组件来拆解平台的 SDLC。 然而,与任何系统一样,你必须警惕那些需要多个组件一起发布的相互依赖关系。 这些组件必须协调发布。
你的开发人员需要能够透明和不透明地使用这些系统。 这意味着,虽然平台中的每个工具的使用应该自动化融入工作流程中,但开发人员应能够看到发生了什么,并在必要时调试各个部分的问题。 例如,如果一个 Tekton CI 任务失败,我们示例中的应用开发人员需要能够看到失败的任务,并深入了解任务失败的原因,从而帮助他们修复应用或 CI 任务。 CI 任务。
了解如何有效管理平台生命周期,同时尽量减少对用户的影响,可能就像是在“喝自家酿的香槟”,意味着平台团队使用他们提供给用户的相同流程和技术。 平台的构建和生命周期管理可以利用相同的 DevOps 工具; 代码即配置(star () as code)* 等模式,如 文档即代码(docs as code)、 *基础设施即代码(infrastructure as code)*和 配置即代码(configuration as code) 都对平台的构建和发布有重要影响。 然而,与软件应用程序不同,对于平台的最终用户来说,这些声明式的内容对他们的影响最大,尤其是与诸如银行应用等完全封闭的 DevOps 方面相比,后者对最终用户完全是黑盒处理。 这些 DevOps方面对于平台最终用户来说完全是黑盒的。 客户的身份会极大影响软件工程的某一方面相对于其他方面的重要性,因此区分了 软件开发和 平台开发。
以用户为中心的设计同样是理解平台成功所需优先考虑的事项的关键。 虽然用户自身会在很大程度上提供相关信息,但随着本章的推进,我们将重点强调我们认为对成功平台最为重要的方面。 成功的平台。
通过对平台的 SDLC 有了新的理解,我们可以开始利用这些知识来阐明将平台构建为产品的意义。
减少认知负荷
在 第一章中,我们提到了 减少认知负担的重要性。随着技术和系统的发展,它们变得更加复杂。 高度的复杂性意味着理解系统的每个部分将成为更大的心理负担。 曾经的单体应用程序时代已经过去, Linux, Apache, MySQL, PHP (LAMP) 堆栈 已经成为历史。 现在,微服务、云、虚拟网络等已成为软件应用程序日常操作的组成部分。
在现代软件架构中,完整栈软件工程师不再需要理解运行应用程序代码的服务器或操作系统。 平台为他们处理了这些问题。 然而,尽管平台可以抽象出许多细节,开发人员仍然需要对底层技术有一定的意识和理解,特别是当这些技术与他们的应用程序相关时。 因此,平台必须在简化用户操作和提供所需信息之间找到平衡,避免提供那些不相关的“噪音”。
如果我们对比 和对照不同角色的优先级, 我们可以将平台的范围与这些用户的需求进行对照。
| 平台团队 | 双方 | 开发团队 |
|---|---|---|
| 租户应用程序 | 平台可用性 | 应用程序可用性 |
| 用户管理 | 团队 RBAC | 应用程序 SLO/SLI |
| 用户认证 | 安全 | 通过/失败 CI 测试 |
| 网络 | 合规性 | CD 工作成功 |
| 平台 SLO/SLI | 日志聚合 | 应用程序升级 |
| 平台升级 | 错误和 异常追踪 | 应用程序 资源基准测试 |
| 平台扩展性 | 政策违规 | |
| 平台架构 | 配额消耗 | |
| 平台可维护性 | 应用程序性能 |
表 6.1:对比开发团队与平台团队的优先级
正如你所看到的,即使有平台团队 负责许多应用程序交付给最终用户的方面,应用程序的开发团队仍然有许多需要考虑的问题。 平台可以处理共享的考虑因素,例如安全性和合规性,但平台的工作并不是保证开发团队生产的应用程序具有良好的性能。 你可以将你的 IDP 视为一种托管服务,这对于 该产品来说并不是一个坏的定义。
作为一种托管服务,旨在实现“做”和“启用”之间的平衡,前面的表格成为了构建成功平台的路线图。 例如,通过在可观察性和可靠性工具方面进行投资,平台可以支持开发团队管理他们的考量,同时抽象掉它能抽象掉的其他一切。 一个不必关心聚合日志如何存储的开发人员,但知道如何在需要时访问它们,可以显著简化日常工作流程,从而让更多的精力投入到功能开发中,而不是运营负担。 这种认知负荷的减少可以导致团队的上下文切换更少,从而减少错误和压力。 通过这种方式,对平台的投资就是对开发团队生产力和健康的投资。 开发团队。
如果我们回想一下我们虚构的公司,Financial One ACME,我们可以想象在公司进行云转型战略时,认知负荷会变得更加重要。 由于公司并不是从零开始构建一切,开发人员不仅要维护遗留系统和架构,还要进行新的时代重构。 支持这一工作的平台可以让开发人员学得更少。 他们只需要知道平台在他们进行重构和 迁移战略时对他们的期望。
平台对减少认知负荷的承诺本质上是向开发人员做出的承诺,即你将帮助他们更高效地工作。 减少上下文切换、迅速直接的反馈循环和易用性,所有这些都有助于提高开发人员的工作满意度。 快乐的开发人员是压力更小的同事,有助于创造更加积极的工作环境,这对开发团队和 平台团队来说都是双赢的局面。
在平衡认知负荷的同时使用平台
尽管平台的许多功能是为了构建应用程序,但它同样必须能够支持应用程序的运行。 尽管平台的需求应当有详细的文档记录且易于理解,但人类往往容易出错;因此,平台必须强制执行其规范。 幸运的是,Kubernetes 在一定程度上本身就支持这一点,通过准入控制器 和 基于角色的访问控制 (RBAC),但你也可以利用工具如策略引擎来确保那些不符合规范的工作负载或操作被拒绝,并给用户返回错误信息。 越早收到反馈,用户的认知负担越轻,因为他们不需要切换上下文来响应 平台。
那么,如何通过内部 开发者平台来减少工程团队的认知负担呢?
如果你不知道从哪里开始,考虑到实现细节时,从同理心出发。
假设你是使用该平台的工程师之一。 现在是凌晨四点,你正处于慌乱中,而没有其他人能帮助你。 问问自己:“这会容易使用吗?” 当你对自己的方法感到满意时,找一位工程师来验证 你的假设。
每个细节都应与这一原则保持一致。 如果核心功能简单且能简化用户的操作,用户就无需承担认知负担。 在考虑其他方面时, 比如集成,遵循相同的方法。 直接的架构往往是最有效的。 如果“为什么会有这个架构存在?”这个问题无法用“因为它帮助了用户”来回答,也许它根本不应该存在。 完全没有必要。
通过微调 IDP 的操作,仍然可以取得显著的提升。 此外,设定平台上应用程序的标准,你的团队可以为它们定义一个成熟度模型。 这个成熟度模型将帮助定义一个应用程序需要具备哪些条件,才能成为平台环境中的成功成员,这意味着开发者可以以此为检查表进行工作,而无需进行实验、向人请教, 或猜测。
前期生产与生产阶段
一个平台既涵盖 预生产 环境,也涵盖生产环境,用于软件应用程序。 平台中应用程序 所在的部分被称为 应用程序 着陆区。
尽管平台必须考虑这两者,但这两者永远不应混合,因此平台必须在环境之间强制执行逻辑分隔。 它可以通过架构或策略来实现。 然而,架构是最好的,也是最安全的模型,我们将在 第七章 中进一步讨论。在当前章节中,尽管我们可能会提到安全最佳实践和分隔策略,但详细的解释可以在 第七章 中找到。本章中关于这些环境的重要要点应是它们如何与认知负荷互动,以及平台如何旨在减少这两个范围内的负荷。
身份验证与租户管理
随着平台 的扩展和用户的增加,它实际上变成了多租户平台。 这是由于在安全环境中确保最小权限策略的重要性,以及帮助确保用户之间不会互相干扰。 在 多租户环境下,仍然需要提供单租户的体验,这意味着其他用户和租户的存在必须对 每个用户隐藏。
你将添加到平台的第一个集成通常与身份验证和用户管理相关。 大多数 OpenID Connect (OIDC) 提供商 可以集成到常见的身份提供者(IDP)工具链中,因此选择正确的提供商不会很困难。 从那时起,用户管理就变得相对直接,尽管不同的工具之间有所不同。 为了减少认知负荷,越少越好,因此一种跨平台进行身份验证的方式意味着用户需要记住的登录流程更少,每天的登录次数更少,密码也更少,同时提供与其他工具相同的统一体验, 这对于公司内部的其他工具也适用。
让我们在一个更现实的场景中来看这个问题。 Financial One ACME 是一家银行。 这意味着它们处于一个高度监管的行业,需要保护大量非常敏感的数据。 控制谁能访问哪些数据,以及如何访问这些数据,是公司安全性和合规性方面最重要的内容之一。 期望用户每次都完美地记住并执行所有这些安全和合规实践,实在是过于苛刻。 因此,平台需要程序化地强制执行 这些规则。
满足任何行业的这些要求,无论是否高度监管,最终都归结于RBAC —— 如何将用户相互隔离,并确保他们只拥有需要的权限,而没有不必要的访问权限。 请查看下图,了解一个 多租户身份提供者(IDP)的示例。
图 6.4:IDP 集群中的多租户
在这个例子中,两个租户的工作负载可以共享一个节点的空间,但通过 RBAC,租户无法看到在同一节点上运行的其他工作负载。 另一种选择是确保每个节点上只能运行一个租户的工作负载。 然而,这种隔离程度可能会带来负面影响,因为它可能会影响 高可用性 ,或者需要更多节点来确保租户之间完全隔离并保持高可用性。 我们在 第三章讨论了平台的高可用性,这些相同的原则也适用于你的开发团队正在开发的应用程序。 与平台一样,高可用性是应用程序韧性的一项特征。 这可能意味着它通过扩展来处理负载的能力,或者仅仅是其整体的正常运行时间。 影响高可用性的因素之一是位置。 虽然你可能在 一个 副本集(ReplicaSet) 中有三个 Pod,但如果这三个 Pod 都在同一个节点上,并且该节点被重启(例如平台升级时),那么应用程序将会停机;因此,它就不是高可用的。 通常,高可用的应用程序会将副本集分布在多个节点上,如果这些节点位于不同的可用区,则该应用程序会增加一层额外的韧性。 遵循云原生平台最佳实践的开发人员通常会期望他们的应用程序具备高可用性,这意味着至少有两个副本集在 不同节点上运行。
RBAC
无论租户模型如何,Kubernetes 系统中的 RBAC 将成为平台自服务的骨架。 你将使用 RBAC 限制用户对 IDP 的访问,仅限于他们的工作负载将落在 IDP 中的命名空间和环境。 此外,你将在面向最终用户的环境中也有类似的 RBAC 策略,应用程序将在该环境中运行。 生产环境中的 RBAC 策略将比开发环境中更为严格,因为需要对这些权限进行更高程度的审查。 你将在 第七章中学习更多关于此的内容。
进一步阅读
Kubernetes 的官方文档可以在此找到 : https://kubernetes.io/docs/reference/access-authn-authz/rbac/。
借用官方文档中的一个示例,我们可以看到一个 RBAC 示例,在该示例中,分配给用户的角色使其能够读取机密,但不能编辑或 删除它们:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
# "namespace" omitted since ClusterRoles are not namespaced
name: secret-reader
rules:
- apiGroups: [""]
#
# at the HTTP level, the name of the resource for accessing Secret
# objects is "secrets"
resources: ["secrets"]
verbs: ["get", "watch", "list"]
与这个示例类似,通过利用 RBAC,你可以为用户设置适当的守护措施,让他们成功地在平台上导航。 那么,如何确定一个强健的 RBAC 策略需要具备哪些条件,以便在访问、操作和限制之间找到平衡呢? 这时,自助服务成为核心。 让我们看看你的用户可能会在平台上期望的工作流。
图 6.5:IDP 用户的示例工作流
在图 6**.5中展示的示例中,我们有多个团队,因此,团队内可能有多个用户,他们依赖于同一平台中的相同系统。 有时,他们会在同一时间依赖于相同的系统。 根据你选择的 CI/CD 系统,最佳实践可能有所不同;然而,一般而言,平台团队的目标是确保为 IDP 所需的组件能够扩展 到用户。
用于 Kubernetes 集群的身份验证系统也可以用于类似 Argo CD 的预生产工具,允许用户在同一个 Argo CD 实例中利用独立的项目。
虽然这不是最佳实践,但技术上可以对生产级访问实施相同的访问模式;然而,当访问生产环境时,最佳做法是进行更严格的身份验证,并且一旦验证通过,权限集应更加有限。 我们将在 第七章中进一步讨论, 构建一个 安全平台。
防止“噪音邻居”问题
噪声邻居如何影响认知负荷可能不是显而易见的,但防止这种事件的发生有助于避免生产事件,这些事件可能很难排查和解决,尤其是在多租户环境成为标准的情况下。 它还帮助用户排查他们的应用程序,或帮助平台团队排查生产问题,通过排除随着更多保护措施到位而变得不太可能的场景。 由于你的开发团队应当预期有限访问 IDP 的生产端,并且无法访问其他团队的指标和工作负载,因此所有防止噪声邻居的工作都有助于确保这些工作负载不太可能进入每个团队的 认知负荷。
在 IDP 的上下文中,噪声邻居指的是与其他工作负载在同一环境中运行,并且占用资源的工作负载,无论是通过大量的网络流量、CPU 或内存使用,还是其他不太邻里友好的行为。 就像城市会有条例防止邻居封锁街道或播放过大声的音乐一样,平台可以实施规范,确保一个工作负载不会对另一个工作负载产生负面影响。 影响。
在许多实际场景中,一个 Kubernetes 集群被用于多个环境。 对于云支出预算较为有限的组织,开发、QA、预生产和生产环境可能都位于同一个集群上。 从技术角度讲,整个 IDP 可以是一个集群。 虽然这不是我们认为的最佳实践,但它是一个极其常见的做法,用于降低成本。 正是在这些场景中,噪声邻居最有可能发生,但即使在生产环境完全隔离的情况下,也有可能发生,原因可能是某个工作负载遇到软件漏洞,或因为有效原因导致资源使用率高于正常水平。 容器化工作负载在防范这些共享环境中的噪声邻居场景方面有优势,因为这些 Pod 和容器定义可以持有资源定义,工作负载的封装性是一种优势,特别是在资源管理方面。 虽然绝大多数防范噪声邻居场景的保护应当作为软件应用的防御性编程的一部分,另外还应通过准确地对这些应用程序的资源需求进行基准测试和识别,但平台本身也有能力进行硬化。 硬化。
你如何实现这种硬化? 以下是实现方法:
-
即使在强制实施工作负载隔离最佳实践的情况下,也要注意资源共享,例如 etcd、API 服务器和 网络栈
-
采取预防措施避免 CPU 饥饿
-
集群自动扩展——自动添加(或移除) 节点
etcd 的维护和管理
etcd 是一个键值存储,用于保存集群中所有 Kubernetes 对象的定义——如 Pod 定义、作业定义、StatefulSets、DaemonSets, CustomResourceDefinitions (CRDs) 等等,这些都保存在 etcd 中。 这意味着,随着平台使用量的增加,etcd 可能会变得相当满。 etcd 项目 建议为 etcd Pods 分配一组最小资源,以确保生产环境中的顺利运行。 详细的推荐可以在 etcd 文档中找到(https://etcd.io/docs/v3.6/op-guide/hardware/),其功能领域包括磁盘、网络、CPU 和内存。 随着集群使用量的增加,etcd 的需求也会增加。 通常,etcd 的最佳实践包括确保其优先使用 CPU、网络和专用磁盘,具有高吞吐量和低延迟。 如果可能,应该使用固态硬盘 (SSD) 来存储 etcd 数据。 etcd 文档中有一些关于 etcd 调优的建议,我们这里不再重复。 保持跟进项目本身的最新建议非常重要,因为技术会随着时间不断演进。 由于 etcd 是 Kubernetes 集群成功运行的关键组件,因此必须仔细监视其可观察性,并主动应对磁盘压力或其他资源问题的迹象,这对于保证 平台的可用性至关重要。
即使在优化了 etcd 配置之后,etcd 的实际使用或过度使用仍然可能导致资源竞争。 例如,云原生 CD 系统 Argo CD 将作业存储为 CRD 条目,而不是 Kubernetes 作业。 随着 Argo CD 中团队和部署数量的增加,etcd 中的条目数也会增加。 如果 etcd 填满,Kubernetes API 服务器将会宕机。 为防止这种情况,必须确保 etcd 健康并且不会 过度填充。
一种方法是确保你从 etcd 中修剪掉旧的 CRD 条目。 在 Argo CD 的情况下,这个功能是原生集成到应用中的。 在标准 Kubernetes 作业的情况下,可以通过在作业定义的 spec 字段中应用所需的时间,来使用生存时间(TTL)机制。 这样,在作业成功或失败后经过一定时间,垃圾回收机制将运行并移除该条目,从而管理 etcd 的大小和健康状态。
类似地,其他 Kubernetes 类型也有各自的清理机制。 对于部署(Deployments),有一个规范是 revisionHistoryLimit,它决定了将保留多少个旧版本的部署。 如果该数字为 0,那么在生产问题发生时,部署将无法回滚,但 etcd 会保持干净,像 口哨一样清晰。
如何调整这些清理措施,将取决于用户数量以及这些用户生成的 etcd 条目数量。 etcd 的大小也是一个因素,而这个因素可以通过增加控制平面节点的大小来扩展。 因此,何时扩展,何时修剪,何时考虑向 IDP 环境中添加集群,将是一个成本管理、投资回报率(ROI)和平台生命周期的基本部分。 它还需要成为你为 IDP 选择的集成工具的一个因素,但我们会在 第八章中更详细地讨论成本管理。
理解 CPU 和调度器以避免饥饿现象
在集群范围内共享的下一个资源是 CPU,无论租户模型如何。 如前所述 ,在讨论可靠性时,我们提到过,在 Kubernetes 中,每个节点对象可以使用的内存是有限制的。 所有 Pod 及其内存请求都会被汇总,如果总内存超过分配给节点的内存,Pod 将被调度到有足够空间的其他节点,或者调度失败。 如果 Pod 尝试使用超出节点可用内存的内存,将会被终止并重新调度。 内存分配在节点之间有严格的边界。 CPU 看起来也具有相同的功能,但实际上,所有 CPU 都可以被所有节点使用。 这意味着,虽然应用性能可能主要是开发团队的考虑因素,但开发人员不必担心平台是否能够支持应用的需求。 虽然开发人员需要告知平台工作负载的需求,但平台需要能够 支持这些需求。
Kubernetes 中的 CPU 管理的实际实现是 完全公平调度器 (CFS),它 与 Linux 内核 使用的是相同的调度器。 从功能上来说,这意味着,如果 CPU 是可用的, 节点上的工作负载可以使用超出节点分配的 CPU 资源。
虽然这种超额分配 CPU 的能力在某些情况下可能有用,因为它可以在技术上支持“二进制打包”,允许平台支持更多的工作负载,只要它们不会同时运行,并且不需要保留更多的虚拟硬件(或者如果你在裸金属上运行,就是实际硬件),但也有一些原因,你可能希望防止部分或所有 CPU 以这种方式被使用,即使它在 技术上是可用的。
例如,在生产环境中,可能会有一些工作负载必须始终保持高性能,或者特别敏感于 CPU 缓存,而其他操作则可以稍微慢一些,或者更自由地重新调度到新节点。 对于这些情况,你可以通过更改 CPU 管理策略 更好地保证 CPU 可用性。
要了解 Kubernetes 中 CPU 管理的完整实现, 请阅读文档 此处: https://kubernetes.io/docs/tasks/administer-cluster/cpu-management-policies/。
该平台需要在 kubelet 配置中启用 CPU 管理策略,以便将 CPU 从池中排除。 你需要定义预留多少 CPU,但可以保留所有 CPU,减去运行 Kubernetes 关键工作负载所需的部分。 为了达到预期的结果,即保证某个 Pod 具有所需的 CPU,而其他 Pod 则牺牲部分资源,开发者需要在 Pod 配置中指定请求(requests)和限制(limits),并且这两个值 必须匹配。
这是一个示例 Pod 配置:
spec:
containers:
- name: myCPUPrivilegedPod
image: docker-registry.nginx.com/nap-dos/app_protect_dos_arb:1.1.1
resources:
limits:
memory: "200Mi"
cpu: "2"
requests:
memory: "100Mi"
cpu: "2"
一旦你的节点按这种方式配置,它将被平台认定为 保证的 ,因此能够使用独占 CPU。 该 kube-scheduler (调度器 简称为调度器) 会优先安排它运行在支持 Pod CPU 需求的节点上。 如果只有一个节点符合条件,那么调度器会优先选择它;然而,如果你配置了多个或所有节点以使用独占 CPU,则虽然 Pod 的优先级较高,因而不太可能被迁移(除非遇到极端情况),它仍然可能被放置到 新的节点上。
为了确保始终选择特定节点,或者始终选择某种类型的节点,你可以为节点添加标签。 这样做有助于调度器将 Pod 匹配到在 Pod 配置中定义标签的节点上。 这进一步确保了 Pod 的正确放置,除了防止“吵闹邻居”外,还可用于其他场景。 。
如果你决定使用本地 kind 集群并且还没有设置,先让我们来设置这个集群。 首先,克隆本书的 GitHub 仓库,并切换到 第六章:
git clone https://github.com/PacktPublishing/Platform-Engineering-for-Architects.git
//asuming Linux
cd Platform-Engineering-for-Architects/Chapter06
kind create cluster --config kind-config.yaml --name platform
创建好命名为集群的平台后,将上下文设置为本章的 kind 集群。 这样就无需在每个 kubectl 命令末尾添加 --context kind-platform 了:
kubectl config set current-context kind-platform
现在我们让工作变得更简单一些,接下来可以开始 Pod 标签演示。
这是节点标签命令和 示例输出:
$ kubectl label nodes platform-worker2 reserved=reserved
node/platform-worker2 labeled
运行此命令查看节点的标签: 该节点的标签为:
$ kubectl get nodes platform-worker2 --show-labels
NAME STATUS ROLES AGE VERSION LABELS
platform-worker2 Ready <none> 3d23h v1.29.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=platform-worker2,kubernetes.io/os=linux,reserved=true
我们的最终 Pod 看起来像这样:
apiVersion: v1
kind: Pod
metadata:
name: my-app-nginx
labels:
env: prod
spec:
containers:
- resources:
limits:
memory: "200Mi"
cpu: "2"
requests:
memory: "100Mi"
cpu: "2"
name: my-app-nginx
image: docker-registry.nginx.com/nap-dos/app_protect_dos_arb:1.1.1
imagePullPolicy: IfNotPresent
nodeSelector:
reserved: reserved
重要提示
为了让 CPU 管理策略在之前和新的工作负载上得到执行,你必须清空节点并 重新启动它。
除了 CPU 外, 调度器还会考虑其他几个不同的因素来决定如何优先调度或移动 Pods,包括明确的 Pod 优先级。 如果 Pod 优先级没有得到充分理解,或者平台的租户情况不明确,工程师可能不会立刻明白为什么不应该把应用 Pod 的优先级设得很高。 就开发人员而言,他们的生产应用程序可能是最重要的工作负载。 然而,如果使用不当,这可能会导致“吵闹的邻居”问题。 一个被过度优先的 Pod 看似无害。 实际上,它就像是一个不断加剧的拒绝服务攻击,随着规模扩大而变得更严重。 如果 Pod 被分配到一个副本集,并且随着集群的扩展,其实例数量增加,那么被过度优先的工作负载也会逐渐增多。 如果这个问题扩展到多个 Pod,那么问题会加剧。 这会导致平台服务退化,增加底层硬件的压力,并使故障排除变得更加困难。 因此,平台可能需要限制或防止这种分配。 这可以通过执行配额或 准入控制器来实现。
我们将在本章后面讲解配额和准入控制器。 但是,如果你希望在平台中使用 Pod 优先级,以下是启用它的一般步骤:
-
首先,创建一个 优先级类。 这种类型不限于命名空间,而是更广泛地在集群中可用。 通过将以下内容保存到一个 YAML 文件 中来创建该类,文件名为
priority.yaml:apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: critical-priority value: 1000000 preemptionPolicy: Never globalDefault: false description: "This priority class should be used for platform Pods only." -
接下来,应用 优先级类:
$ kubectl apply -f priorityclass.yaml priorityclass.scheduling.k8s.io/critical-priority created -
现在,当我们创建一个 Pod 时,可以选择该优先级。 将以下内容保存为 文件
pod.yaml:apiVersion: v1 kind: Pod metadata: name: my-app-nginx labels: env: prod spec: containers: - resources: limits: memory: "200Mi" cpu: "2" requests: memory: "100Mi" cpu: "2" name: my-app-nginx image: docker-registry.nginx.com/nap-dos/app_protect_dos_arb:1.1.1 imagePullPolicy: IfNotPresent nodeSelector: reserved: reserved priorityClassName: critical-priority -
保存 Pod 后,使用
kubectl create创建它:$ kubectl create -f pod.yaml pod/my-app created
在 Kubernetes 中有许多其他调整选项,可以确保调度器做出你所希望的决策。 也可以运行额外的调度器配置文件,或者完全用一个遵循 不同规则的客户配置文件替换默认的调度器。
这是一项相当高级的操作,因为它需要深入理解调度器的工作原理,以及如何根据需要进行更改以优化平台。 然而,如果您确实决定这是必要的,我们建议在替换默认配置之前,先从额外的配置文件开始。 替换默认配置。
重要说明
对于 OpenShift 用户,尽管最终结果相同,但在 OpenShift 中实现独占 CPU 的过程略有不同,并且使用了机器集(machine sets)。 请查阅最新的 OpenShift 容器平台文档,以获取有关该产品的最新操作指南。 产品相关的最新信息。
速率限制和网络健康
速率限制 是对服务响应请求者请求的网络流量或 HTTP 请求的限制。 每个响应都有计算成本,因此速率限制作为一种保护措施,防止端点被恶意利用,无论是故意的还是由于 错误导致的。
系统的常见摧毁方式是 分布式拒绝服务 (DDoS)攻击。 虽然此类攻击可以由恶意行为者发起,但也可能由于人为错误或软件缺陷而意外发生。 DDoS 是最经典的噪音邻居示例。 计算成本高,它可以通过大量请求淹没网络层,消耗计算资源,或者通过请求返回天文数字般庞大或计算上昂贵的数据来执行。 这些请求可能导致的数据返回需要非常庞大或计算量极大的资源才能获取。 获取这些数据的代价极高。
可以实现速率限制,以防止大量连续请求造成噪音邻居现象。 可以在集群入口(例如 nginx)中处理此问题。 但在 API 服务器中也有一些独立的 Kubernetes 功能可以利用。 同样可以使用。
在 API 服务器本身,您的团队可以设置 API 优先级和公平性规则,帮助防止流量泛滥。 如果噪音邻居问题来自内部源(即集群入口不再是一个因素),这尤其重要。 例如,如果备份作业配置错误,并且它试图从集群内部查询数据来创建备份,或者过于频繁地推送备份,应用速率限制可以帮助防止该作业淹没网络层。 默认情况下,启用了基本的优先级和公平性规则,尽管如果需要,完全可以更改或禁用这些规则。 然而, 不推荐这么做。
集群扩展和其他策略
还有哪些策略可以防止“吵闹邻居”情况? 自动扩展 是作者们最明显的选择,尽管肯定还有更多策略。 所有的调优和规划,如何将工作负载压缩到平台上,最终都无法避免集群需要扩展(或缩小)规模。 最经典的“吵闹邻居”场景,往往是由于平台规模过小,无法满足 用户的需求。
平台中的资源约束将决定集群如何扩展。 如果需要为在工作节点上运行的非系统组件提供额外资源,那么向集群中添加一个节点就可以解决问题。 然而,如果尽管已经采取了先前的措施,etcd 仍然填满,或者 API 服务器遇到约束,则需要更大的控制平面 来解决。
可观测性数据是 衡量何时以及如何扩展集群的最佳标准。 这些数据可以通过工作负载的计算资源利用率和工作负载性能来收集。 随着利用率的增加,如果剩余资源的阈值被达到或超过,则可以触发集群扩展事件。 您的可观测性数据也可以被更加创造性地使用。 一个常见的技术面试问题是排查问题。 解决方案总是一个定时任务(cron job)占用了过多资源,导致系统崩溃。 有了可观测性,您不需要人工去发现这一点;您可以使用数据。 无论是在 Pod 级别还是节点级别,资源利用的显著波动都可以被捕获并发出警报。 但如果您需要响应警报,那么这也是一个自动化的机会。
最后,如果所有方法都失败了,您可以使用事件驱动系统,基于可观测堆栈收集的数据反应,重新启动故障 Pod。 这能有效工作,并保持关键任务组件的运行,但它不能永久替代人为解决导致资源利用问题的根本原因。 最初的原因。
简而言之,保持集群健康和适当的用户与工作负载隔离是确保平台用户能够专注于最重要事项的关键,帮助我们为成功平台的下一个方面—— 自助服务。
启用自助服务开发者门户
我们已经明确指出,如果自助服务不是平台设计的核心要素,那么它就不是一个真正的平台。 现在,是时候理解这意味着什么,以及它如何与减轻认知负担的承诺相关。 平台团队不可能时刻在场来审批所有事情。 因此,将一些控制权交到最终用户手中是至关重要的,同时也不能增加他们的认知负担。 这种二分法需要找到一个小心的平衡点,这必须通过利益相关者之间的讨论和协商来达成成功。 平台团队如果过于紧密地控制一切,会阻止平台随着用户的发展而扩展,因为它将始终受限于团队的规模和位置。 这意味着现在是时候思考如何帮助用户自助。 开发者或团队应该能够在无需更高审批级别的情况下做什么? 哪些操作应该被限制? 如何实现合理的自助服务,又如何强制执行合理的限制? 我们已经讨论过将平台视为产品的重要性,但从功能上讲,这个产品类型是服务。 任何以“服务”形式销售的产品,不仅需要保证功能,还要保证公司提供的参与度和体验。 这适用于软件即服务(SaaS) 或 平台即服务(PaaS) 。即便你没有销售你的 PaaS,你也应该采纳服务心态,以确保用户始终处于你的 可用性考虑的核心。
简而言之,自助服务 意味着用户可以以合理的方式完成合理的操作,而不会对无关的用户或团队产生负面影响。 理解自助服务如何适应你的平台,首先需要了解用户的需求。 了解你的用户所在的工作环境非常重要。 如果你的用户是那种整天都待在命令行界面上的人, 那么 命令行界面(CLI) 可能是他们最需要的工具来完成工作。
然而,如果你的 IDP 被用于文档作为代码,而不是代码作为代码,那么使用它的团队可能会从 UI 中受益,而不是 命令行界面(CLI)。
最终用户希望如何实现他们的目标? 最终用户希望如何从平台作业中获取反馈? 当我们接近自助服务时,这些是我们需要问的问题。 一般来说,平台团队应该有一些开发者可以操作的参数。 例如,你可以保证某些工作负载只会落在特定的位置,并且可以将资源(内存和 CPU)限制绑定到你的 Kubernetes 集群中的命名空间。
提示
一旦你开始调整工作负载如何分配和消耗资源,这将开始人工影响集群容量。 在真正需要时,或者在创建了全面的集群扩展和 容量规划后,才应实施此策略。
强制执行配额
用户或团队 应该能够将他们的应用程序部署到应用程序着陆区。 然而,他们不应该在没有合理限制的情况下这样做。 这些限制,如资源消耗,可以在每个命名空间的基础上进行限制。 这可以通过指定 ResourceQuota 类型来完成。 你需要创建一个 YAML 文件,并将其应用到命名空间。 继续阅读,看看它将 是什么样的。
首先,创建一个 YAML 文件;我们称其为 your-dev-quota.yaml :
apiVersion: v1
kind: ResourceQuota
metadata:
name: your-dev-quota
spec:
hard:
requests.cpu: "2"
requests.memory: 1Gi
limits.cpu: "4"
limits.memory: 6Gi
persistentvolumeclaims: "5"
services.loadbalancers: "1"
services.nodeports: "0"
保存文件,现在将 YAML 应用到命名空间。 你的命令看起来像这样: 类似这样:
kubectl apply -f your-dev-quota.yaml --namespace=your-dev-namespace
现在,你的开发者可以根据需要在他们的命名空间中安排任何 Pods,但工作负载必须保持在分配给他们的配额边界内。 为命名空间创建配额有助于在平台中实现多租户。 将资源分配给用户的方式可以在不超负荷集群的情况下利用集群容量,从而允许用户独立操作,但仍然在 合理的边界内。
配额也可以通过 GitOps 来管理,从而简化用户增加或更改配额的请求。 如果用户调整配额的过程是发起一个 拉取请求 (PR),然后 等待批准,这样可以使他们更容易与平台互动。 易用性将是平台在组织内成功的一个重要因素。 易用性将是平台在 组织内成功的关键因素。
简单的可重复工作流
我们谈到了 简化工作流对于减少认知负担的重要性,实际上,这个话题我们可以在本章的任何地方提到。 然而,这最符合自服务功能,因为它是核心特性。 为了让平台实现自服务的承诺,它必须易于使用,而且使用方式必须容易 记住。
我们再次提到我们的黄金路径。 一个开发人员构建应用程序并提交了一个提交。 该更改通过 CI/CD 系统直到最终,应用程序安装或升级被推送到应用程序落地区。 该 PR 的整个过程必须对开发团队来说易于理解、直观且可预测。 如果团队需要更改 CI 工作或与其应用程序特定的 CD 逻辑,那么他们应该能够轻松访问管理系统来进行 这些操作。
让我们重新回到 Financial One ACME 团队。 我们可以进一步扩展这个例子,再次设想公司包含多个角色或用户类型。 如果我们考虑一个开发团队和一个相应的文档团队,那么每个团队从 CI 系统中需要什么呢?
可以肯定地说,两个团队都需要能够在系统内完成他们的工作,了解工作是否成功或失败,并知道整体 CI 管道是否成功或失败。 然而,他们可能在与 CI 系统交互时有不同的需求。 开发人员或 DevOps 工程师可能更倾向于使用 CLI,而文档团队或非技术团队可能更喜欢使用 图形用户界面 (GUI) 来利用 这个系统。
开源 CI/CD 项目 Tekton 和 Argo CD 都配备了 GUI。 这两个系统还可以利用专门设计的 CLI 工具来与项目进行交互。 对于这些系统的用户,只要他们获得了正确的访问权限,他们可以根据自己的需求选择与这些系统交互的方式,并决定哪种方式更容易,保持本地 CLI 更新,还是偶尔使用 GUI。 通过灵活性,这些工具和你的 IDP(如果你采用它们)能够让用户根据 他们的便利性自助服务。
然而,对于像 CI 管道这样的任务,可能需要很长时间才能完成,其他选项,如 API 调用或 Webhook 集成到 Slack 等常用工具中,可能更容易满足用户需求。 就像输入一样,平台应该在输出时满足用户需求,因为用户需要频繁切换上下文的次数越少,他们的认知负担就越轻,自助服务目标就越容易实现。
重要提示
CI 管道 通常是一系列任务;其中一些同步运行,一些异步运行,以根据最近的更改构建和验证应用程序。 这可能是每次一个 PR(CI 的核心),但也可能不那么频繁,比如每天。
即使是高度技术化的用户也喜欢一个好的 GUI,如果他们已经在使用像 GitHub 或 GitLab 这样的工具进行 PR 审查,他们可能不想再改变范式。 因此,尽管普通用户可能永远不需要看到他们交互的 Kubernetes 集群的内部细节,但我们确实说过,平台应该“自酿香槟”。 这包括一些最佳实践,比如在用户所在的位置与他们会面。 一些 Kubernetes 解决方案,如 OpenShift,随附 GUI 层。 对于那些没有 GUI 的解决方案,Lens(https://k8slens.dev/)提供了一个 易于使用的 GUI。
图 6.6:连接到本地 kind 集群的 Lens GUI,代表一个 IDP
总之,开发者自助服务是关于在用户所在的位置与他们会面,并为他们提供成功使用平台的工具。 这意味着理解他们的需求,同时还要赋予他们将平台与他们工作方式整合的能力,而不是让他们的工作方式适应平台。 平台。
落地、扩展和集成你的 IDP
如果你按照我们在 第三章中的建议,已经采访了用户,已经绘制了用例,并且设计了你的平台。 现在是有趣的部分——将它变成 现实。
让我们来看一下最基础的 IDP。 它是做什么的? 最重要的是,用户可以通过适当的访问权限和权限来认证它。 它具有 CI 系统、CD 系统,至少一些基本的安全工具,如扫描功能,现如今还可以生成 SBOM。 那么开发人员编写的软件的生产位置在哪里呢? 它在哪里? 促销流程是怎样的? 让我们再看一下之前提到的 IDP 黄金路径 图 6*.3*。
这个图中有很多组成部分,软件开发应该是迭代性的,这意味着我们不会一次性完成所有看到的内容。 所以,让我们来看看如何实现一个 IDP。 最简单可行的平台 是什么样的?
它可能是这样的 :
-
身份验证 和 RBAC
-
安全检查
-
CI/CD
-
一个应用 登陆区
从技术上讲,CD 系统并不是必须的,只要有某种促销过程将应用特性和增强功能推向生产环境。 采用这种精简的方法,或者最简可行平台,我们可以很容易地看到如何在一个组织中实现 IDP。 新产品的吸引力甚至可能促使用户去尝试,并帮助推动初期的采纳。 然而,保持这些用户的参与,并吸引更多犹豫不决的用户,需要扩展初始的提供内容,并确保用户使用的东西是易于理解的 且高效的。
不过,扩展你的 IDP——这看起来是什么样的? 扩展可能看起来像是容量规划,但随着平台采用的增长,你如何调整和配置你的资源? 在 Kubernetes 中,这意味着要确定集群的大小和/或 集群数量。
扩展还将包括特性和增强功能的添加,以及不仅是开发和推出的规划,还要考虑这些增强功能的运营。 增加另一个功能或集成会对平台产生什么影响? 如何衡量增强功能的成功,并且如何利用数据来确定其投资回报率? 投资回报率如何?
强制执行平台特定的标准
如果我们回顾本章开头,我们提到过,平台特定的标准对于终端用户来说应该容易理解,以帮助确保低认知负荷,并且这必须包括对 这些标准的强制执行。
没有一种固定的方法来做这件事,因为不同平台的规范可能会有所不同。 然而,我们发现有一些在过程和技术上都行之有效的做法,适合你 的组织。
成熟度模型
成熟度模型更倾向于认知负荷减少的社会技术方面。 为软件应用定义成熟度模型,并将其引入平台,可以帮助开发者确保他们以正确的方式构建应用。 这还可以确保认知负荷的减少不会由于忽略或遗忘某些内容而导致终端用户体验变差。 从本质上讲,它确保了双方的利益从 平台的角度是对齐的。
一个成熟度标准的例子是,新的应用程序只有在经过高可用性和灾难恢复配置后,或者有单元测试,或者团队已经编写并提交了他们的 Prometheus 查询和 Grafana 仪表盘以供应用可观测时,才会引入平台。 这些类型的模型可以通过 CI 作业进行强制执行,检查平台认为适当的内容是否存在,或者它们可以简单地作为指南发布给团队 进行遵循。
通过常见平台集成扩展平台
将一个平台从最薄的可行平台扩展到一个强大的系统,将需要利用新的集成并更好地利用最初的功能集。 一些常见且有用的集成在 本节中进行了概述。
静态分析
用户如何知道自己是否与预期的成熟度模型和 平台规范相匹配? 如果不匹配,他们如何获得反馈? 一种与用户建立这种执行和反馈循环的方法是通过静态分析。 当开发团队提交 PR 时,CI 作业会启动。 在第一次 CI 作业时,静态分析工具处于最佳位置;这样,如果 PR 失败,计算资源就不会浪费在运行测试或生成 SBOM 上,反馈循环也能更快地将错误信息反馈给最终用户。 减少反馈时间有助于保持开发人员的工作效率高,并降低认知负荷,因为开发人员不需要记得在较长延迟后再回去检查平台。
例如 可以在 PR 时使用的静态分析工具包括安全审计,这些审计将镜像版本与已知的关键漏洞利用 (CVE)进行比较, 以确保不会推送有漏洞的代码。 截至目前, Snyk 是一个流行的选择,因为它是开源的,并且提供免费的 选项。 CodeQL 是另一个流行的选择,也对开源项目免费,因此很容易找到它的使用示例。 这两款工具都很受欢迎,因为它们与 GitHub 工作流集成得很好。
然而,对于规范的执行,也有一些静态 分析工具,用于验证应用程序是否符合云原生最佳实践。 来自 StackRox 社区, KubeLinter 就是一个 很好的例子。 它带有多种检查,但可以配置为跳过不需要的平台检查,或者接受 自定义检查。
上述提到的静态分析工具都利用了 GitHub 工作流,这是一种声明式 YAML 格式,用于定义当 PR 被提交时,应该在仓库中执行哪些操作。 例如,要在 GitHub 工作流中对仓库的主分支运行 KubeLinter, .github/workflows 目录会被添加到 GitHub 仓库中,然后一个 YAML 文件会被提交到该目录。 该 YAML 文件的内容大致如下 kubelint.yaml 文件:
name: StaticValidation
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
lint:
runs-on: ubuntu-latest
# Checks-out your repository
- uses: actions/checkout@v4
- name: Scan yamls
id: kube-lint-scan
uses: stackrox/kube-linter-action@v1
with:
directory: app-of-apps
#config: .kube-linter/config.yaml
准入控制器和策略代理
Admission 控制器 是 Kubernetes 的另一个特性。 它们有两种形式,验证型和 变更型 Webhook。 两者都能减少认知负担,并启用自助服务。 然而,它们是相对高级的主题,可能不是你平台 MVP 的一部分,甚至可能不在前五名集成中。 随着平台的成熟,这些特性可以 被采纳。
如果变更型 Webhook 允许平台强制执行某个规范,例如对用户透明地设置环境变量,这让用户可以减少对平台本身的关注,更多地关注应用程序。 然后,如果环境变量发生变化,开发者在他们这边不需要做任何不同的操作。 验证型 Webhook 检查所应用的对象是否符合预期的参数。 你可以将这两种类型的 Webhook 用于工作负载,通常情况下,如果你使用变更型 Webhook,你应该始终使用验证型 Webhook,以确保 Webhook 响应后没有其他地方错误地修改对象。 已响应。
Admission 控制器是 强大的,但有些复杂。 它们需要设置服务器来运行控制器,并编写 Webhook 和响应。 由于它们的体积和复杂性,相较于其他平台规范强制执行方法,它们不适合新的 IDP,但对于拥有大量用户的大型组织,它们可以成为一个 强大的工具。
Admission 控制器可以是定制的,但开源 策略代理 例如 Open Policy Agent (OPA) 或 Kyverno 利用 相同的特性,并能帮助简化它们的使用。 利用基于策略的规范可以提升平台性能,并防止工作负载之间的冲突。 工作负载之间的冲突。
观察性
在下一节中将更深入地讨论,观察性 是 IDP 从第一天开始就应该具备的特性,并且可以扩展。 观察性应该被视为一个持续的集成过程,意味着它始终在增长和修改,以匹配 平台的生命周期。
一些常见的可观察性工具包括Prometheus、Grafana 和 Loki,它们可以单独使用,但与OpenTelemetry (Otel)及 Otel 协议结合使用时效果更佳。 当你将这些工具与 Thanos 结合用于长期存储时,你可以获得更多的数据来进行分析,这对于理解可观察性数据的上下文和历史非常有帮助。
这些工具分别专注于对 Kubernetes 对象的指标查询以及这些指标的分析、指标的可视化和日志聚合。 此外,这套工具可以被多个用户的单租户使用,或者作为一个多租户工具使用。 可以利用 IDP 的用户认证和 RBAC,就像我们之前讨论的 CI/CD 解决方案一样。
平台的 MVP 和黄金路径应该是初始可观察性实施的主要焦点。 可观察性应该以衡量用户满意度的方式进行,且 服务级目标 (SLOs)直接 映射到用户在平台上的成功。 因此,作为最佳实践,请确保黄金路径的核心组件可用,并为成功和延迟设定目标,同时确保平台团队能够利用可观察性来做出数据驱动的决策,包括跟踪某些功能的使用情况以及其中的失败 率。
举个例子,假设 Financial One ACME 最初发布了一个新的平台 MVP,包含了 Tekton 用于 CI,Argo CD 用于 CD。 但是,借助可观察性堆栈,平台团队发现只有 1%的用户在使用 Tekton 集成。 这表明 CI 需求可能在其他地方得到了满足。 凭借这些数据,团队可以决定弃用 Tekton 部署并回收一些集群容量,同时让用户继续以他们喜欢的方式工作。 另外,他们也可以深入理解为什么这个平台功能没有被使用,并寻求帮助用户采纳新的 CI 系统。
集成你的平台
平台集成 不仅仅是将各个组件集成在一起;它还意味着将 IDP 融入到公司的工作方式中。 这意味着服务优先的文化、以用户为中心、为运营卓越提供反馈回路,并且要与用户保持一致。
再一次,我们回到 Financial One ACME。 这家公司需要平衡现代技术栈和遗留技术栈。 在这里有一个机会,通过将现有的身份验证工作流整合到新平台中,来满足这些用户的需求。 如果他们已经在使用 GitHub 或 GitLab,那么他们很可能已经在使用某种单点登录,这应该与任何 基于 Kubernetes 的 IDP 兼容。
融入公司的工作方式可能意味着某些解决方案无法在当前满足用户的需求。 例如,如果 Financial One ACME 开发团队中 Tekton 采用率低的原因是因为 GitHub 工作流与 Argo CD 足以处理他们所有的 CI/CD 需求,那么与 Tekton 的整合可能是一个回报较低的努力。 正在重构单体应用的开发者,可能会发现能够使用共享 CI 工具会更有利。 这将帮助他们在旧的单体系统与新系统之间创建一组共同的测试和检查,确保在过渡过程中保持一致性。 在 Tekton 采用率低的例子中,事前分析决策可能防止了浪费时间,而这段时间可能更好地用于集成数据转换工具,如 Apache Airflow 或 Argo Workflows。
由于成功的 IDP 采用将依赖于数据驱动,一个关键方面是 可观察性。平台的 可观察性将有助于推动其对消费者和必须 维护该平台的团队的价值。
平台中可观察性的架构考虑
自助服务与认知负载——我们已经暗示过这些话题中可观察性的重要性。 没有优先考虑可观察性的情况下,你的平台无法为用户提供服务。 换句话说,你应该期望监控和 衡量一切。
可观察性 有两种类型;第一种是为平台团队利益而设的可观察性。 这就是平台团队如何收集数据和信息以帮助提高其可靠性。 通过使用 SLO 的站点可靠性实践,平台团队可以通过为平台设定 SLO 来衡量客户满意度,并通过创建可观察性来支持 这些目标。
好消息是,在 Kubernetes 世界中,由于对象和微服务,这一切变得更容易。 坏消息是,由于微服务,现在需要衡量的事物比以往多得多,而且很难知道自己是否已经捕获了所有信息。 你的可观察性应该有一个生命周期,像你的软件一样。 从良好的开始,逐步改进,并意识到可观察性是一个活的东西,可能永远不会达到一个 完成的状态。
在 Prometheus、Grafana、Loki 及其他 自由开源软件 (FOSS) 项目中,有一个标准的可观察性工具箱 ,它帮助观察和衡量工作负载,并提供对其操作的洞察。 通常,使用可观察性数据的消费者是 站点可靠性工程师 (SREs) 或 DevOps 工程师,但任何关心其应用程序运营卓越性的用户,都应能够轻松地审查和使用 这些信息。
同样重要的可观察性工具,适用于故障排查生产问题的工具包括网络追踪、存活探针和堆栈跟踪。 一个平台应该提供该工具链的实现和验证,并以受限方式向用户提供,确保安全性和合规性 得到维护。
自然,日志和遥测数据的保留策略引入了自身的扩展性问题,而日志记录和备份系统可能是最严重的噪声邻居行为的制造者。 因此,这些系统的护理和运维工作由平台团队负责,以确保其顺利集成到预生产和生产环境中。 关于可观察性的其他最佳实践,如清理日志并将平台和应用日志隔离开来,这些都是重要的,但我们将在 第七章 中更详细地讲解这些话题, 当构建一个 安全平台时。
平台中的可观察性
可观察性简单来说就是你 衡量平台的方式。 在基于服务的方法中,SRE 实践中的 SLO 或多个 SLO 的设计旨在维护黄金路径。 如果你考虑一下你的 SDLC,这条路径应该在规划阶段已经定义好。 一个支持用户的 SLO,确保他们在黄金路径上顺利前行,代表了一个观察性策略的最佳实践。
如果我们回顾一下 Financial One ACME,以及在这个组织中为成功的 IDP 定义的黄金路径,我们可以看到观察性策略开始发展。 平台团队可以衡量工作流中最关键项目的 Pod 健康状况。 如果 Argo CD 发生崩溃循环,或者某个组件 Pod 未准备好,那么你就知道 CD 相关的用户期望未能 得到满足。
如果任务因安全检查或 SBOM 生成失败,那么安全性和合规系统需要关注。 这可能会阻止 PR 合并,打断开发人员的工作流,他们需要弄清楚为什么必要的检查没有通过。 在整个黄金路径上,总有一些可以衡量的内容,能够帮助指导平台健康和 开发人员的满意度。
平台中的可观察性应该支持 SLO。 这些通过收集和分析数据来衡量客户满意度。 当我们讨论平台演进中的数据驱动决策时,在 第一章中,我们倡导它作为维持平台产品思维的一种方式。 SLO 是一个具体的数据驱动方式,能够创建一个反馈循环,你的团队可以 在此基础上进行迭代。
如果你的组织无法满足 SLO,那么用户可能会不满意。 然而,即便你满足了 SLO 目标,SLO 仍然可以改进。 开始时设置一个 SLO,例如“任务 80%的时候会成功”,然后将这个数字提高到 99.99%。 这样,你依然可以不断改进平台,而不会增加用户的认知负担。
重要提示
100%是一个糟糕的 SLO 目标,因为这是一个不可能达成的指标;然而,可以应用三九、四九甚至五九的可用性概念。
关于 SLO 的进一步阅读,我们推荐 《站点可靠性工程:谷歌如何运行 生产系统》。
集中式可观察性——你什么时候以及为什么需要它
当平台 遭遇重大故障时,会发生什么? 开发人员、DevOps 团队、SRE 或任何负责响应生产环境 故障的人如何弄清楚发生了什么? 立即登录生产系统进行调试和故障排除是一种选择,但这不是最安全的做法。 最终,可能有必要这么做,但它应当被视为最后的手段,而不是 第一步。
你怎么知道你的可观察性堆栈没有出现故障呢? 在单一集群的 IDP 中,这些问题很容易回答。 但是在多集群或多云的 IDP 中,这些问题变得更加复杂。 这就是为什么集中式可观察性堆栈对于任何应用程序的成功至关重要,包括 IDP 本身。 集中式可观察性堆栈使得环境无关的可观察性成为可能。 集中式系统可以连接到 IDP 集群并通过探针验证其存活状态,或者它可以仅仅在没有数据时发出警报。 如果 IDP 集群停止连接主系统或停止发送日志,那是一个很好的指示,说明存在 问题。
重要指标
由于平台可以对工作负载的运行方式和位置做出规范,它也可以对工作负载的测量方式做出规范。 例如,DORA DevOps 指标可以被收集并用于提供服务质量或服务健康状况的指示。 平台可以通过提供服务来支持这一点,以暴露必要的数据点,或者它可以通过强制收集必要的指标来执行指标计算,因为它是环境的控制实体。 的环境。
虽然 SLO 和服务级别指标不能完全标准化跨多个服务,平台应该支持收集、聚合和保留任何关键应用程序指标的策略。 这些指标必须易于获取,并且具备适当的 RBAC 权限控制。 这使得用户可以自助定义指标并排除软件中的问题。 软件。
为开发人员提供的可观察性服务
平台的消费者的可观察性 与平台团队可能看到和操作的情况略有不同。 尽管工具箱大体相同,但所需的数据有所不同。 除了他们的应用程序性能和正常运行时间,平台上的开发人员更关心失败的 CI 任务、DORA DevOps 指标、应用程序日志和异常。 他们需要能够查看所有的可观察性数据,而不包括平台级别的可观察性或其他租户的可观察性。 这不仅有助于维持安全性和合规性,还减少了认知负担,因为来自所有源的数据可能让开发人员难以理解他们需要什么数据,以及如何 解读这些数据。
有几种方法可以实现将清晰的信号传递给开发人员。 Thanos 的可观察性服务专门设计为 Prometheus 指标的长期存储解决方案,原生支持多租户。 查询语言仍然是 Prometheus 的 PROMQL,这为 Thanos 中与可观察性数据的交互提供了一种熟悉的方式。 然而,它确实需要一定量的专用云存储来支持 其操作。
当我们深入探讨在平台中存储和提供可观察性数据的含义时,平台扩展性的问题会呈现出新的形式。 我们将在后续章节中更具体地讨论成本管理。 不过,现在我们可以先看一下几个高层次的考虑因素。
由于设置 Thanos 需要创建对象存储并允许它与平台之间的访问,它为平台增加了另一层复杂性。 必须设置存储,还必须创建并维护将其与平台其他部分连接的网络路径。 对于需要长期存储可观察性数据的大规模操作而言,这样做是有回报的,但对于较小的 IDP 或平台的早期版本来说并不理想。 相反,Prometheus 也可以以相同的方式使用,但它还可以通过本地模式将数据保存在持久卷中,或者根本不持久化数据,这意味着如果 Prometheus Pod 重新启动,它曾经知道的指标将 丢失。
在一个 IDP 内,团队可能对指标的长期保存有不同的需求;因此,使用联邦的多个 Prometheus 实例是一种相当常见的模式。
联邦本质上是一个 Prometheus 实例从另一个实例抓取所需的数据。 这些实例可以位于同一集群中,也可以跨不同集群。 在多租户身份提供者(IDP)的使用场景中,Prometheus 联邦是一个合乎逻辑的选择。 平台拥有应用程序的着陆区,因此它将持有其可观察性中的资源使用真实数据源。 然而,如果开发团队也使用 Prometheus 通过金丝雀路由和合成探针等方式来衡量其应用程序的健康状况,那么将这一数据开放给所有用户既不明智也不安全。 开发人员可能希望将这些数据与平台拥有的数据结合起来,以全面了解其应用程序性能。 通过允许开发人员的 Prometheus 实例以联邦方式访问所需的数据,开发人员可以在不超出其 RBAC 的范围内获取这些信息。 此外,这种模型通过创建有用的信噪比 来保持低认知负荷。 信噪比 是开发人员接收到的所有信号数量与可操作信号和噪声的比例。 开发人员暴露的噪声越少,他们就能越有效地解析所需的数据 进行审查。
实际实现并不困难;只需在 Prometheus 的 YAML 文件中添加几行新代码。 关键要点是,应用程序可以从平台和任何必要的共享服务中获取信息,但不能相互获取信息。 由于应用程序级别的可观察性数据更有可能包含个人识别信息或其他敏感数据,因此这有助于平台团队确保遵循安全性和合规性最佳实践或要求 。
这个例子的推荐配置是跨服务联邦。 在这种情况下,最常见的模型是拥有一个中央 Prometheus 服务器进行联邦操作,并由其他可能不支持联邦的 Prometheus 服务器抓取数据。
图 6.7:Prometheus 联邦模型
平台 Prometheus 实例的 YAML 看起来会像这样:
global:
scrape_interval: 15s
evaluation_interval: 15s
external_labels:
primary: 'platform-prometheus'
scrape_configs:
- job_name: 'prometheus'
scrape_interval: 5s
static_configs:
- targets: ['api.example.com:3000']
- job_name: 'federation'
scrape_interval: 60s
honor_labels: true
metrics_path: '/federate'
params:
'match[]':
- '{job="github-exporter"}'
- '{job="namespace-cpu-usage"}'
static_configs:
- targets:
- "prometheus.tenant-1-app:9090"
- "prometheus.tenant-2-app:9090"
在 图 6*.7* 示例中,存在一个共享服务,理解哪些服务对多个应用程序的可观察性栈很重要可能会很困难。 一个使用场景可能是 DevOps DORA 指标。 在 DORA 中,如果团队希望实施 DORA 成熟度模型,他们会衡量四个关键领域:
-
恢复的平均时间:从故障中恢复所需的时间
-
变更失败率:部署导致失败的频率
-
变更的提前期:变更到达生产环境所需的时间
-
部署频率:变更部署的频率
要获取这些数据,需要查询 CI/CD 系统,或者可以配置它们通过 Webhook 发送数据,并使用 Prometheus 来获取和标准化这些数据,从而使所有比较的数据以相同格式存在,这样可以简化计算。 其实现细节将涉及一个 Prometheus 导出程序。 Prometheus 导出程序是一个服务,它从可能不是立即支持 Prometheus 的系统中查询数据,然后将其转换为兼容的数据格式。 例如,为了计算变更的提前期,Prometheus 导出程序会从 CI 系统获取数据,标准化后,通过 Prometheus API 查询或联合查询使数据可用。
无论你最终使用何种可观察性实施路径,数据及其重要性都应对所有能够利用它的人开放,以支持平台和它所支持的产品的成功。 在这样做时要小心,同时也要理解数据是用户的一个关键资产。 对于用户来说,数据至关重要。
为社区和协作开放你的平台
我们经常使用 术语 开源,但开源并不总是意味着免费,反之亦然。 然而,当我们讨论 FOSS(自由开源软件)时,软件最重要的方面不是技术,而是其背后的社区。 开源社区促进协作,并帮助以革命性的方式自然发展软件, 从而改变整个行业。
当我们之前讨论了解用户时,主要是在启动阶段的背景下,但你需要保持相同的以用户为中心的思维,以确保产品持续运行并保持相关性。 充分利用用户的最佳方式是将他们带到团队的桌面上,创建一个像 FOSS 项目那样的社区,邀请合作、贡献和交流。 通过这种方式,你可以拥有一个数据驱动、用户主导的平台,随着服务的组织的不断发展而不断演化。 许多时候,一个项目会起步,但其后续的路线图却模糊不清。 随着初期推进 IDP 的兴奋感消退,失去动力和变得自满的风险随之而来。 社区的作用应该是充当燃料,保持 车辆前行。
在这一点上,你可能会问,我们该如何创建这个社区呢? 我们如何在不破坏我们为平台所努力减少的认知负担的情况下,将人们吸引到一起呢? 我们的平台该如何实现这一目标?
对此并没有一种魔法般的解决方案,而是可以通过几种方式同时进行,例如在代码库中添加贡献者指南,设定对期望的贡献类型以及如何评审贡献的明确要求。 贡献将如何被审查。
另一个方面是,你可能会在平台中加入一些工具,以帮助吸引合作。 例如,策略引擎能够吸引合作,因为用户可以提出新的策略或对现有策略进行调整。 通过设立一个面向最终用户开放的策略库,你可以邀请用户为政策提出 PR,提出新的权限或调整现有权限。 正如我们在自助服务中讨论的那样,这利用了 Git PR 和 同行评审的已知有效工作流程。
类似地,环境配额的使用是一种可以通过 GitOps 进行管理的配置;通过 PR 请求更多配额,并利用一些 GitHub 工作流帮助开发者申请配额。 在这两个例子中,利用 Git 仓库和 PR 允许用户以更直观的方式与平台互动,更符合他们的工作流程。 再一次,站在“客户”的角度帮助减少他们的认知负担,同时吸引合作是确保平台能够继续满足他们需求的必要条件。
一个平台应该自饮 自己的香槟。 如果产品安全团队对应用程序提出了平台必须支持的要求,比如生成 SBOM 或自动化渗透测试工作,那么平台本身也应该按照相同的安全标准进行评估。 这样做不仅是一次智力诚实的实践,确保安全标准的执行方式合理,并且不会妨碍服务性和自助服务,而且还是与内部社区互动的途径,推动平台的开发和加固。 平台。
一位有洞察力的读者可能会问,“你如何调和用户认知负担的减少与他们在 平台体验中的参与之间的矛盾?”
这是一个重要问题,因为无休止的反馈请求可能导致沉默,尤其是当用户认为反馈不会被采纳时,生成反馈的心理负担可能会非常重。 被采纳。
没有一种保证能成功的单一方式来获取社区参与,但当我们回顾 FOSS 社区中的一些最佳实践时,我们可以看到 一种趋势。
开放式规划
定期的计划会议和社区会议,由平台团队参与,并且对任何人开放,帮助创建和培养围绕平台建立开放协作的社区文化。 在一个 Kubernetes 项目中,大多数项目会议都在公开日历上,个人可以订阅邮件列表,及时了解每个项目的最新消息。 定期的产品更新和计划通过邮件发送,且公司内部公开透明,有助于保持兴趣和参与,希望能带来必要的积极反馈,以确保 IDP 的长期成功。
接受贡献
接受贡献可能是部署和扩展 IDP 过程中最具后勤难度的社会技术性方面之一。 然而,一些开源 IDP 项目非常适合这种范式。 最近的开源宠儿 Backstage 提供了许多其他功能,其中包括一个支持插件的架构。 换句话说,当一个团队发现缺少某些功能或能从一些额外的生活质量特性中受益时,通过将 Backstage 作为 IDP 框架来铺设,他们可以利用插件框架通过 PR 向内部 IDP 项目提议新功能。
虽然你不必使用这个精确的框架来实现相同的结果,但 Backstage 所展示的模式能够让用户轻松贡献,并通过动手贡献而不是仅仅向平台团队的待办事项列表发送请求,提供一种标准化的变更提案方式。 团队的待办事项列表。
总结
在本章中,我们探讨了如何为开发人员构建平台以及认知负荷的重要性。 保护用户免受不良结果的影响,有助于减轻他们的认知负荷,减少他们的担忧。如果出现问题,减少需要调试的内容,可以帮助开发人员保持高效工作。 此外,我们还探讨了认知负荷与自助服务之间的关系,并审视了一些常见的工具和模式,以提供一个能够满足用户需求的平台。 如果你正在使用集群并跟随学习,可能还会获得一些关于这些建议的实践经验。 虽然我们简要提到了每个主题的安全方面,但在下一章中,我们将深入探讨这些内容以及更多话题。 下一章。
第三部分 – 作为产品的最佳实践平台
在上一部分,我们将为你提供优化平台以实现成本效率的工具,从而帮助用户也能做到这一点。 我们将通过概述在你的成本环境中建立透明度所需的简单步骤,并为你提供减少基础设施费用的最佳实践来实现这一目标。 从基础设施成本开始,我们将讨论技术债务,如果不正确处理,它们可能会对所选技术栈的维护成本产生负面影响。 你将学习如何使用工具和框架评估技术债务,以及记录决策的重要性。 最后,我们将展望未来。 你将了解到变革的必要性,以及为什么你必须成为推动变革的积极一员。 你可能会发现一个关于你的黄金道路的新视角,以及一些关于未来相关技术的想法。 相关技术。
本部分包含以下章节: 以下章节:
-
第七章, 构建安全合规的产品
-
第八章, 成本管理与最佳实践
-
第九章, 选择技术债务以避免平台崩溃
-
第十章, 为未来打造平台产品
第七章:构建安全和合规的产品
在数字化时代,网络犯罪日益增多。 虽然并非每个组织都有银行或政府机构那样严格的合规要求,但那些高度监管环境的安全标准和最佳实践可以并应该被推广到你的平台。 每一层次的安全性都能帮助防止安全漏洞的发生, 从而减少风险。
在本章结束时,你应该能更好地理解安全标准、框架和趋势。 这包括理解和 利用 软件材料清单 (SBOM),了解开源项目如何提升平台安全,以及理解策略引擎技术(包含示例和用例)。 你应该能够利用这些学习,定义正确的行动来保障平台安全,同时不限制你的功能,并确保应用交付过程提供加固且安全的 软件/容器包。
因此,本章将涵盖以下主要内容:
-
调和安全左移与 零信任
-
理解平台安全——如何构建既安全又灵活和 开放的系统
-
查看 SBOM 实践
-
理解流水线安全——你需要考虑的事项,以确保你的 持续集成/持续交付 (CI/CD) 流水线
-
理解应用安全——制定并 执行策略
-
自由和开源软件 (FOSS) 在平台安全中的应用以及如何 使用它
调和安全左移与零信任
安全左移 和 零信任 是当前网络安全领域的 流行术语。 这些术语——或者说是流行短语——无疑会逐渐消失,但它们所代表的实践将在未来几年继续作为最佳实践。
安全左移 将构建和交付软件的过程视为一个从左到右的线性流程图。 该流程图可能会像这样:
图 7.1: 简单的应用程序开发工作流
在这个简化的示例中,开发人员编写代码,代码随后被放入源代码管理,最终作为应用程序供用户使用。 查看右侧的这个安全工作流程,尤其是在应用层面本身,虽然重要,但为时已晚。 已经有三个明显的地方,缺乏安全性可能会造成漏洞,这些漏洞可能 被利用。
在人员层面解决安全问题是向左安全的核心,但这并不是安全的终点;它只是安全的起点。 安全必须贯穿于流程图的每个步骤,以便当我们进入更现实的示例时,能够看到安全如何随 业务范围的扩展而扩展:
图 7.2:扩展的开发和交付工作流程
在前面的 图中,你已经可以看到在用户尝试与应用程序及其支持基础设施互动时,实施的一些常见安全最佳实践。 然而,开发团队和开源依赖项层面的安全性,源代码管理中的安全性,CI/CD 中的安全性,以及应用程序本身的安全性并未被解决。 即便是密钥和机密的存储,虽然代表着一种最佳实践,也需要对访问这些密码的过程应用安全措施。 向左的安全帮助你从产品生命周期的开始,到将完成的应用程序交付给最终用户的全过程中构建安全故事。 在平台工程中,这可能会在最安全的平台和实现完美自助服务的平台之间产生不和谐感。 由于平台需要支持开发人员的自助服务,因此平台完全拥有安全故事的做法开始与自助服务所带来的灵活性相冲突。 因此,它可能会施加足够的限制,使得平台显得有很高的准入门槛,从而危及平台的采纳以及 开发人员的幸福感。
在过去, 信任,但要验证 将是解决这个问题的安全模型。 其含义不言自明。 你信任开发人员已经做了所有必要的工作,以维持应用程序所需的安全态势,但在平台团队那边,你并不拥有端到端的安全控制。 该平台会尽最大努力验证所有正确的措施已经到位,而不会干扰 自助服务。
如今,安全最佳实践发生了变化, 零信任 成为了主流。 零信任 本质上假设每个人都是恶意行为者(无论是否故意都不重要)。 为了保持这种安全姿态,平台需要遵循最佳实践,但它不能承担应用程序的责任。 换句话说,平台需要为开发团队和利益相关者提供所有必要的支撑,以支持一个安全且合规的产品。 例如,如果需要使用 Python 语言,那么可以在镜像注册表中提供一个经过安全处理的 Python 二进制文件,无论是内部加固的,还是来自受信任的供应商,所有平台的用户和应用程序都可以访问。 使用来自 Docker 注册表的 Python-Slim 镜像也是一个更安全的选择,并且更容易获得。 对于大多数使用场景,Slim 镜像应该是足够的。 从这里出发,一个合理且自服务的限制是拒绝那些不使用已知安全来源镜像的工作负载。 在 CI 管道中可以处理这个检查。 尽可能将检查推到最左边可以节省每个人的时间,同时也避免了在不符合安全姿态的更改上浪费计算资源。 然而,在这一部分添加检查可能会有点痛苦,因为它需要编写一个作业来扫描、分析,然后根据存储库中所有 Dockerfile 的内容做出决策。 这些文件可能嵌套在子目录中,虽然这不是不可能完成的挑战,但确实可能比较麻烦。 此外,将这种功能写入 CI 管道应该被视为超出普通 平台团队的职责范围。
从平台的角度来看,另一种方法是使用策略引擎和准入 Webhook 来拒绝任何未使用受信任镜像源的 Pod 定义。 这虽然没有达到理想的最左边,但希望开发团队及其遵循的流程能够避免这种情况的发生。然而,在零信任环境中,这项政策将作为最后的防护措施,确保只有正确的软件被推广到生产环境。 可以认为,来自更多公共来源的镜像对于 IDP 中的原型设计是可接受的,因此,防护措施仅对生产环境是必要的。 这使得平台可以继续为团队服务,而不会不必要地 妨碍他们。
另一个例子是,只有当提交被签名时,才能接受将提交推送到 GitHub 仓库。 签名表明作者及代码自签名后未被篡改。 这可以通过 GitHub 仓库中的 webhooks 来强制执行,尽管平台团队对任何公司的 GitHub 组织的影响力可能有限;如果有的话,安全团队可能会要求此项操作。 尽管这是“零信任”与左移安全性共同作用的一个很好的例子,但这很可能超出了 平台团队的范围。
虽然这些短语听起来像是空洞的格言,但如果团队忽视它们所代表的基本原则,就会失败。 做得好的安全性是公司可以做出的最佳投资之一。 将安全性左移意味着尽早进行测试并频繁地进行测试。
每一个重大的安全漏洞和风险都可以通过测试被发现。 有各种类型的测试,包括一些由安全专业人员执行的测试,但无论是渗透测试还是仅仅是质量工程过程中的基础负面测试,都应该定期测试安全性和合规性,以确保没有意外的表面区域。 这可能表现为以意外的方式使用软件,或者仅仅是验证组织内角色的权限设置 是否正确。
现在我们已经介绍了左移安全性和“信任但验证”这两个概念,接下来让我们看看如何构建一个既安全又灵活的系统 和灵活的系统。
理解平台安全性——如何构建一个既安全又灵活、开放的系统
平台并不是组织安全态势的全部;它只是方程式的一部分。 在评估如何将网络安全或 DevSecOps 集成到平台时,必须保持平衡。 将安全性左移有助于减少平台团队需要投入的努力,但明确和清晰的安全范围有助于每个人理解自己在 安全故事中的角色。
将问题拆解成可消化的小块
安全性和灵活性也可能让人觉得是两个完全对立的词。 良好的安全性本质上是僵化的;然而,对于 IDP(身份验证平台)的成功来说,平衡这两者是可能且必要的。 我们如何实现这一点呢? 第一步 是 定义安全范围。
范围界定的第一步是了解所需的最低安全水平。 显然,我们总是应该做得比最低要求更多,因此,如果你想了解最高安全水平可能是什么样的,虽然这不是一个坏主意,但它可能会人为地增加范围,使你无法看到整体的全貌。 因此,我们建议从一个狭窄的重点开始。 一旦你知道平台必须执行的最低安全级别后,就可以开始考虑平台 必须支持的最低安全级别。
当你走上安全这条道路时,很容易迅速意识到互联网世界有多么危险,从而产生过度修正。 这些过度修正可能会增加认知负担,并为使用平台设置进入障碍。 如果一个优秀的开发人员懒惰,那么平台应当帮助他们懒惰,而不是引入额外的复杂性。 正因为如此,尽管平台不能承担组织整个网络安全姿态的责任,但它在这一姿态中扮演着至关重要的角色。 安全是好的;但安全表演 却不是。
了解多少是过多,多少是恰到好处,是一种随着时间推移而不断磨练的技能,并且在什么时候一个安全措施过多或恰到好处,没有明确的界限。 答案永远是: 视情况而定。例如,确保一个环境不对公共互联网开放,可能会妨碍项目人员在家办公,但如果这个项目涉及航天飞机或核反应堆,那么这种“空气隔离”环境是正确的,而不是 过度修正。
许多安全专家花费了多年时间研究安全,并定义了什么是安全和合规的明确标准。 美国 国家标准与技术研究院 (NIST) 是美国商务部下属的一个部门,汇聚了这样的专家,他们定期发布新的标准并随着 行业的发展更新现有标准。
了解这些组织的工作有助于你在发展对安全性和灵活的 内部开发者 平台 (IDP) 交集的理解。 由于这些机构通常发布针对与政府合作的公司的标准,值得注意的是,它们的出版物是针对特定类型的受众,并不能替代与在你 特定行业中有经验的网络安全专家的交流。
解决 OWASP 十大安全问题
如果你仍然不确定从哪里开始,另一个组织发布了可以作为界定安全范围的极好起点的指南。 这个 开放全球应用安全项目 (OWASP)(owasp.org)是一个 专注于网络安全的非盈利组织。 作为一个受人尊敬的安全专家团体,他们的 十大安全问题 列表已成为预见并防止软件安全问题的重要指南。 他们会定期重新发布此列表,2021 年出版的版本就是他们的 当前列表:
-
A01:2021–访问控制 破坏
-
A02:2021–加密失败
-
A03:2021–注入攻击
-
A04:2021–不安全的设计
-
A05:2021–安全配置错误
-
A06:2021–脆弱和 过时的组件
-
A07:2021–身份识别和 认证失败
-
A08:2021–软件和数据 完整性失败
-
A09:2021–安全日志记录和 监控失败
-
A10:2021–服务器端请求 伪造(SSRF)
OWASP 更进一步,2022 年还推出了针对 Kubernetes 的 十大安全问题 列表:
-
K01: 不安全的 工作负载配置
-
K02: 供应链漏洞
-
K03: 过于宽松的 RBAC 配置
-
K04: 缺乏集中式 策略执行
-
K05: 日志记录不足 与监控
-
K06: 破损的 认证机制
-
K07: 缺失的网络 分段控制
-
K08: 机密管理失败
-
K09: 配置错误的 集群组件
-
K10: 过时和易受攻击的 Kubernetes 组件
这些列表大致匹配,但都适用于基于 Kubernetes 的身份提供平台(IDP)。 这些列表不应被视为全面的安全姿态指南,而应被认为是 IDP 安全姿态中需要解决的最基本事项,但仍然是启动你的 范围项目的一个全面起点。
实施威胁建模
在你定义安全范围后,第二步 是 威胁建模。威胁模型是对可能影响你的应用程序或在本案例中平台安全的所有因素的表示。 执行威胁建模是如何得出正确结论的一个典范,这对于组织的安全姿态至关重要。 你可以使用这些 十大 列表来引导你关于威胁建模的讨论。 根据 《威胁建模宣言》(threatmodelingmanifesto.org)的作者,威胁模型应当回答以下 四个问题:
-
我们正在 做什么?
-
可能会 出什么问题?
-
我们将如何处理 这个问题?
-
我们做得够好 吗?
例如,你可以从以下开始: 我们正在研究用户如何向 IDP 进行身份验证。然后,接着讨论 过于宽松的 RBAC 配置可能会出什么问题? 并逐一处理 Kubernetes 列表中的每个 前十名 项目。 这些问题看似简单,但随着第二个问题(可能会出什么问题?)和第一个问题(我们在做什么?)的比例是多对一的,第三个问题(我们要如何解决?)与第四个问题也有类似的关系。 无论如何,如果 我们做得够好吗? 的答案不是一个决定性的“是”,那么这些问题的循环应该继续。 成功的威胁模型和应对计划是通过与所有 平台利益相关者的协作进行的。
这种在安全方面的协作是成功将安全置于中心的一项重要策略,且不牺牲可用性、接受贡献的能力或自助服务。 正是通过协作的威胁建模过程,可以解决安全方面的社会技术风险。 “安全向左”不仅意味着到达开发者的电脑,甚至包括开发者本人。 确保他们采取适当的预防措施,了解恶意行为者如何试图操控情况以窃取凭证,并且通常遵循最佳实践——例如,不要将公司笔记本电脑留在车里,以免被盗。
常见的安全标准和框架
进入安全标准的世界,首先需要尝试弄清楚一系列缩略语的含义。 目标并不是一夜之间成为安全专家,而是了解自己所需的安全级别,并确保平台做出一切必要措施来符合该安全标准。 解决这个问题的一个简单方法是,查看你所在公司所服务的行业以及相关的安全框架。 例如,美国的医院或大型医疗集团需要 遵守 健康保险流动性与责任法案 (HIPAA)合规性要求。 因此,任何与类似机构合作的供应商,无论其所在地如何,都需要能够遵守相同的标准。 通过了解最终用户和开发团队的需求,平台团队可以确定超越标准最佳实践之外所需的安全性和合规性级别。
让我们快速概述一些安全标准。 这并不是一个详尽无遗的列表,但涵盖了一些更常见的标准: 常见标准:
| 标准 | 地区 | 级别 | 描述 | 备注 |
|---|---|---|---|---|
| PCI DSS | 国际 | 1-4 | 支付卡行业数据安全标准。定义了安全性和合规性要求。 适用于任何处理、传输或存储信用卡信息的公司。 级别基于 交易 量。 | 由信用卡品牌创建,而非 政府机构。 |
| DPDPA | 印度 | 不适用 | 数字个人数据保护法。定义了个人数据的处理方式。 | 印度政府在 2023 年通过了该法案,虽然它与 通用数据保护条例 (GDPR)相似,但也有显著的差异。 |
| FedRAMP | 美国 | 中等, 高 | 联邦风险与授权管理程序。定义了提供软件和服务给 联邦政府所需的安全性 和合规性。 | 美国 联邦政府;与 州政府不同。 |
| HIPAA | 美国 | N/A | 健康保险可携带性与责任法案 。 | 这是 90 年代设定的标准,随着技术的发展,必须不断演变 以适应新需求。 |
| DGA | 欧洲 联盟 (欧盟) | N/A | 数据治理法案。定义了 欧盟在数据使用 和共享方面的政策。 | 适用于 公共部门数据和数据利他主义的背景,以及什么可以共享,什么不能共享。 填补了 GDPR 标准中的空白。 |
| GDPR | 欧盟 | N/A | 个人数据如何使用 以及如何不使用。 | 这是一次历史性的举措,彻底改变了全球的数据 处理方式。 |
表 7.1:安全性与合规性框架说明
虽然这些框架有所不同,并且它们旨在实现不同的目标,但从核心上讲,它们是相同的。 由应用程序捕获和存储的数据必须在传输过程中和静止时都得到保护,并且它必须仅进入预期的位置,只有预期的用户可以访问。 这一点部分通过 RBAC 实现,但仅靠 RBAC 不足以保障安全。 诸如 PCI DSS 这样的安全标准包括对硬件、服务器及其物理设备和存放位置的实际检查,才能获得合规认证。 合规性和安全性通常是紧密相关的,但实际上是不同的。 一个系统可以是安全的,但不合规,反之亦然。 尽管我们在此不会深入讨论这些差异,因为它们超出了 IDP 本身的范围,但了解安全性不仅仅是勾选清单上的框是非常重要的。 这些框应该有助于指导威胁建模的工作应在系统中扩展到何种程度,以及你如何在任何 受监管行业中开发平台的角色。
资产保护
坚持数字空间的范式,我们讨论的资产是你服务的数据库。 大多数安全性和合规性法规关注数据处理。 你应当理解为,数据是普通组织最有价值的资产,应该被 珍视。
你的数据有三种状态:它要么在传输中,要么在使用中,要么处于静止状态。 因此,针对它的安全性必须涵盖所有状态。 由于数据存储在平台上,所以平台有责任确保这一部分的安全性。 这是一个极少数不需要极高安全保护的数据产品,因此,确保数据安全几乎不可能过度。
静态数据保护
你的数据将 大部分时间处于静止状态。 数据设计和整体数据库安全需要非常具体的关注和定期审查,但静态数据的高级概念 归属于 以下几个类别:
-
分类
-
这是什么类型的数据,它包含了什么信息?
-
它有多重要? 重要性如何?
-
根据监管需求或 业务重要性进行数据的物理隔离
-
数据如何与系统交互可以 为分类提供信息
-
-
加密
- 每一层的加密,包括物理层和 数字层
-
盐值 和哈希
-
不仅仅是加密, 还有压缩
-
如果你希望数据 保持人类可读性,这就不太理想
-
在内存中可能会大幅扩展,并可能创建 一个意外的 分布式拒绝服务 攻击 (DDOS)
-
-
-
限制访问
-
可以根据 分类进行调整
-
应该有正式的审查流程, 角色与职责
-
-
冗余备份
-
三是高可用和 低可用系统的魔法数字
-
数据的冗余不需要完全一致;可以根据数据丢失的成本来设计策略 数据
-
-
数据 保留政策
-
你 到底需要它保存多久? 是否有 相关法律 约束?
-
那 监管链 是什么 关于 那个 决策的?
-
与冗余相同;不需要为 所有数据制定统一政策
-
-
你首先需要它吗?
-
挑战 每一部分:
-
膨胀发生在当人们认为数据 是必要的
-
不确定性让人们要求比 他们需要的更多
-
展示数据如何增加价值和责任,并使得后续使用和 成本合理化变得容易:
-
积极地减少 没有正当理由的部分
-
记录系统如何以及为什么被创建的历史 过程
-
员工入职 变得容易
-
回答关于系统的问题变得容易,包括 不公平的问题
-
公共汽车编号失去 其重要性
-
-
保留 这些数据的商业理由是什么?
-
它会 带来利润吗?
-
这能 节省资金吗?
-
我们会从中 学习吗?
-
做这件事 是正确的吗?
-
我们面临哪些风险 如果我们保留它的话?
-
-
-
操作使用案例是什么? (例如:故障排除项目、访问日志、 审计跟踪)
-
-
-
-
热 存储 和 冷存储
-
热 存储更 容易访问
- 需要 访问规则
-
冷存储则不 那么容易访问
-
不需要频繁关注但仍然重要的旧数据进入 冷存储
-
更难访问,通常由更高级别的 权限控制
-
-
数据主权
许多 国家正在采纳数据主权 法律,本质上规定在该国边界内由人们创建的数据不得离开该国的物理边界。 这并不总是意味着数据不能在国外查看(使用中的数据),而是数据存储必须保持在区域边界内。 这解决了静态数据合规性问题,但并没有解决 安全性问题。
保障数据在传输中的安全
当 数据在传输过程中,它正被微服务之间传输,这意味着它暴露在平台的网络和/或外部端点中。 数据在传输过程中需要加密,但最终,数据需要被使用,接收端点将在某个时刻解压数据:
-
限制存储在内存中的数据及其存储时间—这能保护平台的健康和数据安全(要聪明地管理 缓存)
-
不要在广泛开放或特权的端口上传输数据
-
仅记录数据交易中绝对必要的信息,而不是交易本身的数据(参见日志清理)
-
通过 清理输入 来防止注入攻击
-
使用网络安全和加密的最佳实践来防止 中间人 (MITM)攻击 以及其他各种类型 的攻击
使用中的数据
使用中的数据 正如其字面意思所示:系统正在查看或 更改的数据。 数据一旦存储并在使用中,它要么从存储中检索,要么被缓存。 它可能保存在内存中,或者通过各种读取和缓存技术直接读取。 数据在初次进入系统时也会处于使用状态。 这包括注册新用户或存储新的日志行。 通常,处于这种状态的数据也可能正在经历数据转换,如聚合或清理操作,以确保数据不能用于注入攻击,甚至可能被删除。 保护使用中的数据,只是我们在数据传输或静止时所应用的相同原则的另一种体现。
保护你的网络安全
Kubernetes 具有可插拔架构,虽然它默认没有网络栈,但某些 Kubernetes 平台选项会有自己的默认设置。 例如,OpenShift 容器平台采用了默认的 容器网络接口 (CNI),并称其为 开放虚拟网络 (OVN)。 除了 OVN,还有其他更安全、可观察性更强的解决方案适用于 Kubernetes 网络。
**
除了网络技术外,网络拓扑在网络安全中也扮演着重要角色。 像防火墙、VPN、VLAN、路由器、交换机等网络工具可能不会直接部署在 Kubernetes 集群上,但它们在集群安全中起着非常重要的作用。 无论最终的网络拓扑如何,集群如何与公共互联网交互(或者可能根本没有交互!),为了进行适当的威胁建模和合规性,你需要能够观察并记录 你的网络。
预生产环境与生产环境之间的隔离
一般的最佳实践 是无论安全性和合规性需求如何,你的系统都应该将生产数据和访问权限与其他环境隔离,以确保数据受到保护。 数据是大多数公司最有价值的资产,因此保护和隔离数据是确保资产安全的最佳方式,能够保证所有公司的安全性和合规性。 数据保护是安全性和合规性的核心。 生产数据绝不能离开生产环境,且必须严格控制对这些数据的访问,确保没有恶意行为者——无论是内部、外部、故意还是意外——能够访问生产数据。 我们再次引用我们的平台架构,来自 第二章:
图 7.3:平台参考组件
每个白盒,即便 是与安全相关的盒子,都必须有自己的安全网关。 例如,组织的 RBAC 管理能力不能开放给任何人修改。 很容易看出,这种情况如何迅速演变成一个问题,并催生了一个专家领域。 我们不会在本书中取代他们的知识和专业技能;然而,我们会分享一些我们认为最重要的 IDP 安全方面,帮助你走上 正确的道路。
任何组织的一个简单胜利是将暂存、开发和生产环境完全隔离开来。 这包括拥有完全不同访问规则的独立数据库,而不是一个包含不同表格和不同 访问规则的数据库。
可以使用单一集群,并通过基于网络策略的隔离、适用于特定命名空间的 RBAC,以及创建类似于多个集群的隔离体验,但集群的 API 服务器、审计日志、 etcd、网络及其他集群范围资源仍然代表着潜在的单点故障,可能影响 隔离的安全性。
因此,出于 安全和合规性的考虑,最好将环境完全隔离。 通过拥有两个独立的集群,可以保证生产数据的隔离,并减少人为错误对安全性的影响。 从这里开始,你还可以拥有不同的网络配置,例如有不同允许规则的防火墙规则,甚至完全与 公共互联网断开连接的环境。
机密和令牌管理
在 Kubernetes 中,Secret 是应用程序可能需要访问的敏感数据,如密码、认证令牌、环境变量、API 密钥等,以确保其正常功能或完成某项任务。 机密管理 成为在依赖自动化如此重的系统中,像 IDP 一样工作的最关键挑战之一。 幸运的是,有一些设计好的模式和技术可以帮助 应对这一挑战。
作为一个基于 Kubernetes 的平台,内建的 Kubernetes 功能用于秘密管理的安全性是关键的起点。 需要明确的是,默认行为并不安全。 秘密信息与 ConfigMaps 的存储方式类似,且没有加密。 它们是编码存储的,但编码仅仅是 base64。这种方式适用于开发环境,但对于生产环境并不安全。 然而,你可以在不安装任何第三方应用的情况下对静态的秘密数据进行加密。
有关静态数据加密的进一步阅读以及可以应用于测试集群的示例,请参阅 Kubernetes 文档中的 数据加密 部分: https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/。
秘密管理的范围通常相当广泛,一旦你有多个集群或多个环境,管理支持应用程序所需的所有秘密变得非常困难。 因此,使用诸如 HashiCorp Vault 这样的秘密存储软件,或如 Bitwarden 和 1Password 这样的密码管理器,已经成为行业标准。 一个标准的最佳实践是,将秘密信息在集群中自动部署的方式是:在代码中存储对秘密的引用,然后通过某种逻辑查找该引用并为应用获取相应的秘密。 因此,应用程序通过引用拉取秘密信息的常见模式是利用 侧车模型。
在侧车模型中,Pod 中的一个容器专门负责在 Pod 启动时获取并提供 供主应用容器使用的秘密信息。 这些秘密信息随后被放入存储卷中,当应用需要时 它会读取这些信息。
侧车模型 如下所示,其中 Pod 同时包含应用程序和 侧车容器:
图 7.4:容器侧车模型
但是,除非 sidecar(或其他服务)在循环中运行,否则这只解决一次,不能解决密钥可能定期更改的环境。 这就是开源软件解决方案是一个绝佳选择的地方。 外部秘密操作员 (ESO)是一个 开源软件,可以实现这一点。 该项目是 Linux Foundation 的财产。 有关其工作原理的详细概述,请访问 external-secrets.io。
他们的参考架构图如下,几乎与我们的 sidecar 参考图几乎完全一样:
图 7.5:ESO 的参考架构
基本上,秘密 存储在集群外的密码存储中,然后操作员可以访问它们。 秘密引用存在 自定义资源 (CRs),操作员知道如何解释和执行检索。 如果秘密发生变化,则操作员会自动将新秘密应用于集群,这意味着秘密在 IDP (SOT)中只有一个 真实来源。
当然,必须首先提供存储的身份验证秘密给操作员才能工作。 这可能有点困难,因为设置此操作员的人需要能够提供凭据以访问集群外的 秘密存储。
ESO 在高度监管的环境中特别有用,例如证书和令牌等秘密经常轮换。 这允许在源头进行轮换,但自动传播到 必要的环境。
ESO 并非集群上秘密管理的唯一开源项目;CNCF 正在孵化其他几个项目,都值得审查。 选择适合您组织的正确工具需要在权衡一系列利弊之后进行,但这些工具利用的模式代表了您应该追求的最佳实践。
日志清理
平台生成的任何日志都应该 进行清理。 应用程序也应该如此;然而,这超出了平台的边界。 日志是数据的一部分,因此是平台安全需要保护的资产。 除了我们之前讨论的存储和传输问题,数据清理是确保即使恶意行为者获得日志数据访问权限,他们也无法利用这些数据进一步危害系统的关键部分。 像 SonarQube(https://docs.sonarsource.com/sonarqube/latest/)这样的代码质量检查工具非常适合检测任何敏感数据是否被发送到错误的位置,从而在发生安全事件之前进行修复。 以防发生安全事件。
已清理的日志不应包含密码或令牌。 例如,在记录 API 请求时,请求的身份验证方式(例如,持有者令牌)可以被记录,但实际的令牌本身需要被清除。 在捕获或存储敏感数据的地方,应进行加盐 并哈希处理。
这些平台日志 也应该尽可能避免包含 个人身份信息 (PII)。 这种类型的数据通常不需要,因此存储它会带来不必要的风险 暴露面。
日志清理也包括保留政策。 就像应用数据在其 生存时间 (TTL) 到期后应进行生命周期管理和销毁一样,日志数据也应如此。 随着平台的不断运行,日志对平台团队的帮助逐渐减少,但其中包含的信息可能在某些情况下仍有价值,尤其是在由于业务需求无法剥离个人身份信息(PII)时。 因此,保留可能对恶意行为者有价值的数据会带来不必要的风险,甚至使用冷存储也无法完全规避这一风险。 何时销毁数据最终是一个业务决策,如果有充分的理由保留平台指标数据,可以对数据进行转换进一步匿名化,以便仅销毁 PII。 保持平台指标 永远存储。
安全访问
我们已经讨论过几次 RBAC 了。 它被明确列为 OWASP 十大之一,因此,确保访问控制正确对组织的安全性至关重要。 确保这一点的方法之一是创建服务账户。 这些账户是非人工身份账户,可以被系统上的工作负载使用。 与人工账户一样,服务账户的认证需要一个令牌,并且这个令牌应定期更换。 通过将访问类型分为人工和非人工,你可以利用 最小权限原则 **(PoLP)**来确保人工用户或工作负载仅拥有其所需的权限,而不会拥有它们 不需要的权限。
最小权限不仅应适用于工作负载,也应适用于人员。 在评估用户应该拥有的权限时,有几点需要注意。 接下来,我们将为您定义一些高层次的最佳实践供您调查:
-
单用户,多 RBAC:
-
用户在预备环境和生产环境中有独立的 RBAC 角色。
-
预备环境应与生产环境保持一致,以便在一个环境中执行的操作能帮助用户在另一个环境中执行相同的操作。
-
可能有破玻璃程序来获取更高的访问权限。 这种访问权限(如果存在)必须是可审计的,这意味着所有相关信息 都必须被记录。
-
-
GitOps 用于 安全:
-
可以 管理 RBAC。
-
减少直接授予访问 集群的需要。
-
Doe变成 单点故障(SPOF)。
-
中心 的安全性已被移除。
-
审计日志
什么是审计日志? 审计日志是 Kubernetes API 服务器看到的操作记录。 这意味着 Kubernetes 集群中的每一个变更,无论是自动化的还是人工发起的,从登录到 Pod 调度,都记录在审计日志中。 如果存在标识信息,它将在审计日志中记录。 这是因为审计日志是了解谁在何时、何地做了什么操作的重要路径,审计日志对于事件解决和安全性 PUT 或 PATCH 等有效载荷也应被记录。 凭据不应作为个人身份信息(PII)记录,应在绝大多数情况下省略。 案例。
利用审计日志 来确定异常行为可以通过一些基础的可观察性实现和相应的警报来完成。 在定义平台时,你应该已经构思出用户故事和关键用户流程。 在这些练习中,你会分析用户将会做什么并期待成功的操作。 但是你是否考虑过用户不会做或不应做的事情,并期待成功呢?
审计日志中发现的行为,如果偏离平台用户的正常行为,是定义潜在异常警报的最简单方法。 一个例子是出现大量的 403 错误,或者来自于 特定 无类域间路由 (CIDR)范围外的 IP 地址的请求数量显著增加。
自动化检测可能会查找的其他项目包括 以下内容:
-
检测异常或无效的用户代理 或机器人
-
来自 不同位置的多个用户登录或会话
通常,违反已知规范的事件应该自动地引起人工注意。 然而,大多数情况下,这些事件仍然需要人工审核,因为它们不一定表示安全事件。 它们可能指示一个软件问题或一个必要的事件,但在设计警报时并未考虑到。 警报不应过于频繁。 虚假信号会造成伤害,尤其是当它们在半夜打扰到你的团队时。 如果虚假信号触发得过于频繁,工程师们可能会很快忽视它们,以维持较低的 认知负荷。
到目前为止,我们已经介绍了安全的基础知识,还有很长的路要走。 现在我们已经学习了一些一般性话题,接下来让我们更具体一点。
查看 SBOM 实践
开源工具、编程语言中的库、包管理器和容器镜像是现代应用程序的构建块,同时在安全 你的 软件供应链方面也带来了独特的挑战。这就是我们亲切地称之为供应链安全难题的原因。 当你并不拥有所有需要 被保护的代码时,你如何保持良好的安全态势呢?
如果我们以可视化的方式表示供应链,它将包含一些未知的人(我们称之为参与者)为开源依赖项做出贡献,以及另一个可能已知的参与者更直接地为你的代码库做出贡献。 这是一个极度简化的图示(这里可能缺少了 10 个框),但它应该有助于你理解 重点:
图 7.6:示例供应链
你的软件供应链就是一切,所有参与者都在发布你的应用程序中发挥作用。 当我们考虑如何维护安全时,我们必须拆解我们的应用程序和基础设施拓扑结构。 SBOM 是跟踪和管理项目风险的重要工具。
在美国政府于 2021 年发布行政命令后,这些文档对于许多公司来说变得强制性。 其基本前提是,公司知道它们构建的软件依赖于什么,以及这些软件的依赖项来自何处。 SBOM 会在一款软件及其所有依赖项捆绑发布时生成。 虽然对于每个公司来说并非绝对必要,但作为最佳实践,当与扫描工具配合使用时,它们有助于审计并理解风险的表面区域,特别是在像 Heartbleed 或 Log4j 这样的 Day 0 漏洞再次被发现时。
SBOM 通常是在 CI 流水线的构建过程中生成的。 Syft 是一个 相当常见的 SBOM 生成工具,通常与扫描器 Grype 配合使用, 因为它们都是 Anchore 提供的免费开源工具。 思科的开源项目办公室最近还发布了一个 名为 KubeClarity (https://github.com/openclarity/kubeclarity),该工具可以协同使用多个 SBOM 和扫描工具,提供软件及其表面区域的最完整视图 以便于风险评估。
SBOM 生成工具仍然相对较新,因此它们还不完美。 可能某个工具未能检测到一个包,而另一个工具则能检测到,反之亦然。 为了了解你的安全态势,少即不等于多,因此获得尽可能最完整的视图是保持安全的最重要部分。 安全优先。
如何使用 SBOM
SBOM 不仅仅是客户或美国政府要求清单上的一个勾选项。 它还是一个有效的漏洞检测和响应工具。 在 图 7*.6*中,我们展示了你的应用程序如何继承代码,从而继承了可能利用的开源依赖和库中的漏洞。 这些依赖关系很难跟踪,这也是为什么 SBOM 可以作为你系统的账本。 这意味着,如果后续发布了重大安全漏洞通报,你可以快速将该通报与 SBOM 进行交叉引用,并及时了解你的软件是否存在漏洞。 这可以通过查看已创建的报告,也可以通过重新生成报告来完成。 如果报告生成与扫描工具配合使用,扫描工具应该在漏洞被录入到关键 漏洞注册表后立即检测到新的漏洞。
获取 GitHub 仓库的 SBOM
查看 GitHub 仓库的 SBOM 的一种简单方法是使用 curl 命令调用你要调查的仓库的 GitHub API。 为了快速演示,我们将介绍如何执行此操作以及如何解读 结果。
GitHub SBOM 采用一种被称为 SPDX 的格式;你可以在 这里了解更多关于该格式的信息 : https://spdx.github.io/spdx-spec/v2.3/introduction/。
要获取 SBOM,请在终端中使用以下代码块来调用 GitHub API。 你不需要进行身份验证就可以运行此命令,但如果需要的话,也可以进行身份验证。 将 $REPOSITORY 和 $OWNER 变量替换为你所需的仓库信息。 为了示例,我们将查看 tag-security CNCF 仓库:
curl -L \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/$OWNER/$REPOSITORY/dependency-graph/sbom
返回的 JSON 会比较长,所以我们只看从 curl 请求中收到的 SBOM 的一部分(https://api.github.com/repos/cncf/tag-security/dependency-graph/sbom):
{
"sbom": {
"SPDXID": "SPDXRef-DOCUMENT",
"spdxVersion": "SPDX-2.3",
"creationInfo": {
"created": "2024-08-16T05:37:53Z",
"creators": [
"Tool: GitHub.com-Dependency-Graph"
]
},
"name": "com.github.cncf/tag-security",
"dataLicense": "CC0-1.0",
"documentDescribes": [
"SPDXRef-com.github.cncf-tag-security"
],
"documentNamespace": https://github.com/cncf/tag-security/dependency_graph/sbom-0a6b74785f7954ee,
输出的顶部包含了一些关于 SBOM 的基本信息,包括它是如何生成的、何时生成的以及相关的数据许可信息。 这只是告诉你关于你分析的仓库的高层次信息。 接下来的部分是 包,它包含了所有的软依赖项以及它们的关系:
"packages": [
{
"SPDXID": "SPDXRef-npm-babel-helper-validator-identifier-7.22.20",
"name": "npm:@babel/helper-validator-identifier",
"versionInfo": "7.22.20",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"licenseConcluded": "MIT",
"supplier": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:npm/%40babel/helper-validator-identifier@7.22.20",
"referenceType": "purl"
}
],
"copyrightText": "Copyright (c) 2014-present Sebastian McKenzie and other contributors"
},
只看 SBOM 中的一个包,你可以看到包信息、使用的版本、许可和版权信息。 MIT 许可证意味着该包是开源的,但版权信息表明谁在维护该包,并且从本质上防止其他软件项目使用该包的名称。 IBM 在此解释了版权的原因: https://www.ibm.com/topics/open-source。输出中还包括了标签供应商和下载位置。 在这个示例中,这两个字段的元数据显示 NOASSERTION。正如 SPDX 文档中所解释的那样(https://spdx.github.io/spdx-spec/v2.3/package-information/),这应该在以下情况下使用: 以下情况:
-
SPDX 文档创建者已尝试但无法做出合理的 客观判断
-
SPDX 文档创建者未尝试确定 此字段
-
SPDX 文档创建者有意没有提供任何信息(不应因此而意味着任何含义) 。
对于其他软件包,这些字段可能包含有关软件包的其他数据,也可能没有任何断言。 在软件包列表之后,按照相同顺序列出软件包与 存储库的关系:
"relationships": [
{
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-com.github.cncf-tag-security",
"relatedSpdxElement": "SPDXRef-npm-babel-helper-validator-identifier-7.22.20"
在这个例子中,这是 非常直接的,因为它说主要元素,标签安全库,依赖于 npm:@babel/helper-validator-identifier 软件包版本 7.22.20。在这种情况下,SBOM 创建者可以提供更多元数据,但目前没有。 有关这些关系的更多信息可以在这里找到: https://spdx.github.io/spdx-spec/v2.3/relationships-between-SPDX-elements/#111-relationship-field。
再次提醒,SBOM 是一个非常简单的工具;它创建一个分类账,列出应用程序或代码库的组成部分。 单独使用时,它并没有太多作用,但作为工具链的一部分,它对理解系统及其依赖的漏洞风险表面有很大帮助。 依赖。
保持在漏洞的顶端
软件漏洞 通常称为 公共漏洞及曝光,或 CVEs。美国 国土安全部 (DHS) 维护公共 CVE 注册表 (https://www.cve.org),您可以了解已知的曝光情况,并可与您的软件和应用进行对比,检查已知的 CVE 和您的风险。 尽管平台团队可以构建专门服务来确定系统中是否存在 CVEs,但无需如此,因为存在大量的 FOSS 工具可以为您完成这项工作。
您可以通过 GitHub 检查 CVEs,方式如下: 以下:
-
Dependabot,一个 GitHub 机器人,扫描您的存储库,并提出拉取请求,通过提升软件包到已知的安全版本来积极解决 CVE 问题
-
Snyk,验证拉取请求以确保不引入任何 新的漏洞
-
CodeQL,类似于 Snyk,评估拉取请求的内容,确保它们不会 引入漏洞
为了跟踪运行时的漏洞,定期扫描图像注册中心,如 Harbor,或使用 Trivy 等工具,至少针对你最关键的环境进行扫描,将帮助你及时掌握环境中的漏洞。
由于 SBOM 生成和漏洞检测通常是 CI 管道的一部分,我们接着讨论管道的其余部分,及如何确保你的CI/CD 过程中的安全性。
理解管道安全——你需要考虑的事项,以确保你的 CI/CD 管道的安全
假设平台团队对 GitHub 或公司使用的其他源代码控制仓库具有影响力或管辖权,那么 CI/CD 管道的安全性 从头到尾将成为身份提供平台(IDP) 安全态势的关键部分。
保护你的仓库
代码仓库的安全性 是“安全向左移”的一个很好的例子。 通过早期强制执行安全规范并将其融入项目的工作方式,组织可以防止问题在后续发生。 一个安全的仓库采用了多个 最佳实践:
-
写保护 主分支
- 如果需要额外的安全性,你可以使用私有 Git 仓库和自托管的 Git 来增强安全性
-
要求 签名提交
- 这验证了 提交作者的身份
-
提交前 Webhook
- 用于验证没有机密被 意外提交
-
强制性 同行评审
- 包括由 代码所有者的签字
-
自动验证 拉取请求
-
依赖 安全扫描
-
测试——应包括安全性 和访问权限的验证
-
-
持续扫描
- 扫描 以防止密码、令牌或其他 机密数据的意外提交
这份清单并非详尽无遗,但它应该为任何组织提供最佳的起点,以确保其源代码能防止恶意行为者和 人为错误的风险。
保护 GitOps
GitOps 已在 第 第五章中详细介绍,因此由于其对平台安全的重要性,我们将简要回顾一下。 GitOps 被松散地定义为一种自动化过程,用于验证 SOT(Git 或其他版本控制系统),以确保期望状态与实际状态匹配。 它通过一次性部署或更改来完成此操作,但也通过一个自动化的调和循环,主动检查期望状态的变化,并对实际状态采取行动,使其与期望状态匹配,或检测到实际状态的变化并对系统进行更改,将其恢复到期望状态。 当前最大的开源 GitOps 项目是 Argo CD,这是一个属于 CNCF 的 CD 工具。 在 Argo CD 和其他 GitOps 模式中,有一些最佳实践需要注意,以确保更 安全的环境。
对于 GitOps,有两种传播更改的模型;一种是推送模型,另一种是拉取模型。 在推送模型中,GitOps 系统将更改作为推送到其 API 端点的方式接收。 不言而喻,这些推送应当经过适当认证,但我们还是要强调,以确保没有混淆。 接收到推送后,GitOps 系统处理更改,然后采取行动。 这个行动可能是将新的软件包推向生产环境,但也可能是配置更改。 然而,在这种模型中,推送源通常不会被验证,Argo CD 也没有配置来验证它。 这就创建了一个攻击向量,因为如果凭证被泄露,攻击者可以通过 CD 系统推送更改,这可能会打开额外的 入口点。
拉取方法正好相反。 GitOps 系统使用其认证机制访问 SOT,然后从端点读取更改。 由于源是已知的安全源,通过自动化应用这些更改被认为是一个安全的操作。 这并不是说拉取模型没有风险。 推送和拉取模型都可能遭受 MITM 攻击,但在正确安全的网络和适当的加密实现下,这些风险应该 被大大限制。
当你的 GitOps 系统采取行动时,它应该以最安全的方式进行。 我们已经讨论过服务账户的使用和价值,因此你应该不会感到惊讶,GitOps 系统应该利用服务账户。 这些服务账户应当只具备完成 GitOps 实施目标所需的最小访问权限, 同时仍能在平台上执行任务。
安全性和 GitOps 可能是选择适合你 IDP 的 GitOps 工作模式时一个重要的因素,我们希望这些指南在为你的组织构建 GitOps 时能发挥作用。
现在我们已经覆盖了应用交付的安全性,让我们在 安全基础上继续深入,看看 应用安全。
理解应用安全性——设定并执行政策
安全性是一个动态目标,随着技术的进步,攻击方式增多,攻击者变得越来越复杂。 因此,团队在安全方面保持的流程和仪式比技术本身还要重要。 这并不是因为技术不重要,而是因为良好的纪律和强有力的流程习惯能够使技术根据行业的发展灵活地替换和调整。
良好纪律的一部分是维护准确的文档和架构图。 如果应用架构发生了重大变更,那么这可能会改变风险面和攻击向量。 例如,对一个库或网络端口的未记录或记录不全的依赖,可能导致暴露在一个更难以识别的漏洞面前。从而增加发现的难度。
基础应用安全
在 第五章中,我们讨论了 构建和交付镜像及工件的方法。 所描述的应用程序语义版本方法代表了创建软件的最佳实践,但不适用于其使用。 当使用 Git 进行源代码控制构建发布时,发布除了一个人为定义的版本外,实际上还会获得一个 SHA-256 签名,这得益于 Git 的现代功能。 与可以重复使用的版本号不同,安全哈希算法 (SHA)是该软件确切构建的签名,并且它始终是唯一的。 因此,对于安全最佳实践来说,使用平台所使用的镜像的完整 SHA 地址,而不是 镜像版本,是非常重要的。
以下是使用镜像版本 docker pull 命令的示例,以及 SHA:
docker pull quay.io/keycloak/keycloak@sha256:520021b1917c54f899540afcb0126a2c90f12b828f25c91969688610f1bdf949
除了镜像版本,还有一种称为 latest的标识方法,但它们几乎可以是任何东西,因为只有行业规范,没有技术限制。 由于标签可以稍后重新指向任何发布镜像,因此这不是拉取依赖项的安全方法。 使用浮动标签的风险 包括 以下几点:
-
无意的 和未经测试/审查的更新至 应用程序组件。
-
限制了立刻了解正在运行的内容以及运行地点的能力 和时间。
-
未经审查的外部软件包可能会自动传播,为恶意行为者创建可利用的后门。 如果一个 Pod 重启并且
latest被用于该 Pod 的 Dockerfile 中 ,这种情况是常见的。
有几个原因可以选择使用浮动标签,或者使用语义版本发布标签,而不是精确的 SHA。 如果你定期测试最新版本的构建,测试依赖项的持续可支持性会更容易。 在开发环境中,始终使用已知良好包的最新版本所带来的灵活性可能是需要的,然后在转向生产环境时,可以将其固定到精确的版本标签或 SHA。 针对浮动标签进行验证,还可以使团队在应对安全漏洞时更加灵活,因此应该对每个依赖项进行风险评估,并制定政策以确定应用程序的规范 是什么。
设置策略的典型方式是使用开源策略代理等工具来捕捉任何偏离已定义规范的行为,并防止这些偏离进入受限 环境。 开放策略代理 (OPA)和 Kyverno 都是免费使用的开源选项。 对于这两个工具,你可以利用 代码即策略 (PaC),它可以方便地审查并保存在 源代码管理中。
OPA 和 Kyverno 是两个可以用于安全的开源软件示例,但它们并不是开源生态系统中唯一的工具。
用于平台安全的 FOSS 及其使用方式
FOSS 项目,无论是在 Linux 基金会还是 CNCF 内部外部,都有大量的项目可以帮助你管理平台的安全态势。 前面提到的项目如 Harbor 和 Trivy 只是其中的两个。 它们只是众多项目中的一部分。
在比较你的安全需求时,比如确保已涵盖 OWASP 十大 针对可用的开源项目,你会发现有工具可以帮助你解决列表上的每一项。
管理安全的模式与工具
平台的作用仅限于管理公司安全态势。 因此,它需要通过提供有用的集成、采取安全优先的策略,并且如前几章所讨论的,保持对支持的开发者社区的贡献开放,来为安全提供坚实的基础。 当你确定了所需的合规级别后,就可以开始查看哪些错误可能导致合规性和安全态势的失败,通过进行基于流程的安全审查和基于技术的 安全审查:
-
一个 基于流程的审查 意味着有人在审查和评估合规性与安全性。 这可能是为了寻求认证而进行的审计,也可能是定期进行的内部审查,以确保最佳实践仍在实施,并且指南是最新的。 。
-
一个 基于技术的审查 将 利用软件自动并可能持续地审查和验证安全性与合规性。 SBOM 和 CVE 扫描是基于技术的软件构建审查的例子,像 OPA 或 Kyverno 这样的策略引擎可以帮助自动治理 IDP。 技术解决方案还可以进一步帮助检测可能代表安全事件的异常和事件。 CNCF 项目 Falco (https://falco.org) 就是这样做的。 它有几个关键的 特点,但 重要的是,它能检测是否存在权限提升,这可能代表着不良的安全和合规态势,或者可能代表一个已经获得系统访问权限的不法分子。 该系统的安全性可能已经被破坏。
任何公司如果希望展示符合某一治理框架的安全性和合规性,那么该合规框架将有助于定义过程执行的频率,并将 这些指导与定期审计相结合,以帮助确保平台 不会出现任何问题。
我们的虚构公司会怎么做呢?
我们的虚构公司“Financial One ACME”是一家长期存在的金融机构,正在进行云原生转型,以便在与年轻的金融科技公司竞争中保持竞争力。 作为一家金融机构,他们有一个固有的目标——最小化风险。 他们还受到监管限制,包括 PCI DSS。
此外,由于他们是一家银行,必须保护货币资产和客户数据,他们已经建模了可能威胁到其系统的潜在风险。 在众多物理和非物理风险中,许多安全行动项目被交给平台团队,在 IDP 的实施过程中解决。 这些问题可能已经得到解决,但由于这个 IDP 是一个全新的应用程序(或者是一个全新的项目),它需要重新处理合规性 要求。
平台团队需要实施的一个事项是确保他们所有新版本的软件包都已 妥善安全。
妥善安全意味着 以下内容:
-
签名 并验证
-
安全存储
-
安全地检索
-
严格的 变更控制
-
审计跟踪
平台团队采取了之前概述的步骤来确保他们的代码库和 GitOps 系统的安全,但这些措施不足以满足这一要求。 因此,平台团队决定利用一个内部镜像注册表,其中所有软件提交和包都会进行签名。 CI/CD 管道基于这些工件创建镜像,并将它们放入私有注册表中,平台应用程序 从中获取镜像。
镜像注册表的选择可能看起来像是以下几种选项之一:
-
支付给一个镜像注册表供应商,帮助他们通过扫描来维持安全姿态,并为他们提供 可信 通用基础 镜像 (UBI)
-
托管他们自己的镜像注册表,如 Harbor,这是一个 CNCF 毕业项目,拥有一个镜像注册表,该注册表 签名、 存储和 扫描 容器镜像(https://goharbor.io/),并且它们将被填充为 已批准的镜像
-
如果他们使用的是 OpenShift,那么集群拓扑中已经有一个镜像注册表,平台团队会用 扫描工具 来补充它。
他们进一步添加了一个准入 webhook,以确保没有容器基于未批准的镜像。 这个用例非常简单:一个验证 webhook 检查容器镜像是否符合定义的预期。 如果不符合,工作负载将被拒绝,Pod 将无法启动。 虽然团队可以自己构建一个准入 webhook 服务,但他们可能会选择 OPA。 虽然它不是唯一提供此功能的开源项目,但它是唯一一个毕业项目,使其成为生产环境中最安全的选择。 在生产中使用。
Sysdig 创建了一个开源版本的此类 webhook,它还会更改或修改 Pod 配置,以便其镜像使用完整的镜像 SHA,而不是发布标签,详细信息见: https://github.com/sysdiglabs/opa-image-scanner。这两个 webhook 都代表了安全性和合规性的最佳实践,是任何平台 工程团队在安全方面轻松获胜的明智之选。
然而,尽管这些限制至关重要,它们可能通过限制 IDP 能力的灵活性而对创新产生负面影响。 因此,平台团队决定将这些限制限制在生产环境中。 这样做允许开发团队在其开发环境中进行实验,而无需担心任何不应意外进入 生产环境。
管理依赖项的一种策略是使用供应。 供应是将代码从你原本会导入代码库的开源库复制过来的过程。 在供应过程中,可能会对该代码进行更改以 增强其安全性,例如启用 联邦信息处理标准 (FIPS) 模式。 FIPS 合规性规定了数据通过 安全套接层 **(SSL) 的加密强度,通常是最佳实践。
平台团队可能会有的另一个行动项目是在疑似安全漏洞事件中成为第一联系点。 这意味着需要立即对问题报告做出反应。 由于平台对公司基础架构的责任很大,从开发到生产能力,团队需要能够迅速响应,以减轻 恶意行为者造成的损害。
应当制定并定期测试这样的 IR 计划 (IRPs)。 与 灾难恢复计划 (DRP) 类似,定期 测试计划及其响应真实安全漏洞的能力可以 减轻损害。
早期检测是该平台所需的另一个关键能力。 对审计日志的分析是理解谁进入了系统(或谁的凭证被泄露)以及恶意行为者在获得访问权限后进行了什么操作的关键。 审计日志还可以用于主动检测,因为它们可以被 用于 机器学习 (ML)模型进行异常检测,这可以比人类更快地发现安全漏洞。 此外,使用像 Cilium 这样具有高度可观察性的云原生网络解决方案可以帮助识别和追踪恶意行为者。 尽管一些硬编码的可观察性实现能够达到相同的结果,但它们需要手动维护,而机器学习模型由于其 自学习的特性,可能具有更多的内在灵活性。
两种方法都不是完美的,因此,在决定如何围绕威胁检测实现自动化时,组织需要对收益、权衡和团队能力做出判断。 在我们虚构公司的情况下,选择前进的路径将是一个关于“自建与购买”的讨论,这可能会因为任务的规模和复杂性以及它们所需维护的高度安全环境而最终做出购买的决策。 为了维持这一点。
谈到这个高度安全的环境,为了向审计员展示组织使用新 IDP 的 PCI 合规性,我们的平台团队需要能够提供网络架构图,并向 审计员解释该图的内容。
任何合规性审计员都希望验证开发和生产环境是否得到充分隔离,无论这是否在物理上得到实现,或者通过 网络实现来完成。
最后,虽然 Financial One ACME 平台团队将确保他们遵循所有已知的构建时最佳实践,但他们也会实施工具,确保他们的运行时同样安全。 很可能,生产环境中唯一有权限创建 Pod 的用户将是那些服务帐户用户,确保 Git 保持为 SOT,并且可以利用 GitOps 来规范平台的 安全态势。
这些示例响应仅涵盖了金融机构(如银行)所需的一部分安全性和合规性要求;它们还可能会受到额外政府法规的约束,这将需要进一步的安全性和合规性风险缓解措施。 正如您的团队所需要的,我们虚构的公司也需要解决其所遵循的合规框架中的每一项条目,并且最 重要的是,创建协作仪式来维持最佳安全性 他们可以达到的水平。
总结
总之,安全性和合规性是一个广阔的领域,许多专家已经发布了专门的著作。 本章不应被视为包罗万象,但应帮助您迈出正确的步伐,为您的 IDP 定义并执行网络安全策略。 了解如何追踪漏洞并在您的组织中建立仪式和工具,以便捕捉和发现 IDP 及其所承载的应用程序中的漏洞是非常重要的。 它所托管的应用程序。
尽管安全性和灵活性并非天生是合作伙伴,但专注于关键安全需求而不阻碍创新的智能实现是为开发人员提供他们成功所需工具和他们所需保护的关键。 保持安全。
请记住——安全事件的成本可能极其昂贵,甚至可能导致破产或诉讼。 虽然日志存储和其他安全要求可能需要费用,但这些费用是可以管理的,并且永远不会比未能保护系统所带来的成本更高。 关于如何管理平台成本的更多信息,我们继续阅读 第八章。
第八章:成本管理和最佳实践
在过去几年里,云迁移一直是许多 IT 组织的首要任务,并且在未来几年仍然是一个战略性、相关的方向。 自建数据中心仍然是一个有价值的选择;相比于云,自建数据中心的设计和成本通常经过更深思熟虑。 在本章中,我们将深入探讨这一问题,并反思云成本管理 和优化的必要性。
你将更多地了解标签策略,以及它们为何是获取云支出可见性和透明度的可行资源。 我们还将探讨如何定义标签策略、最佳实践以及实际方法,确保它们得到妥善设置。 以此为基础,我们将深入探讨成本优化的四大支柱: 流程、 定价、 使用、 以及 设计。
在本章的最后,我们将分享一些实用的建议,帮助你优化平台并长期降低成本,同时为你的用户提供节省成本的价值。 。
总体而言,我们将重点讨论有效的成本管理,以及作为平台工程师,如何实现这一目标。 以下是你可以期望学到的内容:
-
理解成本格局——云是否是最佳选择 ?
-
实施标签策略以揭示 隐藏成本
-
审视成本 优化策略
-
自动扩展、冷存储以及其他成本优化 技巧
了解成本格局——云是否是最佳选择?
成本管理 在平台工程中的应用始于对成本驱动因素的深入理解 ,特别是了解基础设施中哪些平台组件可能会影响这些因素。 成本驱动因素也是直接影响你运营总成本的因素。 理解并随后识别这些因素,有助于做出明智决策,从而提供以成本为导向的 优化平台。
是否选择云——这是一个问题
过去几年,几乎没有办法绕过云采纳战略。 在这些年里,这一运动面临了来自一些成功进行“反迁移”的公司压力,这些公司声称他们的本地部署比云更便宜、更好,并且满足他们的一切需求。 反迁移 就是这个过程的名称。 做这种选择的人数并不十分明确,且很大程度上取决于什么算作其中的一部分。 它变得非常著名,作为 HEY/Basecamp/37signals,他们的首席技术官 David Heinemeier Hansson 曾表示 如果他们不使用云计算,他们将在未来五年节省超过 700 万美元 。 他们购买的服务器与每年在云上花费 190 万美元之间进行的愚蠢对比,显示出他们的购买服务器花费了 50 万美元,这让数学变得非常清晰 [1]*。
当然,Basecamp 或 HEY 与企业是不具可比性的,对吧? 根据我的经验,我可以告诉你,运行大型公司的硬件在自有数据中心比迁移到云端要便宜。 一个关键因素是数据量以及基础设施需要多么动态和可扩展。 计算以拍字节为单位的数据时,云服务会迅速变成一个无尽的金钱黑洞。 相对静态的工作负载也会大大减少所包含的好处,而且还有许多其他的缺点。 另一方面,云市场每年都在持续增长,似乎没有尽头。 因此,要做出决定,选择正确的方式,你需要考虑许多不断变化的因素,从简单的成本对比到可用技能以及你业务的实际需求。 对云是否适合你的全面评估本身就可能是一个项目。 但我们想给你一些关于应该考虑的标准,以便你能在 这个阶段做出决策:
-
成本:评估本地部署的初始资本支出(CapEx)或一次性费用,和云服务的运营支出(OpEx)或持续性订阅费用,并考虑长期财务影响,同时还要意识到,如果你提前支付 1 年或 3 年的费用,你在云计算上将节省最多的钱。 这几乎就像是在购买 硬件。
-
可扩展性:评估你在快速变化情况下动态扩展资源的需求。 这里有个提示:如果峰值增长太快,静态服务器可能比等待几分钟直到新 实例启动更适合用户体验。
-
可靠性:评估正常运行时间保证、冗余和故障转移能力,以保持持续运营。 查看停机历史。 一些云提供商经常 出现问题。
-
合规性:确保遵守法律和监管要求,重点关注数据驻留和主权问题。 在决定之前,你必须明确对主权的观点。 不幸的是,这个术语的涵盖面比明确的要求清单 更广泛。
-
集成:确保与现有系统和软件的兼容性,并提供 API 和集成工具。 提供商应支持 基础设施即代码 (IaC),尤其是 能够通过编程创建账户、用户 和权限。
-
支持:评估技术支持的可用性和质量,以及 服务水平协议 (SLA)。 研究其他用户对所提供支持的满意度;仅仅因为你有合同,某人负责保持系统运行并帮助你,并不意味着该服务 也一定好。
-
新服务的开发:评估提供商向你提供的新功能的速度和数量。 考虑到每月更新过多的情况,也要注意是否几乎没有 新功能。
-
地理考虑:考虑数据中心与最终用户的距离以及服务的区域可用性。 如果你的公司开发全球 可用的 软件即服务 (SaaS),那么构建在公共云上可能比集成多个 区域提供商更为容易。
-
技能与专业知识:评估管理基础设施所需的专业人员和培训要求。 考虑到你仍然需要任何 IT 领域的人才来完成这项工作。 短期计划是什么样的? 长期需要什么? 别再认为云计算很简单了;大多数企业之所以苦苦挣扎,是因为他们没有让 团队接受培训。
-
环境影响和可持续性:考虑提供商的能源消耗、碳足迹和可持续性实践。 他们如何处理废水? 如果他们购买碳补偿,记住你需要为此付费,因为理论上这会增加 电费。
云通常有一个非常强大的优势:几乎没有什么能阻止你立刻开始。 你可以找到大量的模板、蓝图和示例。 在短时间内,你就能开始运行,拥有你的第一个环境作为 平台。
当我们选择云时——我们必须考虑它的隐藏成本
由于我们本书主要聚焦云端,我们暂时假设选择裸金属方式的用户本能上具有更高的成本意识 和敏感度。
云提供商为我们提供了很多即用的服务, 通常带有许多 内置的功能。它们通常包括备份解决方案、可扩展性、 高可用性 (HA),以及集中管理的服务。 在规划云基础设施时,大多数情况下,我们会发现硬性事实和最佳猜测。 硬性事实是指像需要多少个 CPU 或服务器,应该使用哪种数据库,是否是单节点还是高可用等信息。 然而,你应该意识到,几乎每一个你选择的公共云提供商选项都会对成本产生一些 影响。
常见的大成本驱动因素包括 以下几点:
-
负载均衡器:几乎在每个架构中都需要它们,你也不例外。 正如前面所解释的,我们有机会将应用服务从 Kubernetes 命名空间扩展到云端,甚至控制网络——这是一个主要的 成本陷阱。
-
API 网关:负载均衡器的更为邪恶的双胞胎,作为最佳实践时,变得非常昂贵,特别是对于频繁通信的系统和路由 密集型通信。
-
数据:无论是静态还是在传输过程中,数据 迅速成为云账单中最大的成本块。 虽然一切都可以扩展,但你的数据必须 存放在某个地方。
-
备份和快照:它们对于一个成熟的平台非常必要;你需要有一个非常 好的备份策略,既精简 又可靠。
-
可扩展的 托管服务:如果你在使用无服务器架构、消息流或预训练的 AI 模型——任何可以无限扩展的东西——设定限制至关重要,以避免产生 意外成本。
理想的云项目——我们必须强调的是,我们在谈论的是有截止日期的事情——是非常有明确意见的,遵循云服务提供商的最佳实践。 几乎每个云项目都不理想,因定制实现的需要而导致迁移过程中的摩擦,这些实现必须以某种方式使旧世界和云服务提供商能够互相协作。 这些隐性成本通过技能的缺失而积累,因为所需的技能到底从哪里来呢? 从外部技能提供商那里获得帮助意味着他们必须先了解你,才能真正帮助你。 然而,若没有 外部支持,大多数云项目会在更早的阶段就被认为是失败的。
简而言之,我们可以说大多数服务在成本方面可能会反过来对你不利。 作为平台工程师,我们必须能够控制这些元素,建立保护措施和限制。 为了实现这一点,我们需要透明度 关于成本。
在哪里可以找到透明度
与此同时,我们有很多选择可以获取关于我们花费在哪些地方的信息,以及这些开销是否基于利用率等指标被认为是浪费。 所有云服务提供商都提供了探索(甚至细分)成本的可能性,但由于重点关注基础设施,尤其是在应用层上,细节往往缺失。 从以下 AWS 成本浏览器的截图来看,我们得到的是不同服务类型的消费图表。 现在我们需要应用过滤器来获得更多见解,并分析这些成本的根源:
图 8.1:AWS 成本浏览器
像 Apptio Cloudability 这样的商业解决方案 可以从多个不同账户、云服务提供商和环境中收集成本信息,集中在一个工具中。 它们在给定的数据上应用 FinOps 逻辑,并提供预定义的仪表板,比如以下截图中的单位成本和节省仪表板:
图 8.2:Apptio Cloudability 关于单位成本的详细仪表盘
微软定义 单位成本如下:“衡量单位成本是指计算一个业务单元成本的过程,该业务单元能够展示云计算的业务价值。” 单位的具体定义由你决定。 它可以是金融系统中的交易、媒体平台的用户,或你能将不同部分的 基础设施映射到的任何其他内容。
现在,在我们的平台和开源世界中,我们也可以找到一些解决方案来获得更多的透明度,例如 Kubecost(带有商业计划)和 OpenCost。 两者都可以在集群内分配成本,并与云服务提供商资源结合使用。 不幸的是,两者都缺乏良好的分析能力。 因此,我们常常看到自实施的解决方案与 商业智能 (BI) 工具,或 Prometheus 和 Grafana 实现结合,加入一些成本数据。 这些解决方案的效果取决于所投入的时间和 资金。
FinOps 和成本管理
近年来,FinOps 获得了一些 人气,并试图将自己与传统的成本管理区分开。 成本管理通常被描述为一种短期的、孤立的、单一目的的活动,旨在迅速实现成本节省。 当你作为平台团队,具备成本意识并积极管理你的成本时,这也将是一个可持续且持久的解决方案。 但 FinOps 成功之处在于它的整体方法,包括采购部门、 业务部门 (BUs),以及 财务部门,明确目标是实施对云成本及其动态的组织理解。 。
以下 FinOps 框架概述表明,它采取了与平台工程团队相似的方法。 我们可以看到需要与你的原则对齐的原则;需要集成和启用的能力,这将需要你的输入;以及角色——如平台工程师,他们 合作 [2]:
图 8.3:由 FinOps 基金会提供的 FinOps 框架
作为平台团队,您应该与 FinOps 团队合作,并积极管理他们对您平台的影响。 潜在的成本节省必须与您的原则、用户满意度和开发者体验相平衡。 基于数据的建议需要具备架构资格,并考虑替代方案和建议以实现 成本节省。
在接下来的部分,我们将讨论如何实施标签策略。 标签是为云和 Kubernetes 资源提供附加信息的一种简单解决方案,并且能创建某种程度的透明度。 此外,许多成本管理和 FinOps 工具需要标签才能提供 更好的洞察。
实施标签策略以揭示隐藏的成本
标签 和标签 对您来说可能并不陌生,因为它们在各种工具、公有云和 Kubernetes 中都有应用。 我们将使用标签这个词来表示标签,但当使用标签这个词时,我们实际上是专门指 Kubernetes 标签。
应用和使用标签本身可以变成一门艺术。 如果标签过多,可能会让它们应附加到服务的哪些信息变得不清楚。 当然,没有标签显然是没有帮助的。 在涉及多个不同业务单元和部门以及复杂发布机制的组织中,标签可能会被过载,或者只是一些缩写的集合。 关键是,我们需要标签来获得服务的透明度,明确它们属于谁,可能还会指示不同的服务级别或安全等级,最终成为将这些服务与成本结构和资源使用情况匹配的锚点。 因此,标签允许更精确地分析与成本相关的资源 和 成本原因。
使用标签的目的
标签 可以针对不同的目标群体和用户。 根据您询问的是哪个子域,它们应该包含组织、操作、安全甚至架构信息。 我们还可以考虑一些对我们作为 平台工程师 可能有帮助的标签。
组织信息的标签可以定义哪个部门、角色或团队负责该服务,是否是面向内部或外部的解决方案,以及是否存在任何国家、合规或 治理限制。
在操作层面,常见的属性包括服务级别、调度时间和维护窗口,但也有一些可能由第三方工具引入的非常技术性的资讯;例如,通过实现特定云 策略 [3]。
对于特定领域,如安全或平台团队,我们可以将标签和标签看作定义所需的隔离或数据保护,或者类似于典型的扩展模式、上下峰值,或识别微服务架构及其归属于某些组件和 结构。
我们的重点将放在相关的成本 标签上。 这些标签通常与组织标签相似,或与前面提到的操作性或领域特定的标签相关。 明确组织归属或提供有关某事为何如此剧烈扩展的见解是成本管理的关键推动因素。 这些标签有助于为需要提升 成本意识的项目和团队提供清晰的视图。
一些来源还指出,使用标签进行访问管理。 出于多种原因,这需要谨慎处理。 如果你有 基于属性的访问控制 (ABAC) 和权限 管理,标签可以成为实现这一目标的可行且简单的方式。 但这也带来了一个缺点,即你将一个纯粹的信息源变成了一个安全关键元素,需要在使用时进行保护和双重检查。 将其与信息特性混合使用可能会使标签的使用变得复杂,并且很可能会成为正确使用的障碍。 我们之前说过,标签可以用来传递安全信息。 然而,在提供诸如风险分类或安全级别等信息与提供 访问授权能力 方面,存在差异。
因此,像我们的平台一样,定义标签的目的至关重要。 随意地使用并设置标签虽然总比什么都不做好,但当标签是否重要变得不清晰时,未来可能会引发问题。
标签和标签限制
不幸的是,我们遇到了两个 普遍存在 的问题:
-
标签没有标准,这意味着它们的长度、标签数量、允许的字符或 大小写敏感性 等存在很多变体。
-
组织往往在许多不同的提供商 和平台上拥有多个环境
所以,在接下来的步骤中,定义标记策略时,你必须了解你可以可靠构建的最小数量。 标记策略将引入一个标准,必须适用于任何平台。 为每个平台定义多种不同的方法将导致混淆 和错误。
举个例子,我们将 比较不同的提供商 和平台:
| AWS | GCP | Kubernetes | Azure | |
|---|---|---|---|---|
| 每服务的最大标签数量 限制 | 50 | 64 | 没有 指定限制 | 50 |
| 最大 字符标签 名称长度 | 128 | 63 | 63 | 512 |
| 最大 字符标签 值长度 | 256 | 63 | 63 | 256 |
| 特殊字符 | 字母数字字符、空格以及 + - = . _ : / @ | 字母数字字符、- 和 _ | 字母数字字符、- . _ | 字母数字字符、<, >, %, &, , ?, / 不允许 |
| 区分大小写 | 是 | 是 | 是 | 是 |
表 8.1:云提供商和 Kubernetes 的标签限制
虽然 63 个字符 作为一个常见的方向看起来很好,但我们在实际应用中发现,对于许多组织来说,甚至 256 个字符也不足够。 这只是另一个例子,说明为什么标记策略必须经过 深思熟虑。
此外,一些结构在不同的提供商之间无法通用。 假设我们的 Financial One ACME 公司使用 AWS 和 GCP,包括其托管的 Kubernetes 服务作为平台,为什么 Financial One ACME 会达到允许的最大标签数量限制呢? AWS 团队首先发现了这个问题,并发现它可以使用更长的标签和一些特殊字符,从而允许某种嵌套结构。 于是,他们开始将不同的标签合并成一个,得出了 以下结果:
department-responsible=financial-one-ACME/domain-sales+marketing/stream-customer-management/squad-frontend/operations-squad-lazy-turtle
该值有 112 个字符,包含了 / 来表示层次步骤和 + 来表示 和。团队对结果感到满意,并将这个新标签推送到共享存储库。 一段时间 后,平台团队收到投诉,在销售和营销 IT 部门中,由于 一些标签的问题,最新的发布没有得到推出。
此外,您会发现其他限制,如每个账户 或订阅的最大标签数。
定义标记策略
正如 之前所解释的,标签可以具有许多不同的用途;主要是要在技术标签和业务标签之间保持平衡。 业务标签可以分为组织标签和成本标签。 最终,许多标签往往具有重叠的信息。 因此,为了找到正确的方法,通常有必要为定义设定一些基本规则和边界。 在第二步中,您必须在操作、平台和开发之间进行协调,从技术方面找到所需的标签集,然后再设置组织标签。 这个方向是有道理的,因为通常运营所需的标签也包含了关于 组织的信息。
让我们从一些共同的 基本规则开始:
-
标签名称和值 必须 要短于 63 个字符。
-
标签只能是 字母数字组合。
-
标签只能包含
-和_. -
请关注标签值及其内容,因为您通常会对此进行筛选和搜索。
-
标签名称对于排序 和分组更为相关。
-
不要将 个人可识别信息 (PII)写入 标签中。
-
建立一个大小写样式。 企业可能更喜欢帕斯卡样式,而在技术中,烤串样式更为常见。
-
使用更多的标签比使用更少的标签更好,但尽量保持 10%至 20%的可用 标签。
根据这些规则,我们可以创建下一个相关的元素。 首先,我们需要考虑为这些元素打上标签,使其比我们之前讨论的更加细化。 一个好的做法是考虑以下分类: 以下分类:
-
所有权——部门、团队、 组织、流。
-
环境——无论你们的预发布 环境是什么。
-
项目或产品——项目或产品集群,用于识别哪些组件 属于同一组。
-
成本中心——谁是 负责人? 是否有任何共享成本需要 额外考虑?
-
合规性——监管限制和要求、政策,及 合规规则。
-
操作——生命周期、备份、 维护窗口。
从这里开始,我们必须制定命名约定。 如前所述,重要的是不要过度加载单个标签。 遵循给定的规则和限制,并结合许多软件和系统组件的特性,标签约定可能如下: 如下所示:
-
department=public-relations-content-management -
owner=department-public-relations -
data-classification=personal-identifiable-information
在一些语言中,推荐使用 Pascal(Hello-My-Name-Is-Pascal)或驼峰命名法(i-Am-A-Camel)。 将所有字母都写成小写的风格称为 kebab 风格。 你需要非常明确地定义这些命名模式。 名字应该有多少个字符?包含多少个单词? 是更具描述性,还是仅仅匹配已经确定的内容? 同样也适用于值。 确保这些约定已被文档化、沟通,并且在工程师的入职或培训中被包括。
现在,最后一步是开始建立一个 带有这些基本规则的标签目录。建议创建这些目录,以便你有空间描述标签的含义、目的以及面向的群体。 这还可以帮助你保持标签简短,因为你不需要再为标签的值添加更多描述。 下表是一个简单的标签目录示例。 它可以根据组织的独特需求进行扩展。 重要的是将任何你可能需要包含在标签中的信息转移到目录中,以免使标签显得过长 并且难以阅读:
| # | 标签名称 | 预期值 | 含义 | 目的 | 标签利益相关者 |
|---|---|---|---|---|---|
| 1 | 部门 | 部门处理或代码;例如, DE-22-P | 应用程序所属的 BU 或区域 | 明确应用程序的所有者 | 应用程序 |
| 2 | 应用程序 | 应用程序名称;例如, internal-cms | 应用程序的名称 | 识别 该应用程序 | 应用程序所有者; 运营;架构师 |
| 3 | 维护-允许 | 可以执行维护的日期和时间;例如, sunday-0800-1230 | 该标签标识了日期或日期范围,以及以 0800表示的时间段,意味着 上午 08:00 | 定义应用程序何时可以关闭以更新/修补或发布 新版本 | 运营; 一级支持 |
表 8.2:标签目录示例
标签自动化
这将是不可能的 手动标记所有资源,尤其是在有许多活动组件的 IDP 上。 某些工具可以帮助我们在资源创建时自动标记它们,甚至在事后“修补”它们。 正如我们之前所见,在平台上,我们必须区分平台运行的基础设施和用户在其上运行或管理的工作负载 集群。
当您使用 Terraform/OpenTofu 等工具管理基础设施时,您可以在代码中给组件打标签 如下所示:
resource "aws_ec2_tag" "example" {
resource_id = aws_vpn_connection.example.transit_gateway_attachment_id
key = "Owner"
value = "Operations" }
对于某些提供商,您甚至可以给所有资源使用相同的标签。 这需要小心处理,因为您不希望错误地标记其他资源,尤其是当这些资源触发第三方集成或导致 错误计费时:
provider "aws" { # ... other configuration ... default_tags {
tags = {
Environment = "Production"
Owner = "Ops" } } }
几乎所有的基础设施即代码(IaC)解决方案都提供这种方法,并且可以适配任何类型的基础设施,无论是云端 还是本地部署。
这也可以应用于 Kubernetes 资源。 我们在这里跳过常规部署文件,因为这些文件很可能会以不同的方式处理,例如使用 Helm。 以下示例展示了 Helm 如何从 Helm chart 值文件中获取标签的值。 模板 值由一个 _helper.tpl 模板文件动态创建,而.Release 值则是内建信息。 另一种选择是从用户提供的 values.yaml 文件中读取值:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{template "grafana.fullname".}}
labels:
app: {{template "grafana.fullname".}}
chart: {{template "grafana.chart".}}
release: {{.Release.Name}}
heritage: {{.Release.Service}}
Helm 的一个优点是我们可以将其与简单的 if 语句结合使用。 通过这种方式,我们可以根据部署的目标位置或任何 其他触发条件传递预定义的信息。
注意
在您的 Backstage 模板中使用预定义的标签,强制用户至少定义一些基本的 标签。
这两种方法都是高度声明式的,并且可能会因为实施或被忽视而失败。 当然,您可以在 CI/CD 管道中检查标签的正确使用,但这可能导致许多部署失败。 这引出了策略引擎。 一方面,策略引擎可以用于测试部署中的标签,但像 Kyverno 这样的工具 也可以在需要时添加这些信息。 以下是一个 Kyverno 策略示例,它为任何 Pod foo=bar 添加标签 或服务:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-labels
annotations:
policies.kyverno.io/title: Add Labels
…
policies.kyverno.io/subject: Label
spec:
rules:
- name: add-labels
match:
any:
- resources:
kinds:
- Pod
- Service
mutate:
patchStrategicMerge:
metadata:
labels:
foo: bar
我们可以以非常精细的方式选择并修补这些信息,并且在以后需要时可以随时更新。 如果需要。
合并与分开的成本和计费报告
在许多 组织中,我们看到有合并成本和账单报告的需求。 虽然从组织的角度看,这是理解基础设施支出的最快方式,但它也需要许多标签来确保适当的拆分和细节级别。 当组织规模过大,且业务标签数量大于技术标签时,这是一个明显的信号,表明某些事情出了问题。 从激进的角度来看,此时做合并成本和账单报告就成了一种反模式。 那么我们该怎么办呢? 例如,企业通常使用字母数字编码来识别部门。 因此,部门如果不是叫做 财务和会计,可能会有类似 B-2-FA-4的编码。这种编码是消除许多其他组织或业务标签并将其放入匹配表或数据库的完美基础,尤其是当你希望在其他地方进行编程匹配时。 需要考虑的其他事项包括,例如,暂存环境和集群。 大多数时候,开发、集成和生产系统是独立的。 这意味着,如果你的应用运行在 曼哈顿 集群上,这可以被翻译为 prod-1234-eu,那么我就不需要另一个标签来表示 stage=production 或 region=eu。需要明确的是,你应该限制自己不要将过多信息添加到 单一值中。
然而,故事中有一个很大的 但是 这让我们回到了话题的起点。 从成本管理的角度来看,你需要拥有许多良好的标签,以便能够进行分析 和研究。
仅有标签本身是不够的;它们是成本优化的一个重要部分,但你需要采取哪些优化措施呢? 在接下来的部分,我们将探讨一些通用的优化策略,这些策略不仅包括调整规模和 减少基础设施。
查看成本优化策略
减少云和平台成本的最快方法是关闭那些你不需要的部分。 最大的问题是,参与该过程的每个人可能都有一些理由,为什么他们需要现有的基础设施。 但从现在开始,我想要转换你对这个话题的视角: 成本优化不是我们应该事后引入的东西。以下是你应该考虑的另一个原则: 在平台设计时要具备成本意识和高效性 。
注意
平台中发现的成本优化潜力越大,说明我们作为平台工程师 和架构师做得越差。
我们可以在平台的任何部分发挥潜力。 然而,这本身就可以写成一本书来覆盖所有相关方面。 因此,我们将讨论适用于平台内任何组件的原则。
精简流程
流程、业务和技术方面,都会 导致更高的成本。 我将给你举一个导致不必要成本的模式/反模式的例子。 Kubernetes 的发布分为三个主要版本,在其中的几周里,你会获得补丁、bug 修复和安全补丁。 Kubernetes 发布团队自动化了发布过程中的大部分工作,包括夜间构建,并且持续不断地 进行所有的测试 在 Kubernetes 基础设施上运行几千个测试。 当这种方法被引入时,社区将其视为顶级科技公司的圣杯。 突然间,每个人都想要他们容器的夜间构建,常常跳过测试部分,认为测试过程 太过繁琐。
这种方法的缺点是,大多数夜间构建从未被部署。 它还阻止了构建服务器在晚上关闭。 容器注册表一直保持忙碌,包括 CVE 扫描,企业容器通常较为庞大,增加了传输和存储成本。 大多数组织没有理解的是,Kubernetes 是一个全球性项目。 此外,如果在某个地方是夜晚,其他地方的人仍然在工作,开发新功能并推送到 Kubernetes 仓库。 最终,Kubernetes 是全球最大的开源项目之一,由全球最大的 数字原生公司 Google 发起。
好好想想!
仅仅因为别人都在做,考虑清楚它是否真的 有意义!
回到流程。 几乎每个流程都可以得到改善,因为它们通常是历史上逐步积累的。 在平台空间内,定期引入的工具替代了脚本和自定义开发的工具。 更好的流程不仅可以降低成本,还可以提高 效率,减少风险,最小化错误,并 提供一致性。
经过验证的流程优化方法可以在 精益 和 六西格玛 方法论中找到。 为了改善一个流程,它们教你执行以下操作: 以下内容:
-
定义/识别 流程的问题。
-
衡量 交易的性能和时间。
-
分析效率低下 和依赖关系的表现。
-
改进流程以 解决效率低下问题。
-
通过引入 金丝雀部署 来控制和审查新流程。
平台的有趣之处在于,大多数流程可以在 CI/CD 流水线和 GitOps 实现中找到。 这些将我们的目标转化为技术步骤,以确保实现预期状态。 复杂的地方在于,当我们在 Kubernetes 内以及其上方的组件中有流程依赖关系时。 作为一个最终一致的事件驱动系统,识别哪些依赖关系导致了哪种行为是一项挑战。 有时候,在不造成 进一步影响的情况下,我们无法改变组件的反应。
在技术世界中,流程常常听起来像是一堆脚本一起运行。 理想的状态是采用云原生的方法,利用 Kubernetes 标准化的 API、控制器 功能和资源定义。 它解耦了不同的功能,并通过这些流程,使它们更容易优化。 此外,它使得引入新的、历史上积累的 流程垃圾变得困难。
寻找最优惠价格的最佳交易
一种 优化成本的直接方式是比较列表价格,并选择最便宜的选项。 便宜 因此是相对的,因为你可能会降低性能、吞吐量、可用的 IP 地址等。 成本和利用率是很好的指标,但必须评估它们的二级影响。 例如,减少实例大小可能会降低吞吐量和性能,这会导致你不得不引入另一个实例。 这样,你可能会用两个实例达到 80%-90% 的利用率,但仍然支付和使用更大实例的费用相同,甚至更多。 即使是利用率更低。
如果你不受地域限制,某些地区比其他地区便宜;即便如此,它的影响不大。 目前一个强有力的选择是利用 ARM 服务器。 如果你无法在其上运行你自己或用户的工作负载,至少可以将其用于托管服务,例如数据库。 这可以节省 20%-30% 或更多 的成本。
这些都是非常明显的步骤。 找到并识别在正确区域中不被过度配置的正确资源。 如前所述,当你保持资源精简时,通常能实现更好的成本效益。 并且清晰明了。
有很多工具,如 Cloudability 或 Flexera,可以帮助你找到更便宜的选项,有些工具甚至建议改变架构来降低成本。 这些工具非常有助于入门,但它们通常也有很高的价格,而它们所编码的知识并不是你不能通过一些自己的研究或参加免费的成本 优化 课程获得的。
设计以实现最高利用率和最低需求
随着平台工程师减少 基础设施,它很可能会影响用户体验。 平台变得越灵活,它就越能适应给定的情况。 你可以引入主动组件,简化系统并在可能的情况下减少系统大小,同时教育用户了解增加或减少工作负载的可能性。 将他们的利用率与账单匹配可以激励用户负责任地使用资源。 责任使用。
之前,你了解了许多小节点与少数大节点之间的困难。 我们还发现,我们可以轻松支持不同的 CPU 架构,并动态分配资源。 所有这些因素在定义平台核心时都起着重要作用。 我总是脑海中浮现出蚂蚁巢穴的图景。 在它们的中心,有很多活动发生,但核心是一个稳定的结构,里面包含着大部分蚂蚁。 当巢穴,也就是我们的平台,发展壮大时,我们将扩展这一部分。 但有时,我们并不知道这是否只是暂时的。 在整本书中,你已经看到不同的方法来处理动态工作负载,在接下来的章节中,我们将展示一些扩展的最佳实践。 扩展。
那么,理想的图景是什么样的呢? 除了取决于你的需求之外,平台应尽可能利用其资源,同时没有额外的基础设施开销。 简单来说,作为一个平台,这并不容易做到。 工作负载并不完全掌握在你手中,它可能从静态的长时间运行的资源密集型软件,到成千上万个不断变化的无服务器容器。 我们可以从中得出的结论是,你可以尽情地设计平台, 但最终,一切 都归结为一个持续的工作过程:分析、反应和调整。 随着时间的推移,你可以在平台中构建一个自动反应的基础层,覆盖大多数情况,但你仍然需要继续优化。
在本章的最后部分,我们将通过一些具体的例子来看一下如何进行扩展和优化。 你将学习到反应性和预测性扩展,以及 成本意识 工程。
自动扩展、冷存储和其他成本优化的技巧
在 第四章中,在 自动伸缩集群和工作负载 一节中,我们已经讨论了 Kubernetes 带来的一个关键好处和 核心功能。 K8s 内置了多种不同的工具和机制来实现伸缩,例如配置 ReplicaSets(我们希望每个工作负载的实例数量)或使用可观察性数据来驱动自动伸缩 决策,利用 水平 Pod 自动伸缩器 (HPA), 垂直 Pod 自动伸缩器 (VPA),或 Kubernetes 事件驱动自动伸缩 (KEDA)。 有 一个很棒的免费教程,详细介绍了所有 Kubernetes 提供的自动伸缩选项, Is It Observable 提供的。这里是 YouTube 教程链接,里面还包含了 GitHub 教程的链接: https://www.youtube.com/watch?v=qMP6tbKioLI。
自动伸缩的主要用例 是确保工作负载拥有足够的计算、内存和存储,以实现特定的可用性目标。 以我们的金融公司 ACME 为例,这可能意味着他们使用自动伸缩来确保其金融交易后台能够在 100 毫秒的响应时间内处理 1000 个并发交易。 尽管自动伸缩可以帮助我们实现可用性目标,但它也有成本,因为伸缩资源意味着需要为额外的计算(CPU)或内存付费。 不当的自动伸缩——伸缩过多、从不缩减,或在错误的时间伸缩——也会导致计划外的成本激增,同时无法实现自动伸缩的真正目标!
正确的自动伸缩 是我们所期望的。 正确地做自动伸缩不仅可以帮助我们实现业务和技术目标,还能让我们利用自动伸缩来控制成本。 让我们来看看平台工程师应该了解的几个自动伸缩话题,以及我们还能做些什么来优化成本。 请记住,接下来我们讨论的一些实践也适用于任何类型的工作负载:无论是云工作负载还是其他。
自动伸缩的多种形式
云计算和 Kubernetes 作为核心平台的可扩展性是平台工程师手中非常强大的能力。 然而,扩展的方法有很多种。 有不同的触发点可能导致系统进行扩展,而且,扩展不仅仅是一个方向(通常是向上),我们还必须考虑将系统缩小——甚至缩小到零,以避免浪费资源! 以至于毫无意义!
向上扩展——不仅仅是基于 CPU 和内存
大多数 工程师都熟悉基于 CPU 和内存进行扩展。 你会发现的多数 HPA 示例通常是根据该 Pod 的某个平均 CPU 利用率来扩展 Pod 的副本。 这就是我们通常所说的响应性扩展,因为我们是基于达到某个阈值来进行扩展的 响应。
然而,基于 CPU 或内存进行扩展并不总是最佳选择。 这也不是我们唯一的选择,尽管大多数扩展框架最初都是基于 CPU 和内存进行扩展的,因为这两个是可以在 Pods 或命名空间中设置的关键限制。
对于针对特定吞吐量优化的服务来说,例如,基于并发请求来进行扩展会更有意义。 同样,对于 CPU,除了根据平均 CPU 利用率进行扩展,还可以在 Kubernetes 开始限制 Pod 的 CPU 时进行扩展,这可能会更好。 在前面的章节中,我们提到了像 KEDA 以及 云原生计算基金会 (CNCF) Keptn 项目,它们可以提供来自各种可观测性源(如 Prometheus、Dynatrace、Datadog、New Relic)的任何类型的度量,用于事件驱动的自动扩展。 要查看这个如何工作,请查阅 Keptn 文档中的完整示例: https://keptn.sh/stable/docs/use-cases/keda。
关键要点是,并不是每个工作负载都受限于 CPU 或内存,并且 Kubernetes 不仅仅限制你根据这两个关键属性来定义扩展规则。 根据真正使工作负载更高效地执行的因素来定义扩展规则,还会导致更高效的资源利用,从而带来一个在成本上也得到了 优化 的系统!
预测性扩展与响应性扩展
通常认为 自动扩展是瞬时工作的。 但事实并非如此! 想想金融公司 ACME。 如果在发薪日,每个人都想查看他们的新账户余额,这可能意味着当天前几个小时内流量会激增 10 倍。 然而,云服务提供商不能保证所有这些资源能够立即提供,因为你正在与许多其他组织竞争,这些组织也在尝试同时请求云资源。 此外,工作负载本身也不能立即处理传入的请求,因为许多 Pod 在准备好之前都有一定的启动时间。
这个问题可以通过预测性扩展来解决。 与反应性扩展——即在达到某个阈值时进行扩展,如前一节所述——相比,预测性扩展是基于潜在的未来情境并在达到阈值之前进行反应。 预测性并不意味着我们需要一个魔法玻璃球来告诉我们未来。 它可以像在我们预期流量激增前几小时启动扩展那样简单;例如,在发薪日之前或在某个特定时间开始的营销活动前。 具体时间。
其他预测可能更为动态:
-
季节性:可以通过查看历史数据来基于季节性做出预测。 电子商务是一个很好的例子,一年四季中总会有一些日期流量激增,例如黑色星期五或网络星期一。 这些激增很容易 预测 到!
-
相关数据源:另一个预测方式是查看其他数据源。 保险公司通常会查看恶劣天气数据。 当预报有暴风雨并且这些暴风雨有可能造成损害时,预测性扩展是合理的,特别是对于客户提交保险索赔时所使用的服务。 在这种特定情景下,您甚至可以在接近 暴风雨的特定区域进行扩展。
-
系统依赖:在复杂系统中,基于系统其他部分的负载行为来扩展依赖组件也是一个可选方案。 以酒店行业为例。 如果我们看到更多人搜索航班,因为他们想在长周末出游,我们也可以预测性地扩展提供酒店、租车或其他可预订活动推荐的后端服务。 前往 旅游目的地的推荐。
用例 – 预测性存储扩展以优化成本和可用性
现在 我们已经了解了不同的预测性扩展方法,接下来让我们将其应用于一个非常高成本的 例子:存储!
我们的数字系统生成的数据比以往任何时候都要多,而且预计这一趋势将持续下去。 存储 —— 尽管看似有充足的空间 —— 对许多组织来说仍然是一个重要的成本因素。 对我们金融一号 ACME 公司来说也是如此,假设我们需要存储系统处理的每一笔财务交易的所有细节。 作为一个组织,我们需要确保可以始终保存所有记录,但同时我们也希望确保不为目前不需要的存储空间支付费用。 因此,我们希望将空闲磁盘空间保持尽可能小,以避免为不需要的磁盘付费。 另一方面,我们还需要确保有足够的空闲磁盘空间,以防交易量激增,因为我们无法承受交易丢失的风险。 考虑到大规模扩展磁盘不能瞬间完成,可能需要几个小时,我们可以应用预测性存储扩展来满足所有 我们的需求!
以下截图帮助我们理解这一过程是如何工作的。 它展示了随时间变化的磁盘空闲空间百分比。 我们写入的数据越多,空闲磁盘空间就越少。 与其在某个固定阈值上扩展我们的云存储,我们可以使用预测模型,在预计会在扩展所需的时间内达到某个低阈值时进行扩展——从而确保我们始终拥有足够的空闲磁盘空间,而不会为 过多的空间支付费用:
图 8.4:云存储的预测性扩展,以优化成本和可用性
前面这个例子来自一个真实的使用案例,通过持续根据预测性扩展方法调整存储大小,实现了显著的成本节约!
缩放到零——在不需要时关闭系统
虽然我们运营的许多系统需要 24/7 全天候可用,但有些系统并没有这个要求。 这些可能是仅在正常工作时间由员工使用的系统,或是仅在一天、一个月或一年中特定时间需要执行某些任务的系统。 我敢肯定,我们都能想到一些经常处于空闲状态但仍消耗宝贵资源的系统,尽管它们当前并不 被需要。
虚拟机 (VM) 是缩放到零的一个很好的例子。 多年来,我们一直在做这件事:在不需要时关闭虚拟机,并在需要继续工作时将其重新启动。 许多组织通过自动化的方式实现这一点:在工作日结束时自动关闭用于日常业务任务的虚拟机,并在第二天早上再次启动它们。 单单这一点就带来了巨大的成本节省机会,因为许多虚拟机可以在晚上 和周末关闭!
在 Kubernetes 中,我们也有机会将工作负载缩放到零。 我们可以使用前面讨论过的 KEDA,也可以考虑像 Knative(可以运行无服务器工作负载)或 kube-green 这样的工具。后者旨在减少 K8s 工作负载和集群的 CO2 足迹,并且能够在不需要时将工作负载、节点或集群置于休眠状态。 想了解更多关于 kube-green的信息,请访问以下 网站: https://kube-green.dev/。
我们仍然需要回答一个问题:哪些工作负载可以缩放到零,以及可以缩放多久? 我们从这些工作负载的所有者那里获得这些数据,明确它们何时以及需要多长时间。 另一种方法是直接利用可观察性数据,查看一天中的哪些时段使用了哪些工作负载,并基于此创建一个 kube-green 休眠配置来将工作负载缩放到零。 这种实现的一个示例可以在 可持续性研讨会 Henrik Rexed 的讲座中找到: https://github.com/henrikrexed/Sustainability-workshop。
从工作负载扩展到集群
到目前为止,我们讨论了很多关于调整规模或扩展工作负载的内容,以确保我们拥有足够的资源来满足业务目标,同时也避免过度配置,以便我们可以优化 成本。
随着我们扩展工作负载的规模, 底层 Kubernetes 集群也需要相应地调整大小。 这时,集群自动扩展功能就派上用场,它会扩大集群的节点数量,确保有足够的资源来运行所有工作负载,同时还会在节点未被充分利用且工作负载能够分配到剩余节点时,缩减节点。 这确保了底层集群节点机器的优化,从而最终 节省了成本。
Kubernetes 文档网站上已有大量现有文档: https://kubernetes.io/docs/concepts/cluster-administration/cluster-autoscaling/。
有一些特定的自动扩展工具,比如 Karpenter——最初由 AWS 开发——它帮助调整 Kubernetes 集群的规模,同时控制成本。 Karpenter 与你的云服务提供商的 API 集成,能够配置处理特定工作负载所需的适当节点大小。 如果不再需要,它还会缩减节点。
除了像 kube-green (之前提到过)这样的工具外,Karpenter 是一个很好的选项,可以在考虑成本的同时扩展你的集群。 要了解更多关于 Karpenter 的信息,请访问 https://karpenter.sh/。
作为平台工程师, 了解所有不同的扩展选项非常重要。 其中许多可以配置来调整工作负载和集群的规模。 对于一些情况,与工程团队和工作负载所有者密切合作,定义适合特定工作负载的扩展策略也非常重要。 总体而言,自动扩展——无论是计算、内存还是存储——是成本效益优化 平台工程的关键推动力之一!
成本感知工程
现在我们已经了解了如何在平台中构建内容,以实现适当规模和自动扩展来节省成本,我们还需要讨论如何让工程团队从一开始就更具成本意识。 最好的成本优化从组织中的每个人意识到他们的行为对成本的影响开始,因此他们会默认地构建和设计更具成本效益的系统。 基于标签的成本报告是一种让团队意识到他们的成本的方法。 这种策略在本章前面已经讨论过。
让我们来看看一些我们认为每个人都应该考虑的额外选项,因为它们有可能 带来 成本意识的工程实践!
只需要你所需的请求方式
在云计算初期,许多组织给予其工程团队完全访问云门户的权限。 这种便捷的“自助服务”提升了生产力,因为每个人都可以轻松地创建新的虚拟机、存储服务,甚至是 Kubernetes 集群。 然而,这种“西部荒野”式的做法导致了许多组织的成本爆炸,因为用户只是创建新服务,却没有考虑基本问题,例如:我究竟需要多大的虚拟环境,且需要多长时间? 我需要它多久?
作者曾与之合作的一个组织是一家金融机构。 他们没有给每个人完全访问云门户的权限,而是构建了自己的自助服务门户,允许工程团队创建新的虚拟机、数据库、集群等。 作为自助服务门户的一部分,团队必须定义他们需要资源的应用程序和环境,以及需要多长时间使用这些机器;例如,仅在工作时间内。 该结果是,通过自动关闭不再需要的服务,成本减少了 60%。 下图显示了工程师如何请求他们所需的资源。 右侧还可以看到详细的报告以及该组织正在实现的整体优化目标:
图 8.5:一款报告并降低成本的自助平台
在这个用例中,报告不仅关注成本,还包括碳足迹,这对大多数组织来说是一个重要话题。 通过中央平台提供这一自助服务,使得该组织可以从一开始就让工程师更加关注成本,同时也展示了他们的行动所带来的正面影响。 tive impact their actions have。
租赁模式与固定费用资源
前面的例子 非常好,但要求团队在一开始就考虑清楚他们确切需要哪些资源、需要多长时间。 另一种做法是采用 租赁模式。这意味着什么呢?
当 A 开发团队请求一个资源——比如他们需要用来做开发工作的 Kubernetes 集群——他们可以简单地请求一个默认的时间段;例如,1 周。 这个 1 周的时间段就成为了他们对该资源的“初始租期”。 时间段和团队所有权将通过创建的资源标签进行管理。 通过自动化,在租期结束前 1 天,可以通过电子邮件或聊天消息提醒团队他们的租期即将到期。 消息还可以提供给他们一个选项, 延长租期 一天或一周,并提醒他们这段额外时间会带来的费用。
这种方法已在多个组织中实施,并确保任何团队仍然可以通过自助服务获得他们需要的资源。 它还确保那些被遗忘或不再需要的资源会被关闭,而无需事先指定资源需要的确切时间。 exactly how long a resource will be needed.
现在我们已经讨论了如何仅在资源真正需要时运行它们以节省成本,接下来让我们讨论一下工程师如何优化 他们的代码以带来 成本影响!
绿色工程 – 优化你的代码
高效 的代码通常不仅执行更快;它通常还需要更少的 CPU、内存,甚至可能需要更少的磁盘存储或网络带宽。 一切资源的减少也意味着成本的降低。 那么,为什么不是每个人一开始就编写高效的代码呢?
工程师们往往面临着时间压力,需要交付新功能,或者组织没有投资能够测试并提供优化建议的工具,作为软件交付生命周期的一部分。 作者们过去曾与许多软件组织合作,识别出一些非常常见的模式,这些模式导致了低效的、因此成本高昂的代码。 以下是 一些例子:
-
请求过多数据:开发人员不是利用查询语言只请求特定操作所需的数据,而是检索更多数据,然后在内存中进行过滤和处理。 这导致了更多的网络流量来传输数据,更多的内存用于存储数据,以及更多的 CPU 用于在 客户端代码中进行迭代和过滤。
-
低效使用库或算法:许多软件库存在是为了完成某些任务;例如, 对象关系映射器 (ORMs) 用于将数据库中的数据映射到 开发语言中的对象。 然而,开发团队并不总是有时间来正确测试或配置这些库,以便为特定的使用案例进行优化。 因此,导致了低效的使用,进而导致更高的 CPU、内存、网络和 磁盘访问。
-
过度日志记录:软件工程师使用日志框架来记录代码执行过程中的信息。 日志通常用于分析、诊断和故障排除失败或有问题的代码执行。 然而,日志经常被过度创建或重复记录,且没有适当的格式或足够的上下文信息;例如,未设置日志级别。 这导致了在创建日志时的开销,同时也导致了当这些日志被摄取、转换和由 可观察性平台分析时的开销。
在软件工程中,还有许多 模式会导致性能或可扩展性问题。 除了检测这些模式外,应用程序的架构评审还可以通过更高效的架构或重写代码来实现成本降低。 一个显著的例子是亚马逊 Prime Video,它放弃了基于 AWS 的分布式无服务器架构,转而使用被描述为 单体架构 的视频质量分析,这样做将基础设施成本降低了 90% [4]。最终,这些模式也意味着低效的代码执行,从而导致更高的成本。 作为平台工程团队,我们有机会利用现代可观测性工具分析这些模式,并将这些信息反馈给工程师,提醒他们不仅要关注代码带来的成本,还要告诉他们在哪里可以开始进行优化,正如下一张截图所示。 这两张图表展示了每个服务创建了多少日志,并突出了哪些日志没有正确配置;例如,没有设置日志 级别:
图 8.6:为团队提供关于过度日志等模式的简单洞察
这将引导我进入本节的最后部分,即为工程师提供教育机会,让他们从 编写的第一行代码 开始就意识到成本!
教育机会
尽管这可能不会 成为平台工程团队的主要职责,但由于工程团队使用我们的平台作为自助服务来部署他们的应用程序,我们可以利用这个平台来教育每个人,提醒他们在使用平台部署软件 服务时所产生的成本影响。
在之前的部分中,我们已经强调了使用案例,比如向工程团队发送成本和使用报告,或识别和突出低效的代码模式。 实现这一目标的关键是正确的标签(例如,谁拥有基础设施和应用程序的哪一部分),以及良好的可观测性(例如,哪些系统使用了多少 CPU、内存、网络等)。 拥有这些信息,平台工程团队可以主动将这些数据推送给各团队,并借此持续展示他们的应用程序带来的成本影响。 持续进行这一过程还将产生教育效果,使工程师能够更清楚地了解 他们行为的成本影响。 行为的成本影响。
总结
在本章中,你应该已经培养了对成本的敏感度,并且有了如何在你的平台上处理这个话题的想法。 优秀的平台为用户提供透明度,并提供灵活的选项以根据不同触发条件调整工作负载。 此时,你应该能够结合前几章学到的方法,如动态资源分配与 GPU 配合使用,以实现高利用率和最佳 成本分配。
记住,仅仅从成本角度来看并不足以降低整体平台成本,因为一些服务器尺寸的减少可能会因为云提供商的其他限制而增加对多个小型服务器的需求。 标签策略为控制和透明度奠定了基础。 看似简单的事情可能最终会导致许多组织讨论。 为了优化成本,你还可以利用其他元素,如流程,并达成长期承诺,以获得更好的 定价优惠。
最后,我们为你的平台提供了一些实际的示例和最佳实践。 我们回顾了不同的扩展方法,以及预测性扩展与反应性扩展之间的区别,并重点讨论了除 CPU 之外的其他扩展因素,如内存 和存储。
总结一下,当你理性思考并将你在平台上花费的钱视为自己的钱时,你就能变得非常具有成本效益。 作为一个平台工程团队,你还可以制定一个度量标准,定义平台的效率,以便与你的管理层达成一致,使用这部分免费预算进行进一步的投资和优化。 记住,尽管云给我们提供了一个 无限的 资源量,我们不必仅仅因为这些资源 存在就全部使用它们。
让我们直接进入最后一章。 正如我们之前所说,唯一的不变就是不变。 在我们最后一章中,我们将讨论持续变化以及如何在轻量架构和可持续理念的推动下生存,并探索变革的黄金路径。 为了结束本章,我们敢于展望未来,讨论一些可能或可能不会在 未来几年内变得相关的 技术趋势。
进一步阅读
-
[1] 我们为何离开云端 – 大卫 海内梅尔·汉森:
-
[2] FinOps 框架高清 海报: https://www.finops.org/wp-content/uploads/2024/03/FinOps-Framework-Poster-v4.pdf
-
[3] 云端 托管人: https://cloudcustodian.io/
-
[4] Prime Video 成本 优化: https://www.thestack.technology/amazon-prime-video-microservices-monolith/
4633

被折叠的 条评论
为什么被折叠?



