← 返回蜂巢洞察

如何从家庭实验室环境逐步搭建出一个可用于生产环境的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这套可选工具可以帮助你在本地机器上完整地运行整个系统。

本书的核心原则很简单:在介绍任何解决问题的工具之前,都会首先让你了解所要解决的具体问题。

如何完成这个实验

在整个学习过程中,在介绍每一种解决问题的工具之前,有三种习惯会帮助你顺利完成每一个阶段的学习任务。

  1. 先阅读说明再执行操作:每个命令之前的解释部分会告诉你为什么要执行这个命令。如果跳过这些说明,虽然你可以照着步骤操作,但却无法真正理解其背后的原理;而掌握这些原理正是你获得工作机会的关键。这些命令本身就是证明你理解了相关内容的证据。

  2. 要有明确的选择理由:这里提到的每一种工具都是为了解决特定的问题。为什么选择使用Vault而不是Kubernetes Secrets?为什么要把代码和配置文件分成两个仓库来管理?不要只是盲目地跟随步骤操作,而要思考“如果跳过这个步骤,会引发什么问题?”如果你真正理解了问题的本质,那么解决方案自然也会记在心里。

  3. 按顺序进行操作并验证每个检查点:每一个阶段都依赖于前一个阶段的结果。当遇到问题时,请仔细阅读错误信息。遇到困难并进行调试也是学习过程的一部分——雇主们更愿意听到“我遇到了X问题,然后通过Y方法解决了它”这样的描述。

在每个✋实践检查点处,请按照以下步骤操作:

  1. 执行相应的命令。

  2. 将你的输出结果与预期结果进行对比。

  3. 如果结果不一致,请先修复问题后再继续下一步。

  4. 当`make check-N`命令通过后,再执行`make snapshot STAGE=N & make snapshots`。只有在看到`clearledger.stageN`命令成功执行后,才能继续下一步操作。

请避免以下错误:

  • 不要因为之前的检查点已经通过就跳过它。

  • 在未先查看可用快照的情况下,切勿直接运行`make restore`命令。

  • 在所有需要输入用户名的地方,请使用你真实的Docker Hub或GitHub用户名进行替换。

  • 请在虚拟机内部执行相关命令(命令提示符会显示`ubuntu@clearledger`),而不要在你的Mac上操作。

  • 在每个重要的检查点处,请截图留念。这些截图将成为证明平台能够正常运行、能够检测到各种活动并记录相关数据的证据。

    你将使用的工具

    当有新的工具名称出现,而你想知道“为什么偏偏现在会这样”时,请回到下表查看。表格中的每一条记录都只占一行,其中列出了该工具的功能以及它出现的阶段。 **在您的笔记本电脑上:** ```html
    • Multipass用于创建Ubuntu虚拟机。

    • Docker用于构建应用程序镜像。

    • make命令会将较长的指令封装成make setupmake check-N等形式来执行。

    • /etc/hosts文件中添加如clearledger.local这样的条目,可以让浏览器顺利访问集群。

    ``` **该应用程序本身:** ```html
    • 包含三个Python API(用于身份认证、账本管理和通知功能)以及一个Web前端界面。

    • Postgres数据库用于存储数据。

    • Redis使得账本系统能够无需直接调用通知机制就能发布警报信息。

    • nginx负责将浏览器的请求路由到相应的服务节点上。

    ``` **工具功能与对应阶段:** ```html <>每次代码提交时都会构建应用程序镜像并更新基础设施相关代码;运行器位于虚拟机内部,以便能够访问本地集群。 <>监控clearledger-infra仓库中的配置变化,确保集群状态与Git代码库保持一致;同时会自动回滚未经授权的修改。 <>阻止包含敏感信息(如API密钥、令牌等)的提交操作。 <>用于检测Python代码中的安全风险,例如不安全的注入语句或硬编码的凭据信息。 <>对Dockerfile及Kubernetes配置文件进行合规性检查,发现任何错误配置。 > 对构建出的容器镜像以及pip/npm包中的漏洞进行扫描。 > 生成软件成分清单,并对最终生成的软件产品进行漏洞检测。 > 为容器镜像添加签名;在部署阶段,未签名的镜像会被拒绝。 > 在集群入口处实施准入控制,阻止不符合安全要求的Pod进入集群(例如缺少限制配置的容器、未签名的镜像等)。 > 将敏感凭据存储在Git和etcd之外,并在Pod启动时通过侧车机制将这些凭据注入到相应的Pod中。 > 通过eBPF技术实时检测容器内部的异常行为,例如在运行中的容器内启动shell命令或读取敏感文件时会发出警报。 > 在Pod之间设置Kubernetes防火墙,从而限制某项服务被攻击后对其他服务的影响范围。 > 故意破坏某些Pod,以验证应用程序是否能够正常恢复运行(可选功能)。 > 收集各种指标数据、生成仪表盘,并支持日志搜索功能,有助于将安全事件转化为可用的证据。 > 提供分布式追踪功能,可以显示某个请求在各个服务之间是如何分布执行的(可选功能)。 > 用于AWS平台的基础设施即代码管理方案,相同的应用程序可以使用云服务商提供的托管服务。
    工具名称 功能简介 所处阶段
    MicroK8s / kubectl 在虚拟机内部运行Kubernetes集群,kubectl用于与之交互。 0
    clearledger(代码仓库) 包含应用程序代码及持续集成工作流程,用于构建最终产品。 1
    clearledger-infra(代码仓库) 仅包含Kubernetes配置文件,用于定义集群应运行的组件;持续集成工具会更新这些配置,ArgoCD负责将其部署到集群中。 1
    GitHub Actions + 自托管运行器 1
    ArgoCD 2
    Gitleaks 3
    Semgrep 3
    Checkov 3
    Trivy 3
    Syft + Grype 3
    Cosign 3
    Kyverno 4
    Vault 5
    Falco 6
    网络策略 6
    LitmusChaos 6.5
    Prometheus / Grafana / Loki 7
    OpenTelemetry + Tempo 7.5
    Terraform / EKS / ECR / RDS 8

    每个阶段都会增加一层新的安全防护措施。这些工具是不可互换的:扫描器会在代码和图像被部署之前对其进行检查,ArgoCD会确保集群与Git保持同步,Vault用于管理敏感信息,Kyverno会在不安全的作业负载运行之前阻止它们被执行,而Falco则会在这些作业负载运行后监控任何可疑行为。

    正因为如此,执行这些步骤的顺序非常重要——你正在一层层地构建起完善的防御体系。

    如何选择适合自己的路径

    在配置集群之前,先根据你的主机内存情况来选择相应的路径。如果在实验过程中因为内存不足导致系统崩溃,或者硬盘空间不足而影响实验进度,那么重新选择路径将会浪费一整天的时间,因此请事先做好决定。

    >
    你的实际情况 应选择的路径你能获得什么
    主机内存为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的内存,如果你的笔记本电脑无法满足这个要求,请选择这条路径。

    本指南中推荐的默认路径假设用户拥有24GB以上的内存,并且可以使用完整的本地虚拟机环境(请参见“开始之前”部分)。如果你的实际情况不符合这些条件,请从与你的硬件配置相匹配的选项开始操作。

    如何保存实验进度

    仅适用于Mac系统且使用了Multipass工具的情况:执行make snapshotmake restore命令时需要使用Multipass。如果你使用的是Linux系统且没有安装Multipass,那么请跳过快照备份功能,如果遇到问题可以按照备选方案B进行操作。

    完成这个实验流程通常需要几天的时间。

    你的源代码保存在电脑上,因此重新创建虚拟机或删除虚拟机并不会导致Git仓库、提交记录、配置文件等数据丢失。

    虚拟机中存储着你的运行环境,包括已部署的Pod容器、Vault中的敏感信息、Postgres数据库的数据以及Grafana监控面板的内容。

    保存你的实验进度

    在完成每个实验阶段后,请先创建一个快照再继续下一步操作。例如:

    make snapshot STAGE=7
    make snapshots
    

    务必执行make snapshots命令,以确保快照已经成功创建。

    恢复你的实验进度

    如果虚拟机在一段时间后变得无法使用,请恢复最新的可用快照:

    make snapshots
    make restore STAGE=7
    
    export KUBECONFIG=~/.kube/clearledger-config
    make check-7
    

    如果虚拟机出现故障会怎样?

    你将保留以下内容:

    • 你的Git仓库

    • 你所做的所有提交操作

    • 文件`/.env`

    • 文件`setup-cluster.local.env`

    • GitHub上的项目`clearledger-infra`

    但是,在最后一次快照之后,虚拟机中存储的所有数据都会丢失,包括:

    • 正在运行的Pod容器

    • Vault中的加密密钥

    • Postgres数据库中的数据

    • Grafana和Loki中的数据

    因此,建议在每个阶段完成后都创建快照。

    恢复最新的可用快照,然后从该阶段继续操作。

    make snapshots
    make restore STAGE=6
    
    export KUBECONFIG=~/.kube/clearledger-config
    make check-6
    

    方案B:没有快照

    需要重新搭建实验环境。

    make teardown
    make setup
    
    export KUBECONFIG=~/.kube/clearledger-config
    

    你的Git仓库仍然完好无损,但Kubernetes集群会恢复到初始状态。请从你之前停下的那个阶段开始继续操作,然后重新搭建平台。

    如果遇到磁盘空间不足、快照创建失败、Mac设备进入睡眠模式或重启问题、Vault认证错误,或者Pod容器陷入CrashLoopBackOff循环等问题,请参阅troubleshooting.md以获取详细的解决方法。

    本书适合哪些人群?

    初级DevOps工程师(工作0–2年):请按顺序完成所有阶段,不要跳过任何步骤。预计完成第0–2阶段需要一整天的时间,第3–7阶段各需半天,第8阶段则只需几个小时。这是正常的流程,请不要着急。

    中级DevOps工程师(工作2–4年):可以快速浏览第0–2阶段的内容以了解整个应用程序的架构,然后重点关注第3–7阶段,因为这些阶段涉及安全配置的相关内容。

    面试准备:请完成第4阶段的所有操作,然后再阅读docs/interview-prep.md。面试中的问题会基于本实验环境中的实际内容来设计。

    如何配置你的机器?

    相关要求详见上文的前置条件部分。在安装之前,请确保你的电脑拥有24GB的RAM、6个CPU核心以及80GB的空闲磁盘空间。

    安装必备工具

    工具名称 功能 macOS Linux Windows
    Multipass 可在你的笔记本电脑上创建轻量级的Ubuntu虚拟机 brew install --cask multipass sudo snap install multipass multipass.run/install
    kubectl 用于通过终端与Kubernetes集群进行交互 brew install kubectl sudo snap install kubectl --classic winget install Kubernetes.kubectl
    Helm Kubernetes的包管理工具(类似于apt或brew,但用于管理集群应用) brew install helm sudo snap install helm --classic winget install Helm.Helm
    Docker Desktop 可在本地机器上构建容器镜像 docker.com docker.com docker.com
    jq 用于格式化JSON数据,使其更易于阅读 brew install jq sudo apt install jq winget install jqlang.jq

    Windows用户:请在WSL2 Ubuntu环境中运行所有命令。本实验不建议使用PowerShell,因为配置过程需要使用make命令以及Bash脚本。

    在继续之前,请先验证以下内容:

    multipass --version
    kubectl version --client
    helm version
    docker --version
    jq --version
    

    如果有任何命令执行失败,请先安装缺失的工具,然后再继续操作。

    如何开始实验

    实验的主要流程从阶段0:运行中的系统开始。

    按照步骤完成配置后,下次可以直接使用以下快捷命令来启动实验:

    make setup
    export KUBECONFIG=~/.kube/clearledger-config
    kubectl get nodes
    

    预期结果应该是:系统中存在一个名为clearledger的节点,其状态应为Ready

    make setup命令会配置Multipass虚拟机、安装MicroK8s、设置磁盘使用限制,并更新/etc/hosts文件。整个过程需要3到5分钟。

    如何管理磁盘空间

    本实验是在一个单节点的MicroK8s虚拟机上进行的,该虚拟机的磁盘容量为80GB。随着时间的推移(尤其是在进行CI构建、Helm升级或执行阶段7的相关操作后),容器镜像、日志文件等数据可能会占用大量的磁盘空间,从而导致系统出现故障。

    make setup命令会自动设置一些预防性措施,例如定期清理日志文件、控制镜像占用的内存大小以及限制journald日志文件的存储容量。有关详细的配置信息,请参阅troubleshooting.md文件。

    检查磁盘健康状况:

    make doctor    # 结果可能为PASS、WARN或FAIL,同时还会显示PVC及Prometheus TSDB的占用情况

    清除虚拟机中未使用的文件,但请不要删除应用程序的数据:

    make reclaim

    如果执行make doctor后仍然显示失败结果,那么您可能需要先执行make teardownmake setup命令,然后从备份状态中恢复系统。详细操作指南请参见troubleshooting.md文件。

    如何在不使用Kubernetes的情况下测试应用程序

    如果您的计算机没有足够的资源来运行Kubernetes,那么您可以使用Docker Compose来启动ClearLedger应用程序。

    docker compose -f docker-composeintegration.yml up --build -d
    

    然后打开http://localhost:3000页面,开始使用该应用程序。

    当您完成测试后,可以关闭Docker Compose环境,然后继续进行阶段0的实验。

    docker compose -f docker-composeintegration.yml down
    

    如何首次登录

    首先,您需要注册账户。每次执行新的up(或down -v)命令后,数据库都会被清空。请使用真实的电子邮件地址进行注册(Pydantic会拒绝接受@*.local这类地址),例如,可以使用test@clearledger.io作为电子邮件地址,SecurePass123作为密码。

    然后使用相同的账号信息登录。

    如果输入的密码错误,系统会显示“电子邮件或密码不正确”。如果仍然遇到问题,可以尝试强制刷新页面,或者在浏览器控制台中执行localStorage.removeItem('cl_token')命令。

    如何运行演示流程

    首先,请访问http://localhost:3000进行注册并登录。然后提交一些借方和贷方交易记录(例如,工资+5000美元,租金-1200美元)。

    接着确认余额变化情况以及交易历史记录是否正确显示。

    现在尝试提交一笔金额≥ 10,000美元的交易,此时警报面板应会显示“LARGE TRANSACTION”提示。

    这里还有一个可选的测试方法:使用相同的基地址来运行以下命令:

    BASE_URL=http://localhost:3000 bash scripts/dast/smoke.sh
    

    如何配置本地域名

    请将ClearLedger的相关主机名添加到您的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环境中进行配置

    首先获取您的WSL IP地址:

    ip -4 addr show eth0 | grep inet
    

    使用获取到的IP地址(或在您的机器上能够正常使用的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
    

    如果您在Windows系统中使用Chrome或Edge浏览器,而不是WSL环境,请将相同的配置添加到:

    C:\Windows\System32\drivers\etc\hosts

    最后验证配置是否生效:

    curl http://clearledger.local/auth/health
    

    阶段0——运行中的系统环境

    起始状态:目前还没有任何应用程序被部署,因此您需要手动构建Kubernetes集群并安装ClearLedger。

    目标:完成这个阶段的配置后,ClearLedger将会在Kubernetes环境中正常运行。您可以手动注册用户、提交交易记录,并查看合规性警报信息,整个过程完全不依赖自动化工具来完成。

    所有的部署、更新和修复操作都是手动完成的。这是有意为之的。在自动化某个平台之前,你首先需要了解该平台在未自动化的状态下是如何运行的。

    0.1:配置集群

    接下来,你需要在自己的笔记本电脑上创建一台虚拟机,这台虚拟机将运行自己的Kubernetes集群。可以把它想象成电脑内部的一个小型数据中心。

    Multipass可以创建轻量级的Ubuntu虚拟机,而MicroK8s则是一个精简版的Kubernetes发行版,它在这些虚拟机中运行。两者结合使用,就可以让你在不依赖云资源的情况下构建一个真正的Kubernetes集群。

    推荐操作:执行一条命令:

    make setup
    export KUBECONFIG=~/.kube/clearledger-config
    kubectl get nodes
    

    预期输出结果如下:

    NAME          STATUS   ROLES    AGE   VERSION
    clearledger   Ready       2m    v1.29.x
    

    命令`make setup`会依次执行`scripts/setup-cluster.sh`(用于配置虚拟机、MicroK8s以及磁盘安全设置)和`scripts/set-up-hosts.sh`(用于修改`/etc/hosts`文件)。整个过程需要3到5分钟。

    磁盘安全相关设置(如日志轮换机制、图像垃圾回收阈值、journald日志存储限制等)会自动完成配置。更多详细信息,请参阅troubleshooting.md文件。

    如果集群的状态显示为`NotReady`,请等待60秒后再尝试一次。

    如果`make setup`命令失败,你需要逐步排查问题,可以参考以下手动配置步骤:

    multipass launch \
      --name clearledger \
      --cpus 6 --memory 12G --disk 80G \
      22.04
    

    获取虚拟机的IP地址(这个地址在配置`/etc/hosts`文件时需要用到):

    multipass info clearledger | grep IPv4
    

    然后需要添加相应的主机条目。具体操作方法请参考上文中的域名配置部分,或者直接运行`sudo bash scripts/set-up-hosts.sh`命令。

    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   # 退出虚拟机,返回主机系统
    

    接下来,从你的主机系统连接至虚拟机,并执行以下命令配置`kubeconfig`文件:

    multipass exec clearledger -- microk8s config > ~/.kube/clearledger-config
    export KUBECONFIG=~/.kube/clearledger-config
    kubectl get nodes
    

    0.2:在部署应用程序之前先了解其工作原理

    在运行任何`kubectl`命令之前,请先阅读这些代码文件。只有先了解代码的具体内容,才能理解后续的所有操作步骤。

    文件 功能
    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用户身份、固定的基础镜像以及健康检查功能。

    请注意,每个Dockerfile中都有一行代码: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
    

    这个密码保存在一个YAML文件中,任何拥有仓库访问权限的人都可以看到它。base64编码其实是一种简单的转码方式,并非真正的加密技术,因此解码过程非常简单。请记住这一点——正因为如此,才需要在第5阶段采取额外的安全措施。

    0.3:Docker Hub配置

    你需要一个容器注册服务,用来存储构建好的镜像,以便集群能够下载这些镜像。Docker Hub是目前最常用的选择,但在第8阶段,你会将其替换为私有注册服务(ECR)。

    在Docker Hub上创建四个公共仓库(使用免费账户,访问地址为hub.docker.com):

    1. 访问hub.docker.com

    2. 点击“创建仓库”按钮

    3. 选择你的Docker Hub用户名作为仓库的命名空间

    4. 从下拉列表中选择一个仓库名称

    5. 将仓库的可见性设置为“公共”

    6. 点击“创建”按钮

    7. 对所有四个服务重复上述步骤

    YOUR_USERNAME/clearledger-auth-service
    YOUR_USERNAME/clearledger-ledger-service
    YOUR_USERNAME/clearledger-notification-service
    YOUR_USERNAME/clearledger-frontend
    

    接下来,需要生成一个访问令牌。请登录hub.docker.com,进入“账户设置”→“安全”选项,然后点击“创建新访问令牌(读/写/删除)”。保存这个令牌,因为之后你不会再看到它了。

    图片截图,用于指导如何创建访问令牌截图展示了如何创建访问令牌
    docker login
    # 用户名:你的Docker Hub用户名
    # 密码:访问令牌(而不是你的账户密码)
    

    构建并推送这四个服务:

    # 将“your-username”替换为你的Docker Hub用户名,在本实验中所有地方都使用相同的字符串
    export DOCKER_USERNAME=your-username
    echo "正在使用用户名 $DOCKER_USERNAME"
    

    ✋ 实践检查点:Docker Hub用户名

    # 必须输出你的真实用户名,而不能是“your-username”这个字面字符串
    echo "$DOCKER_USERNAME"
    

    预期结果:应该显示一行你的Docker Hub用户名(例如 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
    

    ✋ 实践检查点:Docker Hub上的镜像

    打开 hub.docker.com,进入你的个人资料页面,然后选择 仓库。确认这四个以 clearledger-* 开头的仓库都存在,并且每个仓库的标签都显示为 v0.1.0

    截图展示了完成操作后Docker镜像仓库的样子

    在你的笔记本电脑上运行以下命令:

    docker pull $DOCKER_USERNAME/clearledger-auth-service:v0.1.0
    

    预期结果应该是 Status: 已下载到更新后的镜像镜像已更新为最新版本,而不是 仓库不存在操作被拒绝

    0.4:在应用之前先查看清单文件

    Kubernetes使用 清单文件(YAML格式)来描述它需要创建的资源。你不需要点击按钮,只需声明所需的状态,Kubernetes就会自动创建相应的资源。

    在部署ClearLedger之前,请先快速浏览一下这些清单文件:

    • 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:规定集群内部哪些用户可以执行哪些操作。

    你并不需要立刻理解所有这些内容。目前的目标仅仅是了解在Kubernetes创建应用程序之前,它是如何被描述的。

    在§0.6之后的两个可选章节中,你会了解到Ingress路由和RBAC的工作原理。现在,只需关注在Kubernetes创建应用程序之前,相关配置是如何被定义的即可。

    0.5:分层部署ClearLedger

    整个部署过程分为六个阶段。完成一个阶段后才能开始下一个阶段。在完成第2、第3和第6个阶段后,运行`kubectl get pods -n clearledger`来确认进度。

    设置一个路径变量,并确认你的用户名仍然有效:

    export DOCKER_USERNAME=your-username   # 如果在§0.3中已经设置了这个变量,则可以跳过这一步
    STAGE0=stages/stage-0-raw-kubernetes/infra/manifests
    

    0.5.1 — 第1阶段:命名空间与RBAC配置

    在创建命名空间之前,其他任何内容都无法被生成。同样,在工作负载能够引用ServiceAccounts之前,也必须先配置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数据库配置

    在auth-service或ledger-service启动之前,必须先确保PostgreSQL数据库已经运行起来。这两个服务在启动时会连接到Postgres数据库以执行迁移操作并处理请求;如果数据库还没有准备好,这些服务就会陷入无限循环状态。

    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
    

    执行`kubectl apply`命令后,预期会看到以下输出:

    secret/postgres-secret created
    persistentvolumeclaim/postgres-pvc created
    statefulset.apps/postgres created
    service/postgres created
    

    当`kubectl wait`命令成功执行完毕时,命令会无任何输出地结束(退出代码为0)。如果等待时间超过限定值,请先参考下面的“如果Postgres仍处于待处理状态”部分,然后再继续操作。

    验证结果:

    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
    

    如果Postgres仍处于待处理状态(即`kubectl wait`命令超时,或者Pod的状态显示为“0/1 Pending”,PVC的状态显示为“Pending”):

    Postgres需要一个PersistentVolumeClaim,也就是集群中的磁盘空间。MicroK8s通过hostpath-storage插件提供了这一功能。如果make setup过程被中断,或者你采用了手动配置方式且没有执行microk8s enable storage命令,那么PVC就无法绑定到任何磁盘空间上,从而导致Pod无法正常调度。

    查看系统日志,你通常会看到类似这样的信息:

    警告:FailedScheduling……该Pod没有可绑定的PersistentVolumeClaim
    正常状态:FailedBinding……当前没有可用的持久化存储资源,且未设置任何存储类别

    请在虚拟机上解决这个问题,然后重新启动Postgres Pod。请从你的主机上运行以下命令:这个命令在macOS、Linux或Windows PowerShell中都可以使用(因为Multipass已经安装在主机上,它会在虚拟机内部执行这些操作):

    # 启用存储功能(如果之前跳过了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   Running

    在Postgres没有成功启动之前,切勿继续部署auth-service或ledger-service。如果没有数据库,这些服务会陷入无限循环状态而无法正常运行。

    0.5.3 — 第三层:Redis

    为什么需要使用Redis?(举个简单的例子):假设有一笔15,000美元的转账交易,ledger-service会首先将这笔数据保存到Postgres中,然后通过Redis发送一条消息:“大型交易,用户X,金额15,000美元。”notification-service会监听这条消息,并生成相应的合规性警报信息;你后来可以通过访问/notifications/alerts来查看这些警报。

    ledger-service和notification-service并不会直接相互调用。Redis在这里充当了消息中转站的角色:ledger-service负责发送消息,notification-service则负责接收并处理这些消息。因此,在部署notification-service之前,必须确保Redis已经运行起来(这也是为什么要在部署Postgres之后、在应用层之前就先安装Redis的原因)。

    解释Redis工作原理的流程图
    kubectl apply -f infra/manifests/redis/redis.yaml

    验证配置是否正确:

    kubectl get pods -n clearledger -l app=redis

    预期结果:

    NAME                     READY   STATUS    RESTARTS   AGE
    redis-xxxxxxxxxx-xxxxx   1/1     Running   0          30s

    0.5.4 — 第四层:应用程序密钥

    在阶段0中,这些认证信息存储在Kubernetes的Secrets中;而在阶段5中,这些信息会被移至Vault中。

    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_urljwt_secret;而ledger-service-secret文件只包含一项密钥:database_url。在阶段5中,这些密钥将会被替换为Vault中的存储方式,但目前它们仍然以Kubernetes Secrets的形式存在于集群中。

    0.5.5 — 第五层:应用程序工作负载

    您即将启动四个应用程序服务:auth、ledger、notification和frontend。Postgres、Redis以及前两层中配置的Secrets已经准备就绪,现在Kubernetes需要从Docker Hub下载这些镜像,并将它们作为Pod运行起来。

    通常情况下,每个服务都需要对应两个文件:一个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的镜像,但这样的镜像实际上是不存在的。

    为什么我们要使用阶段0文件夹呢?因为这个仓库中保存了多份Kubernetes配置文件。对于这次手动部署操作,请使用路径stages/stage-0-raw-kubernetes/infra/manifests/来获取相应的文件。这些文件是为阶段0准备的,其中包含了占位符DOCKER_USERNAME,后续的命令会将其替换为实际的用户名。请暂时不要使用infra/manifests/路径,因为那些文件是用于后续的GitOps部署流程的。

    请按照顺序部署每个服务。在仓库根目录下执行以下命令,此时请确保DOCKER_USERNAME变量已经设置好:

    1. auth-service:登录与注册功能

    sed "s|DOCKER_USERNAME|${DOCKER_USERNAME}|g" \
      "$STAGE0/auth-service/deployment.yaml" | kubectl apply -f -
    kubectl apply -f infra/manifests/auth-service/service.yaml
    

    2. ledger-service:用于处理交易记录及余额计算(需要使用Postgres数据库以及你在§0.5.4节中创建的秘钥)

    sed "s|DOCKER_USERNAME|${DOCKER_USERNAME}|g" \
      "$STAGE0/ledger-service/deployment.yaml" | kubectl apply -f -
    kubectl apply -f infra/manifests/ledger-service/service.yaml
    

    3. notification-service:该服务会监听Redis中的警报信息,用于通知大型交易的发生(此服务不需要使用任何数据库秘钥)

    sed "s|DOCKER_USERNAME|${DOCKER_USERNAME}|g" \
      "$STAGE0notification-service/deployment.yaml" | kubectl apply -f -
    kubectl apply -f infra/manifests/notification-service/service.yaml
    

    4. frontend:用于提供Web用户界面(相关的部署配置文件都保存在同一个文件中)

    sed "s|DOCKER_USERNAME|${DOCKER_USERNAME}|g" \
      "$STAGE0/frontend/deployment.yaml" | kubectl apply -f -
    

    验证结果(所有应用相关的Pod都应该处于Running状态:由于auth-service和ledger-service需要与Postgres建立连接,因此它们可能需要大约30秒的时间才能完成初始化):

    kubectl get pods -n clearledger
    

    预期结果应该是:除了之前创建的Postgres、Redis容器外,还会看到每个应用对应的新Pod(具体的Pod名称可能会有所不同):

    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
    

    如果发现auth-service或ledger-service处于CrashLoopBackOff状态,請查看相关日志:

    kubectl logs -n clearledger deploy/auth-service --tail=20
    

    常见原因:可能是你使用了infra/manifests/*/deployment.yaml文件,而不是前面提到的Stage 0阶段的配置文件;此时日志中可能会显示“DATABASE_URL未设置”的错误信息。请重新执行这一节中提到的sed命令和kubectl apply命令即可。

    ✋ 实践检查点:在部署Ingress组件之前,需要确认所有相关工作负载都已经准备就绪

    kubectl get deployment -n clearledger
    kubectl get pods -n clearledger --field-selector/status.phase!=Running
    

    预期结果应该是:存在四个部署对象(auth-serviceledger-servicenotification-servicefrontend),且它们的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
    

    预期的最终状态为(所有Pod的状态都显示为“Running”时,按Ctrl+C停止监控):

    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
    

    如果某个Pod的状态仍然显示为“Pending”或“CrashLoopBackOff”,以下两个命令可以帮助你找出问题所在:

    kubectl describe pod POD_NAME -n clearledger
    kubectl logs POD_NAME -n clearledger --previous
    

    0.6:验证系统运行状态

    在浏览器和curl命令中,都应使用同一个测试账户,以避免出现冲突:

    字段
    电子邮件 test@clearledger.io
    密码 SecurePass123

    如果你已经使用另一个密码在浏览器中注册过账户,那么请使用那个密码登录;或者选择一个新的电子邮件地址。因为下面的curl命令必须使用你实际注册时使用的同一组邮箱和密码

    在浏览器中打开http://clearledger.local,你应该会看到ClearLedger的登录页面。

    clearledger login screen UI screenshot

    点击注册按钮,使用test@clearledger.ioSecurePass123创建账户(与下面的curl命令中使用的信息相同)。Pydantic系统会拒绝明显虚假的电子邮件地址,例如test@test.com)。

    使用该邮箱和密码登录。首次登录时,控制面板会自动显示一些演示交易记录。请稍等几秒钟,这些记录就会出现。

    登录后Clearledger界面的截图

    请查看当前余额选项卡。其中应该会显示以美元为单位的数据,并附带一条折线图。

    再看看交易历史记录。你会看到诸如“薪资(Acme Corp)”、“5月租金”之类的条目。

    最后,请注意底部的警报提示面板。你会看到标有红色标记的大额交易警报提示。在演示案例中,有两笔交易的金额超过了10,000美元,因此系统会自动发出合规性警报。

    现在你可以自己尝试提交一笔金额超过10,000美元的交易,然后实时观察警报数量的变化。

    需要注意的事项:

    • 每笔交易完成后,余额会立即更新。

    • 贷方金额会显示为绿色的+$符号,借方金额则会显示为红色的−$符号。

    • 当你提交一笔金额≥10,000美元的交易时,警报提示的数量会增加。

    • 每条警报都会显示交易金额、方向以及交易时间戳。

    登录并完成交易后Clearledger界面的截图

    请截取显示交易记录以及至少一条警报提示的仪表盘截图。这份截图就是你的第一份“作品集”。

    或者,你也可以使用curl命令进行操作(建议使用同一账户;如果浏览器无法正常使用,这个方法会非常有用):

    # 注册账号(如果你已经通过浏览器使用了相同的邮箱注册过,可以跳过这一步)
    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 .
    

    预期返回的结果应该是一个包含idamount: 15000以及direction: "debit"字段的交易对象。

    # 查看当前余额
    curl -s http://clearledger.local/ledger/balance \
      -H "Authorization: Bearer $TOKEN" | jq .
    
    # 确认是否触发了通知警报
    curl -s http://clearledger.local/notifications/alerts | jq .
    

    预期结果(仅使用curl访问,不通过浏览器测试)应该是:对于金额为$15,000的交易,至少会有一条警报信息,例如:{"total":1,"alerts":[{"type":"LARGE_TRANSACTION","amount":15000,...}]}。如果你已经使用过浏览器进行测试,total的值可能会是3或更多(包括两条演示警报和你的那条警报),这也是正常的。

    如果你看到{"detail":"Unauthorized"},说明:你的令牌已经过期了。出于安全考虑,JWT令牌的有效期是有限的,这是有意设计的。请重新运行上面的登录命令来获取新的令牌,然后再尝试执行之前失败的命令。

    这种情况仅会影响当前终端会话中的$TOKEN变量。如果你打开一个新的终端窗口,就需要再次运行登录命令,因为$TOKEN在不同会话之间是不会持续保留的。

    make check-0
    

    了解Ingress机制(可选阅读)

    如果你想了解clearledger.local是如何与你的Pods建立连接的,可以在阅读§0.6之后再阅读这部分内容。

    你的集群运行着四个应用程序服务:前端服务、认证服务、账本服务和通知服务。每个服务在集群内部都有一个内部的Service地址,但在没有Ingress将外部流量引导过来之前,这些服务是无法通过浏览器访问的。

    Ingress就相当于“大门”。当有请求到达clearledger.local时,Kubernetes会根据URL路径将请求转发到相应的服务。例如,对/auth的请求会被转发到认证服务,对/ledger的请求会被转发到账本服务,对/notifications的请求会被转发到通知服务,而对/的请求则会直接到达前端服务。

    请打开infra/manifests/ingress.yaml文件并阅读其中的注释。API路径使用了重写规则:例如,/auth/login在到达认证服务之前会被重写为/login,这样后端的路由结构就能保持简洁明了。

    之后你还可以添加更多的主机名(如grafana.localargocd.local等),每个主机名都会在后续阶段生成对应的Ingress配置文件。目前这个文件仅用于ClearLedger应用程序的配置。

    解释Ingress工作原理的流程图

    了解RBAAC机制(可选阅读)

    Ingress用于控制来自集群外部的流量,而RBAAC则用于控制集群内部的权限分配。

    这个文件为clearledger命名空间创建了相应的身份标识和权限设置。

    请打开infra/manifests/rbac/rbac.yaml文件。文件顶部的注释详细解释了这些配置的含义和用途。

    ServiceAccount是用于表示Pod的身份标识。例如,auth-serviceledger-servicenotification-service各自都会获得属于自己的身份标识。

    Role则规定了该身份标识被允许执行哪些操作。在你的代码仓库中,应用程序所使用的角色权限是非常有限的:它们仅能获取列出

    RoleBinding则负责将身份标识与相应的权限关联起来。如果没有RoleBinding,虽然该角色存在,但没有任何Pod能够获得这些权限。

    clearledger-viewer这个ServiceAccount仅用于只读调试目的。它可以查看Pod、服务、端点、事件以及配置映射文件的内容,但无法读取Secrets文件。

    默认情况下,所有的ServiceAccount都会被绑定到一个没有任何权限的角色上。这样一来,如果某个Pod忘记了设置serviceAccountName,那么它就会使用一个根本无法执行任何操作的“身份标识”来运行。

    这种设计的核心理念就是“最小权限原则”:即使某个Pod受到了攻击,Kubernetes也不会赋予它对整个集群的广泛访问权限。

    0.7:为什么手动部署不可靠

    在阶段0中,你是通过手动方式构建并部署应用程序的。现在你只需对代码进行一个小的修改,然后再重新部署它。这一过程恰恰暴露了手动部署方式存在的问题:这类部署难以追踪、难以回滚,而且也很难验证其实际效果。而阶段1和阶段2则通过使用CI和GitOps技术解决了这些问题。

    步骤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
    

    等待大约30秒,让Kubernetes下载新的镜像并重启相关的Pod:

    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命令。如果有多人拥有对集群的访问权限,那么根本无法确定是谁修改了什么内容。

    • 到底改变了什么?唯一的证据就是Docker Hub上的标签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)正是为了解决这些问题而存在的。它的作用是:每当有人向代码仓库提交代码时,系统就会自动执行相同的构建、测试和打包流程。

    可以把持续集成想象成一条生产线:

    flow diagram explain gihub ci flow
    开发者提交代码
            ↓
    GitHub检测到提交请求
            ↓
    GitHub Actions启动构建流程
            ↓
    运行工具执行相应的任务
            ↓
    Docker镜像被生成并上传
            ↓
    基础设施配置文件会更新为新的镜像标签信息(详见clearledger-infra中的§1.3部分)
    

    关键在于:构建过程不再依赖于你的笔记本电脑。你的笔记本电脑负责编写代码,而自动化流程则负责生成最终的可发布版本。

    <一个持续集成系统由三个部分组成:

    1. 流水线主机:负责控制整个流程。它会检测到代码推送事件,并决定执行哪个工作流。在这个实验中,这个角色由GitHub Actions承担。

    2. 流水线配置文件:其中包含了具体的执行指令。这个文件是一个YAML格式的文件,位于路径.github/workflows/ci.yaml,用于指定需要执行哪些任务。

    3. 执行器:实际负责运行流水线中指令的机器。

    通常情况下,GitHub Actions会使用由GitHub托管的云端执行器。但在本实验中,这种配置并不适用——因为你的Kubernetes集群位于本地虚拟机中,而GitHub的云端执行器无法访问它。因此,你需要在本地虚拟机中安装一个执行器,以便利用本地的Docker守护进程来构建Docker镜像。

    具体来说,你需要在虚拟机中安装一个自托管的执行器。这个执行器会与GitHub建立连接,接收任务指令,然后在本地环境中执行相应的操作,因为本地环境可以访问所有所需的资源。

    本实验需要使用两个仓库:clearledger(包含代码及CI配置)和clearledger-infra(仅包含Kubernetes相关的YAML文件)。第二个仓库将在§1.3节中创建。

    展示自托管GitHub流水线流程的图表
    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相关的配置文件,请不要在这里创建它。

    首先,需要将应用程序代码仓库放置在GitHub Actions能够访问的位置。 登录GitHub,然后点击“新建仓库”:
    • 仓库名称:请填写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信息。

    1. 访问https://github.com/YOUR_USERNAME/clearledger页面

    2. 进入“设置”→“操作”→“构建工具”,然后选择“新建自托管构建工具”

    3. 选择“Linux”和“x64”系统版本

    该页面的标题应该显示为:添加新的自托管构建工具 · YOUR_USERNAME/clearledger

    e0aa537c-a2b2-4ed7-8b48-f686a012ea8d

    这个页面包含三个你需要使用的部分:

    GitHub页面内容 对应操作
    下载 mkdircurltar命令复制到虚拟机中(请使用与下面示例相同的版本)
    配置 从文件./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` 这一标签。工作流程中确实需要使用这个标签:

    图示说明如何在 GitHub 用户界面中添加该标签
    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-hostedclearledger这些关键字。

    如果标签中缺少clearledger这个词,请在重新运行工作流之前将其添加到运行器设置中。仅使用运行器名称是不足以完成配置的。

    ✋ 实践检查点:运行器已准备好执行任务

    仍然在GitHub的“设置”→“动作”→“运行器”页面,再次确认以下信息:

    字段 预期值
    状态 空闲(绿色显示)
    标签 必须包含self-hostedclearledger
    操作系统 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.yamlservice.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`仓库的步骤如下:

    1. 登录GitHub,然后点击“新建仓库”。

    2. 将仓库命名为`clearledger-infra`。

    3. 选择“公共仓库”选项。

    4. 不要为这个仓库添加`README.md`文件。

    5. 最后点击“创建”按钮。

    6. 之后,持续集成流程会自动更新`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以及进行图像签名操作:

      • Docker Hub凭证,用于上传图像。

      • GitHub凭证,用于将图像标签更新推送到clearledger-infra仓库中。

      • Cosign凭证,用于在上传图像后对其进行签名操作。

      **请不要**将这些凭证值直接粘贴到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仓库中使用的令牌。

      Docker界面中用于配置PAT的截图 Docker界面中用于配置令牌权限范围的截图

      秘密信息4和5:COSIGN_PRIVATE_KEYCOSIGN_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密钥时使用的密码 用于在签名过程中解锁私钥
      显示我的仓库秘钥信息的GitHub界面截图

      仓库变量(并非秘钥)(可选),可在后续阶段进行配置。请将它们添加到设置 > 秘钥与变量 > 动作 > 变量选项中:

      >
      变量名称 适用阶段何时启用该变量
      ENABLE_ARGOCDSYNC 保持未设置状态 第2阶段——在ArgoCD完成首次同步且验证通过后启用
      ENABLE_DAST 保持未设置状态 第3阶段——在应用程序在clearledger.local上正式上线后启用

      请不要在第1阶段添加这两个变量。如果你现在就设置它们,CI系统会在集群尚未准备好之前尝试更新ArgoCD或运行ZAP工具,从而导致管道输出结果难以理解。指南中明确指出了每个变量的具体启用时机——你只需要记住这两个变量确实存在即可。

      你所证明的内容:该管道能够在不将凭证硬编码到仓库中的情况下,与外部系统进行身份验证。

      1.5:在激活管道之前先了解其工作原理

      不要把工作流文件视为某种具有神奇功能的工具。在运行它之前,请先打开.github/workflows/ci.yaml文件并仔细阅读其中的内容。

      该管道承担着两项主要职责:

      1. 验证代码及相关镜像是否安全,可以放心发布。

      2. 使用新的镜像标签更新基础设施仓库。

      下面是具体的安全处理流程:

      开发者将代码推送到 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 仓库中
      CICD 安全处理流程的可视化示意图

      构建、扫描、发布(采用生产环境级别的流程控制机制)

      在实际开发中,团队从来不会先提交代码再进行扫描。在.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-imagesscan-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阶段的任务到此结束
      持续集成与交付安全流程图及与GitHub的交互过程

      镜像标签与代码之间的关联关系如下:每次管道任务的执行都是由某个git提交触发的。GitHub会为该提交分配一个唯一的ID,即SHA值(例如`a1b2c3d4e5f6789…`这样的十六进制字符串)。工作流会将`IMAGE_TAG`设置为这个SHA值,并在所有相关环节中使用它:

      1. 构建阶段:使用命令`docker build -t clearledger-auth-service:a1b2c3d4…`来构建镜像

      2. 发布阶段:将镜像推送到Docker Hub,地址格式为`YOUR_DOCKERHUB_USERNAME/clearledger-auth-service:a1b2c3d4…`

      3. 更新配置文件:在`clearledger-infra`文件中执行命令`kustomize edit set image …:a1b2c3d4…`

      4. 提交日志:使用日志信息`ci: deploy a1b2c3d4… — all gates passed`来记录构建过程

      如果生产环境使用的镜像地址是`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,而非集群本身

      在管道运行成功后,会有以下三个变化:

      • Docker Hub上会出现新的图像文件

      • GitHub上的clearledger-infra配置文件中会包含新的SHA值

      • 你的Kubernetes集群保持不变,仍然会运行阶段0时留下的配置

      持续集成系统永远不会执行kubectl apply命令。它只会将更改提交到clearledger-infra配置文件中。这就是阶段1的全部内容:构建和扫描过程是自动化的,但部署步骤目前还不是自动化流程。阶段2会安装ArgoCD,它会读取clearledger-infra配置并自动更新集群。

      Kubernetes Checkov在阶段1中会运行,但它不会阻止整个管道的运行。它只会将检测结果上传出来,让你能够了解后续需要进行的安全加固工作。在阶段4中,这些规则会通过Kyverno在集群层面得到执行。

      所有作业都在你自托管的运行环境中执行(runs-on: [self-hosted, clearledger])。在阶段1中,ENABLE_ARGOCDSYNCENABLE_DAST这两个选项都是未启用的。具体何时启用这些选项,请参见§1.4节。

      如果某个作业失败了,在编辑工作流程之前,请先参考docs/troubleshooting.md中的说明进行排查。

      阶段1的安全防护机制:哪些步骤会阻止流程运行,哪些步骤会在后续阶段执行

      需要注意的是,阶段1并不意味着安全防护完全关闭。有些检查步骤会立即阻止管道的运行,而另一些则会先收集证据,在后续阶段再采取行动。

      目前会阻止管道运行的检查步骤包括:

      • 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

      如果你忘记某个阶段具体负责检查什么内容,可以在这份指南中搜索“阶段1安全防护机制”,或者按照阶段顺序来理解:阶段3会故意让一些检查步骤失败,阶段4会将Checkov的检测结果与Kyverno关联起来,阶段5会将敏感信息从Git中移除,阶段6会添加运行时安全检测功能,阶段7则会添加监控面板。

      在这些步骤之后,运行make check-3make 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工具仍然可以正常运行;这个忽略文件仅用于屏蔽那些已知属于实验用途的代码片段。除非你确认这些数据确实是故意添加的测试用例,否则不要向该文件中添加任何新的内容。

      请仔细查看各项任务的日志信息,而不仅仅是等待任务状态变为“绿色”:

      • Docker登录成功

      • 每个服务对应的镜像都已构建完毕,并被推送到了Docker Hub上

      • 已检出clearledger-infra仓库

      • 部署配置文件中的SHA标签已更新为新的值

      • 更改后的代码已被推回clearledger-infra仓库

      当流水线成功执行完毕后,打开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.yamlledger-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

      所有测试都通过了,接下来进入第二阶段。

      你在第一阶段学到了什么

      • 持续集成系统会将你的笔记本电脑从构建流程中移除。这样,构建过程就能变得可重复、透明,并且能与Git提交紧密关联。

      • “运行器”其实是负责执行具体任务的工具,而不是整个管道系统本身。GitHub负责安排任务,而你自己托管的运行器会在你的虚拟机内部执行这些任务。

      • “构建成果”与“目标状态”是不同的概念。Docker Hub用于存储构建好的镜像,而GitHub上的clearledger-infra仓库则保存了指示应该使用哪个镜像的Kubernetes配置文件。

      • 优秀的管道系统不会偷偷改变集群的状态。这个管道系统只会更新Git代码库,而不会直接执行kubectl命令。

      • 目前还存在一个问题:虽然基础设施仓库已经发生了变化,但集群本身并没有随之改变。因此仍需要有人手动应用这些变更。第二阶段会通过GitOps来解决这个问题。

      你现在可以在简历上写些什么,或者在面试中提到哪些内容:

      我搭建了一个基于自己托管的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"
      

      你应该会看到以下结果:

      • check-1测试通过

      • secretKeyRef这个配置项确实存在于代码库中

      • OK(目前还没有Vault注释)

      快速检查清单:

      • clearledger-infra仓库中包含auth-service/secret.yamlledger-service/secret.yaml文件

      • 你自己托管的运行器目前处于空闲状态,并且被标记为clearledger

      • ENABLE_ARGOCD_SYNC这个配置项尚未被设置(安装ArgoCD后才会启用它)

      当执行 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应用。顶部的三个图标几乎可以说明所有所需的信息:

      显示Argo CD用户界面的截图

      应用状态:正常运行。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-serviceledger-servicenotification-servicefrontend以及redis这些选项。这说明ArgoCD会从clearledger-infra/manifests目录中读取完整的kustomization.yaml文件,而不仅仅是其中的一个文件。

      最后一列显示的是ORPHANED状态。如果该值为No,那就表示ArgoCD确认这个资源确实属于ClearLedger应用程序。只有当某个部署资源缺失,或者ArgoCD在用户界面中显示OutOfSyncDegraded等状态时,你才需要关注这些问题。

      ✋ 实践检查点:首先确保所有部署都已完成同步

      请执行以下四个检查。正常的检查结果应该如下所示:

      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-2make snapshot STAGE=2命令。

      如何启用持续集成与ArgoCD的协作机制

      在第一步中,管道系统虽然更新了clearledger-infra目录的内容,但并未对集群本身进行更新。这是有意为之的设计。

      现在既然已经安装好了ArgoCD,就可以让管道系统在每次成功执行部署任务后自动通知ArgoCD去检查是否有新的变更发生。

      在GitHub上,打开你的clearledger仓库,进入“设置”选项卡,然后选择“秘密与变量” > “动作” > “变量”,最后点击“新建仓库变量”。

      添加以下变量:

      名称
      ENABLE_ARGOCD_SYNC true

      从现在开始,当管道状态显示为绿色时,它就会执行以下两项操作:

      1. 使用新的镜像标签更新clearledger-infra目录

      2. 通知ArgoCD去同步集群配置

      如果管道系统无法立即触发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推送代码后的几分钟内出现以下症状,那就说明你可能推送了错误的代码:

      • 有些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命令进行检查。

      如果这种情况发生在执行推送操作之后,应先进行回滚操作。等应用程序恢复稳定状态后,再调查导致问题的那个提交记录。

      为什么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历史记录中会同时显示问题代码的推送记录以及恢复操作的记录,从而形成完整的审计轨迹
      演示ArgoCD的工作原理及操作流程

      详细操作步骤:

      # 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):

      • 如果有错误的镜像标签或清单被推送到clearledger-infra仓库中,且你还有几分钟时间处理的话

      • 如果infra仓库中的配置变更导致Pod出现故障

      • 在这种情况下,方法1几乎总是正确的选择。它操作快速、安全,而且能够留下清晰的审计痕迹。

      在以下情况下使用方法2(紧急ArgoCD回滚):

      • 如果集群当前处于故障状态,用户正在受到影响,并且你需要在30秒内让集群恢复稳定

      • 如果你还不确定是哪个提交导致了问题,需要时间进行调查:先使用回滚操作使集群恢复稳定,然后通过git log找出问题的根源,之后再使用方法1进行修复

      当某个Pod出现故障,但最近并没有任何内容被推送到infra仓库中时,这两种方法都不适用。这种情况下不属于回滚问题,应该检查kubectl logs的输出、Vault的连接状态以及网络配置。

      stages/stage-2-gitops/argocd/clearledger-app.yaml文件中设置revisionHistoryLimit: 10,意味着ArgoCD会保留最近10次部署记录,以便在紧急情况下进行回滚。如果你的发布频率较高,可以适当增加这个数值。

      你在第二阶段学到了什么

      • GitOps的含义:Git是所有配置信息的唯一来源,而ArgoCD则是用来确保这些配置信息得到正确应用的工具

      • ArgoCD的功能:它会监控Git仓库中的变更,并将这些变更与应用环境进行对比;一旦发现差异,就会自动进行修复

      • 当前整个流程的工作方式:首先将代码推送到Git仓库中,接着通过CI工具生成镜像,然后更新infra仓库的配置,最后ArgoCD会同步应用环境

      • 现在已经没有人需要手动运行kubectl来执行部署操作了。整个流程都是由自动化工具完成的

      • 如何安全地进行回滚:在infra仓库中使用git revert是正确的处理方式,而ArgoCD的紧急回滚功能则是在极端情况下的最后手段。在使用这些功能之前,必须先禁用自动同步功能,否则系统会自动撤销之前的更改。

      你现在可以在简历中写上这些内容,或者在面试时提到:

      我使用了ArgoCD实现了GitOps机制,从而确保了集群状态始终由Git仓库控制;同时系统还具备差异检测功能、自动同步机制,以及针对错误部署的回滚功能。

      执行命令make snapshot STAGE=2 && make snapshots,然后确认clearledger.stage2的状态。有关更多信息,请参阅如何保存你的工作进度

      第三阶段——安全检查机制

      每次代码推送都会触发安全检查。有些安全检查会立即阻止整个部署流程的继续,而另一些问题则可以在后续阶段进行处理并应用到集群中(第四阶段)。

      学习目标:了解六种不同的安全扫描工具:它们各自检测什么内容、能发现哪些问题,以及如何解读扫描结果。在第三阶段,你会故意触发这些安全检查机制,这样当CI任务失败时,你就不会感到意外了。

      你准备好进入第三阶段了吗?

      • 执行`make check-2`命令后,测试会通过。

      • 在GitHub上将`ENABLE_ARGOCDSYNC=true`设置为启用状态(你可以在第2阶段进行这个设置)。

      • 目前`ENABLE_DAST`仍处于未启用状态;如果你需要,可以在本阶段的后续步骤中将其开启。

      • http://argocd.local访问Argo CD时,会显示“已同步”状态。

      • 可选:可以阅读第1阶段的安全配置要求。第1阶段已经运行了许多相关的工具。

      完成条件: 当 `make check-3` 命令通过,并且你已经依次执行了所有的检查步骤(参见§3.4节)时,接下来就可以执行 `make snapshot STAGE=3` 以及 `make snapshots` 命令了。

      首先需要了解的内容

      仅仅使用一个工具是不够的。因为不同的扫描工具负责检测不同层面的安全问题:

      • Gitleaks:用于检测代码或Git历史记录中的敏感信息(如API密钥、令牌等)。

      • Semgrep (SAST):用于查找Python/JS源代码中的安全漏洞(例如注入攻击、不安全的编程模式等)。

      • Trivy (SCA + images):能够检测Python/Node.js包以及Docker镜像中已知的安全漏洞。CVE(通用漏洞与暴露)是指那些被公开记录的软件安全缺陷,它们都有唯一的标识符。

      • Checkov (IaC):用于检查Dockerfile、Kubernetes配置文件以及Terraform配置中的错误设置。

      • Cosign:用于验证这些镜像确实是由你的开发流程构建并签名的。

      目前,阻碍持续集成流程的主要问题包括敏感信息的泄露、代码中的安全漏洞、存在风险的镜像,以及生产环境中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_KEYCOSIGN_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漏洞信息表(严重程度为HIGHCRITICAL)时,即表示测试通过。发布镜像更新清单文件这两个步骤可以跳过。

      恢复原状:

      git checkout app/auth-service/Dockerfile
      git commit -am "恢复原状:测试Trivy配置" && git push
      

      3.5:当扫描检测到你并未引入的CVE漏洞时

      §3.4节的内容是故意设置的特殊情况。这一部分用于描述另一种情况:当你上传了正常的代码,但由于发现了新的安全漏洞,导致镜像扫描失败。


      这种情况很正常。CVE数据库会不断更新,因此不要因此而放弃进行安全扫描。只需修复存在漏洞的软件包或镜像即可。

      首先,需要找到真正的CVE漏洞。在GitHub Actions中,先执行扫描镜像操作,然后进入Trivy扫描所有镜像界面,查找以下信息所在的表格:

      • 软件包名称

      • CVE编号

      • 已安装的版本号

      • 已修复的版本号

        你也可以下载相关的检测结果文件。

      请忽略日志底部那条无关的信息:

      Trivy的0.71.2版本现已可用
      错误:操作以退出代码1结束。

      版本通知本身并不会导致测试失败,但那些可以被修复的高风险/严重安全漏洞才会引发问题。因此,不要通过添加--skip-version-check选项来“解决”这个问题。

      正确的处理方法如下:

      • 对于使用pip包的情况:requirements.txt文件中将相关包的版本更新为已修复的版本(例如:针对CVE-2026-53539,应将python-multipart==0.0.30更新为相应版本)。如果其他依赖服务也使用了相同的包版本,也需要进行同样的更新。

      • 对于操作系统级别的包:可以在Dockerfile中使用更新的基镜像,或者执行针对性的apt/apk升级操作。

      • 如果目前还没有稳定的修复方案:只需将相关安全漏洞添加到.trivyignore.grype.yaml文件中,并附上相应的注释即可(参见案例)。

      请不要删除--exit-code 1选项,也不要降低安全漏洞的严重性等级或禁用扫描功能。如需帮助,请参考Trivy版本通知相关说明以及关于Trivy如何阻止Python服务镜像被构建的相关内容

      完成第三阶段测试

      在生成截图时,只需选择其中一个检测工具即可:

      • Gitleaks:Secrets Scan

      • Semgrep:SAST

      • Trivy:Scan images

      • Checkov:在日志或构建结果中查找CKV_*相关内容。需要注意的是,某些情况下系统可能会显示“绿色”状态,但这并不表示检测没有问题。

      在完成§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
      

      这个配置文件为实验室环境做了三件重要的事情:

      1. 禁用清理任务:旧版本的Kyverno会使用bitnami/kubectl工具,但这个工具已经从Docker Hub上移除,因此在使用旧版本时,清理Pod时会遇到ImagePullBackOff错误。

      2. 将Helm钩子指向bitnamilegacy/kubectl,这样在将来卸载Kyverno时,就不会因为找不到相应的镜像而出现问题。

      3. 延长存活检查的超时时间:默认的配置timeoutSeconds: 5, failureThreshold: 2对于负载较重的单节点虚拟机来说过于严格。在CPU压力较大的情况下,健康检查端点的响应时间可能会超过5秒,从而导致重启循环,使整个节点陷入瘫痪,进而使得API服务器无法正常使用。通过将配置值修改为timeoutSeconds: 30, failureThreshold: 5,可以确保Kyverno在面对高负载时不会崩溃。

      您应该看到的内容:

      “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的状态,重启次数应为012,且重启次数不会持续增加。

      目前还不要运行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:将公钥粘贴到政策文件中

      使用您的编辑器(如或VS Code)打开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 KEYEND 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阶段,而非当前步骤。

      显示infra政策Yaml文件中failurePolicy设置为"Fail"的截图
      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: TrueVALIDATE 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

      其他策略的判断方式也是如此:

      • disallow-privilege-escalation 表示该 Pod 未设置 allowPrivilegeEscalation: false

      • drop-all-capabilities 表示该 Pod 未使用 capabilities.drop: [ALL] 来禁用 Linux 的某些功能

      • require-resource-limits 表示该 Pod 未设置 CPU 和内存的使用请求/限制值

      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 显示为 RunningPending 状态,说明安全策略尚未生效。此时请重新执行命令 kubectl get clusterpolicy,确认所有五项策略都显示为 READY: True

      请截图保存。这将是证明您已成功配置并应用了 CIS Kubernetes Benchmark 5.2.6 安全标准的证据。

      场景 2:缺少资源限制设置

      你正在模拟的情况是:开发人员已经按照要求修改了容器的安全配置,但忽略了资源限制的设置。

      在实际开发中,这种情况很常见:“我们加强了容器的安全性”,但却忘记了设置 CPU 和内存的使用上限。

      这个 Pod 配置文件的问题在于: securityContext 的设置是正确的,但是没有 resourcesrequestsresources.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:未找到任何签名’

      如何解读这些输出信息:

      • 请注意,webhook的名称是mutate.kyverno.svc-fail,而不是validate:在Pod被允许进入集群之前,Kyverno会先通过mutate流程对图像进行验证(包括摘要计算和签名检查)。

      • 未找到任何签名这一提示表示Kyverno已经访问了Docker Hub,找到了相应的图像,但确认该图像并未使用您的infra/cosign.pub密钥进行签名。

      • 如果Pod根本就不存在,那么攻击者即使能够拉取到该图像,也无法获取其shell权限。

      验证结果:

      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-0postgres-1 等),且这些 Pod 必须位于 clearledger 命名空间中,同时资源类型也必须是 Pod。集群中的其他对象仍需遵守严格的规则。

      这些注释主要是为你的团队和审计人员提供的说明:

      annotations:
        reason: "Postgres alpine镜像需要使用UID 70来拥有数据目录的权限"
        approved-by: "platform-team"
        review-date: "2026-01-01"
      

      这些注释并没有实际的技术作用:Kyverno会忽略它们。它们的存在只是为了在未来有人询问“为什么Postgres可以绕过这条规则”时,能够从文件中找到相应的解释。

      关于安全例外情况的处理规则:

      1. 范围要严格限定:只针对确实需要这种例外的资源进行设置,不得超出这个范围。

      2. 需通过Git进行管理:任何例外情况都应通过拉取请求进行审核,并记录在版本历史中,以便后续审计。

      3. 绝不能削弱原有的规则:对于其他所有资源,规则仍然必须保持严格性。

      4. 要定期重新评估:例外情况应尽可能具有临时性,并且需要按照固定的时间表进行重新评估。

      只有当 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阶段学到了什么

      • CI扫描(在代码合并之前进行)与准入控制(在集群层实施)之间的区别

      • Kyverno的作用:它是一种策略引擎,能够拦截所有Kubernetes API请求

      • “强制执行”意味着不良资源根本就不会被创建,而不是“事后才被发现”

      • 如何解读Kyverno的拒绝响应:策略名称 → 规则名称 → 发生故障的JSON路径

      • 如何使用YAML格式编写并应用全集群范围的安全策略

      • 如何在不影响其他用户的情况下限定特定规则的适用范围

      • 运营相关问题(如Helm配置、镜像下载、注册表URL格式等)会影响控制措施是否真正生效

      • 为什么同时需要CI扫描和准入控制:CI扫描用于检测代码中的问题,而Kyverno则能拦截所有与集群相关的操作

      • 事实胜于空谈:仅将策略文件保存在Git中是毫无意义的;只有当这些策略真正被执行时,才能证明控制措施是有效的

      你现在可以在简历中写入或面试时提及的内容:

      通过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尚未准备好而已。

      1. §5.1:将stages/stage-5-secrets-management/.env.example文件复制到.env文件中,然后填写上您的集群密码。

      2. §5.2:使用Helm工具安装Vault及相应的代理注入器。

      3. §5.3:运行setup.sh命令,然后再运行seed-vault-secrets.sh命令(此时密码已经保存在Vault中了)。

      4. §5.4:将启用了Vault功能的部署配置推送到clearledger-infra目录中,然后让ArgoCD进行同步。

      5. §5.5:等待2/2个Pod(应用程序Pod加上辅助容器Pod)启动完成,之后再删除原来的Kubernetes Secret对象。

      6. §5.5b:确认ArgoCD已经完成了同步操作,并且显示“状态正常”。

      7. §5.6:验证登录功能是否正常,同时确认密码和令牌确实保存在Pod内的/vault/secrets/目录下。

      请从§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用户界面的截图 显示Vault用户界面的截图

      验证:查看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-serviceclearledger/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:列表中:

      • 删除与应用程序密钥相关的条目:将以下这两行代码删除,或使用#将其注释掉(这两种方法都可以,因为Kustomize会忽略以#开头的行):

        - auth-service/secret.yaml
        - ledger-service/secret.yaml
        
      • 添加以下这一行代码(与其他资源一起添加):

        - vault/rotation-cronjob.yaml
        

      保留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文件中必须包含runAsNonRootallowPrivilegeEscalation: falsecapabilities.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-secretledger-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-secretledger-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_urljwt_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阶段学到了什么

      • Kubernetes Secrets并不足以满足真正的秘密管理需求。

      • 现在, Vault开始用于存储应用程序的凭据了。

      • .env文件仅被用于在本地将初始凭据加载到Vault中,这些数据从未被提交到版本控制系统中。

      • 当应用程序启动时,Vault会将其所需的凭据注入到Pod中。

      • 必须停止使用clearledger-infra工具来存储secret.yaml文件,因为ArgoCD正是从该仓库中进行部署的。

      • 操作顺序非常重要:先安装Vault,然后生成初始凭据,接着更新GitOps配置,等待Pod正常运行,最后删除旧的Kubernetes Secrets文件。

      现在你可以在面试中这样回答:

      我使用了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阶段之前,请确保:

      • 执行make check-5命令后测试结果为通过。

      • http://clearledger.local上,登录操作和相关交易功能依然可以正常使用。

      • 平台相关的Pod的重启次数应该处于较低水平。

      当你完成第6阶段的任务时,需要满足以下条件:

      • 你至少触发了一次Falco警报。

      • 你已经应用了相应的网络策略。

      • 执行make check-6命令后测试结果为通过。

      最后,请保存你的虚拟机配置:

      make snapshot STAGE=6
      make snapshots

      请按照这个顺序执行步骤

      每个步骤都依赖于前一个步骤的结果。在完成§6.4之前的所有步骤之前,切勿执行make check-6命令,因为该命令会检查你尚未应用的网络策略配置。

      1. §6.1: 运行命令 `bash stages/stage-6-runtime-security/scripts/install-falco.sh`。确认 `falco-*` 相关Pod处于“运行中”状态,并且自定义规则已成功加载。

      2. §6.2: 执行命令 `make demo-6`,在终端中会看到提示信息“✓ 运行时检测已完成”。

      3. §6.3(可选):如果步骤 `make demo-6` 已经成功完成,可以跳过此步骤,手动创建故障场景进行测试。

      4. §6.4: 运行命令 `kubectl apply -f infra/deferred-by-stage/stage-6-runtime-security/netpol/network-policies.yaml`,确认访问地址 `http://clearledger.local/` 时返回状态码200。

      5. §6.6: 执行命令 `make check-6`。

      请从 §6.1 开始操作。如果遇到任何问题,请参考文件《troubleshooting.md》。

      推荐阅读: 阅读文章《How Stage 6 fits the full stack optional-reading》,了解Falco与netpol的作用机制,以及它们与阶段3–5的区别。

      如果在§6阶段遇到困难

      §6阶段包含三项任务:

      1. 安装Falco插件

      2. 触发一次测试警报

      3. 应用网络策略

      不必过于关注Falco用户界面中显示的所有信息,这些信息中可能包含一些干扰性内容。只要能在终端或用户界面中看到测试警报,就说明Falco配置成功。

      如需查看演示效果的截图,请访问:

      http://falco.local

      登录方式:

      • 用户名:`admin`

      • 密码:`admin`

      只有当测试警报真正出现后,才进行截图操作。

      常见故障点

      你的想法: 实际情况:
      “用户界面显示了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
      
      Falco UI界面截图

      Falcosidekick UI界面使用指南

      登录后,你会进入Events页面。在运行任何演示之前,这个页面上的表格可能会看起来很复杂,但这属于正常现象。

      • 规则:表示检测到的事件名称。

      • 优先级:分为CriticalWarningNotice;在本实验中,主要关注CriticalWarning等级的事件。

      • 输出信息:包括Pod名称、相关文件或命令的详细信息。

      • 标签:在实验室生成的警报中,可以查找clearledger这个标签。

      有些背景信息你可以忽略掉: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能够在正在运行的容器内部检测到可疑活动。

      http://falco.local
      网页,并等待用户使用以下用户名和密码登录:

      • 用户名:admin

      • 密码:admin

      auth-service
      容器内部执行以下测试命令:

      kubectl exec -n clearledger \
        auth-service-<pod-suffix>> \
        -c auth-service -- /bin/sh -c 'id && amp; exit'
      
      脚本会自动选取正确的容器名称来执行该命令。

      注意:此过程是非交互式的(不会显示任何输入提示),因此可以在命令行中直接运行 SKIP_PROMPT=1 make demo-6 来跳过提示步骤。

      <这条命令会在应用程序容器内部启动一个shell进程。在生产环境中,这种情况是异常的,因为应用程序容器应该用于执行业务逻辑,而不是打开shell窗口。Falco应当能够检测到这种行为,并生成相应的警报:

      Shell Spawned in ClearLedger Container

      <当脚本显示以下提示时,说明测试成功:

      ✓ 运行时检测确认完成
      <此时请刷新Falco UI界面,查找标题为 Critical 的警报信息,其中应包含以下内容:

      • 规则名称:Shell Spawned in ClearLedger Container

      • 相关容器名称:auth-service-...

      • 执行的命令:sh -c id && amp; exit

      <请忽略来自 postgres-0 容器的警报信息,尤其是 Sensitive File Read 这一警告,因为这些在本次实验中属于正常现象。

      <如果UI界面显示了大量警报信息,可以在页面中搜索 Shell Spawned 这一关键词,或者直接在终端中运行以下命令进行排查:

      kubectl logs -n falco -l app.kubernetes.io/name=falco -c falco --tail=500 \
        | grep 'Shell Spawned'
      
      <在截图时,请务必将 auth-service 容器相关的 Shell Spawned 警报信息截取下来。

      6.3:破坏性测试场景(手动操作,可选)

      这些测试与§6.2中的检测内容相同,但你需要亲自执行每一条命令。如果你已经完成了make demo-6命令,就可以跳过这一部分。

      规则名称 触发该规则的方法:
      在ClearLedger容器中启动Shell脚本 场景1 — kubectl exec … /bin/sh
      在ClearLedger中读取敏感文件 场景2 — cat /etc/passwd
      使用包管理器或进行出站连接 场景3 — wgetcurl

      执行每条命令后,请刷新http://falco.local页面,或者使用§6.2中介绍的检测方法。

      场景1——在正在运行的Pod中启动Shell脚本(命令注入模拟):

      kubectl exec -n clearledger \
        $(kubectl get pod -n clearledger -l app=auth-service -o name | head -1) \
        -c auth-service -- /bin/sh -c "id && exit"
      

      在Falco用户界面或日志中应出现的提示:(大约10秒内会出现) CRITICAL: 在ClearLedger容器中启动了Shell脚本 user=... container=auth-service pod=auth-service-... cmd=sh -c id && exit

      这意味着:第4阶段验证表明该Pod符合安全要求;第6阶段检测到了该Pod内部发生的操作——这正是攻击者在执行命令注入后会采取的行动。

      如果没有收到任何警报:请确认执行的命令中确实包含了-c auth-service选项,规则检查结果显示schema validation: ok,并且该Pod的镜像名称中包含clearledger字样。

      场景2——读取敏感文件(信息收集行为):

      kubectl exec -n clearledger \
        $(kubectl get pod -n clearledger -l app=auth-service -o name | head -1) \
        -c auth-service -- cat /etc/passwd
      

      预期出现的提示: CRITICAL: 在ClearLedger中读取了敏感文件 file=/etc/passwd container=auth-service pod=auth-service-...

      场景3——在运行时下载工具(可选):

      kubectl exec -n clearledger \
        $(kubectl get pod -n clearledger -l app=auth-service -o name | head -1) \
        -c auth-service -- sh -c "wget -q ifconfig.me -O - 2>/dev/null || true"
      

      执行此命令后,可能会触发包管理器的运行,或者出现异常的出站连接行为(警告提示)。

      对于场景1和场景2,请务必截取屏幕截图,这些截图可以作为证明该检测机制在运行时有效性的证据。

      6.4:应用网络策略(零信任分隔机制)

      网络策略其实就是Pod之间的防火墙规则。在完成Falco演示后,请应用这些策略。

      make check-6命令会检查这些策略的存在,因此请仅在本节内容结束后再运行该命令。默认情况下,default-deny-all策略会阻止所有网络流量。

      allow-*策略只会开放ClearLedger所需使用的路径。Falco工具会用来检测异常行为,而网络策略则用于限制Pod可以连接的目标地址。

      应用方法:

      kubectl apply -f infra/deferred-by-stage/stage-6-runtime-security/netpol/network-policies.yaml
      kubectl get networkpolicy -n clearledger
      

      预期结果:应共有七项策略:default-deny-all以及六项allow-*策略(分别对应auth-serviceledger-servicenotification-servicepostgresredisfrontend服务)。

      请验证应用程序是否仍能正常运行:

      curl -s http://clearledger.local/auth/health | jq .
      # 输出:{"status":"ok","service":"auth-service"}
      
      curl -s http://clearledger.local/notifications/health | jq .
      # 输出:{"status":"ok",...}
      

      检查点验证(必不可少):这个步骤用于确认应用功能并未因网络策略的配置而受到影响:

      kubectl get networkpolicy -n clearledger
      curl -s -o /dev/null -w "%{http_code}\n" http://clearledger.local/
      kubectl get pods -n clearledger --field-selector(status.phase!=Running)
      
      结果 含义
      列出了七项策略 网络策略已成功应用
      curl命令的返回代码为200 用户仍能通过Ingress访问应用程序
      第三条命令没有输出任何结果 没有任何Pod出现异常崩溃

      如果应用网络策略后,auth-service或ledger-service开始频繁重启,那很可能是因为出站规则设置得太严格了。请参考troubleshooting.md文件进行排查。

      场景4 – 服务间的通信被阻断(可选步骤)

      如果检查点验证通过,并且你打算运行make check-6命令,那么可以跳过这个步骤。这个测试的目的是验证ledger-service无法直接调用notification-service(因为没有相应的允许访问规则)。如果连接失败,那就说明测试成功了。

      请不要使用旧的wget命令:ClearLedger镜像中并不包含wget/curl工具,而使用head -1命令可能会导致执行异常(例如Terminating pod被触发,或者程序以137码退出)。

      LEDGER_POD=$(kubectl get pods -n clearledger -l app=ledger-service --no-headers \ | awk '$2=="2/2" &;& $3=="Running" {print $1; exit}') echo "使用的Pod为:$LEDGER_POD" kubectl exec -n clearledger "$LEDGER_POD" -c ledger-service -- python3 -c " import urllib.request try: urllib.request.urlopen('http://notification-service/', timeout=5) print('连接成功') except Exception as e: print('通信被阻断,预期结果是失败:', e) "

      预期结果:

      受阻(预期情况):<urlopen错误,超时
      
      或者出现连接被拒绝的情况,绝对不会出现意外结果:连接成功的情况。

      6.6:健康检查

      请在完成§6.4节(网络策略配置)之后运行此检查。该检查可以确认Falco、自定义规则以及网络策略已正确安装,但无法证明是否真的触发了警报机制(这部分内容属于§6.2节的讨论范围)。

      make check-6

      预期看到的输出结果:

      ▶ 第6阶段 — 运行时安全检测(Falco)
        ✓ Falco DaemonSet已应用于所有节点
        ✓ ClearLedger自定义规则配置文件存在
        ✓ 默认的“全部拒绝”网络策略存在
        ✓ “允许auth-service访问”的网络策略存在
        ✓ “允许ledger-service访问”的网络策略存在
        ✓ “允许notification-service访问”的网络策略存在
        ✓ 在应用网络策略后,auth-service能够正常访问
        ✓ 在应用网络策略后,notification-service也能正常访问
      
      所有检查均通过,可以进入下一阶段。

      第6阶段在整体解决方案中的位置(可选阅读内容)

      每个阶段负责监控生命周期中的不同环节。第1至第5阶段会在Pod启动之前或启动过程中发挥作用,而第6阶段则用于监测已经正在运行的容器内部发生的活动。

      • CI系统会在git push操作发生时检测到有问题的代码或镜像。

      • Kyverno会在Pod被创建时阻止那些存在问题的Pod。

      • Vault会在系统启动时注入必要的秘密信息。

      • Falco会监控Pod运行过程中的系统调用行为(例如Shell命令的执行、敏感文件的读取等)。

      • 网络策略会用于过滤Pod之间的通信流量。

      这些机制分别解答了三个不同的问题:Kyverno负责判断是否可以创建某个Pod;Falco用于检测Pod当前正在执行什么操作;而网络策略则规定Pod可以与哪些其他组件进行通信。

      Falco并不会取代CI或Kyverno。如果你跳过了第3至第5阶段,Falco仍然能够发出警报,但这样一来,你就已经通过Git将含有漏洞的代码和秘密信息发布了出去。

      第6阶段已完成,可继续进行第6.5阶段或第7阶段的测试。

      >
      编号 检查项验证方法
      1 Falco正在运行 kubectl get pods -n falco — 结果应显示DaemonSet已应用于2/2个节点
      2 自定义规则已加载成功 `kubectl logs -n falco -l app.kubernetes.io/name=falco -c falco --tail=200`
      3 已经触发了Shell警报,并且你已经看到了该警报信息 make demo-6 → 结果应显示包含cmd=sh -c id && exit命令的Critical级别警报,相关Pod名为auth-service-… — 见§6.2节
      4 网络策略已成功应用 kubectl get networkpolicy -n clearledger — 结果应显示网络策略配置正确
      5 应用程序运行正常 curl命令用于测试auth-service和notification-service的响应情况,应返回200码表示成功
      6 健康检查通过 make check-6 — 结果应为绿色,表示检查合格
      项目组合截图(可选): Falco用户界面中显示的“容器内shell操作警报”以及“敏感文件读取警报”。

      接下来会学到什么:第6阶段会让你了解Falco警报功能及基本的网络策略,之后你可以进一步优化这些网络策略。第6.5阶段是可选的混沌工程测试,使用Litmus工具进行测试;而第7阶段则会添加Grafana仪表板,这样你就可以查看一段时间内的安全事件记录。

      你在第6阶段学到了什么

      • 运行时安全检测能发现哪些CI和准入控制机制无法发现的威胁?——即在正在运行的容器内部出现的威胁。

      • Falco是什么?——它是一种利用eBPF系统调用监控技术,并结合自定义YAML规则来检测异常行为的工具。

      • 什么是网络策略?——它们其实就是Kubernetes中用于控制不同Pod之间数据流动的防火墙规则。

      • 如何触发并解读警报信息?——这涉及到事件响应方面的技能。

      • 完整的解决方案框架:代码扫描、准入控制、密钥管理,以及运行时安全检测,这些共同构成了实现安全监控的能力。

      你现在可以把这些内容写在简历上,或者在面试中提及:

      我已部署了Falco工具,使用自定义规则进行运行时威胁检测,并且能够像值班工程师一样及时触发警报并查看与“容器内shell操作”或“敏感文件读取”相关的报警信息。

      make snapshot STAGE=6 && make snapshots。确认执行clearledger.stage6命令。详情请参阅如何保存学习进度

      第6.5阶段——混沌工程(可选)

      大多数学习者会跳过这一阶段。如果你已经完成了第6阶段的学习,并且执行make check-6命令后没有问题,可以直接进入第7阶段。需要注意的是,第7到第8阶段的学习过程中并不需要使用Litmus工具。

      如果你想进行混沌工程测试或验证系统的弹性恢复能力(大约需要1小时):LitmusChaos会删除一个auth-service Pod,然后验证在Kubernetes替换该Pod后,/auth/health接口的响应状态是否仍然保持为200

      请按此顺序执行步骤

      >
      步骤 所属章节你需要做的事情
      1 §6.5.0 make fix-65-prereqs — 确保auth pods已准备就绪。
      2 §6.5.1 bash ...install-litmus.sh — 安装LitmusChaos操作员界面。
      3 §6.5.2 通过UI进行Pod删除实验,同时使用curl命令验证响应状态是否为200。
      4 §6.5.7 make check-65,并生成快照。

      可选步骤: §6.5.3:你也可以通过终端执行make demo-65命令来进行相同的测试,而无需使用UI界面。

      6.5.0:开始之前需要注意的事项(认证相关Pod必须为2/2状态)

      如果系统中存在混乱状态,Pod们可能会被删除;如果替换操作失败,你将需要排查“CrashLoopBackOff”错误,而无法真正学习到系统的恢复能力。

      export GITHUB_OWNER=YOUR_GITHUB_USERNAME   # 必需设置——如果不配置这个变量,fix-argocd命令会导致ArgoCD仓库链接无法使用
      make fix-65-prereqs
      kubectl get pods -n clearledger -l app=auth-service
      

      合格标准:必须有两个Pod都处于2/2 Ready状态。在满足这个条件之前,切勿安装Litmus工具。

      如果出现故障:

      症状 解决方法
      在执行fix-65-prereqs命令后,ArgoCD出现ComparisonError错误 kubectl apply -f stages/stage-2-gitops/argocd/clearledger-app.yaml
      认证相关Pod的状态为Init:0/1,而Vault工具出现permission denied错误 重新运行第5阶段的脚本setup.shseed-vault-secrets.sh,然后删除auth/ledger相关的Pod
      认证相关Pod的状态仅为1/2,或者Postgres连接出现了超时问题 再次执行make fix-65-prereqs命令(此命令会添加netpol配置文件及启动检测脚本)

      6.5.1:安装LitmusChaos工具(包括操作界面、集群连接功能等)

      bash stages/stage-6.5-chaos-engineering/scripts/install-litmus.sh
      kubectl get pods -n litmus
      open http://litmus.local    # 登录账号:admin / litmus
      

      在开始执行§6.5.2步骤之前,需要确保以下条件满足:在“概览”页面中,应该显示“Active 1”这一状态(而不是0或Pending)。

      检查Pod运行状态:

      kubectl get pods -n litmus
      # litmus-core、chaos前端/后端服务、mongodb以及订阅者代理进程——所有这些进程都应处于Running状态

      如果“概览”页面显示“0 infrastructures”或“PENDING”,该怎么办?

      在订阅者代理程序连接到你的集群之前,用户界面将会是空白的:

      export LITMUS_PASSWORD='litmus'   # 仅在你更改了默认密码的情况下才需要设置这个变量
      bash stages/stage-6.5-chaos-engineering/scripts/connect-litmus-infra.sh
      

      请强制刷新浏览器。务必从http://litmus.local这个链接开始访问界面,切勿使用旧的/account/.../settings书签。

      显示Litmus用户界面的截图 显示Litmus用户界面的截图

      §6.5.2章节中涉及的UI导航顺序

      1. 概览:确认显示“Active 1”这一状态

      2. ChaosHubsPod DeleteLaunch Experiment等选项

      3. Chaos Experiments:可以查看实验从“Running”状态到“Completed”状态的整个过程

      左侧导航栏:概览环境设置ChaosHub混沌实验。对于本次实验,可以跳过弹性测试选项以及深入的设置配置。

      6.5.2:运行你的第一个实验(删除Pod)

      目标:终止一个auth-service Pod,并验证/auth/health接口的响应状态是否仍为200

      在点击用户界面中的“运行”按钮之前,请先打开两个终端窗口:

      # 终端A — 监控Pod状态
      kubectl get pods -n clearledger -l app=auth-service -w
      
      # 终端B — 每5秒检查一次健康状况
      while true; do
        date +%H:%M:%S
        curl -s -o /dev/null -w "health=%{http_code}\n" http://clearledger.local/auth/health
        sleep 5
      done
      

      在用户界面中(http://litmus.local):左侧导航栏 → ChaosHubs → “删除Pod”选项 → 点击“启动实验”。

      显示Litmus用户界面的截图

      关于Litmus用户界面的说明:不同版本的ChaosCenter中,相关选项的标签可能会发生变化(例如,“调整故障设置”、“目标选择”等)。进行匹配时,请根据功能概念来识别选项,而不要仅仅看按钮上的文字。除非下表中列出了具体的值,否则建议使用向导中的默认设置。

      在Litmus用户界面中,依次点击以下路径:

      ChaosHubs删除Pod启动实验

      这样就会打开实验向导。当向导要求输入相关参数时,请使用以下数值:

      • 基础设施:必须选择clearledger-cluster,并且该集群的状态必须是活动状态

      • 命名空间:选择clearledger

      • 目标标签:输入app=auth-service

      • 目标类型:选择Deployment

      • 受影响的Pod比例:设置为50%

      • 实验持续时间:设置为30

      • 故障/实验名称:输入pod-delete

      完成向导设置后,点击“保存”或“创建”,然后再点击“运行”。请不要选择“安排定时执行”选项。

      成功的表现:

      观察位置 成功迹象
      终端A 有一个Pod正在被终止,但其余Pod的状态仍为2/2 已准备就绪
      终端B 即使有一个Pod处于关闭状态,health=200的响应状态依然存在
      Litmus用户界面 实验已开始运行 → 完成

      更喜欢使用终端而不是用户界面吗?可以直接跳过向导步骤,通过终端执行§6.5.3章节中的命令(make demo-65)来完成实验。

      6.5.3 — 通过终端执行相同实验(make demo-65)——可选步骤

      如果您想在不通过Litmus用户界面进行操作的情况下运行pod-delete测试,可以使用此方法。

      请先确保授权相关的Pod处于正常状态:

      make fix-65-prereqs

      之后再运行演示脚本:

      make demo-65

      该脚本会在`litmus`命名空间中应用`auth-service-pod-delete`这个ChaosEngine测试用例。Litmus会删除一个`auth-service` Pod,Kubernetes会自动替换它,然后该脚本会检查`/auth/health`路径是否始终返回`200`状态码。

      测试完成后,请验证结果:

      kubectl get chaosresult -n litmus
      kubectl get pods -n clearledger -l app=auth-service

      如果脚本最终显示“PASS”,且`ChaosResult`的状态为“Completed / Pass”,同时有两个`auth-service` Pod正在运行,那么说明测试成功。

      您也可以在Litmus用户界面中查看测试结果:点击“Chaos Experiments”,刷新页面,然后选择最新的测试记录进行查看。

      如果新的`auth-service` Pod在启动过程中卡在了“Init:0/1”阶段,请重新应用第6阶段的网络策略:

      kubectl apply -f infra/deferred-by-stage/stage-6-runtime-security/netpol/network-policies.yaml

      6.5.3a:实验室集群上的实际测试结果示例

      这些测试样本是在执行了`make fix-65-prereqs`、`make connect-litmus`以及`make demo-65`命令之后,从一台正常运行的集群中获取的。

      make check-65

      ▶ 第6.5阶段 — Chaos Engineering (LitmusChaos)
        ✓ litmus命名空间存在
        ✓ litmus-admin服务账户在litmus中已配置
        ✓ pod-delete混沌测试用例已在litmus中安装
        ✓ Litmus混沌操作程序正在运行
        ✓ 可通过http://litmus.local访问Litmus ChaosCenter
        ✓ Litmus订阅程序正在运行(用户界面已成功连接到集群)
        ✓ auth-service Pod处于正常状态(混沌测试前的基准值)
        ✓ auth-service有2个可用的副本(适合进行混沌测试)
        ✓ allow-postgres网络策略已配置(第6阶段的修复措施)
      
      所有检查均通过,准备进入下一阶段。

      make demo-65测试记录来自实际运行环境(2026-06-01)

      第6.5阶段 — auth-service pod-delete测试
      
      测试前状态:2个auth-service Pod正在运行
      
      正在应用ChaosEngine的auth-service-pod-delete测试用例(命名空间为litmus)
      
      正在监控http://clearledger.local/auth/health接口的输出:
      
        10秒后:健康状态为200,Pod数量为2
        20秒后:健康状态为200,Pod数量为1
        30秒后:健康状态为200,Pod数量为1
        40秒后:健康状态为200,Pod数量为2
        50秒后:健康状态为200,Pod数量为2
        60秒后:健康状态为200,Pod数量为2
      
      测试结果:
        ChaosResult:Completed / Pass
        恢复情况:2个auth-service Pod正在运行
        健康状态:6项检查均返回200状态码
      
      PASS

      如果健康状态显示为“000”,请在您的Mac上运行`bash scripts/setup-hosts.sh`脚本,然后重新进行测试。当虚拟机可用时,该脚本还会尝试执行`multipass exec clearledger -- curl`命令。

      终端B(健康检查循环,预期输出结果)

      22:05:01
      健康状态=200
      22:05:06
      健康状态=200
      22:05:11
      健康状态=200
      

      在替换Pod启动期间,Pod的数量可能会显示为1,这是正常现象。

      终端A在混乱状态下运行时(使用命令kubectl get pods -w查看结果)

      名称                          已准备状态    运行状态      重启次数     创建时间
      auth-service-84cc988c4d-hdb45   2/2         运行中        0            67分钟
      auth-service-84cc988c4d-b59sj   2/2         正在终止      0            15分钟    ← 被手动终止
      auth-service-84cc988c4d-dxz9q   0/2         待启动状态     0            0秒        ← 正在被替换
      auth-service-84cc988c4d-dxz9q   0/2         初始化中       0            2秒
      auth-service-84cc988c4d-dxz9q   2/2         运行中        0            90秒
      

      演示结束后:进行验证

      kubectl get chaosresult -n litmus
      # auth-service-pod-delete-pod-delete   已完成      通过测试
      kubectl get pods -n clearledger -l app=auth-service
      # auth-service-84cc988c4d-xxxxx   2/2         运行中
      # auth-service-84cc988c4d-yyyyy   2/2         运行中
      kubectl get cm subscriber-config -n litmus -o jsonpath '{.data.IS_INFRA-confirmED}'
      # 输出结果为 "true"
      

      订阅者已连接成功,基础设施状态在UI界面中显示为“活跃”

      kubectl logs -n litmus -l app.kubernetes.io/name=subscriber --tail=3
      级别=信息 消息="代理ID:a63c2a2c-... 已被确认"
      级别=信息 消息="服务器连接已建立,正在监听中..."
      

      6.5.4:在运行之前需要了解这些YAML文件的内容

      每个YAML文件实际上都是一个ChaosEngine配置文件,用于向Litmus系统发出指令,例如“对应用程序Y执行实验X,持续Z秒”。

      litmus-install.yaml

      这个文件仅用于创建litmus命名空间。平台相关的工作负载会部署在这个命名空间中,而与clearledger应用程序的Pod是分开管理的。

      litmus-rbac.yaml

      资源名称 其功能
      ServiceAccount litmus-admin(命名空间litmus 为Litmus运行相关的Pod提供身份认证支持
      ClusterRoleBinding → cluster-admin 允许删除clearledger命名空间中的Pod,或在其中引入故障(用于测试环境;在生产环境中应使用最小权限策略)

      auth-service-pod-delete.yaml(实验1:在演示中使用了该配置文件)

      metadata:
        namespace: litmus          # 这个命名空间用于存放ChaosEngine相关资源
      spec:
        appinfo:
          appns: clearledger       # 目标应用程序的命名空间
          applabel: app=auth-service
          appkind: deployment
        experiments:
          - name: pod-delete
            spec:
              components:
                env:
                  - name: PODS_AFFECTED_PERC
                    value: "50"    # 2个Replica中会有50%被终止
                  - name: TOTAL_CHAOS_DURATION
                    value: "30"    # 故障测试持续的时间,单位为秒
      

      应用这些配置后会发生以下情况:

      1. 操作员会读取ChaosEngine的配置,并在litmus集群中创建auth-service-pod-delete-runner容器。

      2. 该容器会从clearledger集群中选择一个auth-service容器,然后向其发送SIGTERM信号或执行删除操作。

      3. Kubernetes的部署控制器会检测到只有1个或2个容器在运行,因此会安排创建新的容器来替换被删除的容器。

      4. 存活下来的容器上。

      5. ChaosResult记录会从Litmus的角度反映出实验的结果(是否成功)。

      ledger-service-network-latency.yaml(实验2——手动操作)

      该配置会为ledger-service容器添加2000毫秒的网络延迟,持续60秒钟。这一设置可以证明:当发生超时情况时,系统会返回503错误码,而不会导致用户界面出现卡顿现象。

      notification-service-memory-hog.yaml(实验3——手动操作)

      该配置会占用容器内存限制的80%,持续60秒钟。这一设置可以验证系统在内存不足时会如何进行OOMKill处理以及随后是否能够重新启动。

      切勿同时应用这三种配置。请先运行一个实验,确认系统能够正常恢复后,再继续进行下一个实验。

      6.5.5:演示结束后需要注意的事项(切勿遗漏)

      1. 在混乱状态发生期间:

      检测指标 正常情况 异常情况
      curl http://clearledger.local/auth/health 当有一个容器处于关闭状态时,响应码仍应为200 响应码可能为502、503或超时
      kubectl get pods -l app=auth-service 应显示1个正在运行的容器以及1个处于初始化/等待状态的容器(新的容器正在创建中) 如果只有1个容器在运行,说明配置有误

      2. 混乱状态结束后:

      检测指标 正常情况 异常情况
      容器数量 应显示2个都处于准备就绪状态的容器 如果只有1个容器处于准备就绪状态,说明配置有误
      系统事件记录 应显示新创建的容器被Killed,随后又进入ScheduledStarted状态 如果持续出现崩溃循环,说明配置有误
      ArgoCD的状态 应显示已成功同步

      3. Litmus的ChaosResult判断结果

      kubectl get chaosresult -n litmus
      

      成功的判断标准如下:

      • 在混乱状态发生的整个期间,/auth/health接口至少应一次返回200响应码。

      • 必须有一个容器被Killed(这一过程会在系统事件记录中体现)。

      • 系统的部署状态应恢复为拥有2个处于准备就绪状态的容器。

      6.5.6:在实验1成功后进行的手动测试

      请等待直到两个auth-service容器都处于准备就绪状态,然后再依次进行一个实验:

      # 实验2 —— ledger-service的网络延迟为2秒(持续60秒)
      kubectl delete chaosengine ledger-service-network-latency -n litmus --ignore-not-found
      kubectl apply -f stages/stage-6.5-chaos-engineering/infra/chaos/ledger-service-network-latency.yaml
      
      # 实验3 —— notification-service的内存占用问题(持续60秒)
      kubectl delete chaosengine notification-service-memory-hog -n litmus --ignore-not-found
      kubectl apply -f stages/stage-6.5-chaos-engineering/infra/chaos/notification-service-memory-hog.yaml
      
      >
      实验名称 相关文件需要验证的内容
      Pod删除测试 auth-service-pod-delete.yaml 在删除Pod时,其健康状态应为200;之后应保留2个副本
      网络延迟测试 ledger-service-network-latency.yaml API响应应为503或超时错误,而不是无限期阻塞
      内存占用问题测试 notification-service-memory-hog.yaml 删除导致Pod因OOM被终止并重新启动,但Redis订阅功能应能恢复

      清理实验环境:

      kubectl delete chaosengine auth-service-pod-delete -n litmus
      

      6.5.7:健康检查

      make check-65
      

      预期结果:详见§6.5.3amake check-65命令执行后的输出)。最低要求如下:

      ▶ 6.5阶段混沌工程测试(LitmusChaos)
        ✓ Litmus订阅器已运行(UI已连接到集群)
        ✓ auth-service有2个可用副本(适合进行混沌测试)
        ...
      所有检查均通过,可进入下一阶段。

      6.5阶段完成:检查清单

      >
      序号 检查项验证方法
      1 Litmus操作器正在运行 kubectl get pods -n litmus —— 应能看到litmus-*相关的Pod在运行
      2 实验配置已安装完成 kubectl get chaosexperiment pod-delete -n litmus
      3 Pod删除演示测试已成功运行 make demo-65命令执行后,相关Pod的健康状态应为200
      4 系统能够正常恢复 auth-service有2个可用副本;删除操作已按计划执行
      5 测试过程中的记录已被保存 run-chaos.sh命令的执行结果已保存(属于DORA实验数据)
      6 健康检查通过 make check-65命令执行后,结果显示为绿色
      7 UI基础设施已连接正常 在“Overview”页面中,应能看到Active: 1(参见§6.5.2节)

      你在6.5阶段学到了什么

      • 检测机制并不等同于恢复能力:Falco警报并不能证明系统具有高可用性

      • 副本、服务与监控探针的作用:为什么必须设置replicas: 2?这并非仅仅是形式上的要求

      • ChaosEngine与YAML文件:如何通过代码实现声明式的故障注入功能

      • 平台命名空间与应用命名空间的区别:Kyverno会阻止在clearledger中执行混沌测试,而相关引擎则在litmus环境中运行

      • 平均恢复时间:从Pod被终止到其再次恢复为可用状态所需的时间(第7阶段会对此进行详细分析)

      你现在可以在简历中写上这些内容,或者在面试时提到:

      使用LitmusChaos进行了各种测试实验(如删除Pod、模拟网络延迟、增加内存压力),以证明该系统具备恢复能力,并且能够区分“检测机制”与“真正的弹性恢复功能”。

      make snapshot STAGE=65 && make snapshots。确认执行clearledger.stage65命令。详情请参阅如何保存进度

      阶段7——安全监控与可见性

      如果无法测量安全指标,就无法证明其存在性。

      本阶段的目的是了解各种指标、日志以及仪表板是如何协同工作的。然后通过在终端中运行相应命令,观察Grafana中显示的数据,再解释每个面板的含义来验证这些内容。

      这个阶段并不是简单地“安装完Grafana就结束了”。只有当你的仪表板上真正显示出Kyverno检测到的违规行为,以及Falco发出的警报时,阶段7才算完成。此外,还需要准备项目组合的截图(见§7.6)。make check-7命令只能证明系统环境已搭建完毕,但并不能证明你能够检测到安全事件。

      在开始之前:请确保make check-6能通过测试(阶段6.5是可选的,可以跳过)。检查虚拟机是否超负荷运行:multipass exec clearledger -- uptime。如果你已经完成了阶段6.5,请先执行§7.0步骤来缩减Litmus的占用资源。这个过程可能需要大约半天的时间。对于单节点虚拟机来说,这是最耗时的一个阶段。

      当你完成§7.6步骤后,说明你的仪表板上已经显示出了Kyverno检测到的违规行为以及Falco发出的警报,此时就可以执行make check-7(§7.7)、make snapshot STAGE=7,然后再执行make snapshots(确认执行clearledger.stage7)。

      已经安装好了相关工具吗?如果通过kubectl get pods -n monitoring命令查看,发现Grafana的状态为3/3、Loki的状态为1/1,那么可以直接跳过§7.1步骤,从§7.2开始操作(验证系统环境配置),然后进行§7.4阶段的实验。

      你需要先了解的内容

      到目前为止,每个阶段都提供了针对集群的不同视角。阶段3会在GitHub Actions中显示CI扫描结果;阶段4会在终端中展示Kyverno如何阻止错误的部署操作;阶段6则通过Falco的UI界面发出警报,同时你也可以随时通过kubectl logs命令查看Pod的日志信息。这些功能确实很有用,但它们分散在不同的工具中。

      阶段7将所有这些功能整合到了一个地方:Grafana。你只需打开一个仪表板,就能清楚地看到安全事件、政策违规情况以及应用程序的健康状况随时间的变化趋势。

      你即将安装的三种工具

      Prometheus负责从集群中收集各种数据,例如“过去一小时内发生了多少次Kyverno拒绝操作”或“每秒有多少个HTTP请求”。它会每隔15到30秒检查这些数据,并保存历史记录以便生成图表进行分析。

      Loki会收集各种日志信息:这些日志与通过kubectl logs查看到的日志类型相同,只不过是同时从多个Pod中收集的。Falco发出的警报、失败的登录尝试以及应用程序出现的错误都会被保存在这里,这样你就可以之后进行搜索了。

      Grafana是一个Web用户界面,它通过Prometheus和Loki获取数据,并生成图表和表格。当你向审计人员展示系统运行情况时,这种可视化展示比简单的终端截图更为有效——因为它能够证明你在事件发生之后确实能够找到并记录这些信息。

      Prometheus本身并不知道该收集哪些数据。因此,需要通过ServiceMonitors和PodMonitors这类配置对象来指定它应该关注的目标。如果Kyverno中没有相应的监控配置,那么即使Kyverno运行正常,其控制面板也会显示为空白;同样地,对于应用程序的请求频率等指标而言,在§7.5章节之前,相关面板也会保持空白状态,直到通过GitOps部署了支持数据收集的镜像之后才会发生变化。

      日志数据的处理流程也与此类似。Promtail会读取容器的日志信息,并将其发送给Loki。如果Loki没有运行,那么Grafana的日志面板就会显示“无数据”,尽管此时通过kubectl logs仍然可以查看个别Pod的日志。

      这些功能是如何与你已经构建的系统相结合的

      当你在第4阶段阻止了一个错误的kubectl apply命令时,Kyverno会记录下这一操作;到了第7阶段,这一信息就会通过Prometheus显示在Kyverno Policy Violations控制面板上。

      当你在第6阶段在某个Pod内部触发了shell脚本时,Falco会生成警报;这些警报会在第7阶段通过Loki显示在Security Event Timeline面板上。

      当ClearLedger处理HTTP请求或检测到登录失败等事件时,这些信息会被传递到Service Health控制面板上(这些数据是由Loki和Prometheus共同提供的)。

      Vault功能在第5阶段发挥作用,网络策略则在第6阶段生效。虽然它们不一定有独立的显示面板,但它们的作用依然非常重要:减少Git仓库中存储的敏感信息数量、阻止异常的Pod通信行为,这些措施都能帮助让集群运行得更加稳定、安静。

      在这个阶段你需要做什么

      你需要在终端中执行某些命令(例如触发Kyverno的政策违规检测或让Falco发出警报),然后稍等一段时间,直到Prometheus或Loki处理完这些事件并更新相关数据。通常在15到90秒之内,Grafana的对应面板就会显示出更新后的信息。

      这就是安全监控机制的核心意义:终端操作可以证明某个事件确实发生了,而控制面板的存在则证明了你可以在事后无需再次登录集群就能检测并分析这些事件。

      7.0版:释放闲置节点资源以实现规模缩减

      第6.5阶段的配置已经完成。在启动Prometheus、Loki和Grafana之后,你不再需要使用Litmus用户界面、MongoDB或混沌工程工具了——因为它们都会在同一台单节点实验虚拟机上争夺CPU资源(默认情况下会占用6个CPU核心,具体配置可参见scripts/setup-cluster.sh文件)。

      将Litmus的相关功能关闭后,系统可以释放大约500到800MB的RAM内存,并降低在部署监控工具之前的CPU使用率。

      kubectl scale deployment,statefulset -n litmus --replicas=0 --all
      kubectl get pods -n litmus
      # 预期结果:应该没有正在运行的Pod(来自“混沌实验”的成功测试用例所创建的Pod应该是正常的)
      multipass exec clearledger --uptime
      # 预期结果:在继续下一步操作之前,1分钟内的平均负载值最好应低于8

      如果以后想要重新运行混沌实验,可以随时将Litmus的服务规模扩大起来(执行命令:bash stages/stage-6.5-chaos-engineering/scripts/install-litmus.sh)。而对于阶段7到7.5,则保持当前的服务规模即可。

      7.1:安装可观测性工具栈

      这个脚本可以多次运行而不会造成问题。它会检查系统中已安装的组件。如果Grafana、Prometheus和Loki都已经正常运行,那么它就会跳过重复的安装步骤,只更新仪表盘配置和数据采集设置。即使之前安装过程中出现了一些问题,再次运行该脚本也不会导致现有的工具栈出现故障或数据重复。

      只有当确实遇到无法继续安装的情况时,才需要添加FORCE=1参数。例如,如果你修改了Helm配置文件,就需要重新进行完整安装;或者Loki在重启后一直出现异常,这时也可以使用这个参数:

      FORCE=1 bash stages/stage-7-observability/scripts/install-observability.sh

      如果是首次安装,请按照下面第1步中的命令进行操作。除非提示需要这样做,否则不要使用FORCE=1参数。

      对于macOS、Linux和WSL2环境:直接使用FORCE=1 bash ...即可。

      但Windows PowerShell不支持这种语法。

      建议在WSL2 Ubuntu环境中运行这些命令,或者先设置变量:$env:FORCE=1; bash stages/stage-7-observability/scripts/install-observability.sh

      第1步:进行安装(等待脚本显示“✓ Stage 7 installed.”即可):

      bash stages/stage-7-observability/scripts/install-observability.sh

      如果在安装阶段7时看到“Waiting for Falco”这样的提示,这是正常的。

      因为在阶段6中你已经安装了Falco,所以阶段7并不会再次安装它。这个步骤只是为了确保现有的Falco配置能够正常将日志和指标数据传输到可观测性工具栈中。

      具体的数据流如下:

      • Falco仍然运行在falco命名空间中。

      • Promtail会将Falco产生的日志发送到Loki。

      • Grafana会从Loki中读取这些日志。

      • “安全事件时间线”仪表盘会显示Falco发出的警报信息。

      安装完成后,Grafana的仪表盘中可能还会显示为空白内容,这是正常的。你需要先在§7.4节中触发新的警报事件,才能让仪表盘显示出有用的数据。

      第2步:检查Pod状态(请在第1步完成后再执行此步骤):

      kubectl get pods -n monitoring

      你应该会看到类似以下的输出结果(Pod名称的后缀可能会有所不同):

      NAME                                              READY   STATUS    RESTARTS   AGE
      kube-prometheus-stack-grafana-....                3/3     Running   0          5m
      kube-prometheus-stack-prometheus-....             2/2     Running   0          5m
      loki-0                                            1/1     Running   0          5m
      loki-promtail-....                                1/1     Running   0          5m
      

      Grafana必须显示3/3已准备就绪(而不是2/3)。Loki必须显示1/1已准备就绪。如果这些Pod仍处于PendingContainerCreating状态,请等待几分钟,然后再次运行kubectl get pods -n monitoring命令。

      预期结果——Loki正常运行:

      kubectl exec -n monitoring loki-0 -- wget -qO- http://127.0.0.1:3100/ready
      
      ready
      

      预期结果——Grafana能够正常访问Loki(使用相同的路径访问日志面板):

      kubectl exec -n monitoring deploy/kube-prometheus-stack-grafana -c grafana -- \
        wget -qO- --timeout=5 http://loki:3100/ready
      
      ready
      

      预期结果——Grafana的用户界面可以正常访问:

      curl -sI http://grafana.local | head -n 1
      
      HTTP/1.1 302 Found
      

      登录到http://grafana.local 使用用户名adminadmin123

      在安装完成后,如果面板显示为空,这是正常现象——因为此时还没有生成任何日志数据。请继续执行§7.2–§7.4节中的步骤。

      如果使用Helm进行部署时出现错误,请等待30秒,然后运行FORCE=1 bash stages/stage-7-observability/scripts/install-observability.sh命令。具体操作方法请参考troubleshooting.md#Stage 7章节。

      ✋ 实践检查点:确认Loki及仪表板已准备就绪

      在打开Grafana之前,需要先确认日志采集系统及仪表板确实已经成功安装。

      在单节点虚拟机上,即使Loki处于崩溃循环状态,或者ClearLedger仪表板无法加载,Grafana的界面也可能显示正常。如果跳过这个检查步骤,你可能会花费大量时间去调试那些空白的仪表板。

      执行以下命令进行检查:

      kubectl get pods -n monitoring
      kubectl get pods -n monitoring -l app.kubernetes.io/name=loki \
        -o jsonpath '{.items[*].status.containerStatuses[*].restartCount}{"\n"}'
      kubectl get configmap -n monitoring -l clearledger_dashboard=1 --no-headers | wc -l
      

      预期结果:

      • 所有监控Pod都处于Running状态

      • Grafana显示3/3已准备就绪

      • Loki显示1/1已准备就绪

      • Loki的重启次数为0,或者次数很少且没有增加

      • 仪表板的数量为6

      如果Loki不断重启,或者仪表板的数量为0,请立即停止当前操作并修复问题后再继续。通常情况下,Grafana界面显示为空,意味着Loki或相关仪表板没有安装成功,而不是安全事件处理出现了故障。

      7.2:在打开仪表板之前,验证Prometheus、Loki及Grafana的状态

      执行这三项检查,这样当某个仪表板显示为空时,你就能迅速判断是哪个环节出现了问题。

      检查1:Prometheus是否收集了Kyverno相关的指标数据

      kubectl exec -n monitoring deploy/kube-prometheus-stack-grafana -c grafana -- \
        wget -qO- 'http://kube-prometheus-stack-prometheus.monitoring:9090/api/v1/query?query=kyverno_admission_requests_total' 2>/dev/null \
        | head -c 400
      

      (Prometheus是以StatefulSet容器的形式运行的,而不是通过Deployment来部署的。这个查询请求是通过Grafana发送到Prometheus服务端的。)

      预期结果: 返回的JSON数据中应包含"status":"success",以及一个"metric"块;在您触发§7.4节中描述的违规情况之前,该块中的数值可能为0

      如果看到"status":"success""result":[],说明Prometheus已经正常运行,不过Kyverno尚未记录任何访问事件。在实验开始之前,这种情况是正常的。

      检查2:Loki是否保存了Falco日志

      kubectl exec -n monitoring loki-0 -- wget -qO- \
        'http://127.0.0.1:3100/loki/api/v1/labels' 2>/dev/null | head -c 300
      

      预期结果: 返回的JSON数据中应列出诸如"namespace"这样的标签;在Falco事件被记录之后,标签值中也会出现"falco"

      快速查询日志内容:在§7.4节中的练习B之前,查询结果可能为空:

      kubectl exec -n monitoring loki-0 -- wget -qO- \
        'http://127.0.0.1:3100/loki/api/v1/query?query=%7Bnamespace%3D%22falco%22%7D&limit=3' 2>/dev/null \
        | head -c 500
      

      预期结果: 应返回"status":"success";如果"result":[],说明Loki中目前还没有Falco相关的日志记录,但这并不表示Loki本身出现了故障。

      检查3:Grafana是否已导入ClearLedger的仪表板

      curl -s -u admin:admin123 'http://grafana.local/api/search?tag=clearledger' | jq -r '.[].title'
      

      预期结果: 应显示六个仪表板的名称:

      ClearLedger - 合规性状况 ClearLedger - DORA指标 ClearLedger - Kubernetes审计日志分析 ClearLedger - Kyverno违规情况 ClearLedger - 安全事件时间线 ClearLedger - 服务健康状态与认证安全设置

      或者,在Grafana的UI界面中,进入仪表板选项卡,然后筛选标签clearledger,应该能够看到这六个仪表板名称。

      7.3:在Grafana中开始的最初10分钟

      这一部分内容仅用于帮助您熟悉Grafana的操作界面,目前还无需进行任何实际操作或验证。

      关于第7阶段的注意事项: 如果某个面板显示为空,通常意味着在所选的时间范围内没有发生任何事件,并不表示Grafana出现了故障。您需要在§7.4节中手动创建相关事件记录。

      步骤1:打开Grafana

      访问http://grafana.local并登录:

      • 用户名:admin

      • 密码:admin123

      步骤2:设置时间范围

      在右上角,选择“过去15分钟”选项。

      在整个第7阶段中,都保持这种设置。像过去24小时这样的较宽时间范围,在单节点的实验环境中可能会使Loki系统负担过重。

      步骤3:逐一打开控制面板

      先打开一个控制面板,浏览其中的内容,然后再切换到下一个。不要一次性打开全部六个控制面板。

      1. Kyverno违规情况:显示第4阶段中出现的政策违规事件。

      2. 安全事件时间线:显示第6阶段中由Falco工具发出的警报信息。在日志表中,你可能会看到一些与postgres系统相关的旧记录。

      3. 服务健康状况+认证信息:显示应用程序的流量情况以及登录尝试次数。

      4. 合规性状况:为审计人员提供的汇总视图。可以先快速浏览一下,等学习到§7.4部分后再仔细研究。

      5. 审计日志分析:在MicroK8s环境中,这个面板默认是空的(因为审计流程并未被启用)。

      6. DORA指标:显示部署频率相关的图表。需要多次执行CI构建流程才能积累足够的数据。初次查看时,这些图表可能会显示为空值,属于可选内容。

      请使用本指南中提供的简短控制面板链接。避免使用那些包含长串随机字符的旧书签地址。

      你也可以在Grafana中找到这些控制面板:进入控制面板选项,然后搜索标签clearledger即可。

      步骤4:如何理解所看到的信息

      Grafana的控制面板会从两个地方获取数据:

      • Prometheus用于显示随时间变化的数据,例如Kyverno违规次数或请求处理速度等。

      • Loki用于显示日志信息,比如Falco发出的警报或认证服务产生的消息等。

      • 如果某个数值面板上的数字超过了零,那就说明某项指标确实发生了变化;如果线形图表显示出数据在某个时间点出现了突然上升的趋势,那么很可能是在你执行了某些操作之后才出现的这种变化。而日志面板则会直接显示具体的文本内容,例如规则名称、CRITICAL等级提示,或者登录尝试失败等信息。

        如果只有日志面板显示连接被拒绝这样的错误信息,请在§7.1部分再次检查Loki系统的运行情况。如果数值面板能够正常显示数据,但日志面板却无法正常工作,那么问题很可能出在Loki系统上,而不是Grafana本身。

        步骤5:继续下一步学习

        先打开控制面板1至3,然后继续学习§7.4部分的内容。在那里,你可以在终端中执行命令,并观察控制面板如何实时显示各种安全事件信息。

        7.4:实践操作环节:通过终端验证控制面板的显示效果

        这一部分是核心学习内容。对于每一个练习步骤,都需要先执行相应的命令,等待一段时间让数据被收集完毕,然后再在Grafana中查看结果。

        注意时间安排:每执行一条命令后,需要等待30到90秒,以便Prometheus能够完成数据采集工作,Loki也能及时处理这些数据。

        两种实践方式

        请亲自执行每条命令,然后查看Grafana的显示结果。这些就是练习A、B和C的内容。

        选项2:使用指导脚本

        该脚本会依次执行相同的步骤,并在每个步骤之间暂停,以便您能够查看Grafana的显示结果:

        bash stages/stage-7-observability/scripts/generate-dashboard-data.sh
        

        或者:

        make demo-7
        

        这两条命令的作用是相同的。脚本会提示您“在查看Kyverno仪表板后按Enter键”。请先切换到Grafana查看相关数据,然后再返回并按下Enter键。

        如果希望脚本无暂停地运行呢?(这样会更快速,也无需人工干预)

        SKIP_PROMPT=1 make demo-7
        

        如果您想了解每个步骤的具体操作过程,请选择选项1;如果您需要有人指导您完成整个流程,请选择选项2;而如果您只是希望尽快生成数据,那么可以使用`SKIP/prompt=1`这个命令。

        练习A:Kyverno阻止不良Pod的创建 → Prometheus接收这些信息 → Kyverno最终显示结果

        在终端中执行以下操作:创建一个违反第4阶段规则的Pod(以root用户身份运行):

        cat << 'YAML' | kubectl apply -f -
        apiVersion: v1
        kind: Pod
        metadata:
          name: stage7-kyverno-lab
          namespace: clearledger
        spec:
          containers:
            - name: test
              image: nginx:alpine
        YAML
        

        如何判断操作是否成功:

        您需要验证Kyverno是否能够成功阻止这个不良Pod的创建。如果操作成功,那么这个Pod就永远不会被创建出来

        如果操作成功,您应该会看到以下结果:

        • 终端会显示“Error from server”以及“denied the request”这样的信息

        • 错误信息中列出的具体规则名称并不重要。输出结果中可能只列出一条规则,也可能列出多条规则(例如`disallow-root-containers`、`require-resource-limits`等)。即使显示了多条规则,也说明操作仍然成功。

        • 在集群中绝对看不到这个Pod的名字

        kubectl get pods -n clearledger | grep stage7-kyverno-lab
        

        预期结果:没有任何输出。

        如果操作失败,请先停止当前步骤并修复第4阶段的相关问题

        • 命令执行完成后应该会正常显示“created”这一信息,且不会出现任何错误

        • 使用`kubectl get pods -n clearledger`命令查看Pod列表时,应该能看到`stage7-kyverno-lab`这个Pod

        如果出现了上述情况,说明Kyverno没有成功阻止这个root Pod的创建。请先运行`make check-4`命令进行检查,然后再继续执行第7阶段的操作。

        成功的终端输出示例:(您的输出结果中可能还会列出其他规则名称):

        Error from server: error when creating "STDIN": admission webhook "validate.kyverno.svc" denied the request:
        policy disallow-root-containers/validate-run-as-non-root fail: Running as root is not allowed
        

        确认Prometheus是否已经接收到了这些信息(此步骤可选,但如果Grafana中没有任何显示结果,这个步骤就非常有用):

        kubectl exec -n monitoring deploy/kube-prometheus-stack-grafana -c grafana -- \
          wget -qO- 'http://kube-prometheus-stack-prometheus.monitoring:9090/api/v1/query?query=kyverno_admission_requests_total{request_allowed="false"}' 2>/dev/null \
          | grep -o '"value":\[[^]]*\]' | head -3
        

        预期结果: 应该能看到一个包含最近生成的Unix时间戳以及一个大于0的数字"value"条目(例如"value":[..., "1"])。如果看到了这样的结果,那就说明即使Grafana面板显示“没有数据”,Kyverno和Prometheus也在正常工作。

        如何查看Grafana界面: 打开Kyverno违规记录页面。

        Kyverno违规记录的截图

        你需要验证的内容: 确保终端中显示的拒绝记录也出现在Grafana界面中。并不需要所有面板都显示出数据,只要有一个明确的迹象证明Kyverno的阻止操作确实被记录下来即可。

        步骤1:快速检查

        1. 违规记录(时间范围): 如果显示的数字较大,说明成功;如果显示“没有数据”,则说明失败。

        2. 违规情况统计(时间范围): 同样,如果这个计数器的数值大于0,也表示成功。

        3. 当前激活的Kyverno规则: 通常这个数值为18。如果这个数字存在,说明Grafana能够与Prometheus正常通信,即使前两个面板仍然显示“没有数据”,这也是正常的。

        步骤2:如果前两个指标正常,再快速浏览其他图表

        • 按资源类型划分的违规率: 在中间那个图表中,查找在运行`kubectl apply`命令前后数值有所增加的“Pod”相关数据。

        • 被阻止最多的资源类型: 在左下角的表格中,查找与“Pod”相关的记录。

        • 按命名空间划分的违规情况趋势: 在右下角的图表中,查找与“clearledger”相关的异常数据。

        需要注意的是,这些图表的显示结果可能会存在延迟。只要在步骤1中看到的数字大于0,就可以继续下一步操作了。这些图表可以作为§7.6节中截图内容的补充证明。

        如果前两个面板显示“没有数据”,但终端中的拒绝记录确实存在:

        这种情况很常见。那些面板显示的是在指定时间范围内新发生的拒绝记录数量,而不是所有被记录下来的拒绝记录的总数。有时,某个拒绝请求会在Grafana的计数器更新之前就已经被Prometheus记录下来了。

        可以尝试以下方法进行验证:

        1. 再次运行`kubectl apply`命令(这样会再次触发拒绝操作,这是预期中的结果)。

        2. 等待60秒钟。

        3. 然后点击右上角的“刷新”按钮。

        在第二次触发拒绝操作后,你应该会在顶部的统计面板中看到数字2。将这个结果截图下来,用于§7.6节的证明。

        如果还是没有数据?可以尝试使用“探索”功能作为补充验证:

        1. 在Grafana的左侧菜单中选择“探索”。

        2. 数据源:Prometheus。

        3. 输入如下命令:`sum(kyverno_admission_requests_total{request_allowed="false"})`

        4. 然后点击“运行查询”。

        成功标准:查询结果应为1或2。

        即使仪表板上的数据显示缓慢,只要在终端中看到拒绝操作的结果,并且通过Grafana的“探索”功能查看到计数大于0,就说明验证成功。

        练习B:Falco Shell → Loki → 安全事件时间线

        你的操作步骤(与练习A相同):

        • 练习A:你执行了某些非法操作,Kyverno阻止了这些操作,随后Grafana的Kyverno仪表板会更新相应数据。

        • 练习B:你在正在运行的Pod内部执行了可疑操作,Falco检测到了这一行为,Grafana的“安全事件时间线”也会随之更新。

        你已经在第6阶段完成过这个操作(使用命令`make demo-6`)。现在你需要再次执行这个步骤,证明警报信息不仅会在`http://falco.local`上显示,也会在Grafana中呈现出来。

        具体操作思路:假设你是一个攻击者,你已经获得了对`auth-service` Pod的shell访问权限:此时Falco应该会发出警报,而这个警报信息也会出现在时间线仪表板上。

        步骤1:触发警报(通过终端执行命令)

        你需要模拟攻击者的行为,进入`auth-service` Pod并运行`id`命令来查看当前登录用户的身份。这种操作属于可疑行为,Falco应该能够检测到它。

        以下是三个需要连续执行的命令,请将其复制粘贴到终端中:

        AUTH_POD=$(kubectl get pod -n clearledger -l app=auth-service \
          --field-selector/status.phase=Running -o jsonpath '{.items[0].metadata.name}')
        echo "正在使用的Pod名称:$AUTH_POD"
        kubectl exec -n clearledger "$AUTH_POD" -c auth-service -- /bin/sh -c 'id & exit'
        

        每条命令的作用如下:

        1. 第1行:查找正在运行的`auth-service` Pod的名称,并将其保存在变量`AUTH_POD`中。

        2. 第2行:输出Pod的名称,确认命令执行成功。

        3. 第3行:在 해당Pod内部运行`id`命令,然后立即退出。这个操作就是模拟的“攻击”行为,Falco会监控这类行为。

        成功标准:命令执行后的输出结果中必须包含以下两行内容:

        正在使用的Pod名称:auth-service-77b7d9cd99-xxxxx
        uid=1000 gid=1000 groups=1000
        
        • 第一行:真实的Pod名称。

        • 第二行:在容器内部运行的`id`命令的输出结果。

        步骤1完成。此时Pod仍在运行中,你的操作没有造成任何故障。

        如果失败,请在进入步骤2之前先停止操作并解决问题:

        • 如果出现“内部错误”或“容器未找到”的提示,说明操作失败。

        • 如果输出结果中只有“正在使用的Pod名称:……”而没有后续信息,也表示操作失败。

        运行命令 `kubectl get pods -n clearledger -l app=auth-service`,当有某个Pod显示为Running状态时,重新尝试该操作。

        步骤2:确认Falco已经记录了这一事件(请立即在终端中查看结果)

        Falco生成的日志是一条很长的JSON字符串。不要试图阅读整个日志内容,只需运行以下命令:

        kubectl logs -n falco -l app.kubernetes.io/name=falco --tail=50 | grep -i 'Shell spawned'
        

        如果搜索成功,你应该会在日志中看到这样一行内容:

        Shell spawned in ClearLedger container ... pod=auth-service-... cmd=sh -c id && exit
        

        或者也会看到规则的名称:

        "rule":"Shell Spawned in ClearLedger Container"
        

        如果找到了这条记录,那就说明步骤B在终端中已经成功执行。请将这条日志截图保存下来,作为你的测试成果。

        需要忽略的内容:

        • Defaulted container "falco" out of: ...:这是正常的kubectl操作产生的信息,可以忽略。

        • postgres-0/etc/passwd相关的记录:这些内容属于第6阶段的背景操作日志,与你的测试无关。

        • 其余的JSON数据(如output_fieldsk8smeta等),无需进行解析。

        如果grep搜索没有找到任何结果:请重新执行步骤1,等待5秒钟后再再次运行grep命令。

        步骤3:确认Loki已经存储了这些日志(请先等待约60秒)

        到目前为止的操作流程如下:

        • 步骤1:你在auth-service容器内触发了警报机制。

        • 步骤2:Falco将这条警报记录到了自己的日志文件中 ✓

        • 步骤3要验证的是:这些日志信息是否已经被存储到Grafana的日志数据库Loki中?

          Falco本身并不直接与Grafana交互,而是通过Promtail将日志数据复制到Loki中。这个复制过程需要60到90秒的时间。因此请在完成步骤1后等待一段时间,再运行此检查命令。

          这条命令的作用是:

          "在Loki中搜索包含Shell spawned这一关键词的日志记录,同时只显示那些也提到了auth-service的记录。"

          kubectl exec -n monitoring loki-0 -- wget -qO- \
            'http://127.0.0.1:3100/loki/api/v1/query?query=%7Bnamespace%3D%22falco%22%2Ccontainer%3D%22falco%22%7D%20%7C%3D%20%22Shell%20spawned%22&limit=3' 2>/dev/null \
            | grep -i 'auth-service'
          

          如果搜索成功,你会看到这样一行记录:

          Shell spawned in ClearLedger container ... pod=auth-service-... cmd=sh -c id && exit
          

          这意味着Loki已经存储了这条警报信息,Grafana也可以正常显示它。

          如果搜索失败(尽管看起来似乎找到了结果):那可能是因为你只搜索了ClearLedger这个关键词,而实际上postgres-0容器在读取/etc/passwd文件时产生的日志信息也被误认为是测试结果。请务必查找与auth-service相关的记录。

          如果搜索结果为空?那也没关系。如果步骤2已经成功完成,可以直接进入步骤4。可能是因为Promtail还在同步数据,或者生成的JSON日志数据量太大,导致快速搜索无法找到结果。不过很多时候,即使这条命令没有输出任何结果,Grafana也会显示相应的警报信息。

          步骤4:打开Grafana

          打开安全事件时间线

          安全事件时间线仪表盘的截图

          这就是正确的仪表盘。顶部的标题应该显示为ClearLedger - 安全事件时间线

          在查看各面板之前:

          1. 时间范围:过去1小时(右上角显示)

          2. 自动刷新:关闭

          3. 如果你的shell命令是在几分钟前执行的,那么请重新执行步骤1

          4. 等待90秒,然后点击“刷新”按钮

          你可能会看到以下内容(这是正常现象):

          • 严重警告(过去1小时):数字可能很大,例如1.08千条。这些警告大多与postgres-0进程循环读取/etc/passwd文件有关(属于第6阶段的正常操作)。这并不意味着你的系统出现了故障。

          • 按规则名称分类的警告(以饼图形式显示):其中大部分与ClearLedger系统中对敏感文件的读取操作有关,这也是正常现象。

          • 最近的严重/警告事件:会显示很多与Postgres相关的记录。你的shell命令产生的警告也会出现在这里,但可能被其他信息掩盖了。

          另一张显示Grafana安全事件时间线仪表盘的截图 e3a363af-676b-47a6-a61a-fe5ee8b7c130

          顶部的时间线Falco警告按优先级排序可能显示“无数据”。这是已知的问题,无需担心,可以直接使用日志面板或浏览器搜索功能来查找信息。

          如何在这个仪表盘上找到你的警告:

          1. 保持当前页面为ClearLedger - 安全事件时间线,不要切换到“探索”或“Tempo”选项卡。

          2. 点击右侧的最近的严重/警告事件列表。

          3. Cmd+F(Mac系统)或Ctrl+F(Windows/Linux系统)键进行搜索。

          4. 搜索关键词auth-serviceShell spawned

          如果搜索结果中出现了与你的Pod相关的记录以及“Shell spawned”这一信息,请立即截图保存。

          常见错误:有些人会误用Grafana的“探索”功能,并选择Tempo数据源来查看ledger-service的相关记录。但实际上,这些信息属于第7.5阶段(OpenTelemetry相关内容),与本次练习无关。Tempo显示的是请求追踪日志,并非Falco安全警告信息。

          选项B的测试方法(请选择一种):

          1. 最佳方案: 在第二步中,使用终端命令进行grep搜索,如果结果显示“Shell spawned”,同时通过安全事件时间线日志也能找到“auth-service”/“Shell spawned”这些信息,那么就说明测试成功——请截取这两部分的屏幕截图。

          2. 另一种可行的方法: 第二步中,只需截取grep搜索的结果的屏幕截图,再加上显示严重警告(过去1小时内)安全事件时间线面板截图即可。这样就能证明Falco → Loki → Grafana这一数据传输流程是正常的,即使你的shell相关日志被其他信息掩盖了。

          3. 备用方案(仅当 dashboard搜索失败时使用): 第二步中,进行grep搜索的同时,打开Grafana的探索功能,并选择数据源为Loki(而非Tempo):

            • 在左侧菜单中选择探索

            • 在左上角的资料源下拉列表中选择Loki

            • 输入查询语句:{namespace="falco", container="falco"} |= "Shell spawned"

            • 点击运行查询

            • 查看结果中是否包含“auth-service”这一信息。

          这是关于§7.6的屏幕截图。

          练习C:登录失败、Loki以及服务健康状态检测

          测试背景:有人正在尝试通过你的登录API猜测密码。你在终端中发送了十次错误的登录请求。auth-service会将其记录在日志中,而Grafana的服务健康状态 + 认证安全面板应该会显示这些错误请求的数量有所增加。

          测试流程与练习A和B相同:先在终端中执行操作,然后查看日志,最后通过仪表板验证结果。

          第一步:发送错误的登录请求(在终端中执行)

          请复制并粘贴以下代码:

          for i in $(seq 1 10); do
            curl -s http://clearledger.local/auth/health >/dev/null
            curl -s -X POST http://clearledger.local/auth/login \
              -H 'Content-Type: application/json' \
              -d '{"email":"lab-attacker@evil.com","password":"wrong"}' >/dev/null
          done
          echo "done"
          

          成功标准:你只需要看到如下输出即可:

          done

          正常情况下,curl命令不会产生任何输出。这个脚本会依次向/auth/health/auth/login发送十次错误的登录请求。

          失败情况:如果出现“curl: (6) Could not resolve host”这样的错误信息,需要在你的Mac上运行bash scripts/setup-hosts.sh脚本;如果仍然无法连接,请检查kubectl get pods -n clearledger -l app=auth-service命令的输出。

          第二步:确认auth-service确实记录了这些错误请求

          请执行以下命令:

          kubectl logs -n clearledger -l app=auth-service --tail=30 | grep -i 'Failed login' | tail -3
          

          成功标准:你应该能看到类似以下的输出:

          Failed login attempt for email: lab-attacker@evil.com

          你可能会看到多行信息(每次登录失败都会对应一行记录)。但实际上只需其中一行即可。请将这一行截图保存下来,用于你的作品集。

          如果使用grep命令后没有输出任何结果:请等待10秒钟后再重新运行该命令。如果仍然没有任何显示,请检查auth pod是否正在运行:kubectl get pods -n clearledger -l app=auth-service

          步骤3:打开Grafana(在完成步骤1后等待约60秒)

          访问Service Health + Auth Security页面。

          这就是正确的仪表板页面。其标题应为ClearLedger - Service Health + Auth Security

          1. 时间范围:过去1小时

          2. 自动更新功能:关闭

          3. 请点击刷新按钮一次

          显示Service Health + Auth Security信息的Grafana仪表板截图

          需要检查的内容(仅针对练习C而言):

          1. 登录失败次数(过去1小时):这个数值应该大于0。这是证明你的系统确实记录了登录失败事件的关键依据。

          2. 登录失败日志流:该面板中会显示相关的日志记录。合格标准:日志中必须包含Failed login attemptlab-attacker@evil.com这些信息。如有需要,可以在面板内使用Cmd+F快捷键进行搜索。

          如果某些面板显示的内容为空,可以忽略它们:

          • 成功登录次数:当这个数值为0时也属于正常情况(因为你们之前发送的都是错误的密码)。

          • 各服务的请求频率:在§7.5章节介绍指标数据之前,这个面板可能为空。对于练习C来说,并不需要查看这个信息。

          当你拥有两张截图时,就说明你已经成功完成了练习C:

          截图1(必需):这张截图应显示步骤2中终端输出的lab-attacker@evil.com登录失败记录,这证明了系统确实记录了这些失败的登录尝试。

          截图2(选择其中一张即可):

          • 选项A:选择登录失败次数(过去1小时)面板,确保该数值大于0(例如10),这样就能证明Grafana确实统计了这些失败记录。

          • 选项B:选择登录失败日志流面板,查看其中是否包含lab-attacker@evil.com这一条记录。如果选项A的面板仍然为空,但日志流中确实有这条信息,那么就可以选择这个选项。

          你只需要截图1以及选项A或选项B中的任意一张即可满足§7.6章节的要求。

          练习D:合规性仪表板(审计员视角汇总)

          你的任务是:打开一个能够整合练习A、B和C内容的仪表板。这个仪表板专门为审计人员设计,能够在同一屏幕上查看访问控制机制、运行时检测结果以及应用程序安全相关的数据。

          条件:只有在你完成了A、B和C三项测试之后,才能进行下一步。

          步骤1:打开控制面板

          合规性检测

          Grafana合规性检测控制面板的截图

          将时间范围设置为过去1小时,关闭自动刷新功能,然后点击刷新

          步骤2:查看顶部的统计信息

          控制面板显示的统计项 来源 结果
          政策违规情况 测试A(Kyverno) > 0
          运行时威胁 测试B(Falco) > 0(这是正常的结果)
          认证失败次数 测试C(异常登录尝试) > 0
          Grafana合规性检测控制面板的截图

          这三项指标的值不需要很高,只要在测试结束后大于零即可。

          如果某一项指标的值仍然是0:请重新运行相应的测试(A、B或C),等待90秒后再刷新页面。对于“政策违规情况”,可能需要进行第二次Kyverno检测。

          这是关于§7.6的第三张截图,这张图能够证明防御措施是有效的。

          ✋ 实践检查:你是否真的完成了第7阶段的测试?

          安装Grafana并不是最终目的,关键在于能否通过这些测试来检测到实际发生的事件,并在控制面板上看到相应的结果。

          可选的终端验证步骤(用于确认Grafana配置是否正确):
          curl -s -u admin:admin123 'http://grafana.local/api/search?tag=clearledger' | jq -r '.[].title'
          
          curl -s -u admin:admin123 'http://grafana.local/api/datasources' | jq -r '.[].name'
          

          执行第一个命令后,你应该能看到六个控制面板的名称:

          • ClearLedger - Kyverno政策违规情况

          • ClearLedger - 安全事件时间线

          • ClearLedger - 服务健康状况与认证安全

          • ClearLedger - 合规性检测结果

          • ClearLedger - Kubernetes审计日志分析

          • ClearLedger - DORA指标

          执行第二个命令后,你应该至少能看到以下两个控制面板的名称:

          • Prometheus

          • Loki

          哪些情况说明你还没有完成测试?:

          make check-7这个命令仅用于检查监控容器是否正在运行。即使输出结果为绿色,也不能证明你已经完成了§7.4阶段的测试。

          哪些情况说明你已经完成测试?:

          你已经完成了§7.4中的练习A、B和C,并保存了§7.6中的截图:

          1. Kyverno拒绝访问机制(终端界面 + 控制面板显示结果)

          2. Falco shell警报功能(终端界面 + 安全事件时间线显示结果)

          3. 登录失败记录(终端界面 + 服务健康状况监控界面)

          4. 合规性评估结果汇总(上述三项指标均大于零)

          如果你已经拿到了这四张截图,那么第7阶段的任务就已经完成了。

          7.5:填写请求率图表(可选步骤)

          这一环节对于第7阶段来说并非必需。练习A–C以及§7.6中的截图已经包含了所需信息,如果你愿意直接跳过这一部分也可以。

          需要特别注意的是,这个步骤与第7.5阶段的内容不同——后者涉及OpenTelemetry/Tempo工具;而当前这个小节仅与“服务健康状况监控面板上的按服务划分的请求率图表”有关。

          本节的用途:

          在“服务健康状况监控”功能中,“登录失败记录”相关的数据是直接从日志文件(Loki)中提取的;而“按服务划分的请求率图表”则需要不同的数据来源:应用程序容器必须暴露出/metrics端点,这样Prometheus才能收集到相应的请求统计信息。

          相关的代码已经存在于代码仓库中(文件名为app/*/prom_metrics.py),Prometheus的配置也已经设置完毕,能够自动抓取这些数据(配置文件为clearledger-podmonitor.yaml)。不过常见的问题是:如果你的集群仍在使用构建过程中未更新之前的旧镜像,那么这部分功能可能无法正常使用。

          步骤1:检查是否已经收集到了所需的指标数据(耗时约30秒)

          首先运行以下命令进行检测。如果检测通过,就可以直接跳过§7.5阶段的其余内容。

          kubectl exec -n monitoring deploy/kube-prometheus-stack-grafana -c grafana -- \
            wget -qO- 'http://kube-prometheus-stack-prometheus.monitoring:9090/api/v1/query?query=http_requests_total' 2>/dev/null \
            | grep -o '"__name__":"httprequests_total"' | head -1
          

          检测通过:系统会输出"__name__":"http_requests_total"这一信息。此时打开“服务健康状况监控”界面并刷新页面,你应该能看到“按服务划分的请求率图表”中已经有数据条目显示了。

          如果没有输出结果:请继续执行步骤2。

          步骤2:部署能够暴露/metrics端点的镜像

          你可以选择以下其中一种方法来完成任务。

          方法A:使用GitOps流程(如果你从第1阶段或第2阶段就开始使用CI/CD工具的话)

          1. 将更改推送到你的应用程序代码仓库中的main分支上。

          2. 等待CI系统生成新的镜像,并更新clearledger-infra相关配置。

          3. 确认ArgoCD工具显示“已同步”且“服务状态正常”的提示后,再进入下一步。

          4. 然后进入步骤3。

          方法B:快速本地部署方式(仅适用于本地开发环境)

          export DOCKER_USERNAME=your-dockerhub-user
          bash stages/stage-7-observability/scripts/build-metrics-images.sh
          

          通过这个命令,可以为所有三个服务生成并部署那些已经集成了指标收集功能的镜像。

          注意:如果 `clearledger-infra` 仍然指向旧的标签,ArgoCD的自动修复功能可能会在几分钟内恢复这些标签。这对于快速进行实验室演示来说没有问题。但如果需要永久解决这个问题,请使用方法A或更新基础设施仓库(详见§2节中的回滚说明)。

          步骤3:验证指标数据是否在部署后约60秒内被正确显示

          kubectl exec -n clearledger deploy/auth-service -c auth-service -- \
            wget -qO- http://127.0.0.1:8000/metrics 2>/dev/null | head -5
          

          合格标准:输出结果中应包含以 `# HELP` 或 `http_requests_total` 开头的行。

          接下来需要确认Prometheus是否已经收到了这些数据:

          kubectl exec -n monitoring deploy/kube-prometheus-stack-grafana -c grafana -- \
            wget -qO- 'http://kube-prometheus-stack-prometheus.monitoring:9090/api/v1/query?query=http_requests_total' 2>/dev/null \
            | grep -o '"__name__":"httprequests_total"' | head -1
          

          合格标准:输出结果中应包含 `"__name__":"http_requests_total"` 这一行。

          现在可以生成一些请求流量(重新运行练习C中的curl脚本,或者多次访问 http://clearledger.local/auth/health),等待60秒后,打开 服务健康状况与认证安全设置 并刷新页面。在按服务分类的请求量选项中,应该能够看到 `auth-service`、`ledger-service` 或 `notification-service 这些服务的相关数据。

          何时停止操作:

          • 如果“请求量”数据显示为空,但“登录失败”面板仍然能正常显示信息?那么说明您已经完成了第7阶段的任务。“请求量”数据的存在虽然很有帮助,但并非必须检查的项目。

          • Prometheus的查询结果正确,但对应的图表却为空?请将时间范围调整为过去1小时,然后生成一些请求流量,等待60秒后再刷新页面。

          7.6:第7阶段的总结与截图检查

          您已经接近完成整个流程了。这一部分主要是为了保存相关证据,以便后续使用。

          您真的完成了吗?

          仅仅打开Grafana并看到六个控制面板是远远不够的。仅仅让 `make check-7` 命令通过也是不够的——那只能证明相关的Pod正在运行而已。

          只有当您完成了§7.4节中的所有步骤,等待相关面板的数据更新完毕,并且从集群中保存了三张截图之后,才能说明您真的完成了整个任务。

          如果这些面板显示的内容为空,或者只显示出Postgres数据库产生的无关数据,请先返回到§7.4节重新开始操作。

          在拍摄每张截图之前:请将时间范围设置为过去15分钟(对于练习B来说,则设置为过去1小时)。同时确保在截图中包含时间选择器和各个控制面板的标题。

          截图1:Falco警报示例(练习B)

          打开 安全事件时间线 页面。

          找出那些包含Shell spawnedauth-service字样的最近发生的严重/警告级别事件。如果Postgres数据库的记录掩盖了这些信息,可以在日志面板中使用Cmd+F键进行搜索,这样也能找到相关数据。

          截图2:Kyverno违规检测结果(练习A)

          打开Kyverno政策违规检测页面

          截取显示“政策违规情况”或“违规记录”的截图,确保其中至少有一条记录。

          截图3:合规性统计结果(练习D)

          打开合规性检测页面

          截取顶部三行数据,确保其中“政策违规情况”“运行时威胁”以及“登录失败次数”这三项数值均大于零。

          截图4(可选):登录失败记录(练习C)

          打开服务健康状态与认证安全设置页面

          截取显示“1小时内的登录失败次数”大于零的截图,或者查看显示“lab-attacker@evil.com”这一用户名的登录失败日志记录。

          将生成的图片文件保存在合适的位置,例如docs/evidence/stage-7-screenshot-1-falco.png。给文件起名时请注明其用途,以便后续识别。

          最终检查:运行命令make check-7(参见§7.7节),保存虚拟机配置后,即可认为你已经完成了第7阶段的测试。

          7.7:验证结果

          make check-7

          预期验证结果:

          ▶ 第7阶段——可观测性测试(Grafana + Prometheus + Loki)
            ✓ Prometheus正在运行
            ✓ 可通过http://grafana.local访问Grafana,且其集群健康状态正常
            ✓ Loki容器正在运行,且未发生重启
            ✓ 可通过http://loki:3100/访问Loki,且其处于准备就绪状态
            ✓ ClearLedger已配置警报规则
            ✓ ClearLedger的仪表板已导入成功(共找到6个仪表板)
          

          如果发现Loki容器重启或仪表板丢失,请先按照§7.1节的要求进行修复,然后再确认第7阶段测试已完成。

          在完成§7.6节的操作以及运行命令make check-7后,记得保存虚拟机配置。

          请参阅下文第7阶段的最后部分内容。

          7.8:出现问题的原因(实验笔记与面试讨论要点)

          系统架构简述:Prometheus用于存储各种数值数据,Loki负责存储日志记录,而Grafana则将这些数据以可视化形式呈现出来。只有当集群中真正发生某些异常情况时,这些数据才会被显示出来。

          在实验过程中你遇到了哪些问题?

          1. 安装完成后仪表板为空:这是正常现象。Grafana本身不会自动生成事件记录,你需要通过§7.4节中的操作来触发这些事件的显示。

          2. Loki响应缓慢或刷新页面时一直停留在“取消”按钮上:Falco生成的日志量非常大,如果选择过去24小时的时间范围进行查询,可能会使小型集群负担过重。建议使用过去1小时的时间范围,并且每次只查看一个仪表板,等待约10秒钟后再刷新页面。

          3. make check-7命令运行成功,但仪表板上仍然没有数据:这只说明健康检查通过了,并不意味着实际存在事件记录。你还需要完成§7.4节和§7.6节中的操作,才能继续进行下一步测试。

          如果在面试中有人问到这个问题...

          仪表盘为空? Grafana仅显示已经发生过的事件。如果指定时间范围内没有发生任何事件,对应的仪表盘就会显示为空内容。这是正常现象,除非你主动触发了某些操作。

          在小规模集群上使用Loki时性能会变慢吗? Falco产生的日志量非常大。因此我们将时间范围设置得较短(15分钟而非24小时),并且每次只打开一个仪表盘进行查看。这种处理方式与在资源有限的生产环境中所采取的策略是一样的。

          你们是如何证明这些功能确实有效的呢?我是亲自进行了测试:首先阻止了一些恶意请求,然后在正在运行的容器中创建了shell账户,还发送了一些失败的登录尝试。之后我查看Grafana中的相关数据,并截取了相应的仪表盘截图。也就是说,先在终端上执行操作,然后再通过仪表盘来验证结果。

          简而言之,你可以这样回答:

          “我将Kyverno和Falco与Grafana连接了起来。为了证明这些功能的有效性,我触发了一些安全策略,并在Grafana的仪表盘中展示了相应的结果。在单节点实验室环境中,当时间范围设置得较广时,Loki的性能会变慢,因此我们刻意限制了查询的范围。”

          早期阶段可能出现的各种问题(比如与Trivy、Kyverno或镜像标签相关的问题)可以在docs/troubleshooting.md中找到——这些内容在准备第7阶段测试时并不需要特别复习。

          7.9:如果在更新仓库后仪表盘显示的内容不正确...

          请重新应用之前配置好的仪表板设置,然后生成真实的事件数据(请参考§7.4章节,不要使用虚假数据):

          bash stages/stage-7-observability/scripts/install-observability.sh
          # 接着执行§7.4章节中的A–C项测试(包括阻止恶意请求、在容器中创建shell账户以及发送失败登录尝试等操作)
          

          打开Grafana,选择过去1小时的时间范围进行查看。每完成一项测试后,请等待约30–60秒,然后再刷新页面。§7.4章节中的测试内容可以帮助你了解每个仪表盘应该显示什么信息。

          你在第7阶段学到了什么?

          • Prometheus可用于记录可量化的安全事件,比如被阻止的恶意请求数量或HTTP请求的频率等。

          • Loki能够提供详细的日志分析信息,比如Falco生成的JSON数据以及认证相关的日志记录。

          • Grafana起到了呈现分析结果的作用,它是整个测试流程中不可或缺的一部分,并非实验结束后需要额外安装的工具。

          • 你可以通过终端操作、后端信号的变化以及仪表盘数据的更新来追踪整个处理流程。

          • ServiceMonitors和PodMonitors是将第4至第6阶段的测试结果呈现为图表的关键工具。

          • 仪表盘为空通常表示“目前还没有发生任何事件”或“指定的时间范围有误”,而不是意味着“安全系统出现了故障”。

          • 通过Grafana的一个界面,你就可以向审核人员展示你的合规性状况。

          • 网络策略必须明确允许monitoring命名空间通过端口8000与应用程序容器进行通信,否则PodMonitor在尝试收集数据时就会因为“超时”而失败。

          • 在MicroK8s环境中,Kubernetes Audit Log的仪表盘默认是空的,因为API服务器的审计流程(audit-policy → file → Promtail → Loki)并没有被启用。

          • 要使“请求率”相关的数据能够正常显示,必须确保整个数据采集链都是完整的:包括包含/metrics路径的应用程序镜像、PodMonitor以及相应的网络策略设置;任何一个环节缺失都会导致仪表盘显示为空内容。

          你现在可以在简历中写上这些内容,或者在面试时提及:

          利用Prometheus、Loki和Grafana构建了安全监控系统(这些仪表板能够将Kyverno检测到的违规行为、Falco发出的警报以及DORA指标关联起来),并且能够证明某个安全事件是从终端开始,最终在仪表板上得到显示的。

          阶段7完成检查清单:

          • 运行`make check-7`命令 → 结果为6/6 ✓ (预计阶段6.5的Litmus测试会失败,因为该测试需要消耗较多内存)

          • 访问`http://grafana.local/d/clearledger-kyverno-violations`,确认违规记录的数量大于0。

          • 访问`http://grafana.local/d/clearledger-security-events`,确保有CRITICAL级别的Falco警报显示出来。

          • 访问`http://grafana.local/d/clearledger-compliance`,确认政策违规记录、运行时威胁信息以及登录失败次数都大于0。

          • 访问`http://grafana.local/d/clearledger-service-health`,确保登录失败次数大于0;只有当你完成了步骤§7.5后,请求率才需要大于0。

          • 保存项目组合的截图1至3张。

          运行`make snapshot STAGE=7 & make snapshots`命令。确认`clearledger.stage7`命令的执行结果是否正常。千万不要跳过这一步。阶段7的操作比较复杂,因此磁盘压力可能会增大。请参阅如何保存你的进度了解更多信息。

          在Mac重启或进入睡眠模式后,auth/ledger pods的状态可能会显示为UnknownInit:0/1,尽管集群本身仍处于运行状态。请参阅故障排除指南进行处理。

          阶段7.5 — OpenTelemetry(可选)

          你可以选择跳过整个阶段7.5。仅仅完成阶段7(收集指标数据与日志信息)就足以完成这个家庭实验室项目,并进入阶段8了。

          只有当你希望在项目组合展示或面试中利用分布式追踪功能,且你的虚拟机拥有足够的剩余内存(大约1.5 Gi的空闲空间)时,才需要执行阶段7.5。

          你将添加什么内容

          阶段7的作用是用来回答是否发生了某些异常事件?这个问题(例如Kyverno阻止了某个Pod的运行,Falco检测到了异常行为,或者登录尝试失败等)。

          而分布式追踪系统的作用则是用来解答针对这个具体请求,究竟执行了哪些步骤?每一步耗时多少?这些问题。

          • 指标数据:记录了多少次请求、发生了多少次错误。

          • 日志信息:应用程序在日志文件中记录的内容,比如错误信息、警告提示以及登录失败的原因等。

          • 追踪记录:例如ledger-service首先调用了auth-service(耗时12毫秒),随后又调用了Postgres(耗时8毫秒)等详细流程。

          在这个阶段,你需要发送一条真实的交易请求,然后在Grafana的Explore功能中查看这条请求的执行过程。你会看到每一个步骤以及它们各自的执行时间:ledger-service、auth-service、Postgres等等。

          开始之前请注意

          1. 完成阶段7的所有要求:完成步骤§7.4中的练习,保存步骤§7.6要求的截图,确保运行`MAKE CHECK-7`命令后结果正常。

          2. 检查虚拟机的内存状况:运行`multipass exec clearledger -- free -h`命令,确认有大约1.5 Gi的空闲内存。

          3. 如果你之前已经执行了阶段6.5的Litmus测试,请先将其规模缩小(按照步骤§7.0进行调整)。

          当你在Grafana Explore中看到完整的请求追踪信息,并且命令make check-75能够成功通过时,说明你的工作已经完成。接下来再执行make snapshot STAGE=75即可。

          在应用日志中忽略此警告

          从第7阶段开始,你可能会看到如下提示:

          WARNING: 在导出追踪数据时遇到了临时错误 StatusCode.UNAVAILABLE

          这个警告其实并无害处。因为应用程序已经设置好了用于发送追踪数据的功能,但直到§7.5.3阶段才会安装接收这些数据的组件。

          你的应用程序仍然可以正常运行,只是那些追踪数据会被直接丢弃而已。在§7.5.3阶段安装相应的收集组件后,这个警告就会消失。

          追踪数据的传输机制

          1. 当应用程序有请求被执行时,会生成追踪数据并发送出去。

          2. OTel收集组件会接收这些数据(端口为4317),然后将其转发给后续的处理环节。

          3. Grafana Tempo会负责存储这些数据。

          4. 通过Grafana Explore(选择Tempo数据源)可以逐步查看整个请求的处理过程。

          应用程序实际上是与收集组件进行通信,而不是直接与Grafana Tempo交互。这样一来,以后即使需要更改追踪数据的存储位置,也不必重新构建应用程序。

          7.5.1:检查内存使用情况与系统负载

          Tempo组件大约需要300MB的内存空间。在安装之前,请先确认你的系统是否有足够的剩余空间:

          multipass exec clearledger -- free -h    # 确保有约1.5Gi的可用内存
          multipass exec clearledger -- uptime      # 根据你的CPU配置,系统的负载应该处于正常范围内
          SKIP_CHAOS_CHECK=1 bash scripts/health-check.sh 7

          如果从第6.5阶段开始还一直在运行Litmus组件,请先将其关闭(参见§7.0节):

          kubectl get pods -n litmus --field-selector=status.phase=Running
          # 预期结果:应找不到任何相关资源

          7.5.2:安装Grafana Tempo

          Tempo是用于存储追踪数据的后端组件。请将其安装在monitoring命名空间中,与Prometheus和Loki放在同一位置:

          helm repo add grafana https://grafana.github.io/helm-charts
          helm repo update
          
          helm install tempo grafana/tempo \
            --namespace monitoring \
            --set tempo.storage.tracebackend=local \
            --set tempo.storage.trace.local.path=/var/tempo \
            --set persistence.enabled=true \
            --set persistence.size=5Gi \
            --wait

          验证Tempo是否已经成功安装并运行:

          kubectl get pods -n monitoring -l app.kubernetes.io/name=tempo
          # 预期结果:tempo-0   1/1   Running
          kubectl exec -n monitoring tempo-0 -- wget -qO- http://localhost:3200/ready
          # 预期结果:ready

          7.5.3:部署OTel收集组件并连接Grafana

          这个步骤会安装OTel收集组件(该组件会从应用程序的Pod中接收追踪数据),并通过sidecar机制自动将Tempo注册为Grafana的数据源:

          kubectl apply -f stages/stage-7.5-opentelemetry/infra/otel/otel-collector.yaml
          kubectl apply -f stages/stage-7.5-opentelemetry/infra/otel/grafana-datasource-tempo.yaml

          确认收集器正在运行:

          kubectl get pods -n monitoring -l app=otel-collector
          # 预期结果:otel-collector-xxxxx   1/1   Running
          

          确认收集器已经启动(此时尚未开始接收追踪数据):

          应用程序会通过 OTLP 将追踪数据发送给收集器;收集器本身并不会主动从 Pod 中获取数据。在此步骤中,你只需要确认收集器处于监听状态即可。

          kubectl logs -n monitoring deploy/otel-collector --tail=15
          # 预期输出:
          #   Starting GRPC server ... endpoint: 0.0.0.0:4317
          #   Starting HTTP server ... endpoint: 0.0.0.0:4318
          #   Everything is ready. Begin running and processing data.
          #   No crash loops or repeated errors.
          

          要证明追踪数据确实正在被收集器接收,可以在 §7.5.6 节中生成一些交易数据后,检查收集器的日志,寻找来自 debug 导出器的相关记录;随后在 Grafana Tempo 中验证这些追踪数据是否已被正确显示(参见 §7.5.7 节)。

          7.5.4:启用 Prometheus 的远程写入接收功能

          OTel 收集器还会通过远程写入方式将 OTel 监控数据发送给 Prometheus;因此,Prometheus 必须能够接受这些数据:

          helm upgrade kube-prometheus-stack prometheus-community/kube-prometheus-stack \
            --namespace monitoring \
            -f stages/stage-7-observability/infra/helm/kube-prometheus-stack-values.yaml \
            --wait
          

          这个命令会应用在 Helm 配置文件中的 enableRemoteWriteReceiver: true 设置。请等待 Prometheus 重新启动(大约需要 60 秒)。

          7.5.5:确认应用 Pod 已成功连接到收集器

          clearledger-infra 目录下的部署配置已经设置了 OTEL_EXPORTER_OTLP_ENDPOINT;一旦收集器开始运行,Pods 会自动与之连接。

          请确认系统中不再出现与 OTEL 相关的警告信息:

          kubectl logs -n clearledger deploy/ledger-service -c ledger-service --tail=20 2>/dev/null \
            | grep -v "opentelemetry\|otlp\|Transient" | tail -10
          # 预期结果:只显示 INFO 级别的日志,没有 WARNING 类型的信息
          

          如果仍然有警告信息出现,可能是网络策略中未允许端口 4317 的数据传输。请应用最新的网络策略配置:

          kubectl apply -f infra/deferred-by-stage/stage-6-runtime-security/netpol/network-policies.yaml
          

          7.5.6:生成一条追踪数据

          现在,请创建一笔交易,并观察这笔交易在系统中的流转过程:

          # 第一步:注册账户(如果已经注册过可以跳过这一步)
          curl -s -X POST http://clearledger.local/auth/register \
            -H "Content-Type: application/json" \
            -d '{"email":"trace-demo@clearledger.io","password":"TracePass123"}' | python3 -m json.tool
          
          # 第二步:登录并获取访问令牌
          TOKEN=$(curl -s -X POST http://clearledger.local/auth/login \
            -H "Content-Type: application/json" \
            -d '{"email":"trace-demo@clearledger.io","password":"TracePass123"}' \
            | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
          echo "获取到的访问令牌为:${TOKEN:0:20}..."
          
          # 第三步:创建一笔交易(这就是你要追踪的交易请求)
          curl -s -X POST http://clearledger.local/ledger/transactions \
            -H "Authorization: Bearer $TOKEN" \
            -H "Content-Type: application/json" \
            -d '{"amount": 5000, "direction": "credit"}' | python3 -m json.tool
          

          确认收集器是否已接收到相关数据:

          kubectl logs -n monitoring deploy/otel-collector --tail=30 \
            | grep -iE "Traces|spans|ResourceSpans" || echo "尚未检测到相关数据 — 请参阅§7.5.5节(OTEL环境配置 / 网络策略)"
          # 在交易成功后,应该会看到显示已导出追踪信息/数据行的日志记录

          7.5.7:在Grafana中查看追踪信息

          打开http://grafana.local,然后进入左侧侧边栏中的探索选项。

          在查询界面的顶部:

          1. 数据源下拉菜单(带有橙色T标志)→ 选择时间范围

          2. 标有“A”字的查询选项卡 → 包含三个标签页:搜索 | TraceQL | 服务图表

          3. 点击搜索按钮,此时会出现下拉筛选框。TraceQL标签页仅是一个文本输入框;如果什么都不输入直接点击该按钮,系统会显示“未返回任何结果”。

          Grafana界面中显示时间范围及ledger服务的截图

          步骤2:按服务名称进行筛选

          搜索标签页中:

          • 输入ledger-service作为服务名称进行筛选

          • 暂时不填写“追踪名称”、“状态”、“持续时间”及“标签”等字段

          • Grafana会自动生成如下查询语句:{resource.service.name="ledger-service"}

          将时间范围设置为过去15分钟,这样就能包含你在§7.5.6节中执行的交易相关数据。

          步骤3:执行查询

          Grafana的“探索”功能并没有“执行查询”按钮;选择服务后,结果会自动显示。如果表格仍然为空,请使用界面右上角的蓝色刷新按钮

          步骤4:查看追踪信息流图

          在查询编辑器下方,找到表格 - 追踪信息选项。你应该能看到至少如下这样的一行数据:

          列名 示例值
          追踪ID 5730edf3…(蓝色链接)
          开始时间 即你执行curl命令的时间)
          服务名称 ledger-service
          操作名称 POST /transactions
          执行时间 约200毫秒(实际时间可能有所不同)
          Grafana界面中显示时间范围、ledger服务及查询结果的截图点击“追踪ID”链接,右侧面板会打开追踪详情视图。 Grafana截图,显示节奏、账本服务及查询结果

          追踪详情视图显示的内容

          请求头信息:ledger-service: POST /transactions

          • 追踪ID:此次请求的唯一标识符

          • 耗时:从开始到结束的总时间

          • 涉及的服务2个(对于普通交易来说,分别是ledger-serviceauth-service

          在时间轴上展开相应的步骤信息:

          ledger-service   POST /transactions          (~总耗时)
            ├── auth-service   GET /verify             ← 通过HTTP进行JWT验证
            ├── ledger-service INSERT / sqlalchemy    ← 执行Postgres写入操作
            └── (可选) redis PUBLISH              ← 仅当交易金额达到通知阈值时才会触发
          交易流程图

          如何阅读追踪详情页面: 节奏追踪详情:包含与auth-service验证步骤相关的ledger-service交易信息

          每一行代表请求中的一个步骤(Grafana将其称为span)。右侧的彩色条状图显示了该步骤所花费的时间,这就是跨度条。条形越长,表示在该步骤上花费的时间越多。

          点击某一行或其对应的条形图,右侧会打开详细信息面板。其中会显示两种类型的元数据:

          • 步骤属性:描述这个具体步骤中发生了什么。
            例如:HTTP方法(POSTGET)、状态码(200),或者数据库操作相关的SQL语句。在您的追踪记录中,可能会看到FastAPI接收请求时的asgi.event.type: http.request这类信息。

          • 资源属性:说明该步骤是在哪个服务中执行的。
            例如:service.name: ledger-servicek8s.cluster.name: clearledgerdeployment.environment: production等。

          简单记忆方法:步骤属性表示该步骤具体完成了什么操作;资源属性则表示该步骤是由哪个服务执行的。

          将追踪信息与日志关联起来:选定某个步骤后,点击“日志”标签页,就能直接查看同一时刻该Pod对应的Loki日志记录。

          将此追踪详情页面截图保存:这可以作为阶段7.5的成果展示材料。

          TraceQL的替代方案

          如果您更喜欢使用文本框输入,可以切换到TraceQL标签页,然后粘贴以下内容:

          显示TraceQL按钮位置的截图
          { resource.service.name = "ledger-service" }
          

          如果表格为空

          如果TraceQL显示未返回任何数据:请使用搜索标签页,或者将上面提到的TraceQL查询语句粘贴到该标签页中。

          如果搜索结果页面没有数据显示:请将时间范围调整为过去15分钟,然后重新运行§7.5.6节中提到的交易相关操作,等待几秒钟后再刷新页面。

          如果Grafana无法连接到Tempo服务器:需要确保数据源URL使用了端口3200。请重新配置数据源设置,并重启Grafana:

          kubectl apply -f stages/stage-7.5-opentelemetry/infra/otel/grafana-datasource-tempo.yaml
          kubectl rollout restart deployment/kube-prometheus-stack-grafana -n monitoring
          

          如果收集器日志中没有相关数据:请参考§7.5.5节的内容进行排查,通常是由于OTEL环境变量设置错误或网络配置导致端口4317被阻塞所致。

          7.5.7b:了解追踪事件发生的具体过程

          在§7.5.6节中,您执行了一条curl命令。Grafana会显示这条请求所经过的所有环节。

          可以将其想象成追踪一个包裹的运输过程:

          1. ledger-service发送了POST /transactions请求

          2. ledger-service会询问auth-service:“该用户是否已登录?”

          3. ledger-service会将相关数据保存到数据库

          4. 只有当交易金额达到10,000或以上时,redis才会被触发执行相应操作

          上述每个环节都对应着Tempo界面中显示的一条记录。实际上这些并不是四次独立的请求,而是一次请求在多个节点之间进行的传递。

          为什么我会看到“ledger-service”和“auth-service”这两个节点?

          因为ledger服务在保存交易数据之前,必须先与auth服务进行交互。Grafana将这些步骤合并显示在一起,这样您就能看到整个数据处理流程,而不仅仅是第一个处理环节。

          为什么我的演示示例中没有显示Redis相关的记录?

          因为在您的示例中,交易金额被设置为5000,而该应用程序只有在金额达到10,000或以上时才会与Redis交互。因此看到“ledger-service”、“auth-service”和“database”三个节点是正常的。

          如果想查看Redis的相关记录,请再次执行§7.5.6节中的操作,将交易金额设置为15000,然后重新在Tempo界面中查询数据。

          可选:将实际代码与这些解释进行对照学习

          打开app/ledger-service/main.py文件,找到create_transaction函数,仔细阅读其实现逻辑。Tempo界面中显示的记录正是按照这个函数的执行顺序生成的:先验证用户身份,然后将数据保存到数据库中,最后可能还会通知Redis服务器。

          可选:同一请求,三种工具

          在您执行 `curl` 命令时:

          • Tempo(此阶段):哪些服务被启动了,以及每个服务的运行耗时是多少

          • Loki(第7阶段):应用程序在日志文件中记录了什么内容

          • Prometheus(第7阶段):在这段时间内发生了多少次请求

          同一时刻,通过三种不同的工具来获取信息。在第7阶段时,您已经使用过 Loki 和 Prometheus 了。

          7.5.8:验证结果

          make check-75

          预期的输出结果如下:

          ▶ 第7.5阶段 — OpenTelemetry(分布式追踪)
            ✓ OTel收集器正在运行(有1个副本)
            ✓ Grafana Tempo的数据源配置文件存在
            ✓ Tempo服务正在运行
            ✓ auth-service已设置了OTEL_EXPORTER_OTLP_ENDPOINT

          如果看到警告信息,说明:

          ⚠ 在auth-service中未找到OTel环境变量,请使用更新后的配置文件重新部署

          `check-75`命令会检查部署配置文件中是否包含`OTEL_EXPORTER_OTLP_ENDPOINT`这一环境变量。较旧的第5阶段配置文件可能没有列出这个变量,但即使如此,追踪功能仍然可以正常工作;当该环境变量缺失时,Python应用程序会默认使用`http://otel-collector.monitoring.svc.cluster.local:4317`作为端点。

          如果收集器的日志中显示了相关的追踪数据,且Tempo工具也能展示相应的追踪信息,那么您就可以继续下一步操作。要消除警告信息,只需重新部署相关应用程序的配置文件即可(无需重新部署整个自定义配置系统,因为Kyverno可能会阻止redis或postgres相关补丁的应用):

          kubectl apply -f infra/manifests/auth-service/deployment.yaml
          kubectl apply -f infra/manifests/ledger-service/deployment.yaml
          kubectl rollout restart deployment/auth-service deployment/ledger-service -n clearledger
          make check-75

          在执行`make check-75`命令后,请保存您的虚拟机配置。具体操作步骤请参见第7.5阶段的最后部分说明。

          您学到了什么

          第7阶段帮助您获取了各项指标数据(例如服务的繁忙程度)以及日志信息;而第7.5阶段则进一步提供了追踪数据,让您能够了解某个耗时较长的请求的具体处理流程。

          在实验环境中,您是通过执行一次`POST /transactions`的curl请求来验证这些功能的。在实际生产环境中,原理也是相同的:当用户发起一个API请求时,该请求会经过多个服务环节,而您需要在一个地方就能查看整个请求的处理路径。

          如果有人在面试中问到这个问题:

          为什么需要追踪数据呢?指标数据可能会显示P99延迟时间增加了一倍,日志信息则可能指出某个Pod出现了故障。而追踪数据能帮助您准确判断是哪个下游服务环节导致了延迟,无论是认证模块、数据库、缓存系统,还是第三方API,都能一目了然地找出问题所在。

          你们是如何实现这一功能的呢?我们使用OpenTelemetry为各个服务添加了追踪功能,将收集到的数据发送到相应的收集器,并将追踪结果存储在Grafana Tempo中。应用程序是与收集器进行交互的,而不是直接与后端服务通信,因此之后我们可以随时更改数据存储方式,而无需重新部署所有服务。

          在遇到这类问题时,你会如何处理?首先从日志、指标或警报中找到速度缓慢或出现故障的跟踪信息,在 Tempo工具中打开这些记录,逐个服务地查看调用链,找出时间消耗较大的环节,然后在同一时间戳下查看该服务的日志。这样比在五个容器上逐一查看日志并希望它们能对得上要快得多。

          简而言之,你可以这样表述:

          “我们同时使用这三项工具:Prometheus用于监控请求量和错误情况,Loki用于查看日志细节,而Tempo则用于跨微服务进行请求级别的调试。当延迟突然增加时,我会从某个跟踪记录开始,找出造成延迟的环节——通常是数据库或下游API——然后结合该服务的日志和指标进一步分析问题。”

          make check-75 && make snapshot STAGE=75 && make snapshots。确认clearledger.stage75是否执行成功。请参阅如何保存进度

          第8阶段——迁移至AWS

          你的目标是在AWS上运行与在本地笔记本电脑虚拟机上相同的ClearLedger应用程序。

          你不需要重新编写代码。前7个阶段已经使用GitOps、Kyverno、secret管理工具以及可观测性技术构建了Kubernetes容器环境。第8阶段只是改变应用程序的运行环境而已,其余配置(如镜像、ArgoCD工作流程和安全策略)保持不变,只有底层的云服务会进行更换(例如从MicroK8s切换到EKS,从Vault切换到Secrets Manager等)。

          • 本地开发环境:使用MicroK8s在容器中运行Postgres数据库,配置dev Vault,使用Docker Hub,应用程序地址为clearledger.local

          • AWS环境:使用EKS运行应用程序,数据存储在RDS中,秘密信息由Secrets Manager管理,代码仓库使用ECR,应用访问通过ALB实现

          我是否已经准备好进入第8阶段?

          • 本地开发环境已完成前7个阶段的配置(第7.5阶段可选)

          • 执行make check-7命令后测试通过(如果进行了跟踪分析,还需要执行make check-75

          • 已注册AWS账户,并启用了账单提醒功能。使用make aws-up命令可以创建可计费的资源

          • 请快速浏览§8.2章节,了解make aws-up的具体作用(即使你选择的是快捷配置路径)

          完成条件:应用程序能够在AWS的ALB上正常访问,ArgoCD能够顺利同步配置信息,并且在执行完所有操作后运行make aws-down命令以停止计费。

          make aws-up能为你带来什么

          这只是一个演示环境:虽然已经具备了生产环境所需的架构,但尚未完全准备好投入实际使用。当前这个环境仅支持HTTP协议,不支持TLS加密。

          第7阶段配置的可观测性工具已自动安装完毕。持续集成流程仍然会运行Gitleaks、Semgrep、Checkov、Trivy和Cosign等检测工具。

          对于真正的生产环境来说,还需要添加HTTPS支持(具体配置参见ingress-aws-https.example.yaml文件),在正式部署前进行测试,并设置警报路由机制。这些功能虽然有文档说明,但并未被自动配置脚本包含在内。

          GitOps规则:在完成初始配置后,不要手动执行kubectl apply命令来部署应用程序。ArgoCD会负责整个集群的管理(第2阶段)。只需将配置变更推送到Git仓库,然后让ArgoCD自动完成同步工作即可。

          AWS中的秘密管理机制

          在本地实验环境中,Vault会将秘密文件存储在Kubernetes Pod中;而在AWS上,这些秘密数据被保存在AWS Secrets Manager中(该服务是由Terraform创建的)。你的应用程序仍然需要将这些秘密作为环境变量来使用,比如DATABASE_URL

          ESO(本实验环境中的默认方案):其工作原理如下:

          1. Terraform将真实的密码信息存储在AWS Secrets Manager中(例如clearledger/auth-service

          2. External Secrets Operator(ESO)会监视这些秘密数据

          3. ESO会将这些秘密数据复制到Kubernetes集群内部的普通Secret对象中(例如auth-service-secret

          4. 你的应用程序会从这个Kubernetes Secret对象中读取DATABASE_URL,这与第0阶段的做法相同,只不过这些值是从AWS获取的,而不是来自Git中的YAML文件

          你绝对不能将密码信息保存在Git中。ESO会确保Kubernetes Secret对象与AWS Secrets Manager中的数据保持同步。

          CSI(可选方案,详见§8.5节练习):使用相同的数据源,但传递方式不同:这些秘密数据会被以文件的形式挂载到/mnt/secrets/*路径下,而不是作为环境变量使用。这种方式更接近于本地实验环境中Vault的工作方式。

          IRSA:这是一种允许ESO读取AWS Secrets Manager中的秘密数据,而无需在Kubernetes集群中存储AWS访问密钥的机制。AWS会信任Kubernetes的服务账户来获取这些秘密信息。

          通过IRSA,AWS可以信任Kubernetes ServiceAccount,因此不需要在Git或集群中保存AWS_ACCESS_KEY_ID

          更多详细信息请参阅:stages/stage-8-aws-migration/docs/secrets-patterns.md

          8.1:通过第8阶段的两种方法

          快速方案(约45–60分钟):修改terraform/secrets.tf文件(将CHANGE_ME_BEFORE_APPLY替换为其他内容),然后执行以下命令:

          make aws-up    # 运行stages/stage-8-aws-migration/scripts/aws-spinup.sh脚本
          make aws-down  # 完成操作后释放可计费资源

          之后请阅读§8.2节,以便了解具体执行了哪些步骤。

          手动方案(详见§8.3节):依次运行Terraform、ECR推送命令、ArgoCD、Kyverno、ESO等工具,然后自行完成部署流程。这种方案适用于学习、面试或排查启动失败的问题时使用。

          如果你只执行了make aws-up命令,请务必阅读§8.2–§8.5节的内容。否则你将无法了解Terraform、ESO或ArgoCD各自完成了哪些操作。

          在首次进行第8阶段的部署之前,请先阅读§8节:CI路由配置与CLEARLEDGER_CI_TARGET,并且只有当Terraform成功执行完所有操作后,才能将CLEARLEDGER_CITARGET=aws这个设置启用,切勿在仍处于第1–7阶段时进行此项配置。

          8.2:make aws-up命令具体执行哪些操作

          启动脚本会按顺序执行15个步骤:

          准备工作(步骤1–6):检查相关工具及AWS登录账号;运行terraform apply命令来配置VPC、EKS、RDS、ECR、Secrets Manager、GuardDuty、CloudTrail、IAM等服务;确认安全设置是否正确;将构建好的镜像推送到ECR仓库;使用你的注册信息和Git哈希值更新manifests/kustomization.yaml文件;最后配置kubectl工具以适应EKS环境。

          平台配置(第7至12步): 需安装ArgoCD;配置Kyverno与集群策略、Falco监控工具、External Secrets Operator及IRSA服务账户,同时启用CSI秘密管理功能,并搭建第7阶段的观测系统。

          部署流程(第13至15步): ArgoCD应用程序中的`clearledger-aws`模块会同步`stages/stage-8-aws-migration/manifests/`目录中的文件,等待ALB主机名确定后,会输出相应的URL并提醒后续操作。

          脚本执行完成后,在浏览器中访问打印出的地址`http:///`(即可进入ClearLedger登录界面),或参考§8.3 需在何时打开哪些界面了解如何使用Argo CD与Grafana的功能。

          默认的应用程序部署方案会使用ESO进行秘密管理;同时也会安装CSI组件,因此你可以在§8.5章节中直接尝试文件挂载操作,而无需额外配置。

          Terraform代码结构: 并不存在`terraform.tf`文件。`terraform {}`块(包含版本信息、提供程序设置及可选的S3后端配置)位于`main.tf`文件的顶部。各类资源分别被归类到不同的文件中,例如`vpc.tf`、`eks.tf`、`rds.tf`等。

          所有命令都需要在`stages/stage-8-aws-migration/terraform/`目录下执行。

          8.3:手动操作指南

          请先阅读本节中的“开始前的准备”内容,至少亲自尝试一次步骤A中描述的手动操作,而不是直接运行`make aws-up`命令。所有路径都是从仓库根目录开始的。

          命令用于完成具体配置任务,而用户界面则用于验证这些配置是否生效。在Homelab的第2阶段和第7阶段中,你已经学会了如何在浏览器中打开Argo CD与Grafana;第8阶段的操作方式也是如此。

          但在AWS环境中,`/etc/hosts`文件中并不存在`clearledger.local`或`grafana.local`这样的条目。因此,你需要通过端口转发来访问控制平面相关的用户界面,而应用程序则使用公共的ALB主机名进行连接。

          何时打开哪些界面(检查点映射表)

          `make aws-up`命令会依次执行十五个步骤。你并不需要同时打开所有用户界面,只需了解在脚本执行的不同阶段应该查看哪些内容,以及成功的标志是什么即可。

          首先,Terraform会搭建AWS的基础环境。当第2步完成之后,请打开AWS控制台,确认集群、注册表和数据库都已经准备就绪:EKS上的`clearledger`服务应处于活动状态,ECR中应该有包括`frontend`在内的四个仓库(目前这些仓库为空也是可以的),而RDS上的`clearledger-postgres`数据库也应当是可用状态

          具体操作步骤请参阅步骤2之后的AWS控制台操作

          接下来是容器镜像的配置。在完成第4步或CI流程后(当GitHub Actions中的AWS相关任务显示为绿色状态时),请检查ECR:每个仓库中都应该列出你的Git SHA标签值,这些标签值就是ArgoCD在部署应用程序时会下载的内容。

          大约在第7步的时候,脚本会开始安装Argo CD。通过端口转发访问其用户界面,确认登录页面能够正常加载即可。此时你还没有看到实际的应用程序界面,只是需要验证GitOps机制是否能够正常使用。更多详细信息请参阅Argo CD用户界面说明

          步骤12用于实现可观测性。将端口转发到Grafana,登录后确认系统中列出了六个ClearLedger控制面板。在这些面板中,内容可以保持为空状态,直到你生成相应的事件数据。这一操作与在本地实验室进行的步骤7是完全相同的。

          步骤13涉及应用clearledger-aws应用程序。返回到Argo CD,选择Applicationsclearledger-aws。你需要确保用于认证、账本管理和通知功能的Pod处于“已同步”、“运行中”且状态正常。

          步骤14旨在将该应用程序暴露在公共URL上。在浏览器中打开http:///,你应该能看到与在本地实验室使用的clearledger.local相同的ClearLedger登录界面。这个页面实际上是由ALB提供的,因此不需要在/etc/hosts文件中进行任何配置。


          当你想要从终端快速检查该应用程序的状态时,可以使用/auth/health以及其他相关的健康检查URL。

          有关更多信息,请参阅ALB——应用程序首次公开访问的步骤

          如果你需要额外的确认,还可以进行以下检查:进入EC2 → Load Balancers →,然后查看clearledger服务的状态。确保其状态为Active,并且前端界面及API服务也都处于正常运行状态。

          在AWS环境中,该应用程序实际上由四个服务组成,它们都通过同一个ALB进行访问:前端服务位于/路径下(包括登录页面、控制面板和交易相关功能),而三个API服务则分别位于/auth/ledger/notifications路径下。

          对于步骤8来说,你的项目成果展示截图应该就是显示该应用程序用户界面的ALB公共URL地址,例如http://clearledger-xxxxxxxxxx.eu-west-1.elb.amazonaws.com,此时你应该能看到ClearLedger的登录页面或控制面板。

          带有ALB地址的ClearLedger用户界面截图

          对于Argo CD和Grafana来说,在打开浏览器标签页的同时,需要保持一个终端窗口处于开启状态,并持续运行kubectl port-forward命令。如果需要关闭这个隧道连接,可以按下Ctrl+C键。

          在开始之前

          步骤A:在secrets.tf文件中设置真实的密码

          打开stages/stage-8-aws-migration/terraform/secrets.tf文件,查找文本CHANGE_ME_BEFORE_APPLY。这个文本在该文件中出现了四次(分别用于配置Postgres密码、JWT密钥以及两个数据库的URL)。你需要将所有这些地方中的该文本替换为真实的密码:

          • Postgres密码:选择一个强密码,并确保在所有需要使用该密码的地方都使用相同的值。

          • JWT密钥:运行命令openssl rand -base64 64,然后将生成的随机字符串粘贴到相应的位置。

          如果secrets.tf文件中仍然存在CHANGE_ME_before_APPLY这样的文本,那么执行命令make aws-up将会失败。

          步骤B:在终端中进行检查

          aws sts get-caller-identity
          terraform --version
          
          # 在首次运行terraform apply命令之前必须执行此操作;GitHub Actions的OIDC配置文件(ci-aws.yaml)会在执行apply命令时读取这个值:
          cp stages/stage-8-aws-migration/terraform/terraform.tfvars.example \
             stages/stage-8-aws-migration/terraform/terraform.tfvars
          # 修改terraform.tfvars文件中的内容:github_owner = "YOUR_GITHUB_USERNAME"  # 这里应该填写你的GitHub用户名或组织名称,而不是占位符
          
          terraform -chdir=stages/stage-8-aws-migration/terraform validate
          # 如果没有将YOUR_GITHUB_USERNAME替换为正确的值,这个命令会提示“Set github_owner in terraform.tfvars”

          github_owner被设置之前,切勿运行terraform apply命令。如果使用占位符来执行该命令,AWS会创建一个名为clearledger-github-actions-ecr的IAM角色,并为其配置信任关系,允许其访问repo:YOUR_GITHUB_USERNAME/...资源。这样一来,在执行“发布图像到ECR”步骤时,CI流程会因为“没有权限执行sts:AssumeRoleWithWebIdentity操作”而失败。

          解决方法:先修改terraform.tfvars文件中的配置,然后再次运行terraform apply命令。之后使用aws iam get-role命令验证角色是否已被正确创建,如果确认没有问题,就可以重新运行那些失败的CI任务(注意只需重新运行出现问题的步骤,无需重新构建整个管道流程)。

          步骤1–2:使用Terraform进行配置

          cd stages/stage-8-aws-migration/terraform
          terraform init -upgrade
          terraform apply
          
          # 保存配置输出结果:
          terraform output -raw ecrRegistry_url
          terraform output -raw github_actions_ecr_role_arn
          terraform output -raw eso_role_arn
          terraform output -raw auth_service_irsa_role_arn
          terraform output -raw kubeconfig_command
          cd ../../..
          

          步骤2完成后在AWS控制台中的检查内容。

          在操作集群之前,请先确认Terraform是否已成功创建了所需的资源:

          1. EKS → 集群 → clearledger → 状态为“活动状态”,拥有3个节点

          2. ECR → 仓库 → clearledger/auth-serviceledger-servicenotification-servicefrontend(在步骤4或CI流程开始之前,这些仓库中还没有任何图像)

          3. RDS → 数据库 → clearledger-postgres → 状态为“可用”

          确认GitHub是否能够将图像推送到ECR仓库中(仅当您以后打算使用AWS CI时才需要检查这一点)

          GitHub Actions需要具备相应的权限才能将图像上传到您的AWS账户。Terraform会为此创建一个IAM角色,但前提是您必须在运行terraform apply命令之前,在terraform.tfvars文件中填写真实的GitHub用户名。

          验证该角色是否已生效:

          aws iam get-role --role-name clearledger-github-actions-ecr \
            --query 'Role.AssumeRolePolicyDocument.Statement[0].Condition.StringEquals."token.actions.githubusercontent.com:sub"' \
            --output text
          

          如果显示的结果为:repo:your-real-username/clearledger:environment:production,则表示配置成功;

          如果显示的结果仍为:repo:YOUR_GITHUB_USERNAME/clearledger:...,说明您忘记修改terraform.tfvars文件中的配置了。

          请修正文件中的错误,再次运行terraform apply命令。之后在GitHub的“Actions”页面中找到那些失败的CI任务,点击“Re-run failed jobs”即可重新尝试上传图像的操作。这样只需重新执行上传步骤,无需重新构建整个系统。

          如果您目前仅使用make aws-up命令,而还没有启用AWS CI功能,那么可以跳过这一整段操作。

          ECR仓库是什么时候创建的?

          ECR仓库是在执行terraform apply命令时创建的(即步骤2),而不是在您执行docker push操作时。Terraform会创建一些空的图像仓库,例如clearledger/auth-serviceledger-service等,因此在执行完配置命令后暂时看不到任何图像也是正常的。

          图像会在第4步中通过手动执行`docker push`命令上传,或者当GitHub Actions的持续集成流程成功完成时被上传。

          请将您的AWS CLI区域设置为 eu-west-1

          本实验中的所有操作都在eu-west-1区域(爱尔兰)进行。如果您的CLI默认区域设置为us-east-1,即使相关资源确实存在,命令也会提示资源缺失:

          aws configure set region eu-west-1
          aws configure get region   # 预期输出:eu-west-1
          

          步骤3–4:安全服务与ECR镜像的配置

          AWS_REGION=eu-west-1   # 或者使用上述命令设置区域
          
          # 步骤3:验证安全服务的配置(必须指定--region eu-west-1)
          aws guardduty list-detectors --region "${AWS_REGION}"
          # 预期输出:DetectorIds: ["]" — 如果结果为空,说明区域设置错误,并非表示“安全服务未创建”
          
          aws cloudtrail get-trail-status --name clearledger-trail --region "${AWS_REGION}
          # 预期输出:IsLogging: true
          # 如果出现“Unknown trail ... us-east-1”这样的错误,说明您忘记指定--region eu-west-1
          
          # 步骤4:构建镜像并将其推送到Terraform已经创建的ECR仓库中
          ECR_REGISTRY=$(terraform -chdir=stages/stage-8-aws-migration/terraform output -raw ecrRegistry_url)
          AUTH_ECR=$(terraform -chdir=stages/stage-8-aws-migration/terraform output -raw auth_service_ecr_url)
          LEDGER_ECR=$(terraform -chdir=stages/stage-8-aws-migration/terraform output -raw ledger_service_ecr_url)
          NOTIFY_ECR=$(terraform -chdir=stages/stage-8-aws-migration/terraform output -raw notification_service_ecr_url)
          TAG=$(git rev-parse --short HEAD)
          
          aws ecr get-login-password --region "${AWS_REGION}" \
            | docker login --username AWS --password-stdin "${ECR_REGISTRY」
          
          docker build -t "${AUTH_ECR}:${TAG}" app/auth-service && docker push "${AUTH_ECR}:${TAG}"
          docker build -t "${LEDGER_ECR}:${TAG}" app/ledger-service && docker push "${LEDGER_ECR}:${TAG}"
          docker build -t "${NOTIFY_ECR}:${TAG}" app/notification-service && docker push "${NOTIFY_ECR}:${TAG}"
          
          # 确认镜像已成功上传(可选操作)
          aws ecr describe-images --repository-name clearledger/auth-service --region "${AWS_REGION}" \
            --query 'imageDetails[*].imageTags' --output table
          

          ECR控制台(在完成第4步或持续集成流程成功后): 打开每个仓库,进入“Images”选项卡。您应该能看到与您的git提交SHA相匹配的标签信息。如果仓库中还没有镜像,ArgoCD之后会显示ImagePullBackOff状态。

          如果使用GitHub Actions进行持续集成而非手动推送: 依次进入“Repo” → “Actions” → “Workflow CI”,然后配置AWS相关参数(ECR和OIDC认证信息)。

          如果所有任务的状态都是绿色的,说明镜像上传成功。这是部署前的重要验证步骤。

          步骤5:使用GitOps确保配置的一致性

          kustomization.yaml文件中替换占位符内容(操作方法与aws-spinup.sh脚本中的第5步相同):

          AWS_REGION=eu-west-1
          ECR_REGISTRY=$(terraform -chdir=stages/stage-8-aws-migration/terraform output -raw ecrRegistry_url)
          TAG=$(git rev-parse --short HEAD)
          KUST=stages/stage-8-aws-migration/manifests/kustomization.yaml
          
          sed -i.bak \
            -e "s|REPLACE_ECR_REGISTRY|${ECR_REGISTRY}|g" \
            -e "s|REPLACE_IMAGE_TAG|${TAG}|g" \
            "${KUST}"
          rm -f "${KUST}.bak"
          
          # 如果当前区域不是eu-west-1,需要修改相关配置文件
          if [[ "$AWS_REGION" != "eu-west-1" ]]; then
            sed -i.bak "s|region: eu-west-1|region: ${AWS_REGION}|g" \
              stages/stage-8-aws-migration/manifests/external-secrets.yaml \
              stages/stage-8-aws-migration/manifests/csi/auth-service-spc.yaml \
              stages/stage-8-aws-migration/manifests/csi/ledger-service-spc.yaml
            rm -f stages/stage-8-aws-migration/manifests/external-secrets.yaml.bak \
                  stages/stage-8-aws-migration/manifests/csi/*.bak 2>/dev/null || true
          fi
          
          # 在提交代码之前进行验证
          grep -E 'newName:|newTag:' "${KUST}"
          # 预期输出:YOUR_AWS_ACCOUNT.dkr.ecr.eu-west-1.amazonaws.com/clearledger/... 以及您的git提交SHA值
          
          git add stages/stage-8-aws-migration/manifests/kustomization.yaml
          git commit -m "stage8: ECR镜像配置完成,标签为${TAG}"
          git push
          

          同时,请将 ArgoCD 应用程序仓库的 URL 更改为你的 GitHub 用户名:

          # 示例:YOUR_GITHUB_USERNAME/clearledger — 检查命令:git remote get-url origin
          sed -i.bak 's|YOUR_GITHUB_USERNAME|YOUR_ACTUAL_GITHUB_USER|g' \
            stages/stage-8-aws-migration/argocd/clearledger-aws-app.yaml
          rm -f stages/stage-8-aws-migration/argocd/clearledger-aws-app.yaml.bak
          

          步骤 6:集群访问权限设置及 Terraform 输出文件配置

          请从仓库根目录运行相关命令。首先设置 CLI 所在区域(EKS 和 IAM 的相关配置都是按区域划分的),然后配置 kubeconfig 文件,最后输出 IRSA 角色的 ARN 值——步骤 9–10 需要这些信息。

          aws configure set region eu-west-1
          
          eval "$(terraform -chdir=stages/stage-8-aws-migration/terraform output -raw kubeconfig_command)"
          kubectl get nodes
          
          export AWS_REGION=eu-west-1
          export ESO_ROLE_ARN=$(terraform -chdir=stages/stage-8-aws-migration/terraform output -raw eso_role_arn)
          export FALCOROLE_ARN=$(terraform -chdir=stages/stage-8-aws-migration/terraform output -raw falco_role_arn)
          export REPLACE_AUTH_IRSA_ROLE_ARN=$(terraform -chdir=stages/stage-8-aws-migration/terraform output -raw auth_service_irsa_role_arn)
          export REPLACE_LEDGER_IRSAROLE_ARN=$(terraform -chdir=stages/stage-8-aws-migration/terraform output -raw ledger_service_irsa_role_arn)
          export REPLACENotification_IRSA_ROLE_ARN=$(terraform -chdir=stages/stage-8-aws-migration/terraform output -raw notification_service_irsa_role_arn)
          
          # 验证配置是否正确(所有输出都应该是 ARN 值,而不是空字符串)
          echo "ESO:      ${ESOROLE_ARN}"
          echo "Falco:    ${FALCO_ROLE_ARN}"
          echo "Auth IRSA: ${REPLACE_AUTH_IRSA ROLE_ARN}"
          

          步骤 7–12:在集群上部署平台组件

          你已经完成了步骤 1–6(AWS 环境已搭建完成,镜像存储在 ECR 中,`kubectl` 命令也能正常使用)。接下来,请执行步骤 7–12,来安装这些平台组件。这些组件的安装过程与 `aws-spinup.sh` 脚本中的命令相同,但你需要直接运行下面的命令,而不是使用那个脚本。

          对于每个步骤,先运行“安装”相关的命令块,然后立即运行紧随其后的“验证”命令块。在看到“Pod 正在运行”或“ClusterPolicy 列表已生成”之前,不要进入下一步。如果命令执行后没有任何输出,那说明配置有问题。

          > >
          步骤 命名空间安装的内容预计生成的 Pod 数量
          7 argocd GitOps 控制组件 约 7 个 Pod
          8 kyverno 准入控制策略组件 约 4 个 Pod + ClusterPolicies
          9 falco 运行时检测组件 每个节点上 1 个 DaemonSet Pod(该集群共有 3 个节点)
          10 external-secrets + clearledger ESO 服务账户与 IRSA 服务账户 约 3 个 ESO Pod + 3 个服务账户
          11 kube-system + clearledger CSI 驱动程序与 AWS 提供者组件 每个节点上各 3 个驱动程序和提供者组件
          12 monitoring Prometheus、Grafana、Loki 监控工具 约 10 个 Pod

          步骤13至15(部署应用程序、等待应用负载均衡器配置完成、验证用户界面)需要在步骤12之后执行。

          步骤7:ArgoCD

          kubectl create namespace argocd --dry-run=client -o yaml | kubectl apply -f -
          kubectl apply -n argocd --server-side --force-conflicts \
            -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
          kubectl rollout status deployment/argocd-server -n argocd --timeout=180s
          

          验证是否创建了以下资源:

          kubectl get pods -n argocd
          kubectl get svc -n argocd
          kubectl get deploy -n argocd
          

          预期结果: 应该会看到argocd-serverargocd-repo-serverargocd-application-controller等Pod,这些Pod的状态应为运行中,且数量应为1/12/2。其中argocd-server服务会开放443端口。

          用户界面的配置(步骤13之后可选,但在步骤13之前必须完成): 打开一个新的终端窗口,保持该窗口处于运行状态。使用任何未被占用的本地端口进行连接(如果8080端口已被占用,可以使用8081):

          kubectl port-forward svc/argocd-server -n argocd 8080:443
          # 或者如果8080端口已经被占用,可以使用以下命令:
          # kubectl port-forward svc/argocd-server -n argocd 8081:443
          # 然后通过https://localhost:8080(或8081)访问该服务,并使用用户名“admin”和密码进行登录
          kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath '{.data.password}' | base64 -d; echo
          

          在步骤13之前,应用程序列表应该是空的,这是正常的。

          步骤8:Kyverno与策略配置

          cosign.pub文件以及infra/cosign.pub文件在Git中会被忽略(私钥绝对不能被提交到版本控制系统中;公钥则是针对特定学习者设置的)。该仓库会在require-signed-images.yamlrequire-signed-images-ecr.yaml文件中提供示例密钥。如果你在第三阶段重新生成了密钥,在应用这些配置之前,请将你的本地公钥同步到相关的策略配置文件中:

          # 如果本地存在infra/cosign.pub文件,但它在Git中被忽略,那么可以将它复制到已经提交到的策略配置文件中
          bash scripts/embed-cosign-pub-in-policies.sh
          diff infra/cosign.pub <{(grep -A3 'BEGIN PUBLIC KEY' infra/policies/require-signed-images-ecr.yaml | grep -v publicKeys)}
          
          helm repo add kyverno https://kyverno.github.io/kyverno/ --force-update
          helm upgrade --install kyverno kyverno/kyverno \
            --namespace kyverno --create-namespace \
            -f stages/stage-4-admission-control/infra/kyverno/values.yaml \
            --set admissionController.replicas=1 \
            --wait --timeout=180s
          kubectl apply -f infra/policies/
          

          验证配置结果:

          kubectl get pods -n kyverno
          kubectl get clusterpolicy
          kubectl get clusterpolicy require-signed-images-ecr -o jsonpath '{.spec.rules[0].verifyImages[0].attestors[0].entries[0].keys.publicKeys}' | head -3
          

          预期结果: 应该能看到admission-controller、background-controller、cleanup-controller以及reports-controller这些Pod处于运行中状态。

          kubectl get clusterpolicy 命令会列出包括 require-signed-images-ecrdisallow-root-containers 等在内的6种以上策略。输出中的 publicKeys 部分必须显示 -----BEGIN PUBLIC KEY----- 这一格式,而不能是 PASTE_YOUR_COSIGN_PUBLIC_KEY_HERE(因为Kyverno会将这种占位符视为文件路径,从而导致所有部署操作被阻止)。

          require-signed-images-ecr 这一策略在默认情况下会启用审计功能;只有当通过CI流程使用 COSIGN_PRIVATE_KEYCOSIGN_PASSWORD 对ECR镜像进行签名后,这一策略才会生效。未签名的镜像仍然可以正常部署,而是否强制要求使用签名镜像则是可以后期选择的。

          如果 verify-slsa-provenance 策略无法被应用(即仅使用了审计功能而没有执行 mutateDigest 操作),可以在相关配置文件中将 mutateDigest: false 设置为true,或者直接跳过这一步骤。对于第8阶段来说,这一策略是可选的。

          步骤9:部署Falco

          helm repo add falcosecurity https://falcosecurity.github.io/charts --force-update
          helm upgrade --install falco falcosecurity/falco \
            --namespace falco --create-namespace \
            -f stages/stage-6-runtime-security/infra/falco/helm-values.yaml \
            --set driver.kind=modern_ebpf \
            --set "serviceAccount.annotations.eks\.amazonaws\.com/role-arn=${FALCO_ROLE_ARN}" \
            --wait --timeout=300s
          

          验证结果:

          kubectl get pods -n falco -o wide
          kubectl get daemonset -n falco
          kubectl get sa falco -n falco -o jsonpath '{.metadata.annotations.eks\.amazonaws\.com/role-arn}'; echo
          

          预期结果: 应该能看到名为Falco的DaemonSet正在运行,且其部署的Pod数量与配置中指定的节点数相同(本例中应为3个)。同时,“ServiceAccount”注解中应显示指定的角色地址 FALCO_ROLE_ARN

          步骤10:部署External Secrets Operator与IRSA ServiceAccounts

          helm repo add external-secrets https://charts.external-secrets.io --force-update
          helm upgrade --install external-secrets external-secrets/external-secrets \
            --namespace external-secrets --create-namespace \
            --set "serviceAccount.annotations.eks\.amazonaws\.com/role-arn=${ESO_ROLE_ARN}" \
            --wait --timeout=180s
          kubectl apply -f stages/stage-8-aws-migration/manifests/resources/namespace.yaml
          envsubst < stages/stage-8-aws-migration/manifests/clearledger-serviceaccounts.yaml | kubectl apply -f -
          

          验证结果:

          kubectl get pods -n external-secrets
          kubectl get sa -n external-secrets external-secrets -o jsonpath '{.metadata.annotations.eks\.amazonaws\.com/role-arn}'; echo
          kubectl get sa -n clearledger
          

          预期结果: 应该能看到名为external-secrets的部署资源正在运行,通常情况下会包含3个容器,这些容器被集成在一个Pod中。在clearledger命名空间中,应该存在三个ServiceAccounts:auth-serviceledger-servicenotification-service,每个ServiceAccount的“annotations”字段中都会包含eks.amazonaws.com/role-arn这一角色地址。目前还不会有应用程序的Pod被创建出来(这些Pod将在第13步由ArgoCD负责部署)。

          步骤11:CSI驱动程序与SecretProviderClasses的安装

          bash stages/stage-8-aws-migration/scripts/install-csi-secrets.sh
          

          验证结果:

          kubectl get pods -n kube-system | grep -E 'secrets-store|provider-aws'
          kubectl get secretproviderclass -n clearledger
          helm list -n kube-system | grep -E 'csi-secrets|secrets-provider'
          

          预期结果: CSI驱动程序相关的Pod应该3个都处于运行状态(每个节点上各有一个)。AWS提供程序相关的Pod也应该1个每个节点都在运行。在clearledger命名空间中应该存在两个SecretProviderClass对象。通过Helm命令查看,应该能确认csi-secrets-store和/或secrets-provider-aws服务已经成功部署。

          如果Helm报告meta.helm.sh/release-name存在冲突,请重新运行该脚本。此脚本会安装AWS提供程序相关组件,而不会导致驱动程序配置重复。

          步骤12:可观测性功能的配置

          bash stages/stage-7-observability/scripts/install-observability.sh
          

          验证结果:

          kubectl get pods -n monitoring
          kubectl get svc -n monitoring | grep -E 'grafana|prometheus|loki'
          kubectl get configmap -n monitoring -l grafana_dashboard=1 --no-headers | wc -l
          

          预期结果:Grafana相关的Pod应该3个都处于运行状态,Prometheus和Loki相关的Pod也应该都在运行。与监控仪表板相关的ConfigMap数量应该是6个(这些仪表板都属于ClearLedger项目)。脚本执行完成后,会在浏览器中显示http://grafana.local这个地址;如果在EKS环境中使用,也可以通过端口转发来访问仪表板:

          # 打开一个新的终端窗口
          kubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 3000:80
          # 然后访问 http://localhost:3000/admin 或 http://localhost:3000/dashboards?tag=clearledger
          

          在初始阶段,这些仪表板上可能会显示“没有数据”,直到你触发某些事件后,数据才会开始显示出来(§7.4节中的练习也可以在这个集群上尝试)。

          平台组件概览:在执行步骤13之前,先进行一次快速检查:

          for ns in argocd kyverno falco external-secrets monitoring clearledger; do
            echo "=== ${ns} ==="
            kubectl get pods -n "${ns}" --no-headers 2>&/dev/null | awk '{print $3}' | sort | uniq -c || echo "(no pods yet)"
          done
          kubectl get clusterpolicy --no-headers | wc -l | xargs echo "ClusterPolicies:"
          kubectl get secretproviderclass -n clearledger --no-headers | wc -l | xargs echo "SecretProviderClasses:"
          

          预期结果:所有命名空间下的Pod都应该都显示为“运行中”状态(任务已完成的情况下则应显示为“已完成”)。在ArgoCD完成数据同步之前,clearledger命名空间下可能暂时没有Pod。ClusterPolicies的数量应该大于或等于6个,而SecretProviderClasses的数量应该是2个。

          关于创建命名空间的EKS API超时问题:有时你可能会看到“读取响应体时出现意外错误”或“上下文超时”的提示,但最终命名空间仍然被成功创建了。这种情况是由于与EKS API通信时出现了短暂的客户端超时现象(可能是首次请求时网络延迟较大,或者控制平面正在处理其他任务),并不是创建操作本身失败了。你可以通过执行kubectl get namespace argocd命令来确认命名空间确实已经创建成功,如果仍然遇到超时问题,可以尝试重新运行命令或执行kubectl cluster-info命令来检查网络连接是否正常。

          步骤13–14:通过ArgoCD进行部署并查看应用地址

          stages/stage-8-aws-migration/manifests/目录下的应用程序YAML文件并不会被手动应用。步骤13会指示Argo CD与Git仓库进行同步,之后Argo CD会自动生成相应的部署配置、服务设置、Ingress规则等。

          请先确保能够访问代码库

          如果你的GitHub仓库是私有的,需要在Argo CD的“设置”→“代码库”中添加访问权限。如果仓库是公开的,重新加载应用页面后,“ComparisonError: authentication required”这个错误应该会消失。

          如果同步仍然失败,请检查以下常见原因:

          • 如果无法找到external-secrets.io/v1beta1路径,说明你的集群使用的ESO API版本较新。此时需要将external-secrets.yaml文件中的apiVersion值修改为external-secrets.io/v1,然后重新推送。

          • bash scripts/embed-cosign-pub-in-policies.sh命令进行修复,之后再次执行kubectl apply -f infra/policies/

          • SecretSyncedError错误,可能是database_urljwt_secret配置有误。请确认AWS Secret存储中的clearledger/auth-service键值对是否齐全(Terraform会将这些密钥保存在secrets.tf文件中)。修改相关配置后重新运行terraform apply,或者直接在AWS控制台检查这些密钥的信息。

          现在来注册这个应用程序:

          kubectl apply -f stages/stage-8-aws-migration/argocd/clearledger-aws-app.yaml
          

          观察Argo CD的运行状态,直到clearledger-aws的状态变为“Synced”且“Healthy”,此时应用程序的Pod才会出现在clearledger命名空间中。

          步骤13:通过UI和CLI监控ArgoCD的同步过程

          打开之前打开的Argo CD浏览器窗口(步骤7中设置的端口转发配置)。

          https://localhost:8080        ← 如果8080端口被占用,也可以使用8081端口
          

          点击clearledger-aws选项,等待状态变为“Healthy + Synced”(首次部署时可能需要2–5分钟)。你也可以在终端中查看相同的信息,而无需打开浏览器:

          kubectl get application clearledger-aws -n argocd -w
          # 当状态显示为“Healthy”时,按Ctrl-C退出命令行界面
          

          同时,在另一个终端窗口中观察Pod的启动情况:

          kubectl get pods -n clearledger -w
          # 所有Pod应在2分钟内全部进入“Running”状态
          # 当所有Pod都处于运行状态时,按Ctrl-C退出命令行界面
          

          步骤14:获取应用程序的公共访问地址(ALB)

          在 ArgoCD 完成同步后,AWS 需要 2 到 5 分钟的时间来配置负载均衡器。请运行相关命令,并等待直到 “地址” 列中显示出相应的信息为止。
          kubectl get ingress clearledger-ingress -n clearledger -w
          # 最初时,地址栏会显示为空,随后会显示出类似以下的内容:
          # clearledger-xxxxxxxxxx.eu-west-1.elb.amazonaws.com
          # 当主机名出现后,按下 Ctrl-C 键即可。
          

          为后续步骤输出相应的 URL:

          export ALB_DNS=$(kubectl get ingress clearledger-ingress -n clearledger \
            -o jsonpath '{.status.loadBalancer.ingress[0].hostname}')
          echo "您的应用程序的访问地址为:http://${ALB_DNS}"
          

          10分钟后地址栏仍然显示为空?请参阅 troubleshooting.md,了解恢复 ALB/ingress 设置的步骤。

          步骤 15:在浏览器中打开应用程序

          将 ALB 的根 URL 复制并粘贴到浏览器中。无需进行 DNS 配置、端口转发或使用 VPN:

          http://clearledger-xxxxxxxxxx.eu-west-1.elb.amazonaws.com/
          

          您应该会看到 ClearLedger 的登录界面(与本地测试环境中的 clearledger.local 界面相同)。注册或登录后,提交一笔交易,然后确认控制面板能够正常显示。这就是第 8 阶段的成果截图。

          快速的 API 健康检查(可在终端或浏览器中执行):

          curl -fsS "http://${ALB_DNS}/auth/health" && echo
          curl -fsS "http://${ALB_DNS}/ledger/health" && echo
          curl -fsS "http://${ALB_DNS}/notifications/health" &∓ echo
          

          每次检查都应该返回类似以下的 JSON 数据:{"status":"ok","service":"auth-service"}

          从 AWS 的角度来看,部署好的应用程序栈结构如下:

          控制台位置 需要查看的内容
          EC2 → 负载均衡器 名称为 clearledger-… 的负载均衡器,状态应为 Active
          EC2 → 目标组 存在两到三个目标组,所有目标的状态都应显示为 healthy
          ECR → 仓库 clearledger/auth-serviceclearledger/ledger-serviceclearledger/notification-serviceclearledgerfrontend — 这些仓库中都包含最近推送的镜像
          EKS → 集群 → clearledger → 工作负载 clearledger 名称空间中,您的 Pod 会显示为 “Running” 状态
          Secrets Manager clearledger/auth-serviceclearledger/ledger-serviceclearledger/postgres — 这些秘密配置都存在

          如果从 ALB 接收到 502/503 错误码,说明什么?可能是因为负载均衡器已经启动,但相关的 Pod 尚未处于正常运行状态,或者秘密配置尚未同步。请检查以下命令的结果:kubectl get pods -n clearledger(所有 Pod 的状态是否均为 “1/1 Running”?)以及 kubectl get externalsecret -n clearledger(这些秘密配置的同步状态是否均为 “SecretSynced True”?)。

          ✋ 实践检查点:应用程序已可公开访问

          # 这三个命令都必须输出{"status":"ok"...}
          curl -fsS "http://${ALB_DNS}/auth/health"         &;& echo
          curl -fsS "http://${ALB_DNS}/ledger/health"        &;& echo
          curl -fsS "http://${ALB_DNS}/notifications/health" && echo
          
          # 所有Pod都在运行中
          kubectl get pods -n clearledger
          
          # 如果这里没有输出任何内容,说明所有Pod都在运行中;否则未运行的Pod会显示出来
          kubectl get pods -n clearledger --field-selector/status.phase!=Running
          

          如果Pod列表中出现了ImagePullBackOff这一状态,说明ECR中的镜像尚未下载完成。请检查GitHub Actions并重新运行工作流。如果从健康检查URL获取到的响应代码为502,说明该Pod尚未准备好运行,请等待30秒后再重试。

          8.4:验证ESO(默认秘密路径)

          在Argo CD完成同步后,需要确认External Secrets Operator是否已经将AWS Secrets Manager中的数据复制到了Kubernetes的Secrets中:

          kubectl get externalsecret,secret -n clearledger
          kubectl describe externalsecret auth-service-secret -n clearledger | grep -A6 "Conditions:"
          kubectl get pods -n clearledger -l app=auth-service
          kubectl exec -n clearledger deploy/auth-service -c auth-service -- env | grep DATABASE_URL
          

          命令1:ExternalSecrets与Secrets的关联

          你应该会看到两个ExternalSecret以及与之对应的两个Secrets(其中auth服务有2个Secret,ledger服务有1个Secret):

          NAME                                     STORE                    REFRESH INTERVAL   STATUS         READY
          externalsecret.external-secrets.io/auth-service-secret   aws-secrets-manager    1h                 SecretSynced   True
          externalsecretexternal-secrets.io/ledger-service-secret aws-secrets-manager    1h                 SecretSynced   True
          
          NAME                                      TYPE            DATA            AGE
          secret/auth-service-secret                   Opaque           2              3m
          secret/ledger-service-secret                  Opaque           1              3m
          

          STATUS必须显示为SecretSynced,而READY必须显示为True。如果看到SecretSyncedError这一错误信息,请在此处停止操作,并在继续进行§8.5之前的步骤之前修复IRSA相关问题。

          命令2:描述auth服务相关的ExternalSecret

          请查看是否出现了Reason: SecretSynced以及Status: True这些信息:

            Conditions:
              Last Transition Time:   2026-07-10T22:15:00Z
              Message:                Secret was synced
              Reason:                 SecretSynced
              Status:                 True
              Type:                   Ready
          

          命令3:检查auth服务相关的Pod是否正在运行中

          请查看以下输出信息:

          NAME                            READY   STATUS    RESTARTS   AGE
          auth-service-xxxxxxxxxx-xxxxx         1/1     Running   0          2m
          auth-service-xxxxxxxxxx-xxxxx         1/1     Running   0          2m
          

          两个副本都处于运行中状态。如果相关Pod出现了CrashLoopBackOffCreateContainerConfigError错误,那很可能是因为Kubernetes的Secret配置缺失或为空。

          命令4:DATABASE_URL是一个环境变量(属于ESO路径,而非文件路径)

          DATABASE_URL=postgresql://clearledger:*****@clearledger-postgres.xxxxx.eu-west-1.rds.amazonaws.com:5432/clearledger

          正确的情况是使用postgresql://...这样的连接字符串进行连接(密码部分显示为*****,实际应为你的真实密码)。

          而本节中错误的做法是使用/mnt/secrets/database_url这种路径,因为这种路径表示的是CSI文件挂载路径,并非默认的环境变量路径。

          你也可以不显示具体值,直接检查该Secret配置是否存在:

          kubectl get secret auth-service-secret -n clearledger -o jsonpath '{.data}' | grep -o 'database_url\|jwt_secret'
          # 预期结果:会输出database_url和jwt_secret这两个键的值

          如果SecretSynced=False,则需要检查ESO日志以及IRSA的相关信息:

          kubectl logs -n external-secrets deploy/external-secrets -c external-secrets | tail -30
          kubectl get sa auth-service -n clearledger -o yaml | grep role-arn

          ✋ 实践检查点:外部Secret配置确实已经从AWS同步到了Kubernetes系统中

          kubectl get externalsecret -n clearledger
          kubectl get secret -n clearledger

          预期结果应该是:auth-service-secretledger-service-secret这两个Secret对象的SecretSynced状态都应为True,且它们确实存在于clearledger命名空间中。如果出现SecretSyncedError错误,说明IRSA/IAM无法访问Secret Manager服务,请在执行§8.5步骤之前先修复相关角色绑定配置。

          如果你跳过了这个检查步骤,那么在后续的§8.5章节中,CSI驱动程序会基于已经存在的Secret配置进行文件挂载操作;而如果IAM配置出现问题,这种错误可能会表现为与当前操作无关的Pod故障。

          8.5:实践操作:CSI驱动程序与文件挂载

          默认情况下,Kubernetes中的Pod们已经在使用ESO机制来管理Secret配置了:这些Secret信息是以环境变量的形式从Kubernetes Secret对象中获取的。而在本次练习中,我们会将auth-service相关的Secret配置改为通过CSI路径进行访问:这些Secret文件会被挂载到/mnt/secrets/目录下,然后应用程序会从磁盘上读取这些文件。这种配置方式与家庭实验室中使用Vault工具时的设置是相同的(即使用DATABASE_URL_FILEJWT_SECRET_FILE这两个文件路径)。

          由于在启动步骤11中已经安装了CSI驱动程序,因此不需要再额外进行任何安装操作。

          步骤1:确认CSI驱动程序正在运行中

          kubectl get pods -n kube-system -l app=secrets-store-csi-driver
          kubectl get secretproviderclass -n clearledger

          你应该能看到每个节点上都有一个CSI驱动程序Pod,同时还会看到两个SecretProviderClass对象:一个用于auth-service,另一个用于ledger-service

          步骤2:在Git中交换部署文件的位置

          打开文件stages/stage-8-aws-migration/manifests/kustomization.yaml,并修改其中一行代码:

          # 修改前的配置
            - deployments/auth-service.yaml
          
          # 修改后的配置
            - deployments/auth-service-csi.yaml
          

          提交并推送更改后,执行同步操作:

          argocd app sync clearledger-aws
          kubectl rollout status deployment/auth-service -n clearledger
          

          ArgoCD会部署一个新的auth-service Pod,并为其配置CSI卷。

          步骤3:确认文件已正确配置

          # 查找新的Pod
          kubectl get pod -n clearledger -l secrets=csi
          
          # 列出已挂载的秘密文件
          kubectl exec -n clearledger deploy/auth-service -- ls /mnt/secrets
          
          # 检查数据库URL是否配置正确
          kubectl exec -n clearledger deploy/auth-service -- cat /mnt/secrets/database_url
          
          # 确认服务运行正常
          curl -s "http://$(kubectl get ingress clearledger-ingress -n clearledger \
            -o jsonpath '{.status.loadBalancer.ingress[0].hostname}')/auth/health"
          

          你应该能看到database_urljwt_secret这两项被列为文件,而且健康检查的结果应该显示"status":"ok"

          ESO与CSI:实际上有什么区别?

          这两种方式都会从AWS Secrets Manager中读取相同的密码信息,只是获取密码的方式不同而已。

          ESO(默认配置,你在§8.4节中已经验证过这一机制)

          可以把ESO想象成在集群中运行的一个“复制工具”:

          1. ESO拥有自己独立的AWS权限(IAM角色)。

          2. 它会从Secrets Manager中读取clearledger/auth-service文件中的密码信息。

          3. 然后把这些密码信息复制到一个名为auth-service-secret的Kubernetes Secret对象中。

          4. 最终,auth Pod会将这些密码作为环境变量来使用。

          这些密码信息在集群中仅以Kubernetes Secret对象的形式存在一段时间而已。

          CSI(本次实验中使用的文件挂载机制)

          可以把CSI想象成Pod在启动时自行获取密码信息的机制:

          1. auth-service Pod拥有自己独立的AWS权限(其ServiceAccount拥有IRSA角色)。

          2. 当Pod启动时,CSI驱动程序会从Secrets Manager中获取密码信息。

          3. 这些密码信息会被保存在/mnt/secrets/目录下,文件名为database_urljwt_secret

          4. Pod会直接从磁盘上读取这些文件中的密码信息,而不会使用任何Kubernetes Secret对象。

          在这种机制下,根本不会为这些密码信息创建任何Kubernetes Secret对象。

          为什么同样的应用程序代码在两种配置下都能正常运行呢?

          文件app/auth-service/main.py中使用了辅助函数_read_secret()

          • 如果DATABASE_URL_FILE指向的是一个存在的文件,那么就会从该文件中读取密码信息(无论是通过CSI机制还是本地Vault工具)。

          • 否则,就会直接从DATABASE_URL中读取密码信息(使用ESO机制或步骤0–4中的配置方式)。

          相同的图像,相同的代码:你只需要更改Argo CD同步哪些部署配置文件即可。

          若要恢复使用ESO:kustomization.yaml文件中,将auth-service-csi.yaml改回auth-service.yaml,然后提交更改、推送代码,并执行argocd app sync clearledger-aws命令。

          Terraform会根据stages/stage-8-aws-migration/terraform/目录中的.tf文件来配置所有的AWS资源(包括VPC、EKS、RDS、ECR、Secrets Manager以及IAM角色)。

          阶段8中的两项OIDC相关技术

          在阶段8中,OIDC技术被应用在了两个不同的场景中。虽然这些场景听起来相似,但它们实际上解决的是不同的问题。

          GitHub Actions的OIDC功能允许CI流程将图像推送到ECR,而无需在GitHub上存储长期有效的AWS密钥。当某项任务被执行时,GitHub会生成一个短期有效的令牌,用于证明该任务的身份。AWS会信任这个令牌,并提供相应的临时访问权限——这些权限仅够用于推送图像,而不包含其他任何功能。

          IRSA的作用与GitHub Actions类似,但它适用于在EKS中运行的Pod。与GitHub令牌不同,Pod会使用自己的Kubernetes ServiceAccount令牌;AWS会信任EKS集群的OIDC提供者,验证该令牌的有效性,然后返回仅与该Pod所需功能相匹配的临时访问权限。

          GitHub Actions的OIDC机制:
            CI流程会说:“我是YOUR_USERNAME/clearledger生产环境中的某个任务”
            AWS会回复:“这是用于推送图像到ECR的临时凭证,有效期为1小时”
          
          IRSA的机制:
            Pod会说:“我是clearledger命名空间中的auth-service ServiceAccount”
            AWS会回复:“这是仅用于读取auth-service相关秘密信息的临时凭证,有效期为1小时”
          
          展示OIDC与IRSA区别的流程图

          关键在于:没有任何AWS密钥会被存储在任何地方:

          GitHub Secrets中不存在AWS_ACCESS_KEY_ID
          GitHub Secrets中不存在AWS_SECRET_ACCESS_KEY
          Kubernetes Secrets中也不存在任何AWS密钥

          Terraform会创建名为clearledger-github-actions-ecr的角色,并配置相应的信任关系。位于.github/workflows/ci-aws.yaml文件中的CI流程会使用这个角色,将图像推送到ECR,并更新kustomization.yaml文件。ArgoCD会检测到这些变化,然后部署新的图像。

          CI路由机制:阶段1–7与阶段8的对比

          该仓库提供了两份工作流配置文件,但你并不需要同时运行这两份文件。

          ci.yaml文件对应的是阶段1–7中的本地开发环境工作流。它会在你自托管的Multipass虚拟机上运行,将图像推送到Docker Hub,并更新clearledger-infra GitOps仓库。这是默认配置,无需进行任何额外设置。

          ci-aws.yaml 是用于第8阶段的AWS管道。该管道在GitHub上托管的ubuntu-latest运行环境中执行,会将构建生成的镜像推送到ECR,并直接更新这个仓库中的kustomization.yaml文件。只有当你将仓库变量CLEARLEDGER_CI_TARGET=aws设置为相应值时,该管道才会被激活。

          如果你目前处于第1至第7阶段,那么无需采取任何行动。 默认情况下,变量`CLEARLEDGER_CI_TARGET`是未设置的,因此每次提交代码时,系统都会在你在本地搭建的运行环境中正常执行`ci.yaml`文件。AWS相关工作流程文件确实存在于仓库中,但相关任务会被跳过。

          在你的EKS集群启动之前,请不要设置`CLEARLEDGER_CI_TARGET=aws`这个变量。

          如果过早设置了这个变量,那么每次提交代码时`ci.yaml`文件就不会被执行了(也就不会在Docker Hub上构建相关资源),而`ci-aws.yaml`文件则会立即失败,因为此时还不存在ECR、OIDC角色,也没有AWS基础设施。如果你不小心设置了这个变量,请删除它:在GitHub的仓库设置页面 → 秘密和变量操作变量中删除`CLEARLEDGER_CI_TARGET`这个变量。

          要启用AWS CI功能,请在terraform apply命令执行完毕之后再操作。

          production环境中,你需要设置三个仓库变量以及一个秘密信息。

          首先设置这些变量:将`YOUR_USERNAME`替换为你的GitHub用户名:

          gh variable set CLEARLEDGER_CI_TARGET --body aws --repo YOUR_USERNAME/clearledger
          
          gh variable set AWS_ACCOUNT_ID --body "$(aws sts get-caller-identity --query Account --output text)" --repo YOUR_USERNAME/clearledger
          
          gh variable set AWS_REGION --body eu-west-1 --repo YOUR_USERNAME/clearledger
          

          接下来创建production环境,并将OIDC角色的ARN作为秘密信息添加进去:

          # 先创建环境,如果该环境还不存在,gh secret set命令会返回404错误
          gh api --method PUT "repos/YOUR_USERNAME/clearledger/environments/production"
          
          gh secret set AWS Actions Role ARN \
            --env production \
            --body "$(terraform -chdir=stages/stage-8-aws-migration/terraform output -raw github_actions_ecr_role_arn)" \
            --repo YOUR_USERNAME/clearledger
          

          注意:GitHub不允许使用以`GITHUB_`开头的秘密信息名称。请使用`AWS Actions Role ARN`,而不是`GITHUBActionsRoleARN`。

          在运行`terraform apply`命令之前,请确保在`terraform.tfvars`文件中正确设置了`github_owner`变量(可以参考`terraform.tfvars.example`文件)。这样才能让AWS识别并接受来自你的GitHub账户的令牌。

          一旦设置了`CLEARLEDGER_CI_TARGET=aws`,那么每次向`main`分支提交代码时,都会执行AWS相关的管道流程:Gitleaks → Semgrep → Checkov → 构建 → Trivy扫描 → ECR上传 → 配置更新。此时本地的`ci.yaml`文件将不会被执行。

          如果CI流程在ECR上传步骤中失败:

          最常见的错误原因是“没有权限执行sts:AssumeRoleWithWebIdentity”这个操作。这意味着IAM角色的信任策略中,`:sub`条件里仍然使用了占位符`YOUR_GITHUB_USERNAME`。解决方法是在`terraform.tfvars`文件中设置正确的`github_owner`值,然后再次运行`terraform apply`命令,之后只重新执行那个出现故障的任务即可(不需要重新运行整个流程,因为之前的扫描步骤已经成功完成了)。

          GitHub → Actions → 运行失败 → 重新运行失败的作业

          如果在执行`gh secret set`命令时看到“404”错误,说明`production`环境尚未创建。请先运行上述的`gh api --method PUT`命令。

          修复OIDC配置后重新运行作业:仅重新运行那些失败的作业,而不是整个流程。如果之前的阶段(如代码审查、构建、扫描)已经通过,并且相关结果仍然存在于工作流中,那么就无需重新运行所有作业。只有当你修改了应用程序代码,或者希望从零开始重新进行扫描时,才需要重新运行所有作业。

          生产环境安全加固检查清单

          虽然实验室的环境设置与生产环境类似,但真正的生产环境还需要额外的安全防护措施。在将系统视为“已准备好投入生产”之前,请务必添加这些防护措施。

          1. 保护主分支

          需要保护以下两个GitHub仓库:

          github.com/你的GitHub用户名/clearledger
          github.com/你的GitHub用户名/clearledger-infra

          进入每个仓库,然后执行以下操作:

          设置 → 规则 → 规则集 → 新建规则集 → 分支选择:main
          启用以下选项:
          合并前必须收到拉取请求
          需要审核人员的批准
          要求状态检查结果合格
          合并前必须确保分支是最新的
          阻止强制推送操作
          禁止删除分支

          为什么这样做很重要:在生产环境中,任何人都不应该直接将代码推送到仓库或GitOps仓库中。如果有人直接向`clearledger-infra`仓库进行推送,那就相当于提出了一个部署请求。

          2. 使用需要审核流程的GitHub环境

          创建一个受保护的环境:

          clearledger仓库 → 设置 → 环境 → 新建环境 → 名称:production → 所需审核人员:添加你自己或团队成员 → 部署分支:仅限main分支

          在AWS工作流中,需要使用以下配置:

          environment: production

          这样,GitHub就会暂停AWS的部署操作,直到获得审核人员的批准后才会继续执行。这种机制能够有效防止未经审批就进行部署的情况发生。

          3. 优先使用细粒度的访问令牌或GitHub App

          对于实验室环境来说,`INFRA_REPO_TOKEN`可以使用传统的个人访问令牌。但在生产环境中,应该采用更安全的措施。

          更好的选择是:

          细粒度的个人访问令牌
          → 仓库访问权限:仅限yourGitHub用户名/clearledger-infra
          → 权限设置:
             内容:读写权限
             元数据:只读权限

          对于团队来说,最理想的选择是在`clearledger-infra`仓库上安装GitHub App,并为其授予写入内容的权限。这样不仅可以提高审计日志的质量,还能更方便地管理访问令牌的生命周期。

          INFRA_REPO_TOKEN应该被归类为生产环境专用密钥,而不是普通的仓库密钥:
          clearledger
          → 设置
          → 环境配置
          → 生产环境
          → 环境密钥
          → INFRA_REPO_TOKEN
          

          4. 将 AWS OIDC 限制在生产环境中使用

          这并不是一条Shell命令。而是当您运行`terraform apply`时,Terraform会写入AWS中的信任规则。

          repo:YOUR_GITHUB_USERNAME/clearledger:environment:production
          

          只有在`clearledger`仓库的`production`环境中运行的GitHub Actions作业才能使用ECR推送角色。没有该环境的随机分支、分叉或工作流是无法获取AWS凭据的。

          操作步骤:

          1. 在`terraform.tfvars`中设置`github_owner`,然后执行`terraform apply`(第8阶段第2步)。

          2. 在GitHub上:进入“设置”→“环境配置”→“生产环境”。如果该选项不存在,请创建它;如果需要,还可以添加安全规则。

          3. 添加环境密钥`AWS Actions ROLE_ARN`,其值为`terraform output -raw github_actions_ecr_role_arn`。

          4. `ci-aws.yaml`文件已经将ECR作业的环境设置为`production`,因此GitHub会生成相应的令牌。

          验证规则是否生效(可选):

          aws iam get-role --role-name clearledger-github-actions-ecr \
            --query 'Role.AssumeRolePolicyDocument.Statement[0].Condition.StringEquals."token.actions.githubusercontent.com:sub"' \
            --output text
          

          预期输出应为:`repo:your-username/clearledger:environment:production`

          如果选择不使用GitHub CI,那么这个限制也就无关紧要了。只有当您同时启用CI和AWS(ECR + OIDC)时,这个规则才真正发挥作用。

          5. 在生产环境推广之前先进行测试阶段

          实验环境中的流程:将代码推送到`main`分支 → `ci-aws.yaml`脚本会构建并扫描代码 → CI系统会使用新的镜像标签更新`stages/stage-8-aws-migration/manifests/kustomization.yaml`文件 → Argo CD工具会同步`clearledger-aws`环境。

          家庭实验室中的第1至7阶段仍然使用`clearledger-infra`和Docker Hub。而第8阶段的AWS环境则采用仓库内的自定义配置机制。

          在实际的生产环境中,会在中间添加一个测试阶段:首先构建一次镜像,然后将相同的标签或哈希值部署到测试环境,进行测试或获得人工审核,最后再将这些内容推广到生产环境,而无需再次构建代码。

          为什么这样操作呢?因为如果重新构建代码后再推送到生产环境,可能会导致最终发布的版本与测试阶段通过的版本不同。因此,采用“一次构建、两次部署”的安全模式是至关重要的。

          一次构建(生成一个镜像哈希值)
            → 部署到测试环境
            → 进行测试/获得审核通过
            → 将相同的哈希值部署到生产环境

          6. 在可能的情况下使用私有网络

          对于生产环境中的AWS而言:

          - EKS节点应部署在私有子网中;  
          - RDS也应配置为私有子网模式;  
          - 使用私有的EKS API端点,或受限制的公共API端点;  
          - 安全组仅允许所需端口的数据流动;  
          - 仅当应用程序需要公开访问时,才使用ALB;  
          >禁止通过SSH进行部署操作。

          该部署流程应通过IAM/OIDC与AWS API进行交互,并通过GitOps完成部署任务。绝对不允许通过SSH直接连接EC2实例。

          7. 将Terraform配置状态存储在远程位置

          在实验室环境中,使用本地存储Terraform状态是可行的;但在生产环境中,应采用加密机制将状态数据存储在远程服务器上:

          - 使用S3 bucket来存储`terraform.tfstate`文件;  
          - 通过DynamoDB表来实现状态数据的锁定机制;  
          >启用SSE加密功能;  
          >为桶配置版本控制选项;  
          >禁止公共访问。

          `Terraform`后端相关的配置代码已包含在`stages/stage-8-aws-migration/terraform/main.tf`文件中,但当前这些代码被注释掉了。在创建完S3 bucket和DynamoDB表之后,请取消对这些代码的注释。

          适合生产环境的配置总结:

          - 通过CI流程构建应用程序并验证其正确性;  
          >GitHub Environments系统会审核这些应用程序是否适合投入生产环境;  
          >OIDC机制可生成临时性的AWS访问凭证;  
          >ECR用于存储不可更改的应用程序镜像;  
          >`kustomization.yaml`文件用于记录所需的应用程序配置状态;  
          >ArgoCD结合Clearledger-aws工具,能够通过Git完成应用程序的部署工作;  
          >整个部署流程禁止使用SSH、静态AWS密钥,也禁止直接通过CI系统执行kubectl命令。

          点击该链接即可查看更多信息。ClearLedger运行在AWS平台上,其架构和安全措施与之前的环境相同,只不过使用了新的基础设施而已。

          在EKS上运行的Clearledger UI界面截图,通过ALB访问

          完成部署后,请立即删除相关资源,这样就可以避免产生任何费用:

          make aws-down

          有关完整的部署步骤及费用信息,请参阅`stages/stage-8-aws-migration/README.md`文件。

          你在第8阶段学到了什么

          • 容器化应用程序具有很好的移植性:同样的代码既可以在你的笔记本电脑上运行,也可以在AWS平台上运行。

          • Terraform的作用在于将基础设施配置以代码的形式进行定义,从而确保环境配置的可重复性。

          • 在云迁移过程中,哪些内容会发生变化(例如管理型服务、IAM权限设置、网络配置等),哪些内容则保持不变(如应用程序代码、CI构建逻辑、安全策略等)。

          • AWS提供了三种用于传输敏感信息的机制:ESO(默认方式)、CSI文件挂载方式(详见§8.5节),以及在家实验室环境中使用的Vault工具。

          • 专门为AWS设计的安全服务包括GuardDuty(威胁检测系统)、CloudTrail(API审计工具)、GitHub Actions与OIDC结合使用的认证机制(无需长期有效的访问凭证即可完成部署流程),以及IRSA(在Pod层面实现IAM权限控制的功能,同样不需要长期有效的凭证)。

          • 现在你可以将这些知识添加到你的简历中,或者在面试时向招聘人员介绍:

            我们将相同的架构迁移到了AWS上,使用了Terraform提供的EKS、ECR、RDS、ALB等服务,并通过External Secrets Operator和IRSA来管理敏感信息,而无需重新编写应用程序代码。

            当你在AWS上的操作完成后,为了停止费用产生,需要执行以下命令:

            make aws-down

            你的本地实验环境中的虚拟机是独立的。如果你打算再次使用它,应该已经为第7阶段的配置创建了快照(可以通过`make snapshots`命令来确认)。有关更多信息,请参阅保存你的进度

            故障排除(请参阅troubleshooting.md

            Pod处于“Pending”状态无法继续部署:

            kubectl describe pod POD_NAME -n clearledger
            # 如果是内存或CPU资源不足,需要减少资源请求;
            # 如果是图像下载出现问题,需要检查Docker Hub仓库的名称和访问凭据。

            Kyverno阻止了部署进程:

            kubectl get events -n clearledger --sort-by('.lastTimestamp' | tail -10
            kubectl get policyreport -n clearledger -o yaml

            Vault代理没有成功注入敏感信息:

            kubectl logs POD_NAME -n clearledger -c vault-agent-init
            kubectl exec -n vault vault-0 -- vault read auth/kubernetes/role/auth-service

            Falco没有发出警报:

            kubectl logs -n falco daemonset/falco | grep -i error | tail -20

            ArgoCD显示配置不一致:

            argocd app sync clearledger --force
            argocd app get clearledger
            kubectl get events -n clearledger --sort-by('.lastTimestamp'

            无法访问clearledger.local:

            multipass info clearledger | grep IPv4
            grep clearledger /etc/hosts
            # 如果IP地址发生了变化,需要更新/etc/hosts文件

            虚拟机磁盘空间已满,或者某些Pod被强制删除(可能是由于磁盘空间不足):

            make doctor     # 结果可能为PASS、WARN或FAIL,同时会显示PVC和Prometheus TSDB的占用情况
            make reclaim    # 安全地回收资源:仅清除未使用的镜像文件和日志文件,不会影响PVC

            如果执行`reclaim`命令后问题仍然存在,就需要销毁现有的虚拟机环境并重新创建:执行`make teardown & make setup`。更多详细指导,请参阅troubleshooting.md中的“磁盘空间不足”相关章节,以及disk health部分。

            合规性参考

            所有的控制措施都对应着至少一个具体的框架或标准。完整的映射关系请参见docs/compliance-mapping.md

            控制措施 工具 适用阶段 PCI-DSS标准 SOC2标准 CIS K8s规范
            秘密信息检测 Gitleaks 3 6.2 CC8.1
            安全代码扫描 Semgrep 3 6.3.2 CC7.1
            依赖关系扫描 Trivy SCA 3 6.3.3 CC7.1
            IaC配置扫描 Checkov 3 6.3.1 CC6.1
            镜像签名验证 Cosign 3 6.3 CC6.1
            供应链成分清单生成 Syft 3 6.3.3 CC6.1
            非根容器管理 Kyverno 4 6.5 CC6.3 5.2.6
            资源使用限制 Kyverno 4 A1.1 5.2.4
            防止权限升级 Kyverno 4 6.5 CC6.3 5.2.5
            秘密信息管理 Vault 5 3.5 CC6.1
            运行时检测 Falco 6 10.7 CC7.2
            网络隔离 NetworkPolicy 6 1.3 CC6.6 5.3.2
            安全监控能力 Grafana 7 10.6 CC7.2
            DORA指标监测 ArgoCD + Grafana 7
            账户威胁检测 GuardDuty 8 10.6 CC7.2
            API审计追踪 CloudTrail 8 10.2 CC7.3

            欧盟DORA法案(数字运营韧性法案):该法案自2025年1月起适用于欧盟范围内的金融机构。Clearledger与DORA法案的五大核心要求完全对应。具体映射信息可见docs/compliance-mapping.md

            面试准备

            完整的弱项/强项分析资料可见:docs/interview-prep.md

            在完成每个练习阶段后,请及时进行巩固练习:

            阶段0:在Kubernetes环境中,流量是如何到达你的服务端的?当部署过程为手动操作时,首先会出现什么问题?

            阶段1:如何验证某个提交所对应的实际是哪个镜像被部署到了系统中?有哪些措施可以防止开发人员绕过持续集成流程进行操作?

            阶段2:从技术层面来讲,GitOps具体指的是什么?如何确保系统中的配置变更能够被自动检测并得到纠正?

            阶段3:SAST扫描、IaC扫描与镜像扫描之间有什么区别?在判断问题的严重性时,应如何划分这些扫描方法的适用范围?

            阶段4:什么是准入控制机制?它与持续集成流程有何不同?如何安全地为某些特殊情况引入政策例外?

            阶段5:为什么Kubernetes的Secrets并不属于“秘密管理”范畴?如何在不影响系统正常运行的前提下定期更新这些敏感信息?

            阶段6:运行时检测能够发现哪些持续集成和准入控制流程无法识别的问题?收到shell-spawn警报后,你的首要应对措施是什么?

            阶段7:仪表板与警报系统有什么区别?如何生成具有法律效力的审计证据,而不仅仅是口头声明?

            阶段8:当将应用程序迁移至EKS时,实际上哪些方面会发生变化?哪些部分应该保持不变?IRSA机制又是如何帮助降低风险的?

            AWS成本参考

            默认情况下,欧洲西部1区域的阶段8相关费用大致如下:

            资源类型 每月8小时使用量下的费用 每月全天候使用量下的费用
            EKS控制平面 约24美元 约73美元
            3个t3.medium节点 约30美元 约92美元
            NAT网关 约11美元 约33美元
            RDS db.t3.micro数据库 约4美元 约13美元
            ALB负载均衡器 约2美元 约6美元
            GuardDuty与CloudTrail安全服务 约2美元 约5美元
            总费用估算 约73美元 约222美元

            一旦不再需要使用某项资源,务必立即将其销毁:

            make aws-down

            总结

            你现在已经成功构建了一个金融科技应用,并为其添加了八层安全与可靠性防护措施——而这一切都是通过一台笔记本电脑完成的。

            你从最基础的Kubernetes环境开始,首先在阶段0实现了手动部署功能;然后在阶段1加入了持续集成流程,实现自动构建、扫描及签名镜像的操作;阶段2通过ArgoCD将Git系统与集群连接起来;阶段3利用SAST、IaC和镜像扫描技术对所有代码变更进行检测;阶段4使用Kyverno在集群边界处阻止恶意工作负载的入侵;阶段5将敏感信息从Git和Kubernetes的Secrets中移出,存储到Vault系统中;阶段6通过Falco实现了运行时威胁检测,并采用了网络隔离措施;阶段7构建了可生成审计证据的监控仪表板;最后在阶段8将整个系统迁移到了AWS平台上。

            这些阶段中的每一个都并非只是为了练习而设计的。它们每一项都代表着实际开发团队在项目中会遇到的真实问题。你首先体会到了这些问题带来的困扰,然后才找到了相应的解决方案。这就是仅仅阅读有关DevSecOps的相关资料与真正能够将其付诸实践之间的区别。

            请保存好你的截图,在简历中注明你所使用过的具体工具以及取得的成果;当需要在面试中详细说明自己所做的决策时,也可以参考这些准备材料。毕竟,这些都是你亲自完成的实际操作。

            如果你觉得这份指南很有帮助,请将其分享给那些正在学习DevOps或DevSecOps的人,并且在LinkedIn上与我建立联系吧

            我还会发布关于DevOps的实施步骤以及帮助求职者通过面试的技巧;如果你想要了解更多内容,可以关注我的账号或在那里订阅我的内容

相关文章

技术实践

如何让你的反重力技能具备可配置性(同时避免出现代码分支问题)

“反重力智能体技能”是一种非常有效的方法,可以帮助你一次性为人工智能智能体设定工作流程,并让它在各种场景中都能被重复使用。你只需编写一个简短的`SKILL.md`文件,将其放入相应的文件夹中,智能体在需要使用时就会自动加载这些配置。 然而,这类技能存在一个隐藏的局限性:它们是静态的。如果你下载了别人编写的技能代码,但希望它的行为有所改变,你就必须手动复制整个代码并进行修改。而且,最近你也应该注意到了,市面上有很多经过分叉修改的“技能版本”,这些版本往往很难进行维护。 在本次教程中,我将向你展示一种解决方法。这种方法可以让任何智能体技能读取特定项目中的配置文件,因此你完全可以使用现有的技能,只需

阅读全文
技术实践

如何使用 Fastlane 和 GitHub Actions 自动化Flutter应用的发布流程,以便将其分发到 Firebase App Distribution、Google Play、TestFlight 以及 App Store Connect平台上

想象一下:现在是周五下午4点,你的团队刚刚完成了本次迭代周期的最后一项功能开发。但产品经理要求在当天结束前通过TestFlight发送一个新的版本,这样客户就可以在周末进行测试。 你打开Xcode,等待代码打包完成,处理一个昨天还不存在的签名错误,修复问题后重新打包代码,再次等待上传,然后等待App Store Connect进行处理。接着你需要为Android版本重复这些步骤,不过这次是通过Android Studio来操作的。你需要签署APK文件,登录Firebase App Distribution平台,将文件上传进去,添加测试人员,编写发布说明,最后点击发送按钮。 现在已经是下午6点4

阅读全文
技术实践

如何使用针对用户的OAuth访问机制来构建人工智能代理程序【完整手册】

当你的AI代理同时为多个人提供服务时,每一次工具调用都必须明确:该代理究竟是在代表哪位用户行事。让我们通过构建一个能够与Slack和GitHub连接的AI代理来学习如何解决这个问题。 当使用Slack时,系统会使用 해당用户的 workspace;而在GitHub上创建问题时,也会以该用户的身份在其有权访问的仓库中操作。虽然代理可能会犯错,但它绝对不能使用错误用户的权限来进行操作。 解决这个问题的方法分为两个部分,而这两个部分都在本教程的前半部分进行了讲解: 每位用户都需要单独授权。 Alice为自己授权Slack,Bob也为自己授权Slack。 代理传递的是标识符,而不是令牌。 像 alic

阅读全文
技术实践

如何让你的副业项目被人们注意到,并吸引到愿意付费使用的用户

2022年,我在业余时间开发了一个小型微服务产品,最终以几千美元的价格将其卖了出去。如今,有了人工智能工具的帮助,开发这样的产品可能会更加容易。 但真正发生巨大变化的是获取关注的成本,而不是开发软件本身的成本。 我认为,在2026年,产品的分发渠道将比开发本身更为重要。在这篇文章中,我会与大家分享我在产品开发过程中所学到的经验,并试图劝阻大家在开始下一个项目之前,先不要急着直接投入编码工作。 读完这份指南后,你应该能够掌握一些实用的方法和思路,这些方法可以帮助你将自己那些充满热情的项目推向市场。 需要明确的是,这篇文章主要是针对那些正在开发数字产品的人,尤其是软件产品。不过,这些概念同样适用于

阅读全文