如何从家庭实验室环境逐步搭建出一个可用于生产环境的DevSecOps平台,并最终将其迁移至AWS环境——全书详解
在这本书中,你将从零开始构建一个金融科技交易账本,并逐步将其发展成一个可用于生产环境的DevSecOps平台。你还需要将其部署在AWS上。 该应用程序能够处理贷方和借方交易,触发合规性警报,并将所有数据存储在数据库中。你需要自行搭建相关的基础设施:包括自动化系统、扫描工具、政策执行机制、秘密管理机制、威胁检测系统以及监控系统。 完成整个开发过程后,你在面试中就可以详细解释每一个决策的来龙去脉,因为这些决策都是你亲自做出的。 这份指南并不会直接提供现成的解决方案。它会让你先自己探索和理解每个问题,然后再介绍相应的解决工具。 所有的代码、配置文件、脚本以及分步操作说明都存储在配套的仓库中。在开始之
在这本书中,你将从零开始构建一个金融科技交易账本,并逐步将其发展成一个可用于生产环境的DevSecOps平台。你还需要将其部署在AWS上。
该应用程序能够处理贷方和借方交易,触发合规性警报,并将所有数据存储在数据库中。你需要自行搭建相关的基础设施:包括自动化系统、扫描工具、政策执行机制、秘密管理机制、威胁检测系统以及监控系统。
完成整个开发过程后,你在面试中就可以详细解释每一个决策的来龙去脉,因为这些决策都是你亲自做出的。
这份指南并不会直接提供现成的解决方案。它会让你先自己探索和理解每个问题,然后再介绍相应的解决工具。
所有的代码、配置文件、脚本以及分步操作说明都存储在配套的仓库中。在开始之前,请先克隆该仓库:
git clone https://github.com/Osomudeya/clearledger.git
cd clearledger
本书中提到的所有内容都与该仓库中的文件相关。
先决条件
在开始第0阶段之前,你需要在自己的机器上安装以下工具:
Multipass:用于创建轻量级的Ubuntu虚拟机,以确保Kubernetes拥有足够的资源。
kubectl:用于通过终端与Kubernetes集群进行交互。
Helm:用于将应用程序部署到Kubernetes中。
Docker Desktop:用于构建容器镜像。
jq:用于格式化JSON数据,使其更易于阅读。
你还需要在GitHub和Docker Hub上拥有免费账户。
此外,你应该具备以下知识和技能:
基本的Linux命令行操作:能够导航目录、读取文件以及运行脚本。
Git:会使用git进行代码克隆、提交和推送操作。
了解容器的概念,以及Docker是如何构建容器的。
你不需要具备先前的Kubernetes、安全或云技术经验。这份指南会从第0阶段开始逐步教你这些知识。
你的机器至少需要24GB的RAM内存、6个CPU核心以及80GB的空闲磁盘空间。具体的安装命令请参阅如何配置你的机器。
配套仓库的地址是github.com/Osomudeya/clearledger。请给它添加星标,然后克隆它,再继续学习吧。
目录结构
您正在构建的是什么
ClearLedger是一个基于三个FastAPI微服务、PostgreSQL数据库、Redis缓存系统以及Web前端界面构建的金融技术交易账本。
用户可以注册账户、登录系统,记录贷方和借方交易信息,查看自己的账户余额;当某笔交易的金额超过预设阈值时,系统还会发出合规性警告。
这个应用程序的设计初衷就是简洁实用。它的目的并非用于教授金融技术知识,而是为您提供一个真实可用的系统,让您能够像操作生产环境中的平台一样来管理和维护它。
该项目由四个组成部分构成:
认证服务:负责处理用户注册、登录以及JWT身份验证功能。
账本服务:用于处理交易记录、维护账户余额并存储交易历史数据。
通知服务:通过Redis监控大额交易,并在发现此类交易时生成合规性警告。
前端界面:提供登录、查看余额、提交交易记录以及查看警告信息的Web界面。
读完这本书之后,这些服务中的每一个依然会存在。不同的是,它们未来的构建方式、部署流程、安全保障措施以及运营管理方法都会发生变化。
这个应用程序本身只是一种工具;而DevSecOps才是我们最终追求的目标。
平台是如何发展的
在项目初期,您并不会一次性安装所有工具。相反,平台的开发过程会遵循生产系统常见的模式:先出现某个问题,然后针对该问题制定解决方案。
您会从手动部署Kubernetes应用程序开始入手。随后,每个开发阶段都会解决一个具体的运营难题。
第0阶段:原始的Kubernetes环境
您需要手动部署并运行这个应用程序,这样才能在引入自动化机制之前充分了解系统的运作原理。
第1阶段:持续集成
每当有代码被提交时,容器镜像的构建过程就会自动完成,从而省去了人工构建环节。
第2阶段:GitOps
应用程序的部署不再依赖kubectl命令,而是通过Git来管理所有配置信息,有效防止配置错误的发生。
第3阶段:安全检测机制
每次代码提交之前都会经过安全扫描,这样漏洞代码、敏感信息以及配置错误就能在部署之前被及时发现并处理。 第4阶段:访问控制
即使某些代码绕过了安全检查流程,Kubernetes的政策也会阻止不安全的工作负载进入集群环境。 第5阶段:秘密管理
应用程序的认证信息不再存储在Kubernetes Secrets中,而是被转移到Vault系统中,从而有效保护了敏感数据的安全。 第6阶段:运行时安全防护
Falco工具会持续监控正在运行的容器,在部署后及时发现任何可疑行为。 第6.5阶段(可选):混沌工程
通过故意引入故障,我们可以验证平台是否具备自我恢复的能力,而不仅仅是能够检测问题而已。 阶段7:可观测性 各项指标、日志以及仪表盘能够帮助我们了解平台的运行状态、性能表现以及安全性情况。 阶段7.5(可选):OpenTelemetry
分布式追踪功能能够跟踪所有服务中的请求流程,从而清晰地展现一条交易是如何在系统中完成的。 阶段8:迁移到AWS
我们可以使用EKS、ECR、RDS以及应用负载均衡器,在AWS上部署相同的架构,而无需改变应用程序本身的运行方式。 如果你想在尝试使用Kubernetes之前先了解这个应用程序的功能,那么Docker Compose这套可选工具可以帮助你在本地机器上完整地运行整个系统。 本书的核心原则很简单:在介绍任何解决问题的工具之前,都会首先让你了解所要解决的具体问题。 在整个学习过程中,在介绍每一种解决问题的工具之前,有三种习惯会帮助你顺利完成每一个阶段的学习任务。 先阅读说明再执行操作:每个命令之前的解释部分会告诉你为什么要执行这个命令。如果跳过这些说明,虽然你可以照着步骤操作,但却无法真正理解其背后的原理;而掌握这些原理正是你获得工作机会的关键。这些命令本身就是证明你理解了相关内容的证据。 要有明确的选择理由:这里提到的每一种工具都是为了解决特定的问题。为什么选择使用Vault而不是Kubernetes Secrets?为什么要把代码和配置文件分成两个仓库来管理?不要只是盲目地跟随步骤操作,而要思考“如果跳过这个步骤,会引发什么问题?”如果你真正理解了问题的本质,那么解决方案自然也会记在心里。 按顺序进行操作并验证每个检查点:每一个阶段都依赖于前一个阶段的结果。当遇到问题时,请仔细阅读错误信息。遇到困难并进行调试也是学习过程的一部分——雇主们更愿意听到“我遇到了X问题,然后通过Y方法解决了它”这样的描述。 在每个✋实践检查点处,请按照以下步骤操作: 执行相应的命令。 将你的输出结果与预期结果进行对比。 如果结果不一致,请先修复问题后再继续下一步。 当`make check-N`命令通过后,再执行`make snapshot STAGE=N & make snapshots`。只有在看到`clearledger.stageN`命令成功执行后,才能继续下一步操作。 请避免以下错误: 不要因为之前的检查点已经通过就跳过它。 在未先查看可用快照的情况下,切勿直接运行`make restore`命令。 在所有需要输入用户名的地方,请使用你真实的Docker Hub或GitHub用户名进行替换。 请在虚拟机内部执行相关命令(命令提示符会显示`ubuntu@clearledger`),而不要在你的Mac上操作。 在每个重要的检查点处,请截图留念。这些截图将成为证明平台能够正常运行、能够检测到各种活动并记录相关数据的证据。 Multipass用于创建Ubuntu虚拟机。 Docker用于构建应用程序镜像。 在 包含三个Python API(用于身份认证、账本管理和通知功能)以及一个Web前端界面。 Postgres数据库用于存储数据。 Redis使得账本系统能够无需直接调用通知机制就能发布警报信息。 nginx负责将浏览器的请求路由到相应的服务节点上。 每个阶段都会增加一层新的安全防护措施。这些工具是不可互换的:扫描器会在代码和图像被部署之前对其进行检查,ArgoCD会确保集群与Git保持同步,Vault用于管理敏感信息,Kyverno会在不安全的作业负载运行之前阻止它们被执行,而Falco则会在这些作业负载运行后监控任何可疑行为。 正因为如此,执行这些步骤的顺序非常重要——你正在一层层地构建起完善的防御体系。 在配置集群之前,先根据你的主机内存情况来选择相应的路径。如果在实验过程中因为内存不足导致系统崩溃,或者硬盘空间不足而影响实验进度,那么重新选择路径将会浪费一整天的时间,因此请事先做好决定。 本指南中推荐的默认路径假设用户拥有24GB以上的内存,并且可以使用完整的本地虚拟机环境(请参见“开始之前”部分)。如果你的实际情况不符合这些条件,请从与你的硬件配置相匹配的选项开始操作。 仅适用于Mac系统且使用了Multipass工具的情况:执行 完成这个实验流程通常需要几天的时间。 你的源代码保存在电脑上,因此重新创建虚拟机或删除虚拟机并不会导致Git仓库、提交记录、配置文件等数据丢失。 虚拟机中存储着你的运行环境,包括已部署的Pod容器、Vault中的敏感信息、Postgres数据库的数据以及Grafana监控面板的内容。 在完成每个实验阶段后,请先创建一个快照再继续下一步操作。例如: 务必执行 如果虚拟机在一段时间后变得无法使用,请恢复最新的可用快照: 你将保留以下内容: 你的Git仓库 你所做的所有提交操作 文件`/.env` 文件`setup-cluster.local.env` GitHub上的项目`clearledger-infra` 但是,在最后一次快照之后,虚拟机中存储的所有数据都会丢失,包括: 正在运行的Pod容器 Vault中的加密密钥 Postgres数据库中的数据 Grafana和Loki中的数据 因此,建议在每个阶段完成后都创建快照。 恢复最新的可用快照,然后从该阶段继续操作。 需要重新搭建实验环境。 你的Git仓库仍然完好无损,但Kubernetes集群会恢复到初始状态。请从你之前停下的那个阶段开始继续操作,然后重新搭建平台。 如果遇到磁盘空间不足、快照创建失败、Mac设备进入睡眠模式或重启问题、Vault认证错误,或者Pod容器陷入CrashLoopBackOff循环等问题,请参阅troubleshooting.md以获取详细的解决方法。 初级DevOps工程师(工作0–2年):请按顺序完成所有阶段,不要跳过任何步骤。预计完成第0–2阶段需要一整天的时间,第3–7阶段各需半天,第8阶段则只需几个小时。这是正常的流程,请不要着急。 中级DevOps工程师(工作2–4年):可以快速浏览第0–2阶段的内容以了解整个应用程序的架构,然后重点关注第3–7阶段,因为这些阶段涉及安全配置的相关内容。 面试准备:请完成第4阶段的所有操作,然后再阅读 相关要求详见上文的前置条件部分。在安装之前,请确保你的电脑拥有24GB的RAM、6个CPU核心以及80GB的空闲磁盘空间。 Windows用户:请在WSL2 Ubuntu环境中运行所有命令。本实验不建议使用PowerShell,因为配置过程需要使用 在继续之前,请先验证以下内容: 如果有任何命令执行失败,请先安装缺失的工具,然后再继续操作。 实验的主要流程从阶段0:运行中的系统开始。 按照步骤完成配置后,下次可以直接使用以下快捷命令来启动实验: 预期结果应该是:系统中存在一个名为 本实验是在一个单节点的MicroK8s虚拟机上进行的,该虚拟机的磁盘容量为80GB。随着时间的推移(尤其是在进行CI构建、Helm升级或执行阶段7的相关操作后),容器镜像、日志文件等数据可能会占用大量的磁盘空间,从而导致系统出现故障。 检查磁盘健康状况:
清除虚拟机中未使用的文件,但请不要删除应用程序的数据:
如果执行 如果您的计算机没有足够的资源来运行Kubernetes,那么您可以使用Docker Compose来启动ClearLedger应用程序。 然后打开http://localhost:3000页面,开始使用该应用程序。 当您完成测试后,可以关闭Docker Compose环境,然后继续进行阶段0的实验。 首先,您需要注册账户。每次执行新的 然后使用相同的账号信息登录。 如果输入的密码错误,系统会显示“电子邮件或密码不正确”。如果仍然遇到问题,可以尝试强制刷新页面,或者在浏览器控制台中执行 首先,请访问http://localhost:3000进行注册并登录。然后提交一些借方和贷方交易记录(例如,工资+5000美元,租金-1200美元)。 接着确认余额变化情况以及交易历史记录是否正确显示。 现在尝试提交一笔金额≥ 10,000美元的交易,此时警报面板应会显示“LARGE TRANSACTION”提示。 这里还有一个可选的测试方法:使用相同的基地址来运行以下命令: 请将ClearLedger的相关主机名添加到您的 运行以下命令: 或者也可以手动操作: 在完成配置后,请验证设置是否正确: 预期返回值应为 首先获取您的WSL IP地址: 使用获取到的IP地址(或在您的机器上能够正常使用的 如果您在Windows系统中使用Chrome或Edge浏览器,而不是WSL环境,请将相同的配置添加到: 最后验证配置是否生效: 起始状态:目前还没有任何应用程序被部署,因此您需要手动构建Kubernetes集群并安装ClearLedger。 目标:完成这个阶段的配置后,ClearLedger将会在Kubernetes环境中正常运行。您可以手动注册用户、提交交易记录,并查看合规性警报信息,整个过程完全不依赖自动化工具来完成。 所有的部署、更新和修复操作都是手动完成的。这是有意为之的。在自动化某个平台之前,你首先需要了解该平台在未自动化的状态下是如何运行的。 接下来,你需要在自己的笔记本电脑上创建一台虚拟机,这台虚拟机将运行自己的Kubernetes集群。可以把它想象成电脑内部的一个小型数据中心。 Multipass可以创建轻量级的Ubuntu虚拟机,而MicroK8s则是一个精简版的Kubernetes发行版,它在这些虚拟机中运行。两者结合使用,就可以让你在不依赖云资源的情况下构建一个真正的Kubernetes集群。 推荐操作:执行一条命令: 预期输出结果如下: 命令`make setup`会依次执行`scripts/setup-cluster.sh`(用于配置虚拟机、MicroK8s以及磁盘安全设置)和`scripts/set-up-hosts.sh`(用于修改`/etc/hosts`文件)。整个过程需要3到5分钟。 磁盘安全相关设置(如日志轮换机制、图像垃圾回收阈值、journald日志存储限制等)会自动完成配置。更多详细信息,请参阅troubleshooting.md文件。 如果集群的状态显示为`NotReady`,请等待60秒后再尝试一次。 如果`make setup`命令失败,你需要逐步排查问题,可以参考以下手动配置步骤: 获取虚拟机的IP地址(这个地址在配置`/etc/hosts`文件时需要用到): 然后需要添加相应的主机条目。具体操作方法请参考上文中的域名配置部分,或者直接运行`sudo bash scripts/set-up-hosts.sh`命令。 进入虚拟机后,请执行以下命令进行配置: 接下来,从你的主机系统连接至虚拟机,并执行以下命令配置`kubeconfig`文件: 在运行任何`kubectl`命令之前,请先阅读这些代码文件。只有先了解代码的具体内容,才能理解后续的所有操作步骤。 请注意,每个Dockerfile中都有一行代码: 另外,请查看文件 这个密码保存在一个YAML文件中,任何拥有仓库访问权限的人都可以看到它。base64编码其实是一种简单的转码方式,并非真正的加密技术,因此解码过程非常简单。请记住这一点——正因为如此,才需要在第5阶段采取额外的安全措施。 你需要一个容器注册服务,用来存储构建好的镜像,以便集群能够下载这些镜像。Docker Hub是目前最常用的选择,但在第8阶段,你会将其替换为私有注册服务(ECR)。 在Docker Hub上创建四个公共仓库(使用免费账户,访问地址为hub.docker.com): 访问 点击“创建仓库”按钮 选择你的Docker Hub用户名作为仓库的命名空间 从下拉列表中选择一个仓库名称 将仓库的可见性设置为“公共” 点击“创建”按钮 对所有四个服务重复上述步骤 接下来,需要生成一个访问令牌。请登录hub.docker.com,进入“账户设置”→“安全”选项,然后点击“创建新访问令牌(读/写/删除)”。保存这个令牌,因为之后你不会再看到它了。 构建并推送这四个服务: ✋ 实践检查点:Docker Hub用户名
预期结果:应该显示一行你的Docker Hub用户名(例如 构建并推送这四个服务: ✋ 实践检查点:Docker Hub上的镜像
打开 hub.docker.com,进入你的个人资料页面,然后选择 仓库。确认这四个以 在你的笔记本电脑上运行以下命令: 预期结果应该是 Kubernetes使用 清单文件(YAML格式)来描述它需要创建的资源。你不需要点击按钮,只需声明所需的状态,Kubernetes就会自动创建相应的资源。 在部署ClearLedger之前,请先快速浏览一下这些清单文件: 你并不需要立刻理解所有这些内容。目前的目标仅仅是了解在Kubernetes创建应用程序之前,它是如何被描述的。 在§0.6之后的两个可选章节中,你会了解到Ingress路由和RBAC的工作原理。现在,只需关注在Kubernetes创建应用程序之前,相关配置是如何被定义的即可。 整个部署过程分为六个阶段。完成一个阶段后才能开始下一个阶段。在完成第2、第3和第6个阶段后,运行`kubectl get pods -n clearledger`来确认进度。 设置一个路径变量,并确认你的用户名仍然有效: 在创建命名空间之前,其他任何内容都无法被生成。同样,在工作负载能够引用ServiceAccounts之前,也必须先配置RBAC。 验证结果:
在auth-service或ledger-service启动之前,必须先确保PostgreSQL数据库已经运行起来。这两个服务在启动时会连接到Postgres数据库以执行迁移操作并处理请求;如果数据库还没有准备好,这些服务就会陷入无限循环状态。 执行`kubectl apply`命令后,预期会看到以下输出: 当`kubectl wait`命令成功执行完毕时,命令会无任何输出地结束(退出代码为0)。如果等待时间超过限定值,请先参考下面的“如果Postgres仍处于待处理状态”部分,然后再继续操作。 验证结果:
预期输出: 如果Postgres仍处于待处理状态(即`kubectl wait`命令超时,或者Pod的状态显示为“0/1 Pending”,PVC的状态显示为“Pending”): Postgres需要一个PersistentVolumeClaim,也就是集群中的磁盘空间。MicroK8s通过 查看系统日志,你通常会看到类似这样的信息: 请在虚拟机上解决这个问题,然后重新启动Postgres Pod。请从你的主机上运行以下命令:这个命令在macOS、Linux或Windows PowerShell中都可以使用(因为Multipass已经安装在主机上,它会在虚拟机内部执行这些操作): 在Postgres没有成功启动之前,切勿继续部署auth-service或ledger-service。如果没有数据库,这些服务会陷入无限循环状态而无法正常运行。 为什么需要使用Redis?(举个简单的例子):假设有一笔15,000美元的转账交易,ledger-service会首先将这笔数据保存到Postgres中,然后通过Redis发送一条消息:“大型交易,用户X,金额15,000美元。”notification-service会监听这条消息,并生成相应的合规性警报信息;你后来可以通过访问 ledger-service和notification-service并不会直接相互调用。Redis在这里充当了消息中转站的角色:ledger-service负责发送消息,notification-service则负责接收并处理这些消息。因此,在部署notification-service之前,必须确保Redis已经运行起来(这也是为什么要在部署Postgres之后、在应用层之前就先安装Redis的原因)。 验证配置是否正确:
预期结果: 在阶段0中,这些认证信息存储在Kubernetes的Secrets中;而在阶段5中,这些信息会被移至Vault中。 验证结果:
预期结果如下(文件创建时间可能不同,但 您即将启动四个应用程序服务:auth、ledger、notification和frontend。Postgres、Redis以及前两层中配置的Secrets已经准备就绪,现在Kubernetes需要从Docker Hub下载这些镜像,并将它们作为Pod运行起来。 通常情况下,每个服务都需要对应两个文件:一个 那么,为什么我们要使用下面的 为什么我们要使用阶段0文件夹呢?因为这个仓库中保存了多份Kubernetes配置文件。对于这次手动部署操作,请使用路径 请按照顺序部署每个服务。在仓库根目录下执行以下命令,此时请确保 1. auth-service:登录与注册功能 2. ledger-service:用于处理交易记录及余额计算(需要使用Postgres数据库以及你在§0.5.4节中创建的秘钥) 3. notification-service:该服务会监听Redis中的警报信息,用于通知大型交易的发生(此服务不需要使用任何数据库秘钥) 4. frontend:用于提供Web用户界面(相关的部署配置文件都保存在同一个文件中) 验证结果(所有应用相关的Pod都应该处于 预期结果应该是:除了之前创建的Postgres、Redis容器外,还会看到每个应用对应的新Pod(具体的Pod名称可能会有所不同): 如果发现auth-service或ledger-service处于 常见原因:可能是你使用了 ✋ 实践检查点:在部署Ingress组件之前,需要确认所有相关工作负载都已经准备就绪 预期结果应该是:存在四个部署对象( 该设置使集群能够被访问,地址为 验证方法:
预期的最终状态为(所有Pod的状态都显示为“Running”时,按Ctrl+C停止监控): 如果某个Pod的状态仍然显示为“Pending”或“CrashLoopBackOff”,以下两个命令可以帮助你找出问题所在: 在浏览器和curl命令中,都应使用同一个测试账户,以避免出现冲突: 如果你已经使用另一个密码在浏览器中注册过账户,那么请使用那个密码登录;或者选择一个新的电子邮件地址。因为下面的curl命令必须使用你实际注册时使用的同一组邮箱和密码。 在浏览器中打开 点击注册按钮,使用 使用该邮箱和密码登录。首次登录时,控制面板会自动显示一些演示交易记录。请稍等几秒钟,这些记录就会出现。 请查看当前余额选项卡。其中应该会显示以美元为单位的数据,并附带一条折线图。 再看看交易历史记录。你会看到诸如“薪资(Acme Corp)”、“5月租金”之类的条目。 最后,请注意底部的警报提示面板。你会看到标有红色标记的 现在你可以自己尝试提交一笔金额超过10,000美元的交易,然后实时观察警报数量的变化。 需要注意的事项: 每笔交易完成后,余额会立即更新。 贷方金额会显示为绿色的 当你提交一笔金额≥10,000美元的交易时,警报提示的数量会增加。 每条警报都会显示交易金额、方向以及交易时间戳。 请截取显示交易记录以及至少一条警报提示的仪表盘截图。这份截图就是你的第一份“作品集”。 或者,你也可以使用curl命令进行操作(建议使用同一账户;如果浏览器无法正常使用,这个方法会非常有用): 预期返回的结果是: 如果 预期返回的结果应该是一个包含 预期结果(仅使用curl访问,不通过浏览器测试)应该是:对于金额为$15,000的交易,至少会有一条警报信息,例如: 如果你看到 这种情况仅会影响当前终端会话中的 如果你想了解 你的集群运行着四个应用程序服务:前端服务、认证服务、账本服务和通知服务。每个服务在集群内部都有一个内部的Service地址,但在没有Ingress将外部流量引导过来之前,这些服务是无法通过浏览器访问的。 Ingress就相当于“大门”。当有请求到达 请打开 之后你还可以添加更多的主机名(如 Ingress用于控制来自集群外部的流量,而RBAAC则用于控制集群内部的权限分配。 这个文件为 请打开 ServiceAccount是用于表示Pod的身份标识。例如, Role则规定了该身份标识被允许执行哪些操作。在你的代码仓库中,应用程序所使用的角色权限是非常有限的:它们仅能 RoleBinding则负责将身份标识与相应的权限关联起来。如果没有 默认情况下,所有的ServiceAccount都会被绑定到一个没有任何权限的角色上。这样一来,如果某个Pod忘记了设置 这种设计的核心理念就是“最小权限原则”:即使某个Pod受到了攻击,Kubernetes也不会赋予它对整个集群的广泛访问权限。 在阶段0中,你是通过手动方式构建并部署应用程序的。现在你只需对代码进行一个小的修改,然后再重新部署它。这一过程恰恰暴露了手动部署方式存在的问题:这类部署难以追踪、难以回滚,而且也很难验证其实际效果。而阶段1和阶段2则通过使用CI和GitOps技术解决了这些问题。 打开 保存文件后,这就模拟了开发人员提交了一个小的修改并进行了部署的过程。 等待大约30秒,让Kubernetes下载新的镜像并重启相关的Pod: 运行以下命令来检查修改结果: 预期的响应结果是: 如果仍然看到没有 你已经完成了变更的部署,系统也运行正常。但请仔细思考一下刚才发生的事情: 是谁进行了这次部署?没有任何记录可查。你是从笔记本电脑上运行了 到底改变了什么?唯一的证据就是Docker Hub上的标签 如果如何完成这个实验
你将使用的工具
当有新的工具名称出现,而你想知道“为什么偏偏现在会这样”时,请回到下表查看。表格中的每一条记录都只占一行,其中列出了该工具的功能以及它出现的阶段。
**在您的笔记本电脑上:**
```html
```
**该应用程序本身:**
```html
make命令会将较长的指令封装成make setup或make check-N等形式来执行。/etc/hosts文件中添加如clearledger.local这样的条目,可以让浏览器顺利访问集群。
```
**工具功能与对应阶段:**
```html
工具名称
功能简介
所处阶段
MicroK8s / kubectl
在虚拟机内部运行Kubernetes集群,
kubectl用于与之交互。0
clearledger(代码仓库)包含应用程序代码及持续集成工作流程,用于构建最终产品。
1
clearledger-infra(代码仓库)仅包含Kubernetes配置文件,用于定义集群应运行的组件;持续集成工具会更新这些配置,ArgoCD负责将其部署到集群中。
1
GitHub Actions + 自托管运行器
<>每次代码提交时都会构建应用程序镜像并更新基础设施相关代码;运行器位于虚拟机内部,以便能够访问本地集群。
1
ArgoCD
<>监控clearledger-infra仓库中的配置变化,确保集群状态与Git代码库保持一致;同时会自动回滚未经授权的修改。
2
Gitleaks
<>阻止包含敏感信息(如API密钥、令牌等)的提交操作。
3
Semgrep
<>用于检测Python代码中的安全风险,例如不安全的注入语句或硬编码的凭据信息。
3
Checkov
<>对Dockerfile及Kubernetes配置文件进行合规性检查,发现任何错误配置。
3
Trivy
> 对构建出的容器镜像以及pip/npm包中的漏洞进行扫描。
3
Syft + Grype
> 生成软件成分清单,并对最终生成的软件产品进行漏洞检测。
3
Cosign
> 为容器镜像添加签名;在部署阶段,未签名的镜像会被拒绝。
3
Kyverno
> 在集群入口处实施准入控制,阻止不符合安全要求的Pod进入集群(例如缺少限制配置的容器、未签名的镜像等)。
4
Vault
> 将敏感凭据存储在Git和etcd之外,并在Pod启动时通过侧车机制将这些凭据注入到相应的Pod中。
5
Falco
> 通过eBPF技术实时检测容器内部的异常行为,例如在运行中的容器内启动shell命令或读取敏感文件时会发出警报。
6
网络策略
> 在Pod之间设置Kubernetes防火墙,从而限制某项服务被攻击后对其他服务的影响范围。
6
LitmusChaos
> 故意破坏某些Pod,以验证应用程序是否能够正常恢复运行(可选功能)。
6.5
Prometheus / Grafana / Loki
> 收集各种指标数据、生成仪表盘,并支持日志搜索功能,有助于将安全事件转化为可用的证据。
7
OpenTelemetry + Tempo
> 提供分布式追踪功能,可以显示某个请求在各个服务之间是如何分布执行的(可选功能)。
7.5
Terraform / EKS / ECR / RDS
> 用于AWS平台的基础设施即代码管理方案,相同的应用程序可以使用云服务商提供的托管服务。
8
如何选择适合自己的路径
你的实际情况
应选择的路径
>你能获得什么
主机内存为8GB,或者不确定自己的笔记本电脑是否能够支持整个实验流程
先使用Docker Compose
你可以先体验实际的应用程序功能:进行注册、提交交易请求,然后查看系统发出的合规性警告。之后再决定是否需要配置集群。
make integration-up · 本地集成环境
主机内存为16GB
轻量级本地集群
在单个虚拟机上运行:该配置包括Kubernetes、CI/CD工具、GitOps流程、安全检查机制、准入控制功能以及Vault加密服务。如果你希望减少资源消耗,可以在运行
make setup之前编辑scripts/setup-cluster.local.env文件。
主机内存低于16GB,但你需要使用Kubernetes,或者希望完成全部8个实验阶段
云虚拟机
请租用一台远程服务器(配置为4–8个vCPU核心、16–32GB内存),将代码仓库克隆到该服务器上,在那里进行实验。完成后执行
make teardown命令来销毁资源。需要注意的是,第6.5/7/7.5阶段需要24GB的内存,如果你的笔记本电脑无法满足这个要求,请选择这条路径。如何保存实验进度
make snapshot和make restore命令时需要使用Multipass。如果你使用的是Linux系统且没有安装Multipass,那么请跳过快照备份功能,如果遇到问题可以按照备选方案B进行操作。保存你的实验进度
make snapshot STAGE=7
make snapshots
make snapshots命令,以确保快照已经成功创建。恢复你的实验进度
make snapshots
make restore STAGE=7
export KUBECONFIG=~/.kube/clearledger-config
make check-7
如果虚拟机出现故障会怎样?
方案A:你已经有了快照(推荐使用)
make snapshots
make restore STAGE=6
export KUBECONFIG=~/.kube/clearledger-config
make check-6
方案B:没有快照
make teardown
make setup
export KUBECONFIG=~/.kube/clearledger-config
本书适合哪些人群?
docs/interview-prep.md。面试中的问题会基于本实验环境中的实际内容来设计。如何配置你的机器?
安装必备工具
工具名称
功能
macOS
Linux
Windows
Multipass
可在你的笔记本电脑上创建轻量级的Ubuntu虚拟机
brew install --cask multipasssudo snap install multipassmultipass.run/install
kubectl
用于通过终端与Kubernetes集群进行交互
brew install kubectlsudo snap install kubectl --classicwinget install Kubernetes.kubectl
Helm
Kubernetes的包管理工具(类似于apt或brew,但用于管理集群应用)
brew install helmsudo snap install helm --classicwinget install Helm.Helm
Docker Desktop
可在本地机器上构建容器镜像
docker.com
docker.com
docker.com
jq
用于格式化JSON数据,使其更易于阅读
brew install jqsudo apt install jqwinget install jqlang.jqmake命令以及Bash脚本。multipass --version
kubectl version --client
helm version
docker --version
jq --version
如何开始实验
make setup
export KUBECONFIG=~/.kube/clearledger-config
kubectl get nodes
clearledger的节点,其状态应为Ready。make setup命令会配置Multipass虚拟机、安装MicroK8s、设置磁盘使用限制,并更新/etc/hosts文件。整个过程需要3到5分钟。如何管理磁盘空间
make setup命令会自动设置一些预防性措施,例如定期清理日志文件、控制镜像占用的内存大小以及限制journald日志文件的存储容量。有关详细的配置信息,请参阅troubleshooting.md文件。make doctor # 结果可能为PASS、WARN或FAIL,同时还会显示PVC及Prometheus TSDB的占用情况make reclaimmake doctor后仍然显示失败结果,那么您可能需要先执行make teardown和make setup命令,然后从备份状态中恢复系统。详细操作指南请参见troubleshooting.md文件。如何在不使用Kubernetes的情况下测试应用程序
docker compose -f docker-composeintegration.yml up --build -d
docker compose -f docker-composeintegration.yml down
如何首次登录
up(或down -v)命令后,数据库都会被清空。请使用真实的电子邮件地址进行注册(Pydantic会拒绝接受@*.local这类地址),例如,可以使用test@clearledger.io作为电子邮件地址,SecurePass123作为密码。localStorage.removeItem('cl_token')命令。如何运行演示流程
BASE_URL=http://localhost:3000 bash scripts/dast/smoke.sh
如何配置本地域名
hosts文件中。在macOS或Linux系统中使用Multipass进行配置
sudo bash scripts/setup-hosts.sh
VMIP=$(multipass info clearledger | grep IPv4 | awk '{print $2}')
echo "$VMIP clearledger.local argocd.local grafana.local vault.local falco.local litmus.local" | sudo tee -a /etc/hosts
curl -s -o /dev/null -w "%{http_code}\n" http://clearledger.local/auth/health
200。在WSL2环境中进行配置
ip -4 addr show eth0 | grep inet
127.0.0.1),将其添加到/etc/hosts文件中:LAB_IP=<YOUR_IP>
echo "$LAB_IP clearledger.local argocd.local grafana.local vault.local falco.local litmus.local" | sudo tee -a /etc/hosts
C:\Windows\System32\drivers\etc\hostscurl http://clearledger.local/auth/health
阶段0——运行中的系统环境
0.1:配置集群
make setup
export KUBECONFIG=~/.kube/clearledger-config
kubectl get nodes
NAME STATUS ROLES AGE VERSION
clearledger Ready multipass launch \
--name clearledger \
--cpus 6 --memory 12G --disk 80G \
22.04
multipass info clearledger | grep IPv4
multipass shell clearledger
sudo snap install microk8s --classic --channel=1.29/stable
sudo usermod -aG microk8s ubuntu &;& newgrp microk8s
microk8s enable dns ingress storage helm3 rbac
echo "alias kubectl='microk8s kubectl'" > ~/.bashrc
echo "alias helm='microk8s helm3'" > ~/.bashrc
source ~/.bashrc
kubectl get nodes
exit # 退出虚拟机,返回主机系统
multipass exec clearledger -- microk8s config > ~/.kube/clearledger-config
export KUBECONFIG=~/.kube/clearledger-config
kubectl get nodes
0.2:在部署应用程序之前先了解其工作原理
文件
功能
app/auth-service/main.py负责注册、登录以及验证JWT令牌。
app/ledger-service/main.py处理交易记录、查询账户余额,并会调用认证服务来验证所有请求。
app notification-service/main.py订阅Redis消息队列;当账户金额达到10,000美元时,会触发警报通知。
appfrontend/src/app.js这是一个单页应用,它使用的API与通过curl命令执行的操作相同。
app/auth-service/Dockerfile该Dockerfile配置了非root用户身份、固定的基础镜像以及健康检查功能。
USER appuser。这表示该镜像被设计为以普通用户身份运行,而非root用户。Kubernetes的配置文件中也设置了runAsNonRoot: true这一选项。在后续的第4阶段,Kyverno会强制执行这一规则,拒绝那些没有声明以非root用户身份运行的Pod。因此,你的应用程序在早期就经过了相应的配置,从而能够满足这一要求。infra/manifests/auth-service/secret.yaml。其中存储的数据库密码是changeme-stage0,这个密码采用了base64编码格式。你可以使用以下命令将其解码:echo "Y2hhbmdlbWUtc3RhZ2Uw" | base64 -d
# 解码后的密码为:changeme-stage0
0.3:Docker Hub配置
hub.docker.comYOUR_USERNAME/clearledger-auth-service
YOUR_USERNAME/clearledger-ledger-service
YOUR_USERNAME/clearledger-notification-service
YOUR_USERNAME/clearledger-frontend

docker login
# 用户名:你的Docker Hub用户名
# 密码:访问令牌(而不是你的账户密码)
# 将“your-username”替换为你的Docker Hub用户名,在本实验中所有地方都使用相同的字符串
export DOCKER_USERNAME=your-username
echo "正在使用用户名 $DOCKER_USERNAME"
# 必须输出你的真实用户名,而不能是“your-username”这个字面字符串
echo "$DOCKER_USERNAME"
veeno-demo)。如果显示的是 your-username,请停止操作,并在继续构建之前修复 export 命令中的设置。docker build -t $DOCKER_USERNAME/clearledger-auth-service:v0.1.0 ./app/auth-service
docker build -t $DOCKER_USERNAME/clearledger-ledger-service:v0.1.0 ./app/ledger-service
docker build -t $DOCKER_USERNAME/clearledger-notification-service:v0.1.0 ./app notification-service
docker build -t $DOCKER_USERNAME/clearledger-frontend:v0.1.0 ./app/frontend
# 推送
docker push $DOCKER_USERNAME/clearledger-auth-service:v0.1.0
docker push $DOCKER_USERNAME/clearledger-ledger-service:v0.1.0
docker push $DOCKER_USERNAME/clearledger-notification-service:v0.1.0
docker push $DOCKER_USERNAME/clearledger-frontend:v0.1.0
clearledger-* 开头的仓库都存在,并且每个仓库的标签都显示为 v0.1.0。
docker pull $DOCKER_USERNAME/clearledger-auth-service:v0.1.0
Status: 已下载到更新后的镜像 或 镜像已更新为最新版本,而不是 仓库不存在 或 操作被拒绝。0.4:在应用之前先查看清单文件
infra/manifests/namespace.yaml:创建clearledger命名空间。infra/manifests/postgres/:部署PostgreSQL数据库。infra/manifests/redis/redis.yaml:部署Redis服务器。infra/manifests/auth-service/:部署认证服务。infra/manifests/ledger-service/:部署账本服务。infra/manifests notification-service/:部署通知服务。infra/manifests/frontend/:部署Web应用程序。infra/manifests/ingress.yaml:使应用程序能够在clearledger.local地址上被访问。infra/manifests/rbac/rbac.yaml:规定集群内部哪些用户可以执行哪些操作。0.5:分层部署ClearLedger
export DOCKER_USERNAME=your-username # 如果在§0.3中已经设置了这个变量,则可以跳过这一步
STAGE0=stages/stage-0-raw-kubernetes/infra/manifests
0.5.1 — 第1阶段:命名空间与RBAC配置
kubectl apply -f infra/manifests/namespace.yaml
kubectl apply -f infra/manifests/rbac/rbac.yaml
kubectl get namespace clearledger
kubectl get serviceaccount -n clearledger
# 预期输出:auth-service, ledger-service, notification-service, clearledger-viewer
0.5.2 — 第2阶段:PostgreSQL数据库配置
kubectl apply -f infra/manifests/postgres/postgres-secret.yaml
kubectl apply -f infra/manifests/postgres/postgres.yaml
kubectl wait --for=condition=ready pod -l app=postgres \
-n clearledger --timeout=120s
secret/postgres-secret created
persistentvolumeclaim/postgres-pvc created
statefulset.apps/postgres created
service/postgres created
kubectl get pods -n clearledger -l app=postgres
kubectl get pvc -n clearledger
NAME READY STATUS RESTARTS AGE
postgres-0 1/1 Running 0 45s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
postgres-pvc Bound pvc-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 5Gi RWO microk8s-hostpath 45s
hostpath-storage插件提供了这一功能。如果make setup过程被中断,或者你采用了手动配置方式且没有执行microk8s enable storage命令,那么PVC就无法绑定到任何磁盘空间上,从而导致Pod无法正常调度。警告:FailedScheduling……该Pod没有可绑定的PersistentVolumeClaim
正常状态:FailedBinding……当前没有可用的持久化存储资源,且未设置任何存储类别# 启用存储功能(如果之前跳过了make setup步骤,还需要启用Ingress和Rbac功能)
multipass exec clearledger -- microk8s enable storage ingress rbac
# 确认是否存在默认的存储类别
kubectl get storageclass
# 预期结果:microk8s-hostpath(默认值)
# 删除现有的Postgres Pod,以便重新调度到新的存储配置上
kubectl delete pod postgres-0 -n clearledger
# 等待Pod准备好运行
kubectl wait --for=condition=ready pod -l app=postgres \
-n clearledger --timeout=120s
# 检查Pod的状态
kubectl get pods -n clearledger -l app=postgres
# 预期结果: postgres-0 1/1 Running0.5.3 — 第三层:Redis
/notifications/alerts来查看这些警报。
kubectl apply -f infra/manifests/redis/redis.yamlkubectl get pods -n clearledger -l app=redisNAME READY STATUS RESTARTS AGE
redis-xxxxxxxxxx-xxxxx 1/1 Running 0 30s0.5.4 — 第四层:应用程序密钥
kubectl apply -f infra/manifests/auth-service/secret.yaml
kubectl apply -f infra/manifests/ledger-service/secret.yaml
kubectl get secrets -n clearledger | grep -E 'auth-service|ledger-service'
DATA字段的内容必须一致):auth-service-secret Opaque 2 64s
ledger-service-secret Opaque 1 8s
auth-service-secret文件包含两项密钥:database_url和jwt_secret;而ledger-service-secret文件只包含一项密钥:database_url。在阶段5中,这些密钥将会被替换为Vault中的存储方式,但目前它们仍然以Kubernetes Secrets的形式存在于集群中。0.5.5 — 第五层:应用程序工作负载
Deployment文件用于告诉Kubernetes应该运行哪个容器镜像以及需要创建多少个副本;而Service文件则为该应用程序在集群中提供一个固定的名称(例如auth-service),这样其他服务就可以通过这个名称来找到它,而无需知道它的Pod IP地址。通常需要先应用Deployment文件,然后再应用Service文件。sed命令呢?因为Git仓库中的部署配置文件中包含一个占位符文本DOCKER_USERNAME,而每个人的Docker Hub用户名都是不同的。您已经在§0.3节中设置了自己的用户名(export DOCKER_USERNAME=YOUR_DOCKERHUB_USERNAME)。当这些配置文件被发送给Kubernetes时,sed命令会自动将这个占位符替换为实际的用户名。因此,您根本不需要直接编辑Git仓库中的文件。如果跳过sed步骤并直接应用原始文件,Kubernetes将会尝试下载一个名为DOCKER_USERNAME/clearledger-auth-service的镜像,但这样的镜像实际上是不存在的。stages/stage-0-raw-kubernetes/infra/manifests/来获取相应的文件。这些文件是为阶段0准备的,其中包含了占位符DOCKER_USERNAME,后续的命令会将其替换为实际的用户名。请暂时不要使用infra/manifests/路径,因为那些文件是用于后续的GitOps部署流程的。DOCKER_USERNAME>变量已经设置好:sed "s|DOCKER_USERNAME|${DOCKER_USERNAME}|g" \
"$STAGE0/auth-service/deployment.yaml" | kubectl apply -f -
kubectl apply -f infra/manifests/auth-service/service.yaml
sed "s|DOCKER_USERNAME|${DOCKER_USERNAME}|g" \
"$STAGE0/ledger-service/deployment.yaml" | kubectl apply -f -
kubectl apply -f infra/manifests/ledger-service/service.yaml
sed "s|DOCKER_USERNAME|${DOCKER_USERNAME}|g" \
"$STAGE0notification-service/deployment.yaml" | kubectl apply -f -
kubectl apply -f infra/manifests/notification-service/service.yaml
sed "s|DOCKER_USERNAME|${DOCKER_USERNAME}|g" \
"$STAGE0/frontend/deployment.yaml" | kubectl apply -f -
Running状态:由于auth-service和ledger-service需要与Postgres建立连接,因此它们可能需要大约30秒的时间才能完成初始化):kubectl get pods -n clearledger
NAME READY STATUS RESTARTS AGE
postgres-0 1/1 Running 0 15m
redis-xxxxxxxxxx-xxxxx 1/1 Running 0 10m
auth-service-xxxxxxxxxx-xxxxx 1/1 Running 0 45s
ledger-service-xxxxxxxxxx-xxxxx 1/1 Running 0 40s
notification-service-xxxxxxxxxx-xxxxx 1/1 Running 0 35s
frontend-xxxxxxxxxx-xxxxx 1/1 Running 0 30s
CrashLoopBackOff状态,請查看相关日志:kubectl logs -n clearledger deploy/auth-service --tail=20
infra/manifests/*/deployment.yaml文件,而不是前面提到的Stage 0阶段的配置文件;此时日志中可能会显示“DATABASE_URL未设置”的错误信息。请重新执行这一节中提到的sed命令和kubectl apply命令即可。kubectl get deployment -n clearledger
kubectl get pods -n clearledger --field-selector/status.phase!=Running
auth-service、ledger-service、notification-service、frontend),且它们的READY状态都表示相应的副本已经成功创建完毕(其中auth-service和ledger-service的副本数量应为2/2)。第二个命令的执行结果应该没有任何输出,这说明没有Pod处于Pending状态或CrashLoopBackOff状态。0.5.6 — 第六层:入口设置
http://clearledger.local。kubectl apply -f infra/manifests/ingress.yaml
kubectl get ingress -n clearledger
curl -s -o /dev/null -w "%{http_code}\n" http://clearledger.local/
# 预期结果:200
0.5.7:监控直到系统状态稳定
kubectl get pods -n clearledger -w
NAME READY STATUS RESTARTS
auth-service-xxx 1/1 Running 0
auth-service-yyy 1/1 Running 0
frontend-xxx 1/1 Running 0
ledger-service-xxx 1/1 Running 0
ledger-service-yyy 1/1 Running 0
notification-service-xxx 1/1 Running 0
postgres-0 1/1 Running 0
redis-xxx 1/1 Running 0
kubectl describe pod POD_NAME -n clearledger
kubectl logs POD_NAME -n clearledger --previous
0.6:验证系统运行状态
字段
值
电子邮件
test@clearledger.io
密码
SecurePass123推荐使用浏览器进行验证:
http://clearledger.local,你应该会看到ClearLedger的登录页面。
test@clearledger.io和SecurePass123创建账户(与下面的curl命令中使用的信息相同)。Pydantic系统会拒绝明显虚假的电子邮件地址,例如test@test.com)。
大额交易警报提示。在演示案例中,有两笔交易的金额超过了10,000美元,因此系统会自动发出合规性警报。
+$符号,借方金额则会显示为红色的−$符号。
# 注册账号(如果你已经通过浏览器使用了相同的邮箱注册过,可以跳过这一步)
curl -s -X POST http://clearledger.local/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"test@clearledger.io","password":"SecurePass123"}' | jq .
{"user_id":"...","email":"test@clearledger.io"},如果返回错误信息,说明该邮箱已经被其他人注册使用了(如果你之前是通过浏览器注册的,这种情况也是正常的)。# 登录并获取访问令牌(这个令牌必须与你注册时输入的密码相匹配)
TOKEN=$(curl -s -X POST http://clearledger.local/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@clearledger.io","password":"SecurePass123"}' \
| jq -r .access_token)
echo "访问令牌:${TOKEN:0:30}..."
TOKEN为空,或者登录时收到401错误代码,说明你输入的密码不正确。请使用上述注册信息重新尝试注册,或者在JSON请求中使用正确的密码。# 创建一笔金额较大的交易(这条交易会触发警报提示)
curl -s -X POST http://clearledger.local/ledger/transactions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"amount":15000,"direction":"debit","description":"房屋租金支付"}' | jq .
id、amount: 15000以及direction: "debit"字段的交易对象。# 查看当前余额
curl -s http://clearledger.local/ledger/balance \
-H "Authorization: Bearer $TOKEN" | jq .
# 确认是否触发了通知警报
curl -s http://clearledger.local/notifications/alerts | jq .
{"total":1,"alerts":[{"type":"LARGE_TRANSACTION","amount":15000,...}]}。如果你已经使用过浏览器进行测试,total的值可能会是3或更多(包括两条演示警报和你的那条警报),这也是正常的。{"detail":"Unauthorized"},说明:你的令牌已经过期了。出于安全考虑,JWT令牌的有效期是有限的,这是有意设计的。请重新运行上面的登录命令来获取新的令牌,然后再尝试执行之前失败的命令。$TOKEN变量。如果你打开一个新的终端窗口,就需要再次运行登录命令,因为$TOKEN在不同会话之间是不会持续保留的。make check-0
了解Ingress机制(可选阅读)
clearledger.local是如何与你的Pods建立连接的,可以在阅读§0.6之后再阅读这部分内容。clearledger.local时,Kubernetes会根据URL路径将请求转发到相应的服务。例如,对/auth的请求会被转发到认证服务,对/ledger的请求会被转发到账本服务,对/notifications的请求会被转发到通知服务,而对/的请求则会直接到达前端服务。infra/manifests/ingress.yaml文件并阅读其中的注释。API路径使用了重写规则:例如,/auth/login在到达认证服务之前会被重写为/login,这样后端的路由结构就能保持简洁明了。grafana.local、argocd.local等),每个主机名都会在后续阶段生成对应的Ingress配置文件。目前这个文件仅用于ClearLedger应用程序的配置。
了解RBAAC机制(可选阅读)
clearledger命名空间创建了相应的身份标识和权限设置。infra/manifests/rbac/rbac.yaml文件。文件顶部的注释详细解释了这些配置的含义和用途。auth-service、ledger-service和notification-service各自都会获得属于自己的身份标识。获取和列出
RoleBinding>,虽然该角色存在,但没有任何Pod能够获得这些权限。clearledger-viewer这个ServiceAccount仅用于只读调试目的。它可以查看Pod、服务、端点、事件以及配置映射文件的内容,但无法读取Secrets文件。serviceAccountName>,那么它就会使用一个根本无法执行任何操作的“身份标识”来运行。0.7:为什么手动部署不可靠
步骤1:进行一个可见的修改
app/auth-service/main.py文件,找到/health端点,然后更改其返回值,这样就能说明新版本已经成功部署运行了:# 修改前的代码
return {"status": "ok", "service": settings.service_name}
# 修改后的代码——添加了“version”字段
return {"status": "ok", "service": settings.service_name, "version": "0.2.0"}
步骤2:手动构建、推送并部署代码
docker build -t $DOCKER_USERNAME/clearledger-auth-service:v0.2.0 ./app/auth-service
docker push $DOCKER_USERNAME/clearledger-auth-service:v0.2.0
kubectl set image deployment/auth-service \
auth-service=$DOCKER_USERNAME/clearledger-auth-service:v0.2.0 \
-n clearledger
kubectl rollout status deployment/auth-service -n clearledger
步骤3:验证你的修改是否已经生效
curl -s http://clearledger.local/auth/health | jq .
{"status":"ok","service":"auth-service","version":"0.2.0"}"version"字段的旧响应结果,那么请再等待几秒钟后再尝试。因为Kubernetes可能还在部署新的Pod。步骤4:了解手动部署所带来的局限性。
kubectl命令。如果有多人拥有对集群的访问权限,那么根本无法确定是谁修改了什么内容。v0.2.0。然而这个标签并没有与任何具体的代码提交或代码审查记录关联起来。v0.2.0版本出现了问题怎么办?kubectl set image命令来回滚系统。但如果你不记得那个标签号呢?又或者之前的镜像已经被删除了该怎么办?
如果在你正在推送v0.2.0版本的同时,其他人运行了kubectl apply命令并使用了v0.1.0版本会怎么样?集群会自动恢复到旧版本,既不会出现错误,也不会有任何通知。你会以为自己的修改已经生效了,但实际上并没有。
审计追踪记录在哪里?根本不存在。在受监管的环境中(如银行业、医疗行业或政府部门),你需要有证据来证明是谁在什么时间进行了哪些操作。但目前你没有任何这样的记录。
对于演示用途来说,手动部署确实可行。但在团队协作或受监管的环境中,这种方法显然存在诸多问题。请牢记这些局限性,这也是后续步骤存在的必要性所在。
步骤5:在继续之前先撤销你的更改。
取消对app/auth-service/main.py文件中相关代码的修改(删除"version": "0.2.0"这一行)。无需重新构建镜像:目前集群仍然会使用v0.2.0版本运行,而第一阶段流程将会接管镜像的管理工作。
第一阶段负责自动构建镜像,第二阶段则负责完成实际的部署工作。
你在第0阶段学到了什么
如何使用Multipass和MicroK8s来配置本地Kubernetes集群
Kubernetes的配置文件是如何描述系统所需状态的
Ingress是如何将外部流量路由到内部服务的
如何手动构建、推送并部署容器镜像
为什么不能依赖手动部署:没有审计追踪记录,无法回滚更改,也无法保证系统状态的一致性
你现在可以在简历中写上这些内容,或者在面试时提及:
我曾手动将一个多服务应用程序部署到Kubernetes集群中:配置了命名空间、RBAC权限控制、StatefulSet数据库、Deployments服务以及基于路径的Ingress路由规则,并且能够解释为什么各组件需要按这样的顺序进行部署。
执行命令make snapshot STAGE=0 & make snapshots,然后确认clearledger_stage0操作是否成功。有关如何保存你的工作进度,请参阅此处。
阶段1——持续集成管道(GitHub Actions + 自托管运行器)
在阶段0中,你是手动完成构建和部署工作的。而在阶段1中,这一过程将被自动化:执行`git push`命令会触发一个流程,该流程会生成镜像、对其进行扫描,然后将其上传到Docker Hub,并在`clearledger-infra`中记录新的标签信息。
目标:每次向GitHub提交代码时,系统都会自动构建镜像、将其上传到Docker Hub,并更新`clearledger-infra`中的镜像标签。
我准备好进入阶段1了吗?
在开始执行§1.1之前的内容之前,请自己先运行以下命令:
make check-0
echo "$DOCKER_USERNAME" # 这个用户名不能为空,也不能是"your-username"
curl -s -o /dev/null -w "%{http_code}" http://clearledger.local/auth/health
预期结果应该是:健康检查结果显示为绿色,`echo`命令会输出你的Docker Hub用户名,而`curl`命令会返回`200`这个状态码。
进行这一操作所需准备的条件包括:
拥有一个在Docker Hub上拥有四个`clearledger`仓库的账户(详见QUICKSTART.md中的§1b部分)
拥有GitHub账户:你可以通过该账户创建仓库和个人访问令牌
需要大约2到4小时的时间来安装运行工具并配置第一个自动化构建流程(对于初学者来说,这一阶段可能会比较困难)
当`make check-1`命令通过测试,并且你已经确认了§1.7中列出的所有事项后,就可以完成准备了。此时请执行以下命令:`make snapshot STAGE=1 → make snapshots`(确保清除了与阶段0相关的快照文件)
你需要首先了解的内容
在阶段0中,你的笔记本电脑才是实际的部署工具。
你是手动执行`docker build`、`docker push`以及`kubectl set image`这些命令的。这种做法适用于演示目的,但团队在实际开发过程中不应该采用这种方式来发布软件。
手动构建代码会引发许多无法解决的问题:
这个镜像是不是基于最新的代码生成的?
是否有人使用不干净的代码环境来构建了这个镜像?
在另一台机器上,这个构建过程还能正常进行吗?
是哪一次提交产生了当前正在运行的这个镜像?
是谁在什么时间将这个镜像上传到了服务器上的?
持续集成(CI)正是为了解决这些问题而存在的。它的作用是:每当有人向代码仓库提交代码时,系统就会自动执行相同的构建、测试和打包流程。
可以把持续集成想象成一条生产线:
开发者提交代码
↓
GitHub检测到提交请求
↓
GitHub Actions启动构建流程
↓
运行工具执行相应的任务
↓
Docker镜像被生成并上传
↓
基础设施配置文件会更新为新的镜像标签信息(详见clearledger-infra中的§1.3部分)
关键在于:构建过程不再依赖于你的笔记本电脑。你的笔记本电脑负责编写代码,而自动化流程则负责生成最终的可发布版本。
<一个持续集成系统由三个部分组成:流水线主机:负责控制整个流程。它会检测到代码推送事件,并决定执行哪个工作流。在这个实验中,这个角色由GitHub Actions承担。
流水线配置文件:其中包含了具体的执行指令。这个文件是一个YAML格式的文件,位于路径
.github/workflows/ci.yaml,用于指定需要执行哪些任务。执行器:实际负责运行流水线中指令的机器。
通常情况下,GitHub Actions会使用由GitHub托管的云端执行器。但在本实验中,这种配置并不适用——因为你的Kubernetes集群位于本地虚拟机中,而GitHub的云端执行器无法访问它。因此,你需要在本地虚拟机中安装一个执行器,以便利用本地的Docker守护进程来构建Docker镜像。
具体来说,你需要在虚拟机中安装一个自托管的执行器。这个执行器会与GitHub建立连接,接收任务指令,然后在本地环境中执行相应的操作,因为本地环境可以访问所有所需的资源。
本实验需要使用两个仓库:clearledger(包含代码及CI配置)和clearledger-infra(仅包含Kubernetes相关的YAML文件)。第二个仓库将在§1.3节中创建。
GitHub — clearledger(应用程序代码仓库)
用于存储你的代码
在执行git push操作时会启动相应的流水线流程
↓
自托管执行器(位于本地虚拟机中)
负责构建Docker镜像
将构建好的镜像推送到Docker Hub
更新clearledger-infra仓库中的镜像标签 ← 这个步骤将在§1.3节中完成
↓
GitHub — clearledger-infra(基础设施配置仓库)
用于存储包含新镜像标签的Kubernetes配置文件
ArgoCD会在第二阶段监控这个仓库
在实验的第1至第7阶段,会使用.github/workflows/ci.yaml文件以及自托管的执行器。这些步骤包括构建Docker镜像、将其推送到Docker Hub以及更新clearledger-infra仓库。到了第8阶段,则会添加另一个AWS相关的流水线流程,即.github/workflows/ci-aws.yaml文件,该流程会将镜像推送到ECR仓库。在进入第8阶段之前,你不需要配置AWS相关的流水线。
1.1:将应用程序代码仓库推送到GitHub(暂时不推送至clearledger-infra仓库)
这个步骤对应的是clearledger仓库,也就是包含应用程序代码及CI配置的仓库。你需要将本地笔记本电脑上的代码克隆到这个仓库中,也就是之前执行过第0阶段操作的那个文件夹。
clearledger-infra仓库将在§1.3节中创建,它仅用于存储Kubernetes相关的配置文件,请不要在这里创建它。
仓库名称:请填写
clearledger(务必使用这个名称,不要使用clearledger-infra)可见性设置:可以选择“公共”或“私有”。这两种设置都适用于自托管执行器和GitHub Actions。需要注意的是,ArgoCD并不会访问这个仓库(详见§1.3节中的“私有仓库:哪些数据会被同步到哪里”)。
请不要为这个仓库添加README文件或
.gitignore文件。
该仓库已经在本机保存了这些文件。如果GitHub创建了自己的副本,那么你的首次推送操作可能会失败,因为版本历史记录不一致。
在笔记本电脑上,从本地clearledger项目的根目录运行以下命令(其中app/、infra/以及.github/workflows/ci.yaml文件都位于该目录中):
cd ~/Desktop/clearledger # 你的克隆路径
git remote add origin https://github.com/YOUR_USERNAME/clearledger.git
git branch -M main
git push -u origin main
如果git remote add命令执行失败,因为origin这个远程仓库已经存在了,可以尝试以下操作:
git remote -v
git remote set-url origin https://github.com/YOUR_USERNAME/clearledger.git
git push -u origin main
最后在浏览器中访问https://github.com/YOUR_USERNAME/clearledger页面进行验证。你应该能看到app/、infra/manifests/、docs/以及.github/workflows/ci.yaml这些目录,这说明GitHub能够在你下次推送代码时自动触发构建流程。
你验证的内容是:app仓库确实存在于GitHub上,CI构建流程也会从这里开始执行。而用于GitOps部署的配置文件则会被保存在clearledger-infra目录中,具体内容会在第1.3节中介绍。
1.2:在虚拟机内部安装自托管构建工具
工作流文件告诉GitHub应该执行哪些操作,而构建工具本身则负责实际执行这些操作。
由于我们的基础设施是本地的,因此这个实验使用了自托管的构建工具。GitHub的云服务器无法访问位于Multipass虚拟机内部的MicroK8s集群或Docker守护进程,而自托管构建工具正好可以解决这个问题——它驻留在虚拟机内部,与GitHub连接以接收任务指令,然后在本机上完成所有执行操作。
如果构建工具缺失或者处于离线状态,那么构建流程就无法正常运行。此时,工作流可能会被挂起等待处理,或者因为找不到可用的构建工具而失败。
步骤1:打开GitHub的自托管构建工具设置页面(请保持此标签页处于打开状态)
GitHub在一页上提供了完整的安装指南,可以直接使用这些说明进行操作,无需再去其他地方查找URL或token信息。
访问
https://github.com/YOUR_USERNAME/clearledger页面进入“设置”→“操作”→“构建工具”,然后选择“新建自托管构建工具”
选择“Linux”和“x64”系统版本
该页面的标题应该显示为:添加新的自托管构建工具 · YOUR_USERNAME/clearledger。
这个页面包含三个你需要使用的部分:
| GitHub页面内容 | 对应操作 |
|---|---|
| 下载 | 将mkdir、curl和tar命令复制到虚拟机中(请使用与下面示例相同的版本) |
| 配置 | 从文件./config.sh ... --token ...中获取token值,但不要直接运行GitHub提供的./config.sh脚本 |
| 使用自托管构建工具 | 暂时忽略这一部分,实验中的工作流流程需要用到clearledger标签(具体操作见步骤4) |
滚动到配置选项。你会看到类似以下的命令:
./config.sh --url https://github.com/你的用户名/clearledger --token AXXXXXXXXXXXXXXXXXXXXXXXXX
./run.sh
这里的token是指--token后面那段长字符串(以A开头,大约有26个字符)。请只复制这段字符串。
在完成第4步之前,请保持这个标签页处于打开状态:因为该token的有效期约为1小时。如果它过期了,请再次点击“新建自托管运行器”以获取新的token。
步骤2:进入虚拟机
multipass shell clearledger
执行这条命令后,你的命令提示符应该会显示为ubuntu@clearledger:~$。这说明你已经进入了Ubuntu虚拟机。如果命令提示符仍然显示的是你的Mac用户名或MacBook名称,那就说明你还在主机上,此时运行器的配置将会失败。
只有当命令提示符显示为ubuntu@clearledger时,才能继续下一步操作。
从第3步开始的所有操作都是在虚拟机内部进行的,而不是在你的Mac上。
步骤3:在虚拟机中安装Docker
运行器需要构建Docker镜像,因此必须在运行器运行的环境中安装Docker。
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker ubuntu
newgrp docker
docker --version
预期结果:Docker会输出其版本号(例如Docker version 29.x.x)。
现在请验证ubuntu用户环境下Docker是否可以正常使用:因为运行器尚未创建(第4步才会生成~/actions-runner目录):
docker ps
预期结果会看到一个表格标题(如CONTAINER ID、IMAGE等),即使实际上没有容器被列出。绝对不能出现permission denied while trying to connect to the Docker API这样的错误。
如果执行docker ps时出现“权限不足”的错误,说明docker组尚未被添加到用户账户中。请再次运行newgrp docker命令,或者先退出虚拟机(使用exit),然后再通过multipass shell clearledger重新进入虚拟机,之后再尝试执行docker ps。
你验证的内容是:即使你的Mac上没有安装Docker Desktop,虚拟机依然可以正常运行Docker。请继续进行第4步以完成运行器的安装。
步骤4:安装并注册运行器
仍然处于虚拟机内部(命令提示符为ubuntu@clearledger):
下载命令:你可以从GitHub上运行器的页面的下载区域复制所需的命令,或者直接运行下面的代码块。这两种方法得到的结果应该是相同的。请将这些命令粘贴到虚拟机中,而不是你的Mac上。
配置命令:请使用下面的命令进行配置,而不是GitHub提供的./config.sh文件。请将第1步中获得的token替换为YOUR_USERNAME。
mkdir -p ~/actions-runner && cd ~/actions-runner
curl -o actions-runner-linux-x64-2.335.1.tar.gz -L \
https://github.com/actions/runner/releases/download/v2.335.1/actions-runner-linux-x64-2.335.1.tar.gz
tar xzf ./actions-runner-linux-x64-2.335.1.tar.gz
./config.sh \
--url https://github.com/你的用户名/clearledger \
--token YOUR_RUNNER_TOKEN \
--name clearledger-runner \
--labels clearledger,self-hosted,linux \
--work _work \
--unattended
sudo ./svc.sh install
sudo ./svc.sh start
请**不要**在日常使用中运行 GitHub 的 `./run.sh` 命令:实验室环境中是使用 `sudo ./svc.sh` 命令的,这样运行程序才能在虚拟机重启后继续正常工作。GitHub 提供 `./run.sh` 是仅用于快速测试的目的。
执行 `./config.sh` 后,应该会看到类似 “运行程序已成功添加” 的提示。如果出现 “无效令牌” 或 “令牌已过期” 的错误,请返回到第一步,在浏览器中重新获取一个新的令牌。
在设置页面上,GitHub 默认的 `./config.sh` 脚本并不会自动添加 `clearledger` 这一标签。工作流程中确实需要使用这个标签:
runs-on: [self-hosted, clearledger]
GitHub 是根据运行程序所对应的标签来安排任务的,而不是根据运行程序的名称。如果一个名为 `clearledger` 的运行程序没有对应 `clearledger` 标签,那么它虽然会保持在线状态,但相关任务会一直处于 “等待相应运行程序处理” 的状态。
下面这两条命令的具体作用如下:
sudo ./svc.sh install
该命令会在虚拟机内部通过 systemd 注册相应的运行程序。
如果不执行这个步骤,执行 `sudo ./svc.sh status` 时会显示 “未安装” 的结果。
sudo ./svc.sh start
该命令会在后台启动运行程序服务。
完成这个操作后,即使关闭终端,运行程序也会继续运行。
你可以在虚拟机内部,从同一个文件夹中亲自测试一下:
cd ~/actions-runner
sudo ./svc.sh status
预期结果应该是:服务已经安装完毕并且正在运行中。
如果在第三步中 `docker ps` 命令能够正常执行,但后来进行的 CI 测试却因为 “Docker 插孔权限被拒绝” 而失败,那么很可能是因为在运行程序之前还没有将相应的组添加到系统中。请在完成第四步操作后重新启动运行程序(前提是 `~/actions-runner` 目录已经存在):
cd ~/actions-runner
sudo ./svc.sh stop
sudo ./svc.sh start
docker ps # 这条命令应该可以直接执行,不需要使用 sudo
另外,如果你是直接使用 `./run.sh` 命令而不是通过 systemd 来启动运行程序的,可以尝试以下命令:
cd ~/actions-runner
pkill -f "Runner.Listener|Runner.Worker|./run.sh" || true
nohup ./run.sh & > _diag/manual-runner.log 2>&1 &
docker ps
如果看到 “未安装” 的提示,说明 `sudo ./svc.sh install` 命令没有成功执行。请重新运行以下命令:
cd ~/actions-runner
sudo ./svc.sh install
sudo ./svc.sh start
sudo ./svc.sh status
如果安装过程中出现错误,请使用新的 GitHub 令牌再次执行 `./config.sh` 命令,然后再尝试安装和启动运行程序。
步骤 5:退出虚拟机
exit
步骤 6:确认运行程序已连接成功
请访问 github.com/你的用户名/clearledger,然后进入“设置”→“动作”→“运行器”。
你应该会看到标记为clearledger-runner的运行器,其状态显示为空闲且带有绿色图标。打开该运行器的详细信息,确认其标签中确实包含self-hosted和clearledger这些关键字。
如果标签中缺少clearledger这个词,请在重新运行工作流之前将其添加到运行器设置中。仅使用运行器名称是不足以完成配置的。
仍然在GitHub的“设置”→“动作”→“运行器”页面,再次确认以下信息:
| 字段 | 预期值 |
|---|---|
| 状态 | 空闲(绿色显示) |
| 标签 | 必须包含self-hosted和clearledger |
| 操作系统 | Linux |
接下来,用你的笔记本电脑触发一次模拟运行:
git commit --allow-empty -m "测试:验证运行器能否正常接收任务"
git push
打开https://github.com/你的用户名/clearledger/actions页面。在30秒内,工作流应该会从“排队中”状态变为“进行中”,而不会停留在“等待运行器”的阶段。如果超过2分钟仍未有变化,说明标签设置有误,请在GitHub上编辑该运行器的配置并添加clearledger这个标签。
如果看到“离线”状态:
multipass exec clearledger -- sudo systemctl status actions-runner.*.service
multipass exec clearledger -- journalctl -u actionsrunner.*.service --lines=50
这说明什么:GitHub现在已经能够将任务发送到你的本地开发环境中了。
1.3:在GitHub上创建基础设施仓库
现在,我们需要将应用程序代码与部署配置信息分开存储。在第1.1节中,你已经将clearledger应用代码推送到了GitHub仓库中;现在需要再创建一个额外的仓库。
在后续的实验过程中,你会使用这两个仓库:
| 仓库名称 | 存储内容 | 负责维护人员 | 存在目的 |
|---|---|---|---|
clearledger |
应用程序源代码、Docker镜像文件、测试用例、.github/workflows/ci.yaml文件以及实验文档 |
你,作为开发人员 | 这里用于保存代码修改内容 |
clearledger-infra |
仅包含Kubernetes部署配置文件:deployment.yaml、service.yaml、Ingress配置文件及密钥模板 |
CI流水线以及ArgoCD系统会使用这些配置文件来维护集群状态 | 这个仓库用于确保集群始终处于预期的运行状态 |
clearledger看作是这样一个问题:“这个应用程序的具体功能是什么?”它涉及到Python服务、Dockerfile文件、测试脚本以及持续集成工作流程。而clearledger-infra则对应于另一个问题:“当前在Kubernetes环境中应该运行哪个具体版本的软件?”它涉及部署任务、服务配置、入口规则,以及那些指向Docker Hub的镜像标签。各团队会故意将这些文件分开存放。如果你修改了`clearledger`目录中的`README.md`文件,那只是对文档内容的修改,并不会触发任何部署操作。
而如果你修改了`auth-service`代码,那么构建流程会生成一个新的镜像(例如标签为`abc123`的镜像),只有当所有扫描都通过后,这个新镜像才会被记录到`clearledger-infra`目录中:
image: $DOCKER_USERNAME/clearledger-auth-service:abc123
这条配置其实就是一种部署规则:Git系统会指示集群“应该”运行标签为`abc123`的镜像。在阶段1,集群的实际运行状态并不会发生改变(这一点你可以在§1.6节中看到具体证明)。
到了阶段2,ArgoCD会监控`clearledger-infra`目录的内容,将Git中的配置与集群实际运行的状态进行对比,一旦发现差异就会自动更新集群配置。应用代码存储在主仓库中,而基础设施相关的配置则保存在`clearledger-infra`这个仓库中,后者代表了生产环境应有的配置状态。
私有仓库:哪些内容会在哪里进行同步
在这个实验环境中,我们使用了两个GitHub仓库。`clearledger`是你的主项目仓库,其中包含了应用代码、持续集成流程、文档、政策文件以及实验用例文件。这个仓库可以设置为私有的。
clearledger-infra仓库仅包含Kubernetes相关的配置文件。ArgoCD会监控这个仓库,并利用其中的配置来部署应用程序。对于初学者来说,建议将这个仓库设置为公共仓库,这样ArgoCD就可以无需额外认证就能读取其中的内容。
clearledger
应用代码 + 基础设施配置文件/
↓
持续集成流程复制基础设施配置文件/
↓
clearledger-infra
仅包含Kubernetes配置文件
↓
ArgoCD从该仓库中获取配置并更新集群
↓
Kubernetes集群
ArgoCD并不会读取主仓库`clearledger`中的内容,它只关注`clearledger-infra`仓库。如果`clearledger`是私有仓库,那也没关系;但如果是`clearledger-infra`是私有仓库,那么之后你必须为ArgoCD提供GitHub登录凭据。否则,ArgoCD可能会显示“ComparisonError”错误。
在GitHub上创建`clearledger-infra`仓库的步骤如下:
登录GitHub,然后点击“新建仓库”。
将仓库命名为`clearledger-infra`。
选择“公共仓库”选项。
不要为这个仓库添加`README.md`文件。
最后点击“创建”按钮。
Docker Hub凭证,用于上传图像。
GitHub凭证,用于将图像标签更新推送到
clearledger-infra仓库中。Cosign凭证,用于在上传图像后对其进行签名操作。
验证代码及相关镜像是否安全,可以放心发布。
使用新的镜像标签更新基础设施仓库。
构建阶段:使用命令`docker build -t clearledger-auth-service:a1b2c3d4…`来构建镜像
发布阶段:将镜像推送到Docker Hub,地址格式为`YOUR_DOCKERHUB_USERNAME/clearledger-auth-service:a1b2c3d4…`
更新配置文件:在`clearledger-infra`文件中执行命令`kustomize edit set image …:a1b2c3d4…`
提交日志:使用日志信息`ci: deploy a1b2c3d4… — all gates passed`来记录构建过程
Docker Hub上会出现新的图像文件
GitHub上的
clearledger-infra配置文件中会包含新的SHA值你的Kubernetes集群保持不变,仍然会运行阶段0时留下的配置
Gitleaks(Git中的敏感信息检测)
Semgrep(针对Python代码的静态安全分析工具)
Dockerfiles的Checkov检查
Trivy(用于修复图像中可修复的高危/严重漏洞的工具)
Grype对认证服务的SBOM检测(可修复的高风险漏洞)
对
clearledger-infra配置文件的更新操作(必须成功完成)Kubernetes配置文件的Checkov检查——在阶段4中通过Kyverno执行
Cosign签名验证与SLSA认证——在阶段4中执行,未通过的图像会被拒绝
Syft工具生成的SBOM信息——用于供应链安全检测。在阶段3中,你会故意让一些检查步骤失败
ArgoCD的更新操作——在阶段2中执行(需设置
ENABLE_ARGOCDSYNC=true)DAST/ZAP工具的安全检测——在阶段3中执行(需设置
ENABLE_DAST=true)Docker登录成功
每个服务对应的镜像都已构建完毕,并被推送到了Docker Hub上
已检出
clearledger-infra仓库部署配置文件中的SHA标签已更新为新的值
更改后的代码已被推回
clearledger-infra仓库持续集成系统会将你的笔记本电脑从构建流程中移除。这样,构建过程就能变得可重复、透明,并且能与Git提交紧密关联。
“运行器”其实是负责执行具体任务的工具,而不是整个管道系统本身。GitHub负责安排任务,而你自己托管的运行器会在你的虚拟机内部执行这些任务。
“构建成果”与“目标状态”是不同的概念。Docker Hub用于存储构建好的镜像,而GitHub上的
clearledger-infra仓库则保存了指示应该使用哪个镜像的Kubernetes配置文件。优秀的管道系统不会偷偷改变集群的状态。这个管道系统只会更新Git代码库,而不会直接执行
kubectl命令。目前还存在一个问题:虽然基础设施仓库已经发生了变化,但集群本身并没有随之改变。因此仍需要有人手动应用这些变更。第二阶段会通过GitOps来解决这个问题。
check-1测试通过secretKeyRef这个配置项确实存在于代码库中OK(目前还没有Vault注释)clearledger-infra仓库中包含auth-service/secret.yaml和ledger-service/secret.yaml文件你自己托管的运行器目前处于空闲状态,并且被标记为
clearledgerENABLE_ARGOCD_SYNC这个配置项尚未被设置(安装ArgoCD后才会启用它)使用新的镜像标签更新
clearledger-infra目录通知ArgoCD去同步集群配置
有些Pod陷入了
CrashLoopBackOff状态或出现了错误。请使用kubectl get pods -n clearledger命令进行检查。执行
kubectl logs命令,可以查看那些之前并不存在的启动错误信息。-n clearledger --previous ArgoCD的健康状态会从
Healthy变为Degraded,或者一直保持在Progressing状态。请使用argocd app get clearledger --grpc-web命令进行检测。如果应用程序返回5xx错误代码,或者登录功能出现故障,请使用
curl -I http://clearledger.local/health命令进行检查。如果有错误的镜像标签或清单被推送到
clearledger-infra仓库中,且你还有几分钟时间处理的话如果infra仓库中的配置变更导致Pod出现故障
在这种情况下,方法1几乎总是正确的选择。它操作快速、安全,而且能够留下清晰的审计痕迹。
如果集群当前处于故障状态,用户正在受到影响,并且你需要在30秒内让集群恢复稳定
如果你还不确定是哪个提交导致了问题,需要时间进行调查:先使用回滚操作使集群恢复稳定,然后通过
git log找出问题的根源,之后再使用方法1进行修复GitOps的含义:Git是所有配置信息的唯一来源,而ArgoCD则是用来确保这些配置信息得到正确应用的工具
ArgoCD的功能:它会监控Git仓库中的变更,并将这些变更与应用环境进行对比;一旦发现差异,就会自动进行修复
当前整个流程的工作方式:首先将代码推送到Git仓库中,接着通过CI工具生成镜像,然后更新infra仓库的配置,最后ArgoCD会同步应用环境
现在已经没有人需要手动运行
kubectl>来执行部署操作了。整个流程都是由自动化工具完成的如何安全地进行回滚:在infra仓库中使用
git revert>是正确的处理方式,而ArgoCD的紧急回滚功能则是在极端情况下的最后手段。在使用这些功能之前,必须先禁用自动同步功能,否则系统会自动撤销之前的更改。执行`make check-2`命令后,测试会通过。在GitHub上将`ENABLE_ARGOCDSYNC=true`设置为启用状态(你可以在第2阶段进行这个设置)。
目前`ENABLE_DAST`仍处于未启用状态;如果你需要,可以在本阶段的后续步骤中将其开启。
在
http://argocd.local访问Argo CD时,会显示“已同步”状态。可选:可以阅读第1阶段的安全配置要求。第1阶段已经运行了许多相关的工具。
Gitleaks:用于检测代码或Git历史记录中的敏感信息(如API密钥、令牌等)。
Semgrep (SAST):用于查找Python/JS源代码中的安全漏洞(例如注入攻击、不安全的编程模式等)。
Trivy (SCA + images):能够检测Python/Node.js包以及Docker镜像中已知的安全漏洞。CVE(通用漏洞与暴露)是指那些被公开记录的软件安全缺陷,它们都有唯一的标识符。
Checkov (IaC):用于检查Dockerfile、Kubernetes配置文件以及Terraform配置中的错误设置。
Cosign:用于验证这些镜像确实是由你的开发流程构建并签名的。
软件包名称
CVE编号
已安装的版本号
已修复的版本号
你也可以下载相关的检测结果文件。
对于使用pip包的情况:在
requirements.txt文件中将相关包的版本更新为已修复的版本(例如:针对CVE-2026-53539,应将python-multipart==0.0.30更新为相应版本)。如果其他依赖服务也使用了相同的包版本,也需要进行同样的更新。对于操作系统级别的包:可以在Dockerfile中使用更新的基镜像,或者执行针对性的
apt/apk升级操作。如果目前还没有稳定的修复方案:只需将相关安全漏洞添加到
.trivyignore和.grype.yaml>文件中,并附上相应的注释即可(参见案例)。 Gitleaks:
Secrets ScanSemgrep:
SASTTrivy:
Scan imagesCheckov:在日志或构建结果中查找
CKV_*相关内容。需要注意的是,某些情况下系统可能会显示“绿色”状态,但这并不表示检测没有问题。禁用清理任务:旧版本的Kyverno会使用
bitnami/kubectl工具,但这个工具已经从Docker Hub上移除,因此在使用旧版本时,清理Pod时会遇到ImagePullBackOff错误。将Helm钩子指向
bitnamilegacy/kubectl,这样在将来卸载Kyverno时,就不会因为找不到相应的镜像而出现问题。延长存活检查的超时时间:默认的配置
timeoutSeconds: 5, failureThreshold: 2对于负载较重的单节点虚拟机来说过于严格。在CPU压力较大的情况下,健康检查端点的响应时间可能会超过5秒,从而导致重启循环,使整个节点陷入瘫痪,进而使得API服务器无法正常使用。通过将配置值修改为timeoutSeconds: 30, failureThreshold: 5,可以确保Kyverno在面对高负载时不会崩溃。disallow-privilege-escalation表示该 Pod 未设置allowPrivilegeEscalation: falsedrop-all-capabilities表示该 Pod 未使用capabilities.drop: [ALL]来禁用 Linux 的某些功能require-resource-limits表示该 Pod 未设置 CPU 和内存的使用请求/限制值请注意,webhook的名称是
mutate.kyverno.svc-fail,而不是validate:在Pod被允许进入集群之前,Kyverno会先通过mutate流程对图像进行验证(包括摘要计算和签名检查)。未找到任何签名这一提示表示Kyverno已经访问了Docker Hub,找到了相应的图像,但确认该图像并未使用您的infra/cosign.pub密钥进行签名。如果Pod根本就不存在,那么攻击者即使能够拉取到该图像,也无法获取其shell权限。
范围要严格限定:只针对确实需要这种例外的资源进行设置,不得超出这个范围。
需通过Git进行管理:任何例外情况都应通过拉取请求进行审核,并记录在版本历史中,以便后续审计。
绝不能削弱原有的规则:对于其他所有资源,规则仍然必须保持严格性。
要定期重新评估:例外情况应尽可能具有临时性,并且需要按照固定的时间表进行重新评估。
CI扫描(在代码合并之前进行)与准入控制(在集群层实施)之间的区别
Kyverno的作用:它是一种策略引擎,能够拦截所有Kubernetes API请求
“强制执行”意味着不良资源根本就不会被创建,而不是“事后才被发现”
如何解读Kyverno的拒绝响应:策略名称 → 规则名称 → 发生故障的JSON路径
如何使用YAML格式编写并应用全集群范围的安全策略
如何在不影响其他用户的情况下限定特定规则的适用范围
运营相关问题(如Helm配置、镜像下载、注册表URL格式等)会影响控制措施是否真正生效
为什么同时需要CI扫描和准入控制:CI扫描用于检测代码中的问题,而Kyverno则能拦截所有与集群相关的操作
事实胜于空谈:仅将策略文件保存在Git中是毫无意义的;只有当这些策略真正被执行时,才能证明控制措施是有效的
§5.1:将
stages/stage-5-secrets-management/.env.example文件复制到.env文件中,然后填写上您的集群密码。§5.2:使用Helm工具安装Vault及相应的代理注入器。
§5.3:运行
setup.sh命令,然后再运行seed-vault-secrets.sh命令(此时密码已经保存在Vault中了)。§5.4:将启用了Vault功能的部署配置推送到
clearledger-infra目录中,然后让ArgoCD进行同步。§5.5:等待2/2个Pod(应用程序Pod加上辅助容器Pod)启动完成,之后再删除原来的Kubernetes Secret对象。
§5.5b:确认ArgoCD已经完成了同步操作,并且显示“状态正常”。
§5.6:验证登录功能是否正常,同时确认密码和令牌确实保存在Pod内的
/vault/secrets/目录下。删除与应用程序密钥相关的条目:将以下这两行代码删除,或使用
#将其注释掉(这两种方法都可以,因为Kustomize会忽略以#开头的行):- auth-service/secret.yaml - ledger-service/secret.yaml添加以下这一行代码(与其他资源一起添加):
- vault/rotation-cronjob.yamlKubernetes Secrets并不足以满足真正的秘密管理需求。
现在, Vault开始用于存储应用程序的凭据了。
.env文件仅被用于在本地将初始凭据加载到Vault中,这些数据从未被提交到版本控制系统中。当应用程序启动时,Vault会将其所需的凭据注入到Pod中。
必须停止使用
clearledger-infra工具来存储secret.yaml文件,因为ArgoCD正是从该仓库中进行部署的。操作顺序非常重要:先安装Vault,然后生成初始凭据,接着更新GitOps配置,等待Pod正常运行,最后删除旧的Kubernetes Secrets文件。
执行
make check-5命令后测试结果为通过。在
http://clearledger.local上,登录操作和相关交易功能依然可以正常使用。平台相关的Pod的重启次数应该处于较低水平。
你至少触发了一次Falco警报。
你已经应用了相应的网络策略。
执行
make check-6命令后测试结果为通过。§6.1: 运行命令 `
bash stages/stage-6-runtime-security/scripts/install-falco.sh`。确认 `falco-*` 相关Pod处于“运行中”状态,并且自定义规则已成功加载。§6.2: 执行命令 `
make demo-6`,在终端中会看到提示信息“✓ 运行时检测已完成”。§6.3(可选):如果步骤 `make demo-6` 已经成功完成,可以跳过此步骤,手动创建故障场景进行测试。
§6.4: 运行命令 `
kubectl apply -f infra/deferred-by-stage/stage-6-runtime-security/netpol/network-policies.yaml`,确认访问地址 `http://clearledger.local/` 时返回状态码200。§6.6: 执行命令 `
make check-6`。安装Falco插件
触发一次测试警报
应用网络策略
用户名:`admin`
密码:`admin`
规则:表示检测到的事件名称。
优先级:分为Critical、Warning和Notice;在本实验中,主要关注Critical和Warning等级的事件。
输出信息
标签:在实验室生成的警报中,可以查找
clearledger这个标签。
之后,持续集成流程会自动更新`clearledger-infra`仓库的内容。在阶段1,该流程不会执行`kubectl apply`命令,而是直接更新Git仓库中的配置;到了阶段2,ArgoCD会读取这些更新后的配置并应用到集群中。
在推送代码之前:请在Kustomize配置文件中设置你的Docker Hub用户名(镜像标签是在这里确定的,而不是在部署YAML文件中设置的):
# 将“YOUR_DOCKERHUB_USERNAME”替换为与§0.3节中 `$DOCKER_USERNAME` 相同的值
sed -i.bak "s/YOUR_DOCKERHUB_USERNAME/${DOCKER_USERNAME}/g" infra/manifests/kustomization.yaml
rm -f infra/manifests/kustomization.yaml.bak
只需将位于 `infra/manifests/ 目录下的 Kubernetes 配置文件推送到 GitHub,而不要推送 `infra/` 目录下的所有文件:
mkdir -p /tmp/clearledger-infra
cp -r infra/manifests /tmp/clearledger-infra/
cd /tmp/clearledger-infra
git init
git remote add origin https://github.com/YOUR_USERNAME/clearledger-infra.git
git add . && git commit -m "feat: initial manifests" && git push -u origin main
cd -
✋ 实践检查点:确保 GitHub 上的 `infra` 仓库已设置完成(在开始步骤 §1.4 之前请执行此操作)
在您的笔记本电脑上运行以下命令:
grep "docker.io/${DOCKER_USERNAME}/" infra/manifests/kustomization.yaml | wc -l
grep YOUR_DOCKERHUB_USERNAME infra/manifests/kustomization.yaml || echo "OK: placeholder replaced"
预期结果:第一个命令应输出 `4`(表示有 4 行与 Docker Hub 用户名相关的配置内容);第二个命令应输出 `OK: placeholder replaced`,而不会仍然显示 `YOUR_DOCKERHUB_USERNAME` 这一字符串。
在浏览器中访问 `https://github.com/YOUR_USERNAME/clearledger-infra/tree/main/manifests`,并亲自确认以下文件是否存在:
| 文件/文件夹名称 | 是否必须存在 |
|---|---|
kustomization.yaml |
必须存在。打开该文件后会发现,其中使用的 Docker Hub 用户名是 “your”。 |
auth-service/secret.yaml |
必须存在。在阶段 2 至 4 中需要使用这个文件,直到阶段 5 才不再需要。 |
ledger-service/secret.yaml |
必须存在。 |
auth-service/deployment.yaml |
必须存在。打开该文件后会发现其中包含 `secretKeyRef`,但不会包含 `vault.hashicorp.com` 这一字符串。 |
netpol/ |
不需要存在。如果这个文件夹存在于 GitHub 上,请在阶段 2 之前将其删除。 |
vault/ |
不需要存在。Vault 的配置文件仅在阶段 5 中才会被使用。 |
哪些文件夹是重要的?您确实只将 `infra/manifests/` 目录下的文件推送到 GitHub,其他文件目前仍然保留在本地。
某些用于后续阶段的配置文件(如网络策略配置、Vault 相关设置等)保存在 `clearledger` 仓库的 `infra/deferred-by-stage/` 目录下。当到达相应阶段时,您需要手动将这些文件应用到系统中。请不要将这个文件夹复制到 `clearledger-infra` 中,否则 ArgoCD 会过早地执行部署操作。
您可能会注意到 `stages/stage-1-ci-pipeline/` 目录下没有这些配置文件。这是正常的:实验环境并不会在该目录中复制这些 YAML 文件。真正的配置文件副本位于本仓库的 `infra/manifests/` 目录下,而用于 GitOps 部署的实际文件则保存在 GitHub 上的 `clearledger-infra` 目录中。
您所验证的内容:Kubernetes 的配置文件现在有了自己的仓库和 Git 历史记录,这些内容与应用程序代码是分开存储的。每次构建完成后,CI 系统会更新 `clearledger-infra` 目录,而您的应用程序代码仓库则仍然用于保存代码文件和管道配置文件。
1.4:配置GitHub秘密信息
请访问github.com/你的用户名/clearledger,然后进入“设置”→“秘密与变量”→“操作”→“新建仓库秘密”。
该工作流程需要以下凭证来访问Docker Hub、GitHub以及进行图像签名操作:
**请不要**将这些凭证值直接粘贴到YAML文件中,而应将其保存为GitHub Actions的秘密信息。
秘密信息1:DOCKER_USERNAME
这仅仅是你的Docker Hub用户名而已。
示例:
veeno-demo
你可以在Docker Hub的“个人资料”菜单中查看并获取该用户名:hub.docker.com → “账户设置”。
秘密信息2:DOCKER_PASSWORD
这应该是一个Docker Hub的**访问令牌**,而不是你的普通密码。
你可以在这里创建访问令牌:
hub.docker.com
→ “账户设置”
→ “安全”选项
→ “新建访问令牌”
→ “描述”:clearledger-github-actions
→ “访问权限”:读取、写入、删除或读写
→ “生成令牌
请立即复制该令牌,因为Docker Hub只会显示它一次。
秘密信息3:INFRA_REPO_TOKEN
这是一个GitHub个人访问令牌(PAT)。该令牌用于将提交内容推送到clearledger-infra仓库中。
你可以在这里创建这个令牌:
GitHub个人资料设置
→ “设置”
→ “开发者设置”
→ “个人访问令牌”
→ “令牌(经典类型)”
→ 点击“生成新令牌”
→ 选择“生成新令牌(经典类型)”
→ 如果GitHub要求输入密码或进行双重身份验证,请完成相应操作
→ “备注”:clearledger-infra-ci
→ “有效期”:请选择一个适合实验环境的期限
→ “权限范围”:选择“仓库”,这样该令牌才能用于将提交内容推送到clearledger-infra仓库中
→ “生成令牌
请立即复制该令牌,因为GitHub只会显示它一次。
对于这个实验来说,选择“仓库”作为权限范围是最简单的选项。在实际生产环境中,你应该使用更严格的权限设置,例如仅允许在clearledger-infra仓库中使用的令牌。
秘密信息4和5:COSIGN_PRIVATE_KEY与COSIGN_PASSWORD
Cosign会在管道将容器镜像推送至Docker Hub之后对这些镜像进行签名处理。后续,在第4阶段中,Kyverno会使用这一公钥来验证这些镜像确实来自你信任的管道。
请在宿主机上生成密钥对,而不是在Multipass虚拟机内部生成:
# macOS: 使用brew安装cosign
# Linux/WSL2: 使用curl命令下载并安装cosign
curl -sSL -o cosign https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 && chmod +x cosign && sudo mv cosign /usr/local/bin/
cosign generate-key-pair
这样会生成以下两个文件:
cosign.key # 私钥——切勿将此文件提交到代码仓库中
cosign.pub # 公钥——用于后续的Kyverno验证
当Cosign要求输入密码时,请输入一个密码并将其保存在密码管理工具中。如果你之前生成的密钥没有设置密码,那么请为本次实验重新生成一个带密码的密钥对。
请将以下五个秘钥添加到clearledger仓库中,而不是clearledger-infra仓库中:
| 秘钥名称 | 值 | 用途 |
|---|---|---|
DOCKER_USERNAME |
你的Docker Hub用户名 | 管道登录时使用的用户名 |
DOCKER_PASSWORD |
你的Docker Hub访问令牌 | 管道与Docker Hub进行身份验证时使用的凭证 |
INFRA_REPO_TOKEN |
之前生成的GitHub PAT令牌 | 管道用于将镜像标签更新推送到clearledger-infra仓库中 |
COSIGN_PRIVATE_KEY |
cosign.key文件中的内容 |
管道用于对推送的容器镜像进行签名处理 |
COSIGN_PASSWORD |
生成Cosign密钥时使用的密码 | 用于在签名过程中解锁私钥 |
仓库变量(并非秘钥)(可选),可在后续阶段进行配置。请将它们添加到设置 > 秘钥与变量 > 动作 > 变量选项中:
| 变量名称 | 适用阶段 | >何时启用该变量 |
|---|---|---|
ENABLE_ARGOCDSYNC |
保持未设置状态 | 第2阶段——在ArgoCD完成首次同步且验证通过后启用 |
ENABLE_DAST |
保持未设置状态 | 第3阶段——在应用程序在clearledger.local上正式上线后启用 |
请不要在第1阶段添加这两个变量。如果你现在就设置它们,CI系统会在集群尚未准备好之前尝试更新ArgoCD或运行ZAP工具,从而导致管道输出结果难以理解。指南中明确指出了每个变量的具体启用时机——你只需要记住这两个变量确实存在即可。
你所证明的内容:该管道能够在不将凭证硬编码到仓库中的情况下,与外部系统进行身份验证。
1.5:在激活管道之前先了解其工作原理
不要把工作流文件视为某种具有神奇功能的工具。在运行它之前,请先打开.github/workflows/ci.yaml文件并仔细阅读其中的内容。
该管道承担着两项主要职责:
下面是具体的安全处理流程:
开发者将代码推送到 GitHub
↓
GitHub Actions 启动工作流
↓
位于 Multipass 虚拟机中的自托管运行器接收到任务并开始执行
↓
1. 扫描敏感信息(防止泄露)
↓
2> 同时进行代码安全扫描(Semgrep)和基础设施配置检查(Checkov)
↓
3> 准备扫描工具(安装 Trivy/Syft/Grype/Cosign;更新 Trivy 数据库)
↓
4> 构建镜像:使用 docker build 命令构建所有服务对应的镜像(仅使用本地标签,暂时不会上传到 Docker Hub)
↓
5> 扫描镜像:对所有镜像进行安全检查;对认证服务使用 Syft + Grype 进行供应链安全分析,并上传扫描结果
↓
6> 发布镜像:将构建好的镜像推送到 Docker Hub,并使用 Cosign 进行签名验证(仅在扫描通过后执行此步骤)
↓
7> 更新清单文件:将新的镜像标签提交到 infrastructure 仓库中
构建、扫描、发布(采用生产环境级别的流程控制机制)
在实际开发中,团队从来不会先提交代码再进行扫描。在.github/workflows/ci.yaml文件中,该管道将这三个步骤分解为三个独立的任务来执行:
| 任务名称 | 功能描述 | 如果失败会怎样…… |
|---|---|---|
build-images |
使用 docker build 命令构建所有服务对应的镜像,标签为 ${{ github.sha }} |
这样就不会污染注册库,且镜像也不会离开运行环境 |
scan-images |
使用 Trivy 对所有镜像进行安全扫描;对认证服务额外使用 Syft + Grype 进行供应链安全分析 | 如果扫描未通过,发布步骤将被跳过,因此不良镜像永远不会上传到 Docker Hub |
publish-images |
执行 scripts/ci-publish-image.sh 脚本,完成镜像的推送及 Cosign 签名操作 |
仅在扫描通过后才会执行此步骤 |
你绝对不能在提交代码之前自行运行 scripts/ci-publish-image.sh 脚本。GitHub Actions 会自动从仓库中获取代码,并在 publish-images 任务中执行该脚本。
为什么build-images 和 scan-images 可以是独立的任务呢?因为这两个任务都是在 GitHub 托管的运行环境中执行的,它们并不共享任何存储资源。而在你自己的自托管运行环境中,这三个任务都是在同一台 Multipass 虚拟机上运行的,并且使用的是同一个 Docker 引擎。
任务1会运行`docker build`命令,并将生成的镜像留在本地机器上。任务2会使用Trivy工具对这些本地镜像进行扫描,但不会进行任何上传或下载操作。只有当扫描结果合格时,任务3才会将这些镜像推送到Docker Hub。
这种设置非常实用:只需要一台安装了Docker的构建服务器,就相当于现实环境中专门用于持续集成工作的机器。而在第8阶段(AWS环境)中,管道会使用由GitHub托管的运行器来执行相关任务。在这种情况下,`build-images`命令会将生成的镜像保存为文件(格式为`images.tar`),然后将该文件作为工作流输出结果传递给下一阶段的任务,因为这些运行器是临时使用的虚拟机,它们并不共享任何Docker缓存。
接下来就是GitOps流程的介入:
已生成的镜像现在存储在Docker Hub上
↓
运行器从GitHub下载clearledger-infra代码
↓
部署配置文件中的镜像标签会被更新
↓
运行器会将修改后的代码提交并推回GitHub
↓
第1阶段的任务到此结束
镜像标签与代码之间的关联关系如下:每次管道任务的执行都是由某个git提交触发的。GitHub会为该提交分配一个唯一的ID,即SHA值(例如`a1b2c3d4e5f6789…`这样的十六进制字符串)。工作流会将`IMAGE_TAG`设置为这个SHA值,并在所有相关环节中使用它:
如果生产环境使用的镜像地址是`YOUR_DOCKERHUB_USERNAME/clearledger-auth-service:a1b2c3d4`,那么你可以直接使用这个标签,在GitHub上快速找到生成该镜像的具体提交记录。这样就完全避免了猜测或担心`latest`标签是否发生了变化的问题。每个部署的镜像都对应着代码中的某个特定版本,因此进行回滚或调试会变得非常方便。
Kustomize占位符的使用
在`auth-service/deployment.yaml`文件中,实际上使用的是一个标签而不是镜像的直接地址:
image: clearledger/auth-service:gitops
这个标签并不存在于Docker Hub上,它的作用只是告诉Kustomize应该在哪里替换为实际的镜像地址。真实的镜像地址保存在`kustomization.yaml`文件中:
images:
- name: clearledger/auth-service # 与上面的标签匹配
newName: docker.io/YOUR_DOCKERHUB_USERNAME/clearledger-auth-service
newTag: abc123def456… # 真实的提交SHA值 — CI系统会自动写入这个值当ArgoCD进行部署时,kustomize build会将标签替换为完整的地址。
你需要在§1.3节中编辑一次kustomization.yamlnewName:字段中设置你的Docker Hub用户名。之后,每次代码提交成功时,持续集成系统都会自动为图像添加newTag:标签,你无需手动进行任何操作。
阶段1:持续集成系统更新GitHub,而非集群本身
在管道运行成功后,会有以下三个变化:
持续集成系统永远不会执行kubectl apply命令。它只会将更改提交到clearledger-infra配置文件中。这就是阶段1的全部内容:构建和扫描过程是自动化的,但部署步骤目前还不是自动化流程。阶段2会安装ArgoCD,它会读取clearledger-infra配置并自动更新集群。
Kubernetes Checkov在阶段1中会运行,但它不会阻止整个管道的运行。它只会将检测结果上传出来,让你能够了解后续需要进行的安全加固工作。在阶段4中,这些规则会通过Kyverno在集群层面得到执行。
所有作业都在你自托管的运行环境中执行(runs-on: [self-hosted, clearledger])。在阶段1中,ENABLE_ARGOCDSYNC和ENABLE_DAST这两个选项都是未启用的。具体何时启用这些选项,请参见§1.4节。
如果某个作业失败了,在编辑工作流程之前,请先参考docs/troubleshooting.md中的说明进行排查。
阶段1的安全防护机制:哪些步骤会阻止流程运行,哪些步骤会在后续阶段执行
需要注意的是,阶段1并不意味着安全防护完全关闭。有些检查步骤会立即阻止管道的运行,而另一些则会先收集证据,在后续阶段再采取行动。
目前会阻止管道运行的检查步骤包括:
目前会运行但不会阻止流程运行的检查步骤包括:
如果你忘记某个阶段具体负责检查什么内容,可以在这份指南中搜索“阶段1安全防护机制”,或者按照阶段顺序来理解:阶段3会故意让一些检查步骤失败,阶段4会将Checkov的检测结果与Kyverno关联起来,阶段5会将敏感信息从Git中移除,阶段6会添加运行时安全检测功能,阶段7则会添加监控面板。
在这些步骤之后,运行make check-3和make check-4,以确认加固措施已经生效。
设计意图: 第一步旨在证明:在不需要你手动操作Docker的情况下,CI系统能够完成构建、扫描、推送代码以及更新Git仓库的操作。后续步骤则将这些操作转化为实际的执行流程。此处所做的某些调整是经过慎重考虑的。
1.6:激活流水线
请在clearledger应用仓库中执行这些操作,而不是clearledger-infra仓库。
在§1.3阶段,我们仅使用Kubernetes配置文件创建了clearledger-infra仓库。该仓库没有.github/workflows/目录,也没有任何流水线配置。如果你的shell提示符显示的是clearledger-infra,或者你使用的路径是/tmp/clearledger-infra,那么说明你进入的位置是错误的。
cd /path/to/clearledger # 这是你 在§1.1阶段推送到的应用仓库
git remote -v # 显示的路径应该是.../clearledger.git,而不是clearledger-infra
ls .github/workflows/ci.yaml # 在提交代码之前,这个文件必须存在
流水线配置文件已经保存在.github/workflows/ci.yaml中。每当对clearledger仓库中的代码进行任何微小修改时,都可以在main分支上执行以下操作:
echo "# 流水线已激活 $(date)" >&>> README.md
git add README.md
git commit -m "ci: 激活GitHub Actions流水线"
git push origin main
你可以在https://github.com/YOUR_USERNAME/clearledger/actions页面上查看流水线的运行情况(请点击“Actions”选项卡,而不是“infra”仓库对应的选项卡)。
当流水线成功执行完毕后,它会自动更新clearledger-infra仓库。因此,在这一步中,你不需要手动将任何内容推送到clearledger-infra仓库中。
预期结果:大约8分钟后,所有任务的状态都会显示为“绿色”。
✓ 构建并扫描auth-service
✓ 构建并扫描ledger-service
✓ 构建并扫描notification-service
✓ 构建并扫描frontend
✓ 将更新后的配置文件推送至GitHub
DAST测试以及ArgoCD的刷新操作会显示为“已跳过”:这是正常现象,因为这两个功能在后续阶段才会被启用(详见§1.4节)。
注意:本次实验中使用了.gitleaksignore文件,因为某些用于演示目的的敏感信息已经存在于Git历史记录中。不过Gitleaks工具仍然可以正常运行;这个忽略文件仅用于屏蔽那些已知属于实验用途的代码片段。除非你确认这些数据确实是故意添加的测试用例,否则不要向该文件中添加任何新的内容。
请仔细查看各项任务的日志信息,而不仅仅是等待任务状态变为“绿色”:
当流水线成功执行完毕后,打开https://github.com/YOUR_USERNAME/clearledger-infra页面,检查部署配置文件。此时,镜像标签应该已经使用了最新的SHA值。
现在请检查集群状态:
kubectl get deployment auth-service -n clearledger \
-o jsonpath '{.spec.template.spec.containers[0].image}' &;& echo
你可能会看到旧的镜像版本,这是正常的。这是第一阶段最重要的学习内容:
GitHub管道执行成功。
Docker Hub上已经有了新的镜像。
clearledger-infra的镜像标签也发生了变化。
但是Kubernetes集群并没有自动更新这些配置。
这并不表示失败,这只是部署过程中出现的延迟。第一阶段实现了自动化构建过程,但目前还没有控制器在监控infra仓库中的变化。第二阶段会安装ArgoCD来解决这个问题。
1.7 — 实践检查点:验证第一阶段是否真正完成
不要仅仅依赖工作流程状态栏上的绿色标志,请亲自执行每一项检查:
1. infra仓库中仍然包含第二阶段所需的app secrets
打开https://github.com/YOUR_USERNAME/clearledger-infra/tree/main/manifests/auth-service,应该能够看到secret.yaml文件。
在您的笔记本电脑上执行以下命令:
git clone --depth 1 https://github.com/YOUR_USERNAME/clearledger-infra.git /tmp/verify-infra
grep secretKeyRef /tmp/verify-infra/manifests/auth-service/deployment.yaml
grep secret.yaml /tmp/verify-infra/manifests/kustomization.yaml
rm -rf /tmp/verify-infra
预期结果应该是:在deployment配置中能看到secretKeyRef;而kustomization配置文件中会列出auth-service/secret.yaml和ledger-service/secret.yaml文件。如果这些秘密信息缺失,请在进入第二阶段之前重新推送manifests文件,参见§1.3节的说明。
2. 通过CI流程更新了镜像标签
git clone --depth 1 https://github.com/YOUR_USERNAME/clearledger-infra.git /tmp/verify-infra
grep newTag /tmp/verify-infra/manifests/kustomization.yaml
rm -rf /tmp/verify-infra
预期结果应该是:newTag是一个40个字符长的git SHA哈希值(或者是您的提交哈希值),而不会仍然是v0.1.0;除非您从§0.3阶段之后就没有再推送过任何代码。
3. Docker Hub已经为这个管道生成的镜像添加了签名
打开hub.docker.com,然后查找clearledger-auth-service,再进入Tags页面。最新的标签值应该与第2步中得到的SHA哈希值相匹配。
4. 集群状态未发生变化(这种部署延迟是故意设计的)
执行以下命令检查集群状态:
kubectl get deployment auth-service -n clearledger \
-o jsonpath '{.spec.template.spec.containers[0].image}' &;& echo
预期结果应该是仍然使用Stage 0阶段的标签值(例如veeno-demo/clearledger-auth-service:v0.1.0),而不是新的SHA哈希值。这证明CI流程并没有修改集群配置。
5. 运行器仍处于空闲状态
GitHub > 设置 > 操作 > 运行器 > clearledger-runner > 空闲。
make check-1
所有测试都通过了,接下来进入第二阶段。
你在第一阶段学到了什么
你现在可以在简历上写些什么,或者在面试中提到哪些内容:
我搭建了一个基于自己托管的GitHub Actions运行器的持续集成管道系统。每当有人向代码库提交更改时,这个系统都会自动构建容器镜像并将其推送上去;此外,它还能在任何任务开始执行之前,帮助排查可能出现的问题。
make snapshot STAGE=1 && make snapshots。确认clearledger.stage1的状态。请参阅如何保存你的进度。
第二阶段——使用ArgoCD实现GitOps
从这一阶段开始,Git就成为了主导力量。基础设施仓库中保存的配置信息就是实际应该被应用的设置。如果有人手动修改了集群的状态,ArgoCD会立即发现这种差异,并将其恢复到与Git代码库一致的状态。
目标:安装ArgoCD,让它监控clearledger-infra仓库,并将任何变更自动应用到集群中。持续集成管道系统只负责更新Git仓库,而不会直接与Kubernetes交互或执行kubectl命令。
下面是一个更简洁、易于理解的说明:
我准备好进入第二阶段了吗?
在继续之前,请先完成§1.6章节,然后运行以下命令:
make check-1
grep secretKeyRef infra/manifests/auth-service/deployment.yaml
grep vault.hashicorp infra/manifests/auth-service/deployment.yaml && echo "STOP: Vault注释存在" || echo "OK"
你应该会看到以下结果:
快速检查清单:
当执行 make check-2 命令后结果为成功,且http://argocd.local显示ArgoCD正在同步clearledger文件时,说明你已经完成了第二阶段的操作。
最后,请保存你的进度:
make snapshot STAGE=2
make snapshots
确认clearledger.stage2是否出现在快照列表中。
首先需要了解的内容
与第一阶段相比的变化:在第一阶段,CI系统已经负责构建镜像并更新clearledger-infra;而集群本身并不会发生任何变化,直到有人运行kubectl命令。第二阶段的目的就是填补这一空白。
执行者
任务内容
CI(第一阶段)
构建镜像 → 扫描镜像 → 推送镜像 → 更新clearledger-infra中的镜像标签
ArgoCD(第二阶段)
监控clearledger-infra的状态 → 应用配置文件 → 集群会根据Git指令进行相应操作
推送代码 → CI更新clearledger-infra → ArgoCD同步集群状态
预同步检查清单:在执行argocd app sync之前请先运行此检查清单
ArgoCD会应用clearledger-infra中保存的配置文件。如果配置内容有误,会导致相关Pod出现异常状态。因此,请重新运行§1.7节中的检查步骤,确认GitHub端上的配置信息仍然正确;之后再在本地设备上验证这些配置是否有效:
# 应用程序的配置文件必须指向你的基础设施仓库
grep repoURL stages/stage-2-gitops/argocd/clearledger-app.yaml
# 在ArgoCD接管之前,确保第0阶段的工作负载仍处于正常运行状态
kubectl get pods -n clearledger
curl -s -o /dev/null -w "%{http_code}" http://clearledger.local/auth/health
预期结果应该是:repoURL中包含你的GitHub用户名,所有应用程序Pod都处于Running状态,且curl命令的返回码为200。只有当这两个条件都满足时,才能继续安装ArgoCD并执行后续同步操作。
kubectl create namespace argocd 2>&/dev/null || true
kubectl apply -n argocd --server-side --force-conflicts -f \
https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/name=argocd-server -n argocd --timeout=180s
为什么需要使用--server-side --force-conflicts选项?因为Argo CD提供的applicationsets.argoproj.io CRD文件体积非常大。如果使用普通的kubectl apply>命令,尝试将整个配置文件存储在注释中时会遇到256 KiB的限制,从而导致错误。而使用--server-side --force-conflicts选项可以避免这个问题。事实上,Argo CD正是建议大家采用这种方式进行安装的。
获取管理员密码:
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d && echo
为你的NGINX入口配置Argo CD
浏览器会通过HTTPS与入口节点进行通信,而入口节点则会使用普通的HTTP协议与Argo CD服务器进行交互。如果没有这种配置,那么在尝试进行实时更新时,用户界面很可能会出现503错误或ERR_TOO_MANY_REDIRECTS错误,尤其是在访问/api/v1/stream/*这些路径时。
kubectl apply -f stages/stage-2-gitops/infra/argocd-cmd-params.yaml
kubectl apply -f stages/stage-2-gitops/infra/argocd-ingress.yaml
kubectl rollout restart deployment/argocd-server -n argocd
kubectl rollout status deployment/argocd-server -n argocd --timeout=180s
在argocd-cmd-params-cm文件中应该设置以下内容: server.insecure: "true", servergrpc.web: "true", server.url: https://argocd.local.
打开https://argocd.local,使用用户名admin和上面的密码进行登录。如果浏览器出现自签名证书的警告,请接受该警告。
预期效果:应该能够正常加载“应用程序”页面。在浏览器的控制台(按F12键)中,不应该出现401错误或ERR_HTTP2_PROTOCOL_ERROR错误。如果在普通窗口中查看界面也没有问题,那么说明配置已经完成,不需要使用隐私模式或无痕浏览模式。
如果登录失败,出现401 Unauthorized错误(通常是在配置发生变化后或之前的登录尝试失败后),可以尝试使用私密窗口或无痕浏览模式,或者清除argocd.local的相关缓存数据,然后再重新登录。如果问题仍然存在,请参考troubleshooting.md进行排查。
将Argo CD与你的基础设施仓库连接起来,并应用相应的应用程序配置文件:
1. 修改stages/stage-2-gitops/argocd/clearledger-app.yaml文件
将spec.source.repoURL设置为你的基础设施仓库地址(使用的是你的GitHub用户名,而不是git config user.name中设置的用户名)。
2. 将Argo CD与你的基础设施仓库连接起来:
这样就可以让Argo CD自动监控clearledger-infra仓库中的新提交内容。每当部署配置文件发生变化时,Argo CD都会自动更新集群环境。
# 在macOS系统中,可以使用brew安装Argo CD
argocd login argocd.local --username admin --password YOUR_PASSWORD --insecure --grpc-web
# 如果使用公共仓库
argocd repo add https://github.com/YOUR_USERNAME/clearledger-infra.git --grpc-web
# 如果使用私有仓库——请使用第1步中保存的PAT密钥
export INFRA_REPO_TOKEN='ghp_...' # 将此密钥粘贴到这里;GitHub在创建仓库时只会显示一次这个密钥
argocd repo add https://github.com/YOUR_USERNAME/clearledger-infra.git \
--username git --password "$INFRA_REPO_TOKEN" --grpc-web
确认Argo CD能够访问该仓库(在应用相关配置之前请先进行此操作):
argocd repo list --grpc-web
查找名为clearledger-infra的仓库,确认其类型为git,且连接操作能够成功完成。如果显示“失败”或找不到该仓库,说明Argo CD无法进行同步操作。请在进入第4阶段或任何依赖GitOps的阶段之前,先修复相关配置问题。
在恢复虚拟机或重新安装Argo CD之后,可能需要再次运行argocd repo add命令(因为认证信息是存储在集群中,而不是Git仓库中)。
3. 应用配置并同步:
kubectl apply -f stages/stage-2-gitops/argocd/clearledger-app.yaml
argocd app sync clearledger --grpc-web
如何阅读Argo CD的用户界面
同步完成后,在树形视图中打开clearledger应用。顶部的三个图标几乎可以说明所有所需的信息:
应用状态:正常运行。Kubernetes认为这些工作负载正在正常运行,Pod们已经启动完毕(如果显示“正在启动中”,则说明它们仍在启动过程中)。
同步状态:已同步:集群中的数据与GitHub上clearledger-infra>仓库在所示提交版本下的内容是一致的(例如main (2c88aa1))。Git才是数据的真实来源,Argo CD只是根据这些数据进行了配置应用。
最后一次同步操作:成功完成:最近一次从Git仓库获取的配置信息被成功应用到了集群中。如果这次同步失败,请点击相应选项查看具体错误原因。
下方的资源树将同一个应用分解成了多个部分:命名空间、秘密信息、服务、部署配置、Ingress规则等等。绿色标记表示这些配置是从Git仓库中获取并应用的。点击任意一个项目(例如deploy/auth-service),然后比较实际运行中的配置与期望的配置,就能了解Argo CD认为应该运行哪些内容。
快速检测“应用是否真正可以正常使用?”的方法(无需通过Argo CD进行操作):
curl -s -o /dev/null -w "%{http_code}\n" http://clearledger.local/auth/health
如果返回代码为200,说明应用可以从端到端正常访问,而不仅仅是在Argo CD的用户界面中显示为“正常状态”。
当出现异常时:如果应用状态长时间显示为降级或正在启动中,同步状态会变为不同步,资源树中的相关项目也会变成红色。点击这些项目,然后选择事件或日志>选项,即可查看详细错误信息。Kubectl命令也能从终端中验证相同的信息。
确认Argo CD确实正在监控所有工作负载(而不仅仅是Ingress规则):
argocd app resources clearledger --grpc-web | grep Deployment
正常情况下,输出结果应该类似于以下内容:
apps Deployment clearledger auth-service 否
apps Deployment clearledger frontend 否
apps Deployment clearledger ledger-service 否
apps Deployment clearledger notification-service 否
apps Deployment clearledger redis 否
此命令会显示ArgoCD为ClearLedger管理的所有部署资源。你应该能看到auth-service、ledger-service、notification-service、frontend以及redis这些选项。这说明ArgoCD会从clearledger-infra/manifests目录中读取完整的kustomization.yaml文件,而不仅仅是其中的一个文件。
最后一列显示的是ORPHANED状态。如果该值为No,那就表示ArgoCD确认这个资源确实属于ClearLedger应用程序。只有当某个部署资源缺失,或者ArgoCD在用户界面中显示OutOfSync、Degraded等状态时,你才需要关注这些问题。
✋ 实践检查点:首先确保所有部署都已完成同步
请执行以下四个检查。正常的检查结果应该如下所示:
kubectl get pods -n clearledger
# 所有应用程序的Pod都已成功运行(postgres/redis节点可能会显示因虚拟机重启而导致的重新启动记录——这属于正常现象)
kubectl get application clearledger -n argocd \
-o jsonpath='sync={.status.sync.status} health={.status.health.status}{"\n"}'
# 同步状态:Synced;健康状态:Healthy
curl -s -o /dev/null -w "%{http_code}\n" http://clearledger.local/auth/health
# 返回代码:200,表示请求成功
kubectl logs -n clearledger deploy/auth-service --tail=5 2>/dev/null | head -3
# 日志内容示例:GET /health HTTP/1.1" 200 OK,表示请求正常响应
# 如果出现DATABASE_URL未设置的错误信息,则说明配置有误
如果以上四个检查都通过,那么就意味着第一阶段的同步工作已经完成。接下来,请继续启用持续集成功能,并让ArgoCD接管后续的部署流程,然后执行make check-2和make snapshot STAGE=2命令。
如何启用持续集成与ArgoCD的协作机制
在第一步中,管道系统虽然更新了clearledger-infra目录的内容,但并未对集群本身进行更新。这是有意为之的设计。
现在既然已经安装好了ArgoCD,就可以让管道系统在每次成功执行部署任务后自动通知ArgoCD去检查是否有新的变更发生。
在GitHub上,打开你的clearledger仓库,进入“设置”选项卡,然后选择“秘密与变量” > “动作” > “变量”,最后点击“新建仓库变量”。
添加以下变量:
名称
值
ENABLE_ARGOCD_SYNC
true
从现在开始,当管道状态显示为绿色时,它就会执行以下两项操作:
如果管道系统无法立即触发ArgoCD的执行,通常也不会有什么问题。因为ArgoCD本身也会每隔几分钟自动检查clearledger-infra目录的变化,所以最终还是会收到新的Git变更信息。
目前暂时不要设置ENABLE_DAST变量。这个选项可以在第三阶段、当应用程序在clearledger.local上运行稳定之后再启用。
如果ArgoCD的用户界面显示红色Pod状态或“进行中”提示(在截图之前请先阅读此内容)
这种情况属于常见的初始同步过程中的正常现象,并不表示安装过程中出现了故障。
为什么会在第二阶段出现这种问题
ArgoCD会同步clearledger-infra中的所有内容。在部署过程中,必须使用secretKeyRef(第二至第四阶段),而不能通过Vault来注入配置信息。如果你的基础设施仓库中还包含来自早期实验版本的Vault注释,那么在第五阶段之前,授权/账本相关功能都会出现“DATABASE_URL未设置”的错误。
网络策略属于第六阶段的配置内容。在主clearledger仓库中,这些策略存储在infra/deferred-by-stage/stage-6-runtime-security/netpol/目录下,而不是infra/manifests/目录里。因此,在第二阶段千万不要将这些网络策略复制到clearledger-infra仓库中。
如果你的GitHub上的clearledger-infra仓库中仍然包含来自早期实验版本的manifests/netpol/文件,ArgoCD会继续应用这些配置。不过这些策略使用了默认拒绝规则,因此会导致新创建的Pod出现DNS配置问题,从而导致其状态显示为红色的0/1,同时健康检查状态也会显示为“进行中”。
针对第二阶段的解决方法
需要执行两个步骤。仅仅在集群内部删除相关文件是不够的,因为ArgoCD会在下一次同步时从Git仓库重新获取这些配置信息。
第一步:从GitHub上的
clearledger-infra仓库中删除manifests/netpol/文件夹
删除该文件夹后,提交相应的变更请求:`chore: defer network policies to Stage 6`。
第二步:重新同步配置并重启相关服务
argocd app sync clearledger --grpc-web
kubectl delete networkpolicy -n clearledger --all # 在Git仓库中删除相关配置后,这个命令可以确保配置被彻底清除
kubectl rollout restart deployment/auth-service deployment/ledger-service -n clearledger
argocd app get clearledger --grpc-web | grep -E "Sync Status|Health Status"
网络策略会继续保存在clearledger仓库的infra/deferred-by-stage/目录下,直到你在第六阶段真正应用它们为止。
当确认一切正常后,可以继续下一步操作。
等ArgoCD完成同步后,在ArgoCD的用户界面中打开clearledger应用程序。你应该会看到绿色的“健康”状态标志以及“已同步”提示。该应用程序应该会引用你的clearledger-infra仓库,使用manifests目录路径,并将配置应用到clearledger命名空间中。
接着打开该应用程序的详细信息页面,资源树中应该会显示你所有的部署服务,且不应该有任何处于红色状态的资源。
你也可以在终端中验证这些配置是否正确:
argocd app get clearledger --grpc-web
检查是否显示“Sync Status: Synced”以及“Health Status: Healthy”。
ArgoCD出现不同步现象时该如何处理
正常情况下:持续集成工具会复制所有的配置文件并更新Kustomize标签,ArgoCD会在大约3分钟内自动完成同步。
如果10分钟后仍然出现不同步现象:
make fix-argocd
这个命令会重新将官方配置文件同步到clearledger-infra仓库中(同时保留Kustomize的SHA哈希值),重新应用相应的配置,并触发一次强制刷新。请不要直接使用`kubectl apply`命令来部署服务:应该让ArgoCD来完成自动同步过程。
kubectl annotate application clearledger -n argocd
argocd.argoproj.io/refresh=hard --overwrite
argocd app sync clearledger --grpc-web --prune
kubectl get application clearledger -n argocd -o jsonpath='sync={.status.sync.status} health={.status.health.status}{"\n"}'
截取该界面的截图:应用图标或资源树应该显示正常状态。这就是证明GitOps确实在运行的证据。
验证ArgoCD的自我修复功能
现在,我们来验证Git才是数据的真实来源。
在这个演示中,你将手动更改正在运行的集群配置,但不会修改Git代码。ArgoCD应该会检测到集群配置与clearledger-infra不匹配,然后自动将其恢复为正确状态。
在开始操作之前,请确保应用程序运行正常,并且ArgoCD正在管理相关的部署任务:
argocd app resources clearledger --grpc-web | grep Deployment
手动更改集群中auth-service镜像的版本:
# 仅手动更改集群中的镜像版本(Git代码保持不变)
kubectl set image deployment/auth-service \
auth-service=$DOCKER_USERNAME/clearledger-auth-service:fake-tag \
-n clearledger
检查ArgoCD的反应:
# ArgoCD应该在一两分钟内将状态标记改为“OutOfSync”
argocd app get clearledger --grpc-web | grep -E "Sync Status|Health Status"
等待ArgoCD完成自动修复。在这个演示过程中,由于使用了虚假的镜像标签,可能会导致短暂的拉取错误,但这属于预期现象。
# 等待ArgoCD完成自我修复过程(默认同步间隔约为3分钟)
sleep 180
确认镜像版本已经恢复为Git指定的正确值:
# 集群中的镜像版本应该再次与clearledger-infra匹配——因为Git代码从未被修改过
kubectl get deployment auth-service -n clearledger \
-o jsonpath '{.spec.template.spec.containers[0].image}'
如果镜像版本确实恢复了原样,那就说明ArgoCD的自我修复功能正常工作了。你虽然手动更改了集群配置,但ArgoCD最终还是将其恢复到了与clearledger-infra一致的状态。
这就是GitOps的核心理念:Git决定了哪些代码应该被执行,而ArgoCD则确保集群配置始终与Git代码保持同步。
make check-2
如何回滚错误的部署操作
你刚刚已经证明了ArgoCD能够自动撤销未经授权的集群配置更改。现在我们来考虑另一种情况:如果你自己推送了错误的代码怎么办?GitOps的回滚机制实际上就是一种Git操作。这一节将解释其原因,同时向你展示两种回滚方法,并让你在真正需要使用这些方法之前先进行练习。
如何判断是否需要回滚部署
如果在向clearledger-infra推送代码后的几分钟内出现以下症状,那就说明你可能推送了错误的代码:
如果这种情况发生在执行推送操作之后,应先进行回滚操作。等应用程序恢复稳定状态后,再调查导致问题的那个提交记录。
为什么ArgoCD的回滚功能并不只是一个按钮而已
ArgoCD的用户界面中确实提供了回滚按钮,同时也存在`argocd app rollback`这个命令。这两种方法都可以用来进行回滚操作,但只有当你真正理解了它们与`selfHeal`机制之间的交互关系时,这些方法才能发挥应有的作用。
你的应用程序(配置文件路径为`stages/stage-2-gitops/argocd/clearledger-app.yaml`)被设置为如下方式:
syncPolicy:
automated:
selfHeal: true
ArgoCD会确保集群状态与Git仓库中的代码保持一致。在这个实验环境中,Git仓库指的是`clearledger-infra`。
如果有人手动修改了集群配置,ArgoCD会将其视为异常情况,并自动将集群恢复到与Git代码相匹配的状态。
这一机制也会影响回滚操作的效果:虽然通过ArgoCD的用户界面进行回滚可以改变集群状态,但Git仓库中的代码并不会随之改变。如果`clearledger-infra`仍然指向有问题的版本,那么`selfHeal`机制反而会恢复那个有问题版本的代码。
因此,更安全的做法是使用`git revert`命令直接修改`clearledger-infra`仓库中的代码,然后再让ArgoCD将集群状态同步到正确的版本上。
如果你需要紧急进行回滚操作,可以先关闭自动同步功能,然后通过ArgoCD执行回滚操作,之后再手动更新Git仓库中的代码。
方法1:使用`git revert`(推荐首选方法)
这就是GitOps的操作流程:你不需要直接修改集群配置,只需更改Git仓库中的代码,ArgoCD会自动将集群状态同步到正确的版本上。
适用场景:当你有足够的时间来定位`clearledger-infra`仓库中导致问题的那个提交记录时,可以使用这种方法。
操作步骤:
有人将有问题的代码推送到`clearledger-infra`仓库
↓
ArgoCD会自动同步这些更改,但此时集群状态已经出错
↓>
你执行以下命令:`git revert <问题提交记录> && git push`
↓
ArgoCD会再次自动同步更改,此时集群状态就会恢复正常
↓>
Git历史记录中会同时显示问题代码的推送记录以及恢复操作的记录,从而形成完整的审计轨迹
详细操作步骤:
# 1. 进入你的`clearledger-infra`仓库目录
cd ~/clearledger-infra # 如果你将仓库克隆到了其他位置,请调整路径
git pull # 确保你的代码是最新的
# 2. 查找导致问题的那个提交记录
git log --oneline -10
# 输出示例:
# abc1234:将ledger-service的镜像版本更新为v1.4.0 ← 这个操作导致了问题
# def5678:将auth-service的镜像版本更新为v1.3.1 ← 此前运行正常
# 9a1b2c3:添加了用于轮换vault配置的cronjob
# 3. 使用`git revert`命令撤销之前的更改
git revert abc1234 --no-edit
# 4. 将更改推送到仓库
git push
# 5> 确认集群状态已经恢复
kubectl get pods -n clearledger
argocd app get clearledger --grpc-web | grep -E "Sync Status|Health Status"
# 预期结果:Sync Status: Synced, Health Status: Healthy这种方法更为优选,因为它能够确定问题的根源:即 `clearledger-infra` 这个配置项。
当你执行回滚操作后,ArgoCD会检测到新的 Git 状态,并将集群同步到该状态。由于 Git 和集群的状态本就应该是一致的,因此不会出现任何冲突。
此外,这种操作还能留下清晰的历史记录。Git 可以显示哪些部署操作出现了问题、何时进行了回滚操作,以及这些操作是由谁执行的。这样一来,调试问题会变得更加容易,审查过程也会更加便捷,同时也能更好地满足合规性要求。
方法 2:紧急情况下使用 ArgoCD 进行回滚(当集群出现严重问题时)
如果当前集群已经出现了故障,而你没有时间通过 Git 来修复问题,那么就可以使用这种方法。它会立即将集群恢复到之前已知正常的状态。之后你仍然需要对 Git 代码进行修改才能彻底解决问题,但这并不是一种永久性的解决方案。
适用场景:当前正在发生故障,Pods 正在崩溃,用户正在受到影响,而你需要在 30 秒内将集群恢复到已知正常的状态。
操作前的准备:请确认你的 ArgoCD CLI 会话仍然有效。如果会话已经过期,请先重新登录——过期的会话会导致后续的所有命令都失败。
argocd account get-user-info --grpc-web # 如果看到“Unauthenticated”提示,说明需要重新登录: ARGOCD_PASSWORD=$(kubectl -n argocd get secret argocd-initial-admin-secret \ -o jsonpath="{.data.password}" | base64 -d) argocd login argocd.local --username admin --password "$ARGOCD_PASSWORD" \ --insecure --grpc-web
步骤 1:禁用自动同步功能(此步骤非常重要)。如果跳过这一步,系统会在 3 分钟后自动撤销之前的回滚操作。
argocd app set clearledger --sync-policy none --grpc-web
# 确认:自动同步功能现已被禁用
argocd app get clearledger --grpc-web | grep "Sync Policy"
# 预期输出:Sync Policy:
步骤 2:查找上一个已知正常的部署版本 ID
argocd app history clearledger --grpc-web
# 输出结果示例:
# ID DATE REVISION
# 9 2026-06-05 10:12:00 +0000 UTC abc1234 ← 有问题的部署版本(当前状态)
# 8 2026-06-04 14:46:06 +0000 UTC def5678 ← 已知正常的部署版本
# 7 2026-06-01 20:53:19 +0000 UTC 9a1b2c3
# 或者也可以通过 kubectl 来查询(无需使用 ArgoCD CLI):
kubectl get application clearledger -n argocd \
-o jsonpath="{range .status.history[*]}{.id}{"\t"}{.deployedAt}{"\t"}{.revision}{"\n"}{end}'
请使用部署版本的 ID(左侧的那个数字),而不是它的 SHA 值。
步骤 3:将集群恢复到已知正常的版本 ID
argocd app rollback clearledger 8 --grpc-web
步骤 4:确认集群已经稳定下来
kubectl get pods -n clearledger
# 所有的 Pod 都应该处于运行状态
argocd app get clearledger --grpc-web | grep -E "Sync Status|Health Status"
# Sync Status: OutOfSync ← 预期结果——集群当前使用的是版本 8,而 Git 中仍然保存着有问题的版本
# Health Status: Healthy ← 这才是目前最需要关注的信息OutOfSync 这种状态是正常的,也是预期之中的。当前集群仍在使用旧的版本;而 Git 中仍然保留着那个有问题的提交记录。你接下来会解决这个问题。
步骤5:修复 Git 中的问题(切勿让这种情况持续下去)
cd ~/clearledger-infra
git pull
git revert --no-edit
git push
步骤6:重新启用自动同步功能
argocd app set clearledger \
--sync-policy automated \
--self-heal \
--auto-prune \
--grpc-web
# 立即触发同步,以免等待下一次自动检查
argocd app sync clearledger --grpc-web
# 确认一切恢复正常
argocd app get clearledger --grpc-web | grep -E "Sync Status|Health Status"
# 预期结果:Sync Status: Synced, Health Status: Healthy
在任何情况下,都不要让自动同步功能处于禁用状态的时间超过必要的最长时间。这个功能是用于检测系统异常以及提供篡改证据的:如果没有它,未经授权的 kubectl 操作就很难被发现。在完成 Git 代码的修复并推送之后,必须立即重新启用自动同步功能。
现在就在压力未产生的情况下练习如何执行回滚操作吧
千万不要等到真正发生问题时才第一次尝试这个流程。下面的步骤会模拟一种错误的镜像标签部署情况,并引导你按照方法1来操作(这是推荐的做法)。
步骤1:向clearledger-infra 目录推送一个错误的镜像标签
cd ~/clearledger-infra
git pull
# 修改 manifestsnotification-service/deployment.yaml 文件中的镜像标签值,使用一个不存在的标签,例如:
# image: docker.io/$DOCKER_USERNAME/clearledger-notification-service:broken-tag
# 提交更改并推送代码
git add manifests Notification-service/deployment.yaml
git commit -m "测试:使用不存在的镜像标签来模拟错误的部署操作"
git push
步骤2:观察 ArgoCD 如何处理这种错误状态
# 给 ArgoCD 大约3分钟的时间来识别这个问题,或者直接手动触发同步:
argocd app sync clearledger --grpc-web
# 观察 notification-service 这个 Pod 的状态变化
kubectl get pods -n clearledger -w
# 你会看到:notification-service Pod 处于 ImagePullBackOff 或 ErrImagePull 状态
步骤3:使用方法1来执行回滚操作
cd ~/clearledger-infra
# 回退那个有问题的提交记录
git revert HEAD --no-edit
git push
# ArgoCD 会自动触发同步过程,或者你可以手动再次执行同步:
argocd app sync clearledger --grpc-web
# 观察 Pod 的状态变化
kubectl get pods -n clearledger -w
# notification-service Pod 应该会恢复到正常运行状态
步骤4:进行验证
argocd app get clearledger --grpc-web | grep -E "Sync Status|Health Status"
# 预期结果:Sync Status: Synced, Health Status: Healthy
kubectl get pods -n clearledger
# 所有的 Pod都应该处于正常运行状态,且不应该出现 ImagePullBackOff 的情况
你现在已经完整地练习了一次回滚操作。执行 git revert 命令所生成的提交记录会永久保存在 infra 仓库的历史记录中,这实际上就是一次模拟性恢复操作的真实审计记录。
快速参考
在以下情况下使用方法1(git revert):
在以下情况下使用方法2(紧急ArgoCD回滚):
当某个Pod出现故障,但最近并没有任何内容被推送到infra仓库中时,这两种方法都不适用。这种情况下不属于回滚问题,应该检查kubectl logs的输出、Vault的连接状态以及网络配置。
在stages/stage-2-gitops/argocd/clearledger-app.yaml文件中设置revisionHistoryLimit: 10,意味着ArgoCD会保留最近10次部署记录,以便在紧急情况下进行回滚。如果你的发布频率较高,可以适当增加这个数值。
你在第二阶段学到了什么
你现在可以在简历中写上这些内容,或者在面试时提到:
我使用了ArgoCD实现了GitOps机制,从而确保了集群状态始终由Git仓库控制;同时系统还具备差异检测功能、自动同步机制,以及针对错误部署的回滚功能。
执行命令make snapshot STAGE=2 && make snapshots,然后确认clearledger.stage2的状态。有关更多信息,请参阅如何保存你的工作进度。
第三阶段——安全检查机制
每次代码推送都会触发安全检查。有些安全检查会立即阻止整个部署流程的继续,而另一些问题则可以在后续阶段进行处理并应用到集群中(第四阶段)。
学习目标:了解六种不同的安全扫描工具:它们各自检测什么内容、能发现哪些问题,以及如何解读扫描结果。在第三阶段,你会故意触发这些安全检查机制,这样当CI任务失败时,你就不会感到意外了。
你准备好进入第三阶段了吗?
完成条件: 当 `make check-3` 命令通过,并且你已经依次执行了所有的检查步骤(参见§3.4节)时,接下来就可以执行 `make snapshot STAGE=3` 以及 `make snapshots` 命令了。
首先需要了解的内容
仅仅使用一个工具是不够的。因为不同的扫描工具负责检测不同层面的安全问题:
目前,阻碍持续集成流程的主要问题包括敏感信息的泄露、代码中的安全漏洞、存在风险的镜像,以及生产环境中Dockerfile配置的问题。
有些安全问题需要在后续阶段才能被发现。例如,某些Kubernetes相关的问题只有在第1阶段才会被检测出来;这些信息能帮助你了解哪些地方还需要进一步加强安全性。到了第4阶段,Kyverno会将那些重要的安全规则转化为实际的集群执行机制,从而在不安全的工作负载运行之前就阻止它们的执行。
当你执行 `git commit` 操作时,笔记本电脑上的预提交钩子可以先进行扫描;而当你执行 `git push` 时,GitHub Actions也会在运行环境中再次进行扫描。这两种方式的目的都是为了在错误发生之前及时发现它们,从而避免浪费10分钟的时间来处理这些问题。不过,预提交钩子的安装是可选的,因为无论是否安装这些钩子,持续集成流程在接收到推送请求时都会自动执行扫描操作。
在第2阶段之后可以选择启用DAST
DAST(动态应用安全测试)会在 `http://clearledger.local` 上检测正在运行的应用程序。在第1阶段和第2阶段,我们故意没有启用这项功能:第1阶段根本不会将应用程序部署到集群中,而第2阶段的重点则是确保GitOps流程能够正常运行。
如果 `make check-2` 命令通过,并且执行 `curl http://clearledger.local/auth/health` 后得到的返回码是 `200`,那么你就可以启用DAST了:
登录GitHub,进入你的 `clearledger` 仓库,然后依次选择 设置 > 秘密与变量 > 动作 > 变量,接着点击 新建仓库变量:
| 名称 | 值 |
|---|---|
ENABLE_DAST |
true |
提交一个小的更改请求(或者重新运行`main`分支上的最后一个工作流程)。此时,应该会执行 DAST (OWASP ZAP + fintech API测试) 这项任务,而不会被跳过。如果ZAP扫描失败,那就说明确实存在需要处理的安全问题;如果在执行此步骤之前就被跳过了,那只是表示之前没有启用这项功能而已。
3.1:安装预提交钩子
# 在macOS系统上(使用Homebrew安装可以避免遇到PEP 668中提到的“外部管理环境”相关问题):
brew install pre-commit
# 在Linux/WSL2系统中:
# sudo apt install -y pre-commit
# 或者:python3 -m pip install --user pre-commit
pre-commit install
pre-commit run --all-files
如果某个钩子出现了故障,请先查看错误信息。在提交代码之前,确保Gitleaks和Ruff工具能够正常运行。某些与YAML或Terraform相关的钩子问题可能源于后期的文件处理阶段。如果遇到了这类问题,请继续按照相应的步骤进行操作,并参考troubleshooting.md来解决Gitleaks或CI扫描工具出现的问题。
在CI流程开始之前,先在当地测试这些钩子是否能够正确检测到敏感信息:
echo 'AWS_SECRET = "'$(printf '%s%s' 'AKIA' 'IOSFODNN7EXAMPLE')'"' > app/auth-service/main.py
git add app/auth-service/main.py && git commit -m "测试"
# Gitleaks会触发警报并阻止提交操作——请参见下方的“预期结果”
git restore --staged app/auth-service/main.py
git checkout app/auth-service/main.py
如果预提交钩子没有正常安装,那么那条伪造的AWS密钥就会永久地留在你的Git历史记录中(即使后来你删除了那行代码,Git也会保留该记录)。
如果你已经在第1阶段完成了Cosign配置,那么这部分操作就已经生效了。第3阶段并不需要重新生成密钥。请确认文件infra/cosign.pub是否存在,并且GitHub上已经保存了COSIGN_PRIVATE_KEY和COSIGN_PASSWORD。到了第4阶段,这些签名配置就会在集群层面上得到强制执行。
✋ 实践检查点:预提交钩子确实能够阻止敏感信息的泄露
有时候,虽然钩子已经安装好了,但并没有被正确配置,从而导致隐藏的故障。请验证这些钩子是否真的能够发挥作用:
echo 'AWS_SECRET='"$(printf '%s%s' 'AKIA' 'IOSFODNN7EXAMPLE')'" > leak-test.env
git add leak-test.env
pre-commit run --all-files; echo "exit=$?"
git reset leak-test.env > /dev/null; rm -f leak-test.env
预期结果:敏感信息扫描钩子应该会失败(此时exit=1),并且会标记文件leak-test.env为有问题。如果exit=0,说明你的钩子虽然安装了,但并没有起到任何作用:请重新运行命令pre-commit install,并确认文件.git/hooks/pre-commit确实存在。
如果你跳过了这个步骤,那么所有的提交操作都会在未经扫描的情况下直接通过审核,这样你就会误以为第3阶段已经为你提供了足够的保护,但实际上并没有。
3.2:生成Cosign密钥
如果你在第1阶段已经生成了Cosign密钥(参见§1.4节),那么可以直接跳过这个步骤,直接将文件cosign.pub添加到Kyverno策略中,并配置相应的GitHub敏感信息。
Cosign会使用加密密钥为你的Docker镜像添加签名。当你将这些镜像部署到集群中时,Kyverno(第4阶段)可以验证这些签名的有效性,从而拒绝任何未经正确签名的镜像。这样就可以防止有人将恶意镜像上传到Docker Hub,进而让集群执行这些恶意代码。
# macOS: 使用brew安装cosign
# Linux/WSL2: 使用curl下载并安装cosign
curl -O -L https://github.com/sigstore/cosign/releases/download/v2.2.4/cosign-linux-amd64 && chmod +x cosign-linux-amd64 && sudo mv cosign-linux-amd64 /usr/local/bin/cosign
cosign generate-key-pair # 根据提示输入密码
这会生成两个文件:cosign.key(私钥,用于流程中的签名操作)和cosign.pub(公钥,用于Kyverno进行验证)。
将你的公钥添加到Kyverno的政策配置中(用cosign.pub文件的内容替换infra/policies/require-signed-images.yaml中的占位符内容)。
在GitHub上添加这些密钥信息(访问github.com/YOUR_USERNAME/clearledger → 设置 → 密码与变量 → 操作):
| 密钥名称 | 密钥值 |
|---|---|
COSIGN_PRIVATE_KEY |
cosign.key文件的内容 |
COSIGN_PASSWORD |
生成密钥时输入的密码 |
✋ 实践检查点:Cosign密钥已准备就绪
在第4阶段,会使用cosign.pub来验证已签名的图像文件。在继续下一步之前,请确认这些密钥文件确实存在,并且私钥没有被Git跟踪:
test -f cosign.key &;&& echo "私钥存在"
test -f cosign.pub &&& echo "公钥存在"
grep -q "BEGIN PUBLIC KEY" cosign.pub && echo "公钥有效"
git check-ignore cosign.key && echo "私钥已被正确忽略"
预期结果:以上四条命令都应该有输出。
如果git check-ignore cosign.key没有输出任何信息,请在提交任何代码之前,将cosign.key添加到.gitignore文件中。私钥绝对不能被包含在Git仓库中。
请不要跳过这个检查步骤。第4阶段确实需要公钥来执行Kyverno的图像签名功能,而私钥必须保留在本地。
3.3:激活完整的安全性处理流程
所有的安全检测规则已经包含在.github/workflows/ci.yaml文件中了。只需推送任何更改,就能触发整个流程:
git add . && git commit -m "ci: 完整的DevSecOps流程" && git push origin main
3.4:故意触发每个安全检测环节
对于每一个安全检测环节,你都需要故意引发一些错误,然后查看工具给出的反馈信息,进行相应的恢复操作,确保系统能够再次正常运行。可以先在本地测试,如果需要的话,再把结果推送到GitHub Actions上。
# 1. 故意触发故障 2. 在本地执行测试或推送代码 3> 查看错误信息
# 4. git checkout -- path/to/file 5. pre-commit run --all-files (可选) 6. git push
请先从第1个安全检测环节开始测试,然后再进行其他环节的测试。
第1个安全检测环节:Gitleaks(密钥泄露检测)
测试方法:在任意Python文件中硬编码AWS密钥。
这样做的目的是验证密钥扫描工具是否能够正常工作。
以下命令会在app/auth-service/main.py文件中添加一个伪造的AWS密钥:
echo 'AWS_KEY = "'$(printf '%s%s' 'AKIA' 'IOSFODNN7EXAMPLE')'"' >> app/auth-service/main.py
git add app/auth-service/main.py && git commit -m "测试:触发Gitleaks检测"
# pre-commit命令会暂时阻止这次提交——这只是用于测试
# 如果只需要生成CI测试的截图,可以执行以下命令:git commit --no-verify -m "测试:触发Gitleaks检测" && git push完成后的结果如下(在终端中执行预提交操作时会出现这样的输出):
🔑 秘密信息扫描(使用Gitleaks工具)...............................................失败
- 钩子ID:gitleaks
- 出错代码:1
检测结果:AWS_KEY = "已隐藏"
规则ID:aws-access-token
文件路径:app/auth-service/main.py
错误位置:第316行
预期结果:提交操作应该会失败,因为Gitleaks应该会在app/auth-service/目录下检测到相关秘密信息。
这种失败是好事,因为它说明本地的预提交钩子在秘密信息被上传到Git之前就拦截了它。
恢复原始状态:
git restore --staged app/auth-service/main.py 2>/dev/null
git checkout app/auth-service/main.py
pre-commit run gitleaks --all-files # → 应该能够成功执行
第二道检测关卡:Semgrep(安全代码扫描工具)
本地测试环境中的操作:(此时不会对仓库中的文件进行任何修改):
python3 -m venv /tmp/sec-gates-venv && /tmp/sec-gates-venv/bin/pip install semgrep
cat > /tmp/semgrep-bad.py << 'EOF'
import subprocess
from fastapi import Request
def bad(request: Request):
subprocess.run(request.query_params.get("cmd"), shell=True)
EOF
/tmp/sec-gates-venv/bin/semgrep \
--config=p/python --config=p/security-audit --config=p/owasp-top-ten --error \
/tmp/semgrep-bad.py
测试效果:当添加一个Semgrep需要扫描的临时文件后,执行提交操作会导致CI流程中断:Semgrep会报告该问题,相关的安全检查任务也会失败,图像构建任务也不会被执行。
恢复原始状态:
rm -f app/auth-service/gate_test_semgrep.py
git add -A && git commit -m "恢复原始状态:Semgrep测试失败" && git push
预期结果:Semgrep应该会将subprocess-shell-true这一配置项标记为“存在安全风险”,从而导致相关检查任务失败。
第三道检测关卡:Checkov(配置审计工具 / Dockerfile检查)
Checkov会扫描Dockerfile文件以及Kubernetes配置文件,以检测其中是否存在不安全的配置项。
首先,我们先进行一次本地测试。这个测试会从复制的Dockerfile文件中删除HEALTHCHECK配置项,然后展示Checkov是如何识别这一问题的:
python3 -m venv /tmp/sec-gates-venv && /tmp/sec-gates-venv/bin/pip install checkov
sed '/^HEALTHCHECK/,+1d' app/auth-service/Dockerfile > /tmp/Dockerfile-nohc
mkdir -p /tmp/checkov-demo/app/auth-service
cp /tmp/Dockerfile-nohc /tmp/checkov-demo/app/auth-service/Dockerfile
/tmp/sec-gates-venv/bin/checkov --directory /tmp/checkov-demo --framework dockerfile
现在,通过在auth-service的Dockerfile中开放SSH端口22,来触发Checkov的检查:
echo 'EXPOSE 22' >> app/auth-service/Dockerfile
git add app/auth-service/Dockerfile && git commit -m "测试:触发Checkov检查" && git push
预期结果: Checkov的日志或检测结果中应该会出现CKV_DOCKER_1,这说明SSH端口已被公开。
IaC扫描(Checkov)任务的结果可能会显示为红色,也可能不会;这取决于Checkov所指定的严重程度。对于本次练习来说,这两种结果都是可以的。我们的目标只是找到并理解Checkov的检查结果而已。
如果你需要查看GitHub Actions任务失败时的截图,可以使用Gate 1、Gate 2或Gate 4。这些选项的设计目的就是让工作流程显示为红色状态。不过Checkov主要用于查看检测结果,因此它的状态可能仍然保持绿色。
恢复原状:
git checkout app/auth-service/Dockerfile
git commit -am "恢复原状:检查Checkov配置" && git push
Gate 4:Trivy(针对镜像中的CVE漏洞进行扫描)
本地测试:扫描一个旧的基础镜像(不执行构建操作):
trivy image --exit-code 1 --severity CRITICAL,HIGH --ignore-unfixed python:3.8-slim
中断CI流程:在Dockerfile中指定使用旧的基础镜像,然后推送代码,等待扫描镜像操作完成:
sed -i.bak 's/FROM python:3.13-slim/FROM python:3.8-slim/' app/auth-service/Dockerfile
git add app/auth-service/Dockerfile && git commit -m "测试:触发Trivy扫描" && git push
通过标准:当扫描镜像操作完成,且Trivy扫描所有镜像的结果显示退出代码为1,并附带CVE漏洞信息表(严重程度为HIGH或CRITICAL)时,即表示测试通过。发布镜像和更新清单文件这两个步骤可以跳过。
恢复原状:
git checkout app/auth-service/Dockerfile
git commit -am "恢复原状:测试Trivy配置" && git push
3.5:当扫描检测到你并未引入的CVE漏洞时
§3.4节的内容是故意设置的特殊情况。这一部分用于描述另一种情况:当你上传了正常的代码,但由于发现了新的安全漏洞,导致镜像扫描失败。
这种情况很正常。CVE数据库会不断更新,因此不要因此而放弃进行安全扫描。只需修复存在漏洞的软件包或镜像即可。
首先,需要找到真正的CVE漏洞。在GitHub Actions中,先执行扫描镜像操作,然后进入Trivy扫描所有镜像界面,查找以下信息所在的表格:
请忽略日志底部那条无关的信息:
Trivy的0.71.2版本现已可用
错误:操作以退出代码1结束。版本通知本身并不会导致测试失败,但那些可以被修复的高风险/严重安全漏洞才会引发问题。因此,不要通过添加--skip-version-check选项来“解决”这个问题。
正确的处理方法如下:
请不要删除--exit-code 1选项,也不要降低安全漏洞的严重性等级或禁用扫描功能。如需帮助,请参考Trivy版本通知相关说明以及关于Trivy如何阻止Python服务镜像被构建的相关内容。
完成第三阶段测试
在生成截图时,只需选择其中一个检测工具即可:
在完成§3.4节中的每一项测试后,请及时撤销之前所做的配置更改,然后重新推送代码,确保工作流程能够再次显示为“绿色”。只需一张显示GitHub Actions界面呈现红色状态的截图即可用于展示你的项目成果。
运行阶段检查命令:
make check-3 # 结果应为:All checks passed. Ready for the next stage.
预期输出: All checks passed. Ready for the next stage.
同时,你还需要确保在§3.4节中至少触发了一个检测机制。即使是在本地进行的Gitleaks检测失败,也会计入统计结果。
ENABLE_DAST=true这个选项是可选的。只有当你打算后续运行ZAP扫描工具时,才需要启用它。
目前还无需关注以下内容:Checkov是否阻止Kubernetes配置文件的生成,以及Cosign是否影响部署流程。这些功能将在第四阶段通过Kyverno来实现。
接下来,保存你目前的开发进度:
make snapshot STAGE=3 && make snapshots
第四阶段——准入控制(使用Kyverno)
即使持续集成测试通过了,集群仍然有可能拒绝某些请求。
虽然CI工具会在代码和镜像到达GitOps之前对其进行扫描,但它无法监控集群内部发生的所有操作。拥有kubectl权限的用户可以直接修改Kubernetes配置文件。
你安装的Helm图表可能会生成违反安全标准的Pod实例,而这些过程并不会经过构建流程。因此,第四阶段引入了准入控制机制:这一功能直接内置在Kubernetes系统中。
<每当有尝试创建或更新资源的操作发生时,该请求在生效之前会先经过准入机制中的Webhook处理。如果某个Webhook拒绝了该请求,那么该资源就永远不会被创建出来。Kyverno是一款专为Kubernetes设计的策略执行引擎,它利用这些Webhook机制来发挥作用。你可以使用YAML文件来定义策略(而不是编写应用程序代码),而Kyverno会针对集群中所有符合这些策略条件的资源进行相应的检查与限制操作——例如,它会拒绝任何以root用户身份运行的Pod,或者要求所有容器都必须遵守特定的CPU和内存使用限制。
与持续集成工具的区别在于执行时机:持续集成工具会在代码发布之前进行检查,而Kyverno则是在集群层面对这些策略进行强制执行。这两种机制结合起来,能够为你提供两道防护屏障。
在这个阶段,你的目标是将Kyverno安装到系统中,应用位于infra/policies/目录下的策略配置,并通过§4.4节的测试来证明:那些不符合规定的Pod会在容器运行之前就被拒绝访问。
在开始操作之前,请确保前几个阶段建立的基础环境仍然完好无损:make check-3命令应该能够正常通过(预提交钩子和持续集成安全检查机制必须处于激活状态);infra/cosign.pub文件应该存在于第3阶段创建的目录中;同时,ArgoCD也必须仍在正常运行,这样你才能通过http://clearledger.local访问到应用程序接口。如果其中任何一项存在问题,请先解决它们——因为Kyverno是建立在健康的集群环境之上的,而不是有缺陷的集群上。
当§4.4节中列出的所有测试场景都未能通过,且make check-4命令能够成功执行时,你就完成了第4阶段的任务。
与第3阶段相比,Kyverno的变化在于执行机制的加强,而非扫描功能的改进。在持续集成工具中,Checkov虽然能检测出Kubernetes配置错误,但并不会阻止整个构建流程的进行;而Kyverno则会在集群层面对这些问题进行拦截。
从第1阶段开始,Cosign就已经开始为你的应用程序镜像添加签名;现在,Kyverno进一步要求在部署ClearLedger镜像之前必须具备这种签名。这就是第1阶段采取的安全措施如何在实际中发挥作用的地方。如果你想详细了解第1阶段哪些内容起到了阻止错误配置的作用,而哪些内容需要等到第4阶段才能生效,请查阅那一节内容。
首先从§4.1节开始操作,安装Kyverno。如果安装过程、策略配置的设置、测试用例的执行,或者make check-4命令出现故障,请先阅读troubleshooting.md文档,并仔细研究第4阶段:准入控制(Kyverno)相关内容,然后再尝试修改Helm配置参数或策略YAML文件。
Kyverno所执行的检查与限制措施
所有的策略配置文件都存储在infra/policies/目录下。Kyverno本身则是通过Helm命令使用stages/stage-4-admission-control/infra/kyverno/values.yaml文件进行安装的。
| 策略名称 | 所执行的检查内容 | 对应的规范框架 |
|---|---|---|
disallow-root-containers |
禁止使用root用户身份运行容器 | CIS K8s 5.2.6 |
require-resource-limits |
强制设置CPU和内存的使用限制 | CIS K8s 5.2.4 |
disallow-privilege-escalation |
禁止权限升级行为 | CIS K8s 5.2.5 |
drop-all-capabilities |
删除所有容器可使用的功能 | CIS K8s 5.2.7 |
require-signed-images |
要求ClearLedger镜像必须带有Cosign签名 | SLSA 2级安全标准 |
平台稳定性:从第4阶段开始
从第4阶段开始,你将在单个节点的虚拟机上运行更多的控制器程序,例如Kyverno、存储供应工具,以及后来的Prometheus和Loki。有时,一个Pod会显示为“正在运行”状态,但实际上它可能在后台不断崩溃。
当负责平台运行的Pod(如Kyverno控制器、hostpath-provisioner、Prometheus操作程序等)的重启次数过多时,API服务器会开始出现超时现象,使用kubectl>进行操作也会遇到不稳定情况。你可能会花费大量时间去调试错误的组件,因为这些应用Pod看起来似乎并没有问题。
在之后的每个阶段中,都应该让集群有大约十分钟的时间来稳定下来,然后再执行相应的健康检查脚本:
bash scripts/health-check.sh # 例如:4、7、7.5
# 或者使用Makefile快捷方式:
make check-4
该脚本会列出那些重启次数异常多的Pod。你也可以自行查看这些问题最严重的Pod。这个列表会显示整个集群中重启次数最多的15个Pod,当发现系统运行速度变慢但不确定是哪个命名空间出了问题时,这个列表会非常有用:
kubectl get pods -A --sort-by='.status.containerStatuses[0].restartCount' \
-o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,RESTARTS:.status.containerStatuses[0].restartCount' \
| tail -15
注意:在阶段稳定下来后,Kyverno控制器以及其他平台Pod的重启次数应该低于5次。如果发现有某个Pod的重启次数超过了10次,应立即停止该Pod的运行,并根据官方文档中提供的Helm配置值或故障排除指南进行修复。切勿直接使用kubectl patch>来修改配置后就继续下一步操作。一个稳定的平台层是后续所有阶段正常运行的前提条件。
4.1:安装Kyverno
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm upgrade --install kyverno kyverno/kyverno \
--version 3.2.8 \
--namespace kyverno \
--create-namespace \
-f stages/stage-4-admission-control/infra/kyverno/values.yaml \
--wait --timeout=600s
这个配置文件为实验室环境做了三件重要的事情:
您应该看到的内容:
“kyverno”版本并不存在,现在正在安装它。
名称:kyverno
命名空间:kyverno
状态:已部署
…
Kyverno版本:v1.12.6
请确认所有四个控制器都在运行中(在连接速度较慢的情况下,首次拉取数据可能需要几分钟时间):
kubectl get pods -n kyverno
名称 准备状态 运行状态 重启次数 创建时间
kyverno-admission-controller-bd685cd4b-f6kl6 1/1 运行中 0 2分钟
kyverno-background-controller-66fcfc6d87-59wgt 1/1 运行中 0 2分钟
kyverno-cleanup-controller-5c5bf8bc6b-7kspq 1/1 运行中 0 2分钟
kyverno-reports-controller-5cdd6f4c48-qf5wc 1/1 运行中 0 2分钟
如果这些Pod长时间处于ContainerCreating状态,说明该节点仍在从ghcr.io/kyverno下载镜像。请耐心等待,切勿在安装不完整的情况下再次尝试安装Helm。
稳定性提示:请仅在完成§4.2步骤之前安装Kyverno:
在继续下一步操作之前,请确保Kyverno相关的Pod们都处于正常运行状态:
kubectl get pods -n kyverno
预期结果: Kyverno控制器Pod们应显示1/1 Running的状态,重启次数应为0、1或2,且重启次数不会持续增加。
目前还不要运行make check-4命令。因为该检查还会验证您在§4.3步骤中配置的政策文件,所以即使Kyverno已经成功安装,这个检查也可能会失败。
4.2:确认您的Cosign公钥已添加到政策文件中
在第3阶段中,系统会生成infra/cosign.pub文件。Kyverno在创建Pod时会使用这个公钥来验证镜像的签名。政策文件中最初包含的是一个占位符,您需要在§4.3步骤中将其替换为自己的真实公钥。
步骤1:在虚拟机上从仓库根目录显示您的公钥
cd ~/clearledger # 或者进入您克隆仓库的位置
cat infra/cosign.pub
您应该会看到三行内容:-----BEGIN PUBLIC KEY-----、一段较长的Base64编码字符串,以及-----END PUBLIC KEY-----。请将整段内容复制下来(在下一步中会用到它)。
步骤2:将公钥粘贴到政策文件中
使用您的编辑器(如infra/policies/require-signed-images.yaml文件。
找到以下这一行:
PASTE_YOUR_COSIGN_PUBLIC_KEY_HERE
请删除掉那条占位符代码,然后将之前复制的公钥内容粘贴到该位置。最终文件的内容应该如下所示(不过您的Base64编码字符串可能会与示例不同):
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
JFkwEwYHKoZIzj0CAQYIKoFIzj0DAQcDQgZEI...
-----END PUBLIC KEY-----
保存该文件。确保粘贴的密钥内容位于publicKeys: |-下方,并保持缩进格式。BEGIN PUBLIC KEY和END PUBLIC KEY这两行前面也应当有空格,与它们中间的base64编码内容一样。
步骤3:进行三项快速检查
请从仓库根目录依次运行以下命令:
# 检查A——文件中必须不存在占位符
grep PASTE_YOUR_COSIGN_PUBLIC_KEY_HERE infra/policies/require-signed-images.yaml \
&& echo "❌ 失败:文件中仍存在占位符——请重新编辑并保存" \
|| echo "✓ 成功:占位符已被删除"
# 检查B——密钥块必须出现且仅出现一次
grep -c "BEGIN PUBLIC KEY" infra/policies/require-signed-images.yaml
检查B的预期输出结果应为1(如果显示0,说明没有正确粘贴密钥;如果显示2,则说明密钥被重复粘贴了两次)。
# 检查C——政策文件中的密钥内容必须与cosign.pub完全一致
diff infra/cosign.pub \
<>(sed -n '/-----BEGIN PUBLIC KEY-----/,/-----END PUBLIC KEY-----/p' \
infra/policies/require-signed-images.yaml | sed 's/^[[:space:]]*//')
检查C的预期输出结果应为无差异。如果没有差异,说明密钥内容是匹配的;如果diff命令显示了差异,请打开政策文件并修正错误。
如果三项检查都通过,继续执行§4.3节的内容。
如果你跳过这一步,那么在§4.4节中的第3种测试场景将会出现问题:未签名的图像可能会被允许通过,或者已签名的Pod可能会因为Kyverno使用了错误的密钥而被拒绝。
4.3:应用五项核心政策
现在你将应用这五项与CIS控制标准相对应的安全政策。暂时不要应用verify-slsa-provenance.yaml文件,这项政策是可选的SLSA认证机制,用于后续的功能升级。
第4阶段需要应用infra/policies/require-signed-images.yaml政策。该政策设置了failurePolicy: Fail,因此当Kyverno无法验证图像签名时,相关Pod会被阻止而不是被允许通过。而设置failurePolicy: Ignore的ECR政策则适用于第8阶段,而非当前步骤。
kubectl apply \
-f infra/policies/disallow-root.yaml \
-f infra/policies/disallow-privilege-escalation.yaml \
-f infra/policies/drop-all-capabilities.yaml \
-f infra/policies/require-resource-limits.yaml \
-f infra/policies/require-signed-images.yaml
请稍等几秒钟,然后确认所有策略都显示为READY: True且VALIDATE ACTION: Enforce:
kubectl get clusterpolicy
NAME ADMISSION BACKGROUND VALIDATE ACTION READY AGE
disallow-privilege-escalation true true Enforce True 10s
disallow-root-containers true true Enforce True 10s
drop-all-capabilities true true Enforce True 10s
require-resource-limits true true Enforce True 10s
require-signed-images true false Enforce True 10s
如果READY的状态仍然显示为“空”,请查看Kyverno的日志:kubectl logs -n kyverno -l app.kubernetes.io/component=admission-controller --tail=50。
4.4:故意破坏系统规则进行测试
现在,你将通过尝试创建一些存在安全问题的Pod来测试这些策略。
这些Pod肯定会导致测试失败,这就是测试的目的所在。
像Checkov这样的CI工具会通过报告来提醒你这些问题;而Kyverno则更进一步:它会在Kubernetes运行这些不安全的Pod之前就阻止它们被执行。
对于每一次测试,都要仔细阅读错误信息,因为这些信息会告诉你是哪条策略阻止了该Pod的创建,以及到底是哪个字段存在问题。这些错误信息其实就是证明准入控制机制正在正常工作的证据。
| 测试场景 | 你试图模拟的行为 | >被测试的策略 | >成功的结果应该是什么 |
|---|---|---|---|
| 1 | 攻击者创建了一个没有采取任何安全加固措施的Pod | 涉及root权限、能力限制等相关设置 | 四条策略会被触发,导致Pod创建失败,显示“NotFound”错误 |
| 2 | 开发人员修复了securityContext的相关问题,但忽略了资源限制的设置 | 仅涉及资源限制这一项规则 | 一条策略会被触发,导致Pod创建失败,显示“NotFound”错误 |
| 3 | 攻击者将未签名的镜像上传到了Docker Hub | 涉及签名验证的相关规则 | require-signed-images这条策略会被触发,导致Pod创建失败,显示“NotFound”错误 |
场景1:root容器且未设置securityContext
你正在模拟的情况:拥有kubectl访问权限的用户绕过了CI流程,创建了一个极其简单的Pod:既没有设置securityContext,也没有任何资源限制。
这正是Checkov在第一阶段就检测到的问题;而Kyverno现在能够在这一步骤就阻止这种行为的发生。
这个Pod配置存在什么问题:这个容器仅包含了名称和镜像信息。它默认会以root权限运行,会拥有所有的Linux功能,并且没有任何CPU或内存使用限制。
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: root-test
namespace: clearledger
spec:
containers:
- name: test
image: nginx:alpine
EOF
你应该看到的结果:
服务器返回错误:在尝试创建“STDIN”资源时发生了问题。admission webhook “validate.kyverno.svc-fail”拒绝了该请求:
资源 Pod/clearledger/root-test 因以下规则而被阻止使用:
- **disallow-privilege-escalation** 规则:
`check-allowPrivilegeEscalation`: 验证错误:必须将 `allowPrivilegeEscalation` 设置为 `false`。规则检查在路径 `/spec/containers/0/securityContext/` 失败。
- **disallow-root-containers** 规则:
`check-runAsNonRoot`: 验证错误:在 clearledger 命名空间中,根容器是不被允许使用的。需要在 Pod 或容器的 `securityContext.runAsNonRoot` 属性中将其设置为 `true`。规则检查在路径 `/spec/containers/0/securityContext/` 失败。
- **drop-all-capabilities** 规则:
`check-capabilities`: 验证错误:所有容器都必须放弃所有的权限。规则检查在路径 `/spec/containers/0/securityContext/` 失败。
- **require-resource-limits** 规则:
`check-resources`: 验证错误:所有容器都需要配置资源请求限制。规则检查在路径 `/spec/containers/0/resources/limits/` 失败。如何解读这些输出结果:
关键信息在于以下这一行:
resource Pod/clearledger/root-test 因以下策略而无法创建
这意味着 Kyverno 在该 Pod 被创建之前就阻止了它的生成。
在这一行下方,Kyverno 会列出导致 Pod 创建失败的各项策略。例如:
disallow-root-containers:
check-runAsNonRoot:
这说明该 Pod 不符合 disallow-root-containers 这一策略的要求,具体来说是 check-runAsNonRoot 规则未能通过验证。相应的解决方法也会在提示中显示:
将 securityContext.runAsNonRoot 设置为 true
其他策略的判断方式也是如此:
path 这一字段会指出 Kubernetes 期望在何处找到缺失的配置项。例如,/spec/containers/0/securityContext/ 表示需要在 Pod 的配置文件中查找第一个容器的 securityContext 部分。
/spec/containers/0/resources/limits/ 则表示需要查看第一个容器的资源限制设置。
因此,这个有问题的 Pod 同时违反了四项安全策略。由此可见,Kyverno 并不会简单地拒绝请求,而是会明确指出是哪些策略出了问题,以及需要在 YAML 配置文件的哪个位置进行修改。
验证安全策略是否真正得到了执行:
kubectl get pod root-test -n clearledger
# 服务器返回错误:未找到名为 "root-test" 的 Pod
如果某个 Pod 显示为 Running 或 Pending 状态,说明安全策略尚未生效。此时请重新执行命令 kubectl get clusterpolicy,确认所有五项策略都显示为 READY: True。
请截图保存。这将是证明您已成功配置并应用了 CIS Kubernetes Benchmark 5.2.6 安全标准的证据。
场景 2:缺少资源限制设置
你正在模拟的情况是:开发人员已经按照要求修改了容器的安全配置,但忽略了资源限制的设置。
在实际开发中,这种情况很常见:“我们加强了容器的安全性”,但却忘记了设置 CPU 和内存的使用上限。
这个 Pod 配置文件的问题在于: securityContext 的设置是正确的,但是没有 resourcesrequests 或 resources.limits 这两项配置。如果没有资源限制,该容器可能会占用节点上其他任务的资源,从而导致系统性能下降。
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: nolimits-test
namespace: clearledger
spec:
containers:
- name: test
image: nginx:alpine
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
EOF您应该看到的内容:
服务器返回错误:在尝试创建“STDIN”时出现故障,admission webhook “validate.kyverno.svc-fail”拒绝了该请求:
资源Pod/clearledger/nolimits-test因以下策略而被阻止:
require-resource-limits:
check-resources: ‘验证错误:所有容器都必须配置资源请求和限制参数。在路径/spec/containers/0/resources/limits/处,规则check-resources检测失败。’
关键观察点:这一次只有一条策略被触发;securityContext字段满足其他四项规则的要求。Kyverno会独立地评估每一条规则,每个容器属性都对应着一个独立的检查环节。
验证步骤:
kubectl get pod nolimits-test -n clearledger
# 服务器返回错误(未找到相应Pod):无法找到名为“nolimits-test”的Pod
场景3:未签名的ClearLedger镜像
您正在模拟的情况:这是一种供应链攻击:有人会在您的仓库名称(clearledger-auth-service)下,将恶意镜像推送到Docker Hub上,而这一过程并未经过您设置的CI管道。在第三阶段,系统允许使用Cosign进行签名验证;而在第四阶段,这种签名操作在集群层面上被强制要求。
为什么需要这样的设置:Kyverno会对比Docker Hub上的镜像签名与实际图像进行验证,而不会检查您笔记本电脑上的镜像。因此,测试用的镜像标签必须先存在于Docker Hub上。
如果使用像:unsigned这样的虚假标签,并且该标签从未被真正推送过,Kubernetes在后续尝试拉取该镜像时可能会出现ImagePullBackOff错误。但这仅仅说明无法成功拉取镜像,并不能证明Kyverno确实阻止了未签名镜像的上传。
步骤1:一次性推送一个故意设置成未签名的测试镜像:
export DOCKER_USERNAME=your-dockerhub-username
docker pull nginx:alpine
docker tag nginx:alpine ${DOCKER_USERNAME}/clearledger-auth-service:unsigned-test
docker push ${DOCKER_USERNAME}/clearledger-auth-service:unsigned-test
# 这个操作应该会失败,从而证明该镜像没有经过您的CI管道进行签名验证:
cosign verify --key infra/cosign.pub \
index.docker.io/${DOCKER_USERNAME}/clearledger-auth-service:unsigned-test
# 错误信息:未找到任何签名
步骤2:尝试使用符合规定的Pod配置文件来部署该镜像:
由于Pod的配置文件已经包含了严格的安全设置(如securityContext和limits),因此只有签名验证环节可能会失败。在镜像URL中请使用index.docker.io/路径;在Kyverno 1.12版本中,使用docker.io/...可能无法触发verifyImages验证机制。
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: unsigned-test
namespace: clearledger
spec:
containers:
- name: test
image: index.docker.io/${DOCKER_USERNAME}/clearledger-auth-service:unsigned-test
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
EOF
您应该看到的结果:
服务器返回错误:在尝试创建“STDIN”时出现问题,admission webhook “mutate.kyverno.svc-fail”拒绝了该请求:
资源Pod/clearledger/unsigned-test因以下策略而被阻止:
require-signed-images:
verify-cosign-signature: ‘无法验证图像index.docker.io/veeno-demo/clearledger-auth-service:unsigned-test:.attestors[0].entries[0].keys:未找到任何签名’
如何解读这些输出信息:
验证结果:
kubectl get pod unsigned-test -n clearledger
# 服务器返回错误(未找到对应Pod):pod "unsigned-test"不存在
您不应该看到的结果(这些情况说明签名验证机制并未起作用):
| 现象 | 原因 |
|---|---|
| Pod被创建出来,但随后立即退出了 | Docker Hub上不存在相应的标签,请先完成第一步操作 |
| Pod被创建并且处于运行状态 | 使用的图像地址为docker.io/...,而不是index.docker.io/... |
错误信息中并未提及require-signed-images这一策略 |
可能是该策略未被应用,或者cosign.pub>密钥没有被正确嵌入到政策配置文件中 |
对比:已签名的图像是被允许使用的:
在之前的测试中,我们使用了未签名的图像,因此Kyverno拒绝了该请求。
您实际用于ClearLedger项目的图像应该由CI管道进行签名处理。如果Pod也遵守了相关的安全规则,Kyverno就会允许它正常运行。
您可以通过以下命令查看auth-service当前使用的图像地址:
# 如果配置符合要求,那么部署的标签信息应该会如下所示:
kubectl get deployment auth-service -n clearledger \
-o jsonpath '{.spec.template.spec.containers[0].image}'
# 输出结果示例:docker.io/veeno-demo/clearledger-auth-service:v0.1.0
示例输出:
docker.io/veeno-demo/clearledger-auth-service:v0.1.0
在策略生效之前就已经运行的Pod们会继续正常运行。真正需要验证的是:当Kubernetes创建新的Pod时,使用已签名图像的Pod是否能够通过Kyverno的验证流程。
请截取场景3中请求被拒绝时的屏幕截图,这样就能证明集群确实会阻止未签名的图像被使用,而不仅仅是因为CI管道对图像进行了签名处理。
4.5:验证ClearLedger是否仍能正常运行
Kyverno会在新创建的Pod上执行这些规则。而已通过准入检查的现有部署,或者是在这些规则生效之前就已经完成同步的部署,依然会正常运行。请确认您的应用Pod是否处于正常状态:
kubectl get pods -n clearledger
名称 准备状态 运行状态 重启次数 创建时间
auth-service-... 1/1 运行中 0 ...
frontend-... 1/1 运行中 0 ...
ledger-service-... 1/1 运行中 0 ...
notification-service-... 1/1 运行中 0 ...
postgres-0 1/1 运行中 0 ...
redis-... 1/1 运行中 0 ...
如果配置了Ingress服务,可以执行以下命令进行测试:
curl -s http://clearledger.local/auth/health | jq .
# 输出结果应为:{"status": "ok", "service": "auth-service"}
ArgoCD应该会显示“已同步”和“运行正常”的状态。这说明GitOps与准入控制机制是协同工作的,而不是相互冲突的。
4.6:规则例外情况(当某些合法的工作负载需要绕过特定规则时)
Kyverno会阻止任何违反规则的Pod。但是,当某些合法的工作负载确实需要绕过某些规则时,该怎么办呢?
PostgreSQL就是一个例子。官方的Postgres Alpine镜像使用特定的内部用户(UID为70)来管理数据目录。而disallow-root-containers规则要求所有Pod都必须将runAsNonRoot: true这一设置启用。
Postgres确实遵循了这一规则。但是,如果Kyverno被配置为也会检查特定的UID范围,或者如果某个Pod的安全上下文不符合该规则的要求,Kyverno就会阻止该Pod的运行。这样一来,数据库就无法启动,整个应用程序也会随之失败。
您不能为了满足某个数据库的需求而削弱整个集群的规则设置,因为那样会导致所有Pod都能绕过这些规则。相反,您应该为那些确实需要特殊处理的Pod创建规则例外。
请打开文件infra/policies/exceptions/postgres-root-exception.yaml并阅读其中的说明。以下是各个部分的作用:
spec.exceptions部分指明了需要绕过哪条规则:
exceptions:
- policyName: disallow-root-containers
ruleNames:
- check-runAsNonRoot
这段代码的意思是:“只跳过disallow-root-containers规则中的check-runAsNonRoot这条规则,其余规则仍然会正常执行。”
spec.match部分则决定了哪些资源可以享受这一例外规则:
match:
any:
- resources:
kinds:
- Pod
namespaces:
- clearledger
names:
- postgres-*
只有名为 postgres-* 的 Pod 才符合这些条件(即 postgres-0、postgres-1 等),且这些 Pod 必须位于 clearledger 命名空间中,同时资源类型也必须是 Pod。集群中的其他对象仍需遵守严格的规则。
这些注释主要是为你的团队和审计人员提供的说明:
annotations:
reason: "Postgres alpine镜像需要使用UID 70来拥有数据目录的权限"
approved-by: "platform-team"
review-date: "2026-01-01"
这些注释并没有实际的技术作用:Kyverno会忽略它们。它们的存在只是为了在未来有人询问“为什么Postgres可以绕过这条规则”时,能够从文件中找到相应的解释。
关于安全例外情况的处理规则:
只有当 Kyverno阻止了你的 Postgres Pod 运行时,才适用这些例外规则:
kubectl apply -f infra/policies/exceptions/postgres-root-exception.yaml
然后验证 Kyverno是否仍然会阻止其他不符合规则的 Pod 运行(效果与“场景1”中的结果相同):
cat <
4.7:CIS基准测试的证据 kube-bench)
你已经安装了 Kyverno,并且证明了它能够阻止不安全的 Pod 运行。
但这次的操作有所不同。kube-bench并不会阻止任何 Pod的运行,也不会改变集群的状态。它只是会检查 Kubernetes 节点是否符合 CIS基准测试的要求,并保存相应的检测结果。
可以这样理解这两者的区别:
工具
检查内容
解决的问题
Kyverno
Pod以及相关工作负载
“这个 Pod是否可以被允许运行?”
kube-bench
Kubernetes节点的配置设置
“这个 Kubernetes节点是否已经得到了足够的安全加固?”
这两种工具都有其用途,但在这个实验环境中,只有 Kyverno能够真正阻止不安全的 Pod 运行。
现在运行 kube-bench:
bash stages/stage-4-admission-control/scripts/run-kube-bench.sh
该脚本会以 Kubernetes Job的形式运行 kube-bench,并将测试结果保存在这里:
stages/stage-4-admission-control/scripts/kube-bench-report.json
它还会将测试结果与这个基准值进行比较:
stages/stage-4-admission-control/scripts/kube-bench-baseline.json
在MicroK8s环境下,你会看到很多“FAIL”和“WARN”提示。这是意料之中的结果。因为这个实验并没有要求你在单节点的本地虚拟机上修复所有的CIS警告。
真正重要的是最终的结果。
通过测试后的结果显示应该是这样的:
kube-bench: 1个控制项未通过测试(这些控制项在基准值中已有规定——没有出现退化现象)。
kube-bench: 与基准值相比,没有任何退化情况。
这意味着那些已知的MicroK8s问题已经被记录下来,而且你的集群状况也没有恶化。
如果你看到“REGRESSION”提示,或者 kube-bench的“make check-4”命令执行失败,请在进入第5阶段之前立即停止测试并进行排查。
可选操作:确认报告文件是否存在:
ls -la stages/stage-4-admission-control/scripts/kube-bench-report.json
在实际生产环境中,你需要修复那些CIS警告中指出的问题,或者记录那些被认可的例外情况。而在这个实验中,基准值只是用来记录MicroK8s应有的状态而已。
4.8:健康检查
make check-4
你应该看到的结果如下:
▶ 第4阶段——准入控制(Kyverno)
✓ Kyverno正在运行中
✓ “禁止创建root容器”的策略处于强制执行状态
✓ “要求为镜像添加资源限制”的策略处于强制执行状态
✓ “要求镜像必须经过签名”的策略处于强制执行状态
✓ “禁止权限升级”的策略处于强制执行状态
✓ “禁用所有容器的额外功能”的策略处于强制执行状态
✓ Kyverno能够正确拒绝那些没有安全上下文的Pod
✓ kube-bench的基准值文件存在(...)
所有检查均通过。可以进入下一阶段了。
如果kube-bench报告出现了退化现象,你需要手动运行相关脚本,并在审查后更新基准值文件。这些差异记录就是审计所需的证据。
如果Kyverno的安装过程出现问题,或者相关的策略配置失败,又或者“make check-4”命令执行失败,请参考troubleshooting.md中的说明。
第4阶段完成:检查清单(进入第5阶段)
当以下所有条件都满足时,说明你已经完成了第4阶段的测试:
#
检查项
>验证方法
1
Kyverno正在运行中
kubectl get pods -n kyverno —— 应有4个控制器处于“Running”状态
2
策略配置已经应用
kubectl get clusterpolicy —— 应有5条策略,且状态均为“READY: True”, “Enforce”模式
3
root容器被禁止创建
在终端中尝试创建root容器,应会收到拒绝提示(可截图作为证明)
4
未签名的镜像无法被推送
首先尝试推送带有“unsigned-test”标签的镜像,然后使用index.docker.io/路径来访问该镜像
5
应用程序运行正常
kubectl get pods -n clearledger —— 所有应用程序相关的Pod都处于“Running”状态
6
健康检查结果显示为绿色
执行“make check-4”命令后,应显示“所有检查均通过。可以进入下一阶段了。”
项目组合截图(可选): root-pod拒绝策略(§4.4场景1)、未签名图像的拒绝处理(§4.4场景3),以及通过`kubectl get clusterpolicy`命令查看的五项`Enforce`策略。
目前尚未完成的内容:SLSA认证验证(可选)、Vault秘密管理(第5阶段)以及网络策略配置(第6阶段)。当前密码仍存储在Kubernetes Secrets中,第5阶段会将这些密码移至Vault中。
你在第4阶段学到了什么
你现在可以在简历中写入或面试时提及的内容:
通过Kyverno实现了强制性的准入控制:阻止root容器的创建、防止权限升级、拒绝使用未签名的图像,并在资源部署时检查是否设置了限制措施——这些功能都符合CIS Kubernetes基准测试的要求。
执行`make snapshot STAGE=4 & make snapshots`命令,然后确认`clearledger.stage4`操作是否成功。详情请参阅如何保存你的学习进度。
第5阶段:秘密管理(使用Vault)
在本阶段结束时,敏感信息将不再存储在Git或基于etcd的Kubernetes Secrets中,而是由Vault集中管理,并仅在Pod启动时才将其注入其中。
你的目标:从集群中移除`auth-service-secret`和`ledger-service-secret`这两个秘密文件。
由于Vault会在Pod启动时注入凭证信息,因此用户的登录操作和API调用仍然可以正常进行。这才是秘密管理的真正价值所在。
在开始之前,请确保第4阶段的所有工作都已经完成:执行`make check-4`命令,确认所有五项Kyverno策略都在有效执行,并且应用程序能够在`http://clearledger.local`地址上正常响应。在安装Vault之前,务必修复任何出现死循环的Pod。
本阶段有哪些变化
目前,数据库密码和JWT密钥分别保存在GitHub上的`secret.yaml`文件中以及集群内的Kubernetes Secrets中。在第5阶段,这些敏感信息将被移至HashiCorp Vault中进行管理,同时应用程序也会被修改为以新的方式读取这些秘密信息。
当身份验证或账本相关Pod启动时,Vault代理注入器会添加一个小型辅助容器。该辅助容器会使用Pod自身的服务账户登录到Vault系统中,获取密码和JWT令牌,并将这些数据以文件形式保存在/vault/secrets/目录下。
您的应用程序已经知道如何读取这些路径下的数据。这些数据与之前通过secretKeyRef传递过来的数据是相同的,只不过现在是在运行时获取这些数据的,而不是从Kubernetes的Secret对象中提取它们而已。
迁移完成后,敏感信息会保存在Vault中(即长期存储位置),而在容器运行的期间,这些数据也会暂时存在于Pod文件系统中。它们不再保存在Git仓库中了。您需要从clearledger-infra目录中删除secret.yaml文件,然后让ArgoCD同步那些指向Vault的部署配置。
首次使用 Vault时,您需要将一个模板文件复制到本地的.env文件中(详见§5.1节)。这个文件会被Git忽略。运行一次seed-vault-secrets.sh命令,就可以将这些数据导入到Vault中。
实际的敏感信息并不会被保存在已提交的脚本文件中。这些脚本会从本地的.env文件或终端中读取敏感信息,因此密码和令牌始终不会被保存在Git仓库中。
请按此顺序执行步骤
每个步骤都依赖于前一个步骤。如果跳过某个步骤,就很容易导致身份验证或账本相关Pod出现异常,从而使得应用程序看起来像是出现了故障,但实际上只是因为Vault尚未准备好而已。
请从§5.1节开始操作。如果遇到任何问题,请先阅读troubleshooting.md文件,再尝试修改相关配置文件。
5.1:创建仅限本地使用的.env文件(切勿提交到Git仓库中)
这个文件包含两部分内容:一是用于Helm工具的开发环境Vault根令牌(详见§5.2节),二是您将在§5.3节中导入到Vault中的密码信息。
这个文件仅保存在您的本地机器上,切勿将其提交到Git仓库中。其中包含的SEED_*值必须与当前应用程序所使用的值保持一致,这样在您之后删除Kubernetes Secret对象后,登录功能仍然能够正常使用。
请注意,这是两个不同的文件,请不要将它们混淆:
文件
其用途
stages/stage-5-secrets-management/.env.example
这是仓库中提供的空白模板(所有字段均为空值)。请在第一步中复制此文件。
stages/stage-5-secrets-management/.env
这是您需要使用的实际文件(该文件会被Git忽略)。您需要在第二步和第三步中创建并填写这个文件的内容。
本节底部的示例内容仅用于展示完整的.env文件应该是什么样的;除非您的集群环境与这些示例中的配置完全匹配,否则请不要复制这些占位符密码。
步骤1:将模板文件复制到.env
cp stages/stage-5-secrets-management/.env.example \
stages/stage-5-secrets-management/.env
这样您就会得到一个文件,其中VAULT_TOKEN=和SEED_*=这些行的内容都是空的。请在您的编辑器中打开这个文件,然后进行下一步操作。
步骤2:从集群中获取当前的密码值
请从仓库根目录运行以下命令。每个命令都会输出一个密码值,请将这些值复制到.env文件中。
# → 将输出结果粘贴到SEED_AUTH_DATABASE_URL字段中
kubectl get secret auth-service-secret -n clearledger \
-o jsonpath '{.data.database_url}' | base64 -d; echo
# → 将输出结果粘贴到SEED_AUTH_JWT_SECRET字段中
kubectl get secret auth-service-secret -n clearledger \
-o jsonpath '{.data jwt_secret}' | base64 -d; echo
# → 将输出结果粘贴到SEED_LEDGER_DATABASE_URL字段中
kubectl get secret ledger-service-secret -n clearledger \
-o jsonpath '{.data.database_url}' | base64 -d; echo
步骤3:填写.env文件的内容
变量名
应输入的值
VAULT_TOKEN
您可以选择的任何仅限开发人员使用的字符串(例如:my-dev-root-token);在§5.2节中也会使用相同的值。
SEED_AUTH_DATABASE_URL
上述第一个命令的输出结果
SEED_AUTH_JWT_SECRET
上述第二个命令的输出结果
SEED_LEDGER DATABASE_URL
上述第三个命令的输出结果
注意:这仅是一个示例,实际使用时应填写步骤2中从集群获取到的密码值,而不是这些示例字符串:
VAULT_TOKEN=my-dev-root-token
SEED_AUTH_DATABASE_URL=postgresql://clearledger:changeme-stage0@postgres:5432/clearledger
SEED AUTH_JWT_SECRET=stage0-jwt-secret-change-in-production
SEED_LEDGER DATABASE_URL=postgresql://clearledger:changeme-stage0@postgres:5432/clearledger
如果auth-service-secret已经被删除,您可以按照以下方法恢复它:
# 从Postgres的初始化秘密中获取数据库地址(实验室环境中的默认密码通常是changeme-stage0)
PG_PASS=$(kubectl get secret postgres-secret -n clearledger \
-o jsonpath '{.data.password}' | base64 -d)
echo "postgresql://clearledger:${PG_PASS}@postgres:5432/clearledger"
# 将此地址同时用于SEED_AUTH_DATABASE_URL和SEED_LEDGER DATABASE_URL字段
# JWT密钥:使用在步骤0中设置的值,或者如果已经从Vault系统中获取了密码,则直接使用该值:
kubectl exec -n vault vault-0 -- vault kv get -field=jwt_secret clearledger/auth-service 2>/dev/null \
|| echo "(请手动设置SEED_AUTH_JWT_SECRET——确保其值与已发放的令牌相匹配)"
一旦.env文件中设置了所有四个变量,就继续执行§5.2部分。
5.2:安装Vault及Agent Injector
set -a && source stages/stage-5-secrets-management/.env && set +a
helm repo add hashicorp https://helm.releases.hashicorp.com && helm repo update
# 首先进行安装:
helm install vault hashicorp/vault \
--namespace vault --create-namespace \
--set server.dev.enabled=true \
--set server.dev.devRootToken="${VAULT_TOKEN}" \
--set ui.enabled=true \
--set injector.enabled=true
# 如果使用“helm install”时出现“无法重用该名称”的错误,可以改用“helm upgrade --install”:
# helm upgrade --install vault hashicorp/vault \
# --namespace vault --create-namespace \
# --set server.dev.enabled=true \
# --set server.dev.devRootToken="${VAULT_TOKEN}" \
# --set ui.enabled=true \
# --set injector.enabled=true
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/name=vault -n vault --timeout=120s
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io name=vault-agent-injector -n vault --timeout=120s
kubectl apply -f stages/stage-5-secrets-management/infra/vault-ingress.yaml
在浏览器中打开http://vault.local。使用你在stages/stage-5-secrets-management/.env文件中设置的VAULT_TOKEN进行登录。例如,如果.env文件中的VAULT_TOKEN值为my-dev-root-token,那么就使用my-dev-root-token作为登录凭据。
验证:查看Vault相关的Pod是否已经创建:
kubectl get pods -n vault
预期结果:
NAME READY STATUS RESTARTS AGE
vault-0 1/1 Running 0 1m
vault-agent-injector-8d6b668b4-xxxxx 1/1 Running 0 1m
如果执行helm install时出现“无法重用该名称”的错误,说明Vault已经安装完成了。此时请使用上面的helm upgrade --install命令进行升级操作。
5.3:配置Vault(包括平台相关设置及种子KV存储)
需要按顺序运行这两个脚本。这两个脚本都会从你的.env文件中读取VAULT_TOKEN值。
bash stages/stage-5-secrets-management/infra/vault/setup.sh
bash stages/stage-5-secrets-management/infra/vault/seed-vault-secrets.sh
setup.sh:为集群配置Vault环境,包括Kubernetes认证机制、KV密钥存储系统、相关策略及角色设置,这样认证相关的Pod后续就能获取所需的秘钥信息。该脚本不会保存用户的数据库密码,也不会将任何数据写入Git仓库。
seed-vault-secrets.sh:从.env文件中提取SEED_*格式的配置信息,并将其存储在Vault的clearledger/data/auth-service和clearledger/data/ledger-service路径下。该脚本不会将这些值显示在终端上。
重新运行这两个脚本都不会对实验环境造成任何影响。
预期输出:setup.sh的执行结果应如下:
==>> 启用Kubernetes认证机制…
==>> 配置Kubernetes认证相关设置…
==>> 激活KV密钥存储功能…
==>> 创建Vault策略…
==>> 定义Kubernetes认证角色…
==>> 应用RBAC与ServiceAccount权限控制…
✓ Vault配置完成(尚未保存任何秘钥数据)。
下一步:执行bash/stages/stage-5-secrets-management/infra/vault/seed-vault-secrets.sh脚本
预期输出:seed-vault-secrets.sh的执行结果应如下:
==>> 正在登录Vault系统…
==>> 将秘钥信息存储到Vault的KV存储系统中(具体值不会被显示)…
======== 密钥存储路径 ========
clearledger/data/auth-service
======= 元数据信息 =======
Key Value
--- -----
created_time 2026-06-01T15:31:53.538991153Z
version 1
✓ 秘钥信息已成功存储在clearledger/data/auth-service和clearledger/data/ledger-service路径下
仅验证元数据信息(秘钥值不会被显示):
kubectl exec -n vault vault-0 -- vault kv metadata get clearledger/auth-service
Key Value
--- -----
cas_required false
created_time 2026-06-01T15:31:53.538991153Z
current_version 1
delete_version_after 0s
max_versions 0
oldest_version 0
updated_time 2026-06-01T15:31:53.538991153Z
5.4:GitOps操作:更新clearledger-infra项目(解决ArgoCD同步问题)
ArgoCD是从你的clearledger-infra GitHub仓库中部署应用程序的,而不是从你当前正在使用的clearledger主应用仓库中部署。你需要先在clearledger-infra仓库中修改相关配置文件,然后再将这些更改复制到clearledger主仓库中,这样ArgoCD才能完成同步操作。请谨慎操作,并在每完成一个子步骤后进行验证。
5.4a:更新clearledger应用仓库中的配置文件
cp stages/stage-5-secrets-management/infra/manifests/auth-service/deployment.yaml \
infra/manifests/auth-service/deployment.yaml
cp stages/stage-5-secrets-management/infra/manifests/ledger-service/deployment.yaml \
infra/manifests/ledger-service/deployment.yaml\
mkdir -p infra/manifests/vault
cp infra/deferred-by-stage/stage-5-secrets-management/vault/rotation-cronjob.yaml \
infra/manifests/vault/rotation-cronjob.yaml
rm -f infra/manifests/auth-service/secret.yaml infra/manifests/ledger-service/secret.yaml
5.4b. 手动编辑infra/manifests/kustomization.yaml文件
在您的编辑器中打开该文件。在resources:列表中:
保留postgres/postgres-secret.yaml文件,该文件仅用于Postgres的初始化配置,而非应用程序的凭据信息。
保存修改后,请进行验证:
# 确保与应用程序密钥相关的行已被删除—— postgres-secret文件应该没有问题
grep -E '^[[:space:]]*-[[:space:]]+(auth-service|ledger-service)/secret\.yaml' \
infra/manifests/kustomization.yaml && echo "OK: 所有与应用程序密钥相关的条目都已删除"
grep vault/rotation-cronjob.yaml infra/manifests/kustomization.yaml
grep vault.hashicorp infra/manifests/auth-service/deployment.yaml | head -1
kustomize build infra/manifests >/dev/null && echo "OK: kustomize构建过程成功"
预期结果应该是OK,系统中应列出rotation cronjob任务,第一行显示的地址应为vault.hashicorp.com/agent-inject,且kustomize构建过程也应成功完成。
准备就绪后,请在app仓库中提交这些更改:执行命令git add infra/manifests && git commit -m "feat(stage-5): 在标准配置文件中添加Vault部署相关设置"。
5.4c. 将相同的更改推送到clearledger-infra仓库
git clone https://github.com/YOUR_USERNAME/clearledger-infra.git /tmp/clearledger-infra
如果克隆过程中出现“目标路径‘/tmp/clearledger-infra’已经存在”的错误(可能是因为您在§1.3或更早的步骤中已经克隆过该仓库),请直接使用现有的文件夹,无需再次克隆:
cd /tmp/clearledger-infra && git pull && cd -
或者,您可以重新开始操作:先执行rm -rf /tmp/clearledger-infra,然后再运行git clone。
请从clearledger主仓库中运行cp命令,而不是从/tmp/clearledger-infra目录中执行这些命令。您的shell提示符应该显示“clearledger”而非“clearledger-infra”。源路径infra/manifests/...仅存在于主仓库中。
cd ~/clearledger # 进入主仓库目录——如果您的仓库路径不同,请相应调整
cp infra/manifests/auth-service/deployment.yaml /tmp/clearledger-infra/manifests/auth-service/
cp infra/manifests/ledger-service/deployment.yaml /tmp/clearledger-infra/manifests/ledger-service/
mkdir -p /tmp/clearledger-infra/manifests/vault
cp infra/manifests/vault/rotation-cronjob.yaml /tmp/clearledger-infra/manifests/vault/
cp infra/manifests/kustomization.yaml /tmp/clearledger-infra/manifests/kustomization.yaml
rm -f /tmp/clearledger-infra/manifests/auth-service/secret.yaml
rm -f /tmp/clearledger-infra/manifests/ledger-service/secret.yaml
cd /tmp/clearledger-infra
git add -A
git status
git commit -m "feat(stage-5): 在配置文件中添加Vault相关设置;从GitOps系统中删除与应用程序密钥相关的条目"
git push
cd -
✋ 实践检查点:GitOps的第5阶段已成功实施
git clone --depth 1 https://github.com/YOUR_USERNAME/clearledger-infra.git /tmp/verify-s5
test ! -f /tmp/verify-s5/manifests/auth-service/secret.yaml && echo "OK: 应用程序的秘密信息已从Git中删除"
grep vault.hashicorp /tmp/verify-s5/manifests/auth-service/deployment.yaml | head -1
grep vault/rotation-cronjob.yaml /tmp/verify-s5/manifests/kustomization.yaml
rm -rf /tmp/verify-s5
预期结果:应显示“OK”,且Vault相关的注释必须存在,同时kustomization配置文件中也应包含用于实现秘密信息轮换的脚本。
在执行提交操作之前,需要使用git status命令查看以下内容:
modified: manifests/auth-service/deployment.yaml
modified: manifests/ledger-service/deployment.yaml
modified: manifests/kustomization.yaml
new file: manifests/vault/rotation-cronjob.yaml
deleted: manifests/auth-service/secret.yaml
deleted: manifests/ledger-service/secret.yaml
在执行git push操作后,ArgoCD会自动部署已配置Vault功能的应用程序。请继续执行§5.5阶段的操作。不过需要注意的是,在您真正删除这些秘密信息之前,它们仍然存在于Kubernetes集群中,因此此时不会显示“同步完成”的状态。
常见的部署失败原因:
故障现象
解决方法
出现重复的“vault-secrets”配置项
请不要在deployment.yaml文件中声明vault-secrets卷:因为ArgoCD会自动创建这个卷
同一个服务被重复定义了两次
请将Service配置仅保留在service.yaml文件中,而不要出现在deployment.yaml文件的底部
Kyverno中的containers/0容器被设置为runAsNonRoot
请在app容器的securityContext配置中添加runAsNonRoot: true,而不仅仅是在spec.securityContext中设置
Pods的启动状态为1/1(即没有侧车容器被创建)
请确认injector.enabled=true,并且部署配置中必须包含vault.hashicorp.com/agent-inject: "true"
在运行vault-agent-init时出现权限不足错误
请运行setup.sh脚本:确保K8s的认证角色已正确绑定到服务账户上
ArgoCD在尝试同步CronJob/vault-secret-rotation时失败
可能是Kyverno阻止了该任务的执行:infra/manifests/vault/rotation-cronjob.yaml文件中必须包含runAsNonRoot、allowPrivilegeEscalation: false、capabilities.drop: [ALL]等配置项,同时还需要设置CPU和内存限制。请将相应的修复代码提交到clearledger-infra仓库中
5.5:等待Vault注入的Pod启动完成,然后删除Kubernetes中的应用程序秘密信息
请等待直到auth/ledger服务显示出 Vault相关的侧车容器为止(当状态显示为READY 2/2时,说明应用程序和Vault代理容器都已经成功部署):
kubectl get pods -n clearledger -l app=auth-service
kubectl get pods -n clearledger -l app=ledger-service
预期结果:
NAME READY STATUS RESTARTS AGE
auth-service-5756d9fcb9-bmdlr 2/2 Running 0 2m
auth-service-5756d9fcb9-jtgss 2/2 Running 0 2m
检查sidecar容器中提取的秘密信息(通过init容器的日志来查看):
kubectl logs -n clearledger \
$(kubectl get pod -n clearledger -l app=auth-service -o name | head -1) \
-c vault-agent-init
# ... 认证成功,模板正在生成中 ...
只有当所有Pod的状态都变为“2/2”时”,才能删除与应用相关的秘密信息:
kubectl delete secret auth-service-secret ledger-service-secret -n clearledger
预期结果:剩余的秘密信息应如下:
kubectl get secret -n clearledger
NAME TYPE DATA AGE
postgres-secret Opaque 2 6d
postgres-secret仅用于Postgres的初始化过程,并非应用的身份验证凭据。在您单独对Postgres进行安全配置之前,这个秘密信息会一直存在于集群中。
如果执行删除操作后出现NotFound错误,说明这些秘密信息已经被成功删除。此时可以继续执行§5.6节中的步骤。
5.5b:在删除秘密信息后,应立即同步ArgoCD
请在完成§5.5节的内容之后再执行此操作,不要在§5.4节之后立即进行。在删除与应用相关的秘密信息之前,出现“OutOfSync”状态是正常的。Git目录中可能不再显示auth-service-secret或ledger-service-secret这些秘密信息,但它们实际上仍然存在于集群中,直到您通过上述步骤将其彻底删除。
kubectl get application clearledger -n argocd \
-o jsonpath='sync={.status.sync.status} health={.status.health.status}{"\n"}'
在删除秘密信息之前:预期看到的结果应该是sync=OutOfSync health=Healthy,或者在Vault容器正在部署的过程中显示Progressing。如果auth-service和ledger-service的Pod状态都是“2/2”,那么这种状态也是正常的。
在删除秘密信息之后:如果仍然出现“OutOfSync”状态,请执行强制刷新并重新同步操作:
kubectl annotate application clearledger -n argocd argocd.argoproj.io/refresh=hard --overwrite
argocd app sync clearledger --grpc-web --prune
如果同步操作显示“另一个操作已经在进行中”,请稍等片刻:因为ArgoCD已经在自动执行同步任务了。
kubectl get application clearledger -n argocd \
-o jsonpath '{.status.sync.status} {.status.health.status}{"\n"}'
# 同步完成后,状态应显示为Healthy
在ArgoCD开始管理应用部署之后,切勿使用kubectl apply命令来手动更新这些部署配置。ArgoCD会自动确保集群环境与clearledger-infra配置保持一致。如果您手动更改了部署配置,ArgoCD可能会自动恢复到之前的设置。对于第5阶段的操作,请直接在Git中修改相关配置文件,然后让ArgoCD来同步那些启用了Vault功能的部署任务。
5.6:登录与文件注入
kubectl exec -n clearledger \
$(kubectl get pod -n clearledger -l app=auth-service -o name | head -1) \
-c auth-service -- ls /vault/secrets/
database_url
jwt_secret
curl -s -X POST http://clearledger.local/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@clearledger.io","password":"SecurePass123"}' | jq .
预期结果:
{
"access_token": "",
"token_type": "bearer"
}
请截图确认:登录请求返回的JSON数据应符合预期;同时使用命令kubectl get secret -n clearledger检查,确认系统中不存在auth-service-secret或ledger-service-secret这些秘密文件。
5.7:健康检查
make check-5
你应该看到的结果:
make check-5会首先重新执行第4阶段的检测,这是预期中的行为。请关注后面的第5阶段检测结果,以确认Vault系统是否正常运行。
▶ 第4阶段:准入控制(Kyverno)
✓ Kyverno正在运行
✓ 策略“禁止创建root容器”处于强制执行状态
...
✓ kube-bench测试结果符合基准值,没有出现新的故障或回归问题
▶ 第5阶段:秘密管理(Vault)
✓ Vault Pod正在运行
✓ Vault代理程序正在运行
✓ Vault系统已启用
✓ auth-service-secret已被移除,现在Vault是秘密数据的存储来源
✓ 文件/vault/secrets/database_url已成功注入到auth-service中
所有检测均通过,准备进入下一阶段。
如果Vault文件注入或ArgoCD同步过程中出现故障,请参阅troubleshooting.md文档进行排查。
第5阶段已完成:进入第6阶段前的检查清单
#
检查项
>验证方法
1
秘密数据仅存储在Vault中
使用命令vault kv metadata get clearledger/auth-service检查,确认current_version >= 1
2
基础设施Git仓库中不存在应用程序相关的秘密文件
确认文件secret.yaml未出现在目录clearledger-infra/manifests/auth-service/和ledger-service/中
3
ArgoCD已完成同步操作
在应用程序clearledger上,同步状态显示为“Synced Healthy”
4
K8s中的应用程序秘密文件已被删除
使用命令kubectl get secret -n clearledger检查,确认不存在auth/ledger相关的秘密文件
5
文件注入操作成功
认证相关Pod的运行状态为2/2;使用命令ls /vault/secrets/可以查看到文件database_url和jwt_secret存在
6
应用程序能够正常运行
登录后通过curl请求能获取到access_token
7
健康检查通过
执行命令make check-5后,系统会显示“All checks passed. Ready for the next stage.”
在第5阶段,应用程序的凭据会被从Git和Kubernetes Secrets中移除。不过此时Vault尚未达到生产级使用标准。本实验仍然使用Vault的开发模式,并未启用高可用性功能或自动解密机制。
另外,正在运行的Pod依然可以读取/vault/secrets/目录下的文件,因为应用程序确实需要这些凭据才能正常运行。这是正常的现象。在第6阶段,我们会添加Falco工具,以便能够检测到任何可疑的运行时访问行为。
你在第5阶段学到了什么
现在你可以在面试中这样回答:
我使用了HashiCorp Vault来替代Kubernetes Secrets,将应用程序的凭据从Git和Kubernetes Secrets中移除,并确认在Vault在运行时注入这些凭据后,应用程序依然能够正常工作。
保存你的进度:
make snapshot STAGE=5 & make snapshots
确认clearledger.stage5选项出现在快照列表中。
第6阶段——运行时安全防护(Falco)
前5个阶段主要解决了部署内容的安全性问题以及秘密数据的存储方式。而第6阶段则专注于监控运行中的容器在启动后所发生的一切。
你的目标是了解Falco工具能够检测到哪些异常行为,以及这些行为为何重要;然后通过触发Falco警报并像值班工程师一样分析这些警报来验证这一点的正确性。
CI、Kyverno和Vault都是在Pod启动之前或启动时执行的操作,而Falco则填补了它们留下的安全防护空白。它能够实时监控容器内正在运行的软件的实际行为,这才是事件响应和取证工作真正关注的重点,而不仅仅是一个需要安装的工具而已。
在开始第6阶段之前,请确保:
当你完成第6阶段的任务时,需要满足以下条件:
最后,请保存你的虚拟机配置:
make snapshot STAGE=6
make snapshots
请按照这个顺序执行步骤
每个步骤都依赖于前一个步骤的结果。在完成§6.4之前的所有步骤之前,切勿执行make check-6命令,因为该命令会检查你尚未应用的网络策略配置。
请从 §6.1 开始操作。如果遇到任何问题,请参考文件《troubleshooting.md》。
推荐阅读: 阅读文章《How Stage 6 fits the full stack optional-reading》,了解Falco与netpol的作用机制,以及它们与阶段3–5的区别。
如果在§6阶段遇到困难
§6阶段包含三项任务:
不必过于关注Falco用户界面中显示的所有信息,这些信息中可能包含一些干扰性内容。只要能在终端或用户界面中看到测试警报,就说明Falco配置成功。
如需查看演示效果的截图,请访问:
http://falco.local
登录方式:
只有当测试警报真正出现后,才进行截图操作。
常见故障点
你的想法:
实际情况:
“用户界面显示了200多条严重警报,可能是我哪里设置错了”
并非如此。`postgres-0`进程会循环读取`/etc/passwd`文件,Falco因此将其标记为异常警报,但这些警报可以忽略。
“找不到我的测试警报”
在用户界面中使用快捷键 `Cmd+F → Shell Spawned` 进行搜索,或者按照步骤4中的方法使用终端命令`grep`进行查找。如果查询结果中包含`auth-service`以及`id && exit`这些关键字,说明测试成功。
“执行`make check-6`时出现网络策略相关错误”
你是在步骤§6.4之前执行了检查命令。请先应用网络策略,然后再重新运行检查命令。
“应该先运行§6.3还是§6.2?”
只需执行`make demo-6`(即步骤§6.2)即可。步骤§6.3实际上只是用于模拟手动攻击场景,如果`demo-6`已经成功运行,则可以跳过此步骤。
“‘Shell Spawned’是什么意思?”
Falco检测到`auth-service`进程中启动了一个`sh`脚本。在生产环境中这种情况很可疑,但在实验室环境中,这是你故意操作的。具体细节请参见步骤§6.2。
“在场景4中程序出现卡顿或以代码137退出”
可能是使用了旧的`wget`命令,或者相关Pod被强制终止了。可以跳过场景4,或者使用步骤§6.4中的`python3`命令进行测试。创建检查点后再执行`make check-6即可。
6.1:安装Falco及Falcosidekick UI
bash stages/stage-6-runtime-security/scripts/install-falco.sh
该脚本会执行`helm upgrade --install`命令,并启用`modern_ebpf`配置;同时会激活Falcosidekick及Web UI界面,还会启用k8s-metacollector功能(将`collectors.kubernetes.enabled`设置为`true`),这样自定义规则就能匹配`k8smeta.ns.name = clearledger`这样的条件;脚本还会从`infra/falco/clearledger-rules-content.yaml`文件中加载规则,并将这些规则应用到ConfigMap及Ingress配置中。
如果Falco已经安装好了,重新运行这个脚本也是安全的(即不会导致升级问题)。
验证Falco Pod的状态:
kubectl get pods -n falco
预期输出结果:
NAME READY STATUS RESTARTS AGE
falco-w4fh6 2/2 Running 0 2m
falco-falcosidekick-... 1/1 Running 0 2m
falco-falcosidekick-ui-... 1/1 Running 0 2m
falco-falcosidekick-ui-redis-0 1/1 Running 0 2m
Falco DaemonSet的状态应该显示为2/2 Running;而Falcosidekick、UI界面以及Redis相关的Pod们也应该都显示为1/1 Running。不过,你们集群中Pod的命名后缀可能与示例中的不同。
打开`http://falco.local`,你就能看到Falcosidekick UI界面了。使用默认的登录信息进行登录:
字段
值
用户名
admin
密码
admin
如果你想不使用实验室预设的登录信息,而是从集群中获取这些信息来进行登录,可以执行以下命令:
kubectl get secret falco-falcosidekick-ui -n falco \
-o jsonpath '{.data.FALCOSIDEKICK_UI_USER}' | base64 -d && echo
# 输出结果示例:admin:admin
Falcosidekick UI界面使用指南
登录后,你会进入Events页面。在运行任何演示之前,这个页面上的表格可能会看起来很复杂,但这属于正常现象。
:包括Pod名称、相关文件或命令的详细信息。
有些背景信息你可以忽略掉:ArgoCD产生的警报记录,以及来自postgres-0 Pod、用于读取/etc/passwd文件的Critical等级警报(这类警报会每隔几秒重复出现一次)。不过你的演示用到的警报信息应该是不同的,请参见§6.2部分。验证自定义规则是否已成功加载(请在执行§6.2之前完成此操作):
kubectl get pods -n falco # Falco容器正在运行中
kubectl get configmap clearledger-falco-rules -n falco
kubectl logs -n falco -l app.kubernetes.io/name=falco -c falco --tail=200 \
| grep 'rules.d/clearledger_rules'
预期结果: 应显示 clearledger_rules.yaml | schema validation: ok。
--tail=30 运行grep命令并不会导致检测失败,请务必使用 。如果看到 LOAD_ERR_COMPILE_condition 这一错误信息,请参考 troubleshooting.md 文档进行排查。
自定义规则未能成功加载,那么在执行§6.2和§6.3时,系统将显示“检测通过”的结果,但实际上并未发生任何异常行为。
6.2:指导性演示(执行make demo-6命令)
请在完成§6.1步骤后运行此演示程序(此时Falco已安装完毕,规则也已验证通过,UI界面可通过 http://falco.local 访问)。
make demo-6
# 或者:
bash stages/stage-6-runtime-security/scripts/demo-falco-alerts.sh
演示脚本的功能
该演示程序能够证明Falco能够在正在运行的容器内部检测到可疑活动。