diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index 3a6e805..d9fce0d 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -1,5 +1,6 @@
import { defineConfig } from 'vitepress'
import { createPostsSidebar } from './utils/content.mts'
+import { withMermaid } from 'vitepress-plugin-mermaid'
const nav = [
{
@@ -27,12 +28,13 @@ const sidebar = {
'/posts/': createPostsSidebar(process.cwd())
}
-export default defineConfig({
+export default withMermaid(defineConfig({
lang: 'zh-CN',
title: 'NCHJ 的笔记',
description: '技术笔记与工程实践整理',
lastUpdated: true,
cleanUrls: true,
+ mermaid: {},
themeConfig: {
nav,
sidebar,
@@ -47,4 +49,4 @@ export default defineConfig({
lightModeSwitchTitle: '切换到浅色模式',
darkModeSwitchTitle: '切换到深色模式'
}
-})
+}))
diff --git a/docs/posts/k8s-training/01-k8s-architecture.md b/docs/posts/k8s-training/01-k8s-architecture.md
new file mode 100644
index 0000000..c4cc868
--- /dev/null
+++ b/docs/posts/k8s-training/01-k8s-architecture.md
@@ -0,0 +1,294 @@
+---
+title: "第1章:Kubernetes 核心概念与架构"
+date: "2026-05-15"
+category: "k8s"
+tags:
+ - "k8s"
+ - "AI生成"
+---
+
+# 第1章:Kubernetes 核心概念与架构
+
+## 🎯 学习目标
+- 理解 K8S 是什么、为什么需要它
+- 掌握 K8S 集群架构(Master + Worker)
+- 理解核心组件的职责和协作关系
+- 能回答面试中"K8S 架构"相关问题
+
+---
+
+## 📖 学习要点
+
+### 1.1 什么是 Kubernetes
+
+**定义**:Kubernetes(简称 K8S)是一个开源的**容器编排平台**,用于自动部署、扩缩和管理容器化应用。
+
+**核心能力**:
+| 能力 | 说明 | 一句话理解 |
+|------|------|-----------|
+| 服务发现与负载均衡 | 自动分配流量到健康的 Pod | 不用手动配 Nginx upstream |
+| 自动部署与回滚 | 声明式地管理应用状态 | git 一样管理基础设施 |
+| 自动装箱 | 根据资源需求调度 Pod 到节点 | 自动找合适的服务器放容器 |
+| 自我修复 | 自动重启失败的容器,替换不健康的 Pod | 容器挂了自动拉起来 |
+| 水平扩缩 | 根据负载自动增减 Pod 数量 | 流量大了自动加机器 |
+| Secret 和配置管理 | 管理敏感信息和应用配置 | 配置和代码分离 |
+
+**K8S vs Docker Compose 的区别**(面试高频):
+
+```
+Docker Compose:
+- 单机编排
+- 适合开发环境
+- docker-compose up 即可
+- 没有自愈能力
+
+Kubernetes:
+- 集群编排(多节点)
+- 适合生产环境
+- 需要学习成本
+- 自愈、自动扩缩、滚动更新
+```
+
+### 1.2 集群架构
+
+```mermaid
+flowchart TB
+ subgraph K8S_Cluster["K8S Cluster"]
+ subgraph Master["Master Node(s)"]
+ API_Server["API Server"]
+ Scheduler["Scheduler"]
+ etcd["etcd"]
+ Controller_Manager["Controller Manager"]
+ Cloud_Controller["Cloud Controller"]
+ end
+
+ Master -->|"REST API"| Workers
+ end
+
+ subgraph Workers["Worker Nodes"]
+ W1["Worker 1"]:::worker
+ W2["Worker 2"]:::worker
+ WN["Worker N"]:::worker
+
+ W1 --> kubelet1["kubelet"]
+ W1 --> kubeproxy1["kube-proxy"]
+ W1 --> pods1["Pod Pod"]
+
+ W2 --> kubelet2["kubelet"]
+ W2 --> kubeproxy2["kube-proxy"]
+ W2 --> pods2["Pod Pod"]
+
+ WN --> kubeletN["kubelet"]
+ WN --> kubeproxyN["kube-proxy"]
+ WN --> podsN["Pod Pod"]
+ end
+
+ classDef worker fill:#f9f,stroke:#333,stroke-width:2px
+```
+
+> **图 1-1:K8S 集群架构图**
+
+### 1.3 核心组件详解
+
+#### Master 节点组件(控制面)
+
+**① API Server(apiserver)**
+- K8S 的"前台",所有组件和用户都通过它通信
+- 唯一入口,提供 REST API
+- 认证、授权、准入控制
+- **面试考点**:为什么所有组件都通过 API Server 通信而不是直接访问 etcd?
+ > 解耦、安全(etcd 不暴露)、审计、统一鉴权
+
+**② etcd**
+- 分布式键值存储,保存集群所有数据
+- K8S 的"数据库"
+- 状态:集群状态、配置、Secret 数据都存在这里
+- **面试考点**:etcd 挂了会怎样?
+ > Master 功能失效,不能创建新资源,但已有 Pod 继续运行
+
+**③ Scheduler(调度器)**
+- 决定 Pod 运行在哪个 Node 上
+- 调度决策因素:资源请求、亲和性、污点容忍、数据本地性等
+- **工作流程**:Pod 创建 → Scheduler 观察 → 选择最优 Node → 绑定
+
+**④ Controller Manager(控制器管理器)**
+- 运行多个控制器的进程
+- 核心:"控制循环"(Reconcile Loop):不断观察期望状态 vs 实际状态,驱动实际状态趋近期望状态
+- 常见控制器:
+ - **Deployment Controller**:管理 ReplicaSet,保证 Pod 副本数
+ - **Node Controller**:监控节点健康
+ - **ReplicaSet Controller**:保证 Pod 数量
+ - **Service Account Controller**:管理默认 SA
+
+#### Worker 节点组件
+
+**⑤ kubelet**
+- 每个 Node 上的"代理人"
+- 接收 PodSpec(来自 API Server),确保容器按 Spec 运行
+- 健康检查、向 Master 汇报节点状态
+- **面试考点**:kubelet 挂了会怎样?
+ > 该 Node 上的 Pod 不受管理,Master 会将其标记为 NotReady
+
+**⑥ kube-proxy**
+- 维护节点上的网络规则
+- 实现 Service 的负载均衡(iptables/IPVS 模式)
+- 让 Service 能将流量路由到正确的 Pod
+
+### 1.4 核心资源对象(K8S 词汇表)
+
+```
+Namespace → 集群内虚拟分区(多租户隔离)
+Pod → 最小部署单元,一个或多个容器
+Deployment → 管理 Pod 的声明式更新(无状态应用)
+ReplicaSet → 保证 Pod 副本数(通常不直接使用)
+Service → Pod 的稳定网络入口 + 负载均衡
+ConfigMap → 存非敏感配置
+Secret → 存敏感信息(密码、证书)
+Ingress → HTTP/HTTPS 路由(7层负载均衡)
+PersistentVolume → 持久化存储
+PersistentVolumeClaim → 存储申请
+```
+
+### 1.5 声明式 vs 命令式(面试必考)
+
+```
+命令式(Imperative):
+ kubectl run nginx --image=nginx
+ kubectl create deployment ...
+ → 告诉 K8S "做什么操作"
+
+声明式(Declarative):
+ kubectl apply -f deployment.yaml
+ → 告诉 K8S "我要什么状态",K8S 自己搞定
+
+ 优势:
+ ✅ Git 友好(版本控制)
+ ✅ 可审计(谁改了什么)
+ ✅ 幂等(多次执行结果一致)
+ ✅ 可回滚
+```
+
+---
+
+## 📝 习题
+
+### 选择题
+
+**Q1.** Kubernetes 中负责存储集群所有状态数据的组件是?
+- A) API Server
+- B) Scheduler
+- C) etcd
+- D) kubelet
+
+**Q2.** 以下哪个不是 Master 节点的组件?
+- A) Controller Manager
+- B) kubelet
+- C) API Server
+- D) Scheduler
+
+**Q3.** K8S 中最小的调度和部署单元是?
+- A) Container
+- B) Pod
+- C) Deployment
+- D) Service
+
+**Q4.** kube-proxy 的主要职责是?
+- A) 调度 Pod 到合适的节点
+- B) 管理集群状态数据
+- C) 维护网络规则,实现 Service 负载均衡
+- D) 监控 Pod 健康状态
+
+**Q5.** 声明式 API 的优势不包括以下哪个?
+- A) 幂等性
+- B) Git 友好
+- C) 执行速度更快
+- D) 可审计
+
+### 简答题
+
+**Q6.** 请简述一个 Pod 从 `kubectl apply -f pod.yaml` 到实际运行经历的完整流程。
+
+**Q7.** 如果 etcd 集群不可用,K8S 集群会发生什么?已有 Pod 还能继续工作吗?
+
+**Q8.** 请解释 K8S 中"控制循环"(Control Loop / Reconcile Loop)的概念。
+
+---
+
+## ✅ 参考答案
+
+### A1. C) etcd
+etcd 是 K8S 的唯一状态存储,所有集群数据(节点信息、Pod 状态、配置等)都存在 etcd 中。
+
+### A2. B) kubelet
+kubelet 运行在每个 Worker 节点上,负责管理该节点上的容器。它不是 Master 组件。
+
+### A3. B) Pod
+Pod 是 K8S 的最小调度和部署单元。一个 Pod 可以包含一个或多个容器,它们共享网络和存储命名空间。
+
+### A4. C) 维护网络规则,实现 Service 负载均衡
+kube-proxy 运行在每个节点上,通过 iptables 或 IPVS 规则,将访问 Service 的流量转发到后端的 Pod。
+
+### A5. C) 执行速度更快
+声明式 API 并不比命令式执行更快,有时因为 diff 计算反而更慢。其核心优势是幂等、Git 友好、可审计。
+
+### A6. Pod 创建完整流程
+```
+1. 用户执行 kubectl apply -f pod.yaml
+2. kubectl 将请求发送给 API Server(认证+授权+准入控制)
+3. API Server 将 Pod 配置写入 etcd
+4. Scheduler 感知到有未绑定的 Pod,执行调度算法
+5. Scheduler 选出最优 Node,将绑定信息写入 etcd
+6. 目标 Node 上的 kubelet 监听到分配给自己的 Pod
+7. kubelet 调用容器运行时(CRI)拉取镜像、创建容器
+8. kubelet 执行健康检查,向 API Server 汇报 Pod 状态
+9. Pod 变为 Running 状态
+```
+
+### A7. etcd 不可用的影响
+- **Master 功能完全失效**:无法创建/更新/删除任何资源
+- **已有 Pod 继续运行**:kubelet 是直接管理容器的,不依赖 etcd
+- **新 Pod 无法调度**:Scheduler 无法读取/写入状态
+- **自愈失效**:Controller Manager 无法感知状态变化
+- **结论**:集群变成"只读"状态,已有工作负载不受影响但不能管理
+
+### A8. 控制循环(Reconcile Loop)
+控制循环是 K8S 控制器模式的核心:
+1. 控制器观察(Watch)API Server 中的资源状态
+2. 对比**期望状态**(YAML 中定义的)和**实际状态**(集群中真实的)
+3. 如果不一致,执行动作使实际状态趋近期望状态
+4. 循环重复
+
+```
+while true:
+ 实际状态 = getCurrentState()
+ if 实际状态 != 期望状态:
+ 执行动作使实际状态 → 期望状态
+ sleep(一段时间)
+```
+
+**类比**:恒温器 —— 你设定 25°C(期望状态),恒温器不断检测室温(实际状态),低于 25°C 就开暖气。
+
+---
+
+## 🔧 实操练习
+
+```bash
+# 设置集群连接
+export KUBECONFIG=~/.kube/config
+
+# 创建自己的 namespace(所有练习在这个 namespace 下操作)
+kubectl create namespace k8s-training
+
+# 查看集群信息
+kubectl get ns
+kubectl cluster-info
+
+# 练习后清理
+kubectl delete namespace k8s-training
+```
+
+> ⚠️ **提醒**:所有实操练习都在 `k8s-training` namespace 下进行,练习完务必删除资源,Sealos 是付费的!
+
+---
+
+**下一章**:[第2章:Pod 与工作负载管理](./02-pod-and-workloads.md)
diff --git a/docs/posts/k8s-training/02-pod-and-workloads.md b/docs/posts/k8s-training/02-pod-and-workloads.md
new file mode 100644
index 0000000..a376a99
--- /dev/null
+++ b/docs/posts/k8s-training/02-pod-and-workloads.md
@@ -0,0 +1,1321 @@
+---
+title: "第2章:Pod 与工作负载管理"
+date: "2026-05-15"
+category: "k8s"
+tags:
+ - "k8s"
+ - "AI生成"
+---
+
+# 第2章:Pod 与工作负载管理
+
+## 🎯 学习目标
+- 理解 Pod 的生命周期和核心字段
+- 掌握 Deployment、StatefulSet、DaemonSet、Job/CronJob 的使用场景
+- 能手写 YAML 并部署应用
+- 理解滚动更新和回滚机制
+
+---
+
+## 📖 学习要点
+
+### 2.1 Pod 是什么(最底层的运行单元)
+
+**一句话理解**:Pod 是 K8S 中**真正运行的实体**。不管你写的是 Deployment、StatefulSet 还是 DaemonSet,最终跑在节点上的都是 Pod。
+
+```mermaid
+flowchart TB
+ subgraph Pod["Pod (IP: 10.244.1.5)"]
+ App["Container (App)
Port: 8080"]
+ Sidecar["Container (Sidecar)
Port: 9090"]
+ Pause["Pause Container
(共享网络)"]
+
+ App <--> Pause
+ Sidecar <--> Pause
+ end
+
+ note1["共享: Network Namespace"]
+ note2["共享: Volume (挂载点)"]
+```
+
+> **图 2-1:Pod 结构图**
+
+**为什么 K8S 不直接管理容器而要加一层 Pod?**
+- 有些应用天然需要多个进程协作(如日志收集 sidecar)
+- Pod 内容器共享 IP 和端口空间,可以通过 localhost 通信
+- Pod 是调度的原子单位——同 Pod 的容器一定在同一节点
+
+### 2.2 Pod 的两种使用方式(⭐ 核心!先搞懂这个再往下看)
+
+Pod 有两种创建方式,**理解这个就理解了 Deployment 里"隐含的 Pod"是怎么回事**:
+
+```mermaid
+flowchart LR
+ subgraph Way1["方式一:直接创建 Pod(独立 Pod)"]
+ A1["你写 Pod YAML"]
+ A1 --> B1["kubectl apply -f pod.yaml → K8S 直接创建 Pod"]
+ B1 --> C1["你要自己管一切:Pod 挂了就没了,没人帮你重启,没有自动扩缩,没有滚动更新"]
+ end
+
+ subgraph Way2["方式二:通过 Deployment 管理 Pod(托管 Pod)⭐"]
+ A2["你写 Deploy YAML"]
+ A2 --> B2["kubectl apply -f deploy.yaml"]
+ B2 --> C2["Deployment 根据 template 自动帮你创建 Pod
Pod 挂了自动重启
想多副本?改 replicas 就行
想更新?改 image 就自动滚动更新"]
+ end
+```
+
+> **图 2-2:Pod 的两种创建方式**
+
+**关键结论**:
+> **Pod 永远是 K8S 的运行单位,但你不一定需要直接写 Pod YAML。**
+> 写 Deployment YAML 时,Pod 被"嵌在" Deployment 的 `template` 字段里——这就是所谓的"隐含"。
+
+---
+
+### 2.3 Deployment 与 Pod 的关系(⭐⭐⭐ 面试必问,必须彻底理解)
+
+#### 先看本质:Deployment = Pod 的管理者
+
+```mermaid
+flowchart TB
+ subgraph Deploy["Deployment(管理者)"]
+ direction TB
+ D1["我想要 3 个 Pod,标签都是 app=nginx,用 nginx:1.25"]
+ D2["selector: ← 用标签来\"认领\" Pod
app: nginx"]
+ D3["replicas: 3 ← 要维护 3 个副本"]
+ D4["template: ← Pod 的\"模子\"(模板)
metadata.labels.app: nginx ← 新创建的 Pod 打上这个标签
spec: ← 容器怎么定义"]
+ end
+
+ Deploy -->|"Deployment Controller 看着这个模板
发现\"实际只有 1 个 Pod,我想要 3 个\"
→ 自动创建 2 个新 Pod"| Pods
+
+ subgraph Pods[""]
+ P1["Pod-1
app=nginx
1.25"]
+ P2["Pod-2
app=nginx
1.25"]
+ P3["Pod-3
app=nginx
1.25"]
+ end
+```
+
+> **图 2-3:Deployment 与 Pod 的关系**
+
+#### Deployment YAML 中的"三套标签"(最容易混淆的地方)
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: nginx-deployment
+ labels:
+ app: nginx # ① Deployment 自己的标签
+spec:
+ selector:
+ matchLabels:
+ app: nginx # ② 选择器:Deployment 用来找 Pod 的条件
+ template:
+ metadata:
+ labels:
+ app: nginx # ③ Pod 模板的标签:新 Pod 会被打上这个标签
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.25
+```
+
+**② 和 ③ 必须匹配!这是铁律。** 为什么?
+
+```
+Deployment Controller 的工作循环:
+
+while true:
+ 实际Pod数 = 根据选择器②(selector) 找标签=app:nginx 的 Pod
+ if 实际Pod数 < replicas:
+ 用模板③(template) 创建新 Pod
+ if 实际Pod数 > replicas:
+ 删除多余的 Pod
+ sleep(一会儿)
+
+如果 ② 和 ③ 不匹配会怎样?
+ → Deployment 用 app:nginx 去找 Pod
+ → 但新创建的 Pod 标签是 app:web(不匹配)
+ → Deployment 找不到自己创建的 Pod!
+ → 会无限创建 Pod,永远觉得"不够"
+ → 所以 K8S 直接拒绝这种配置(API Server 校验)
+```
+
+#### selector 是怎么工作的?(标签选择器机制)
+
+标签(Label) = 附加在资源上的键值对,用于分组和筛选。
+
+```yaml
+# Pod 上的标签
+metadata:
+ labels:
+ app: nginx # key: app, value: nginx
+ tier: frontend
+ env: production
+
+# Deployment 用 selector 找这些 Pod
+spec:
+ selector:
+ matchLabels: # 精确匹配:标签必须完全一致
+ app: nginx
+ # matchExpressions: # 表达式匹配(高级用法)
+ # - key: tier
+ # operator: In
+ # values: ["frontend", "backend"]
+```
+
+**selector 的工作过程**:
+```
+集群中所有 Pod:
+ Pod-A labels: {app: nginx, tier: frontend} ✅ 匹配 app=nginx
+ Pod-B labels: {app: redis, tier: cache} ❌ 不匹配
+ Pod-C labels: {app: nginx, env: production} ✅ 匹配 app=nginx
+ Pod-D labels: {app: nginx, tier: backend} ✅ 匹配 app=nginx
+
+selector: matchLabels: {app: nginx}
+→ 匹配到 Pod-A, Pod-C, Pod-D → 这些就是"我的 Pod"
+```
+
+> **类比**:selector 是"招聘条件"(要 app=nginx),labels 是"简历标签"。Deployment 根据招聘条件筛所有 Pod 的简历,匹配的就是自己管的。
+
+#### Pod 什么时候"独立",什么时候"被隐含"?
+
+| 场景 | Pod 的存在形式 | 你需要写什么 | 谁管 Pod 的生命周期 |
+|------|--------------|------------|-----------------|
+| **临时调试** | 独立 Pod | `kind: Pod` YAML | **没人管**,挂了就没了 |
+| **部署 Web 服务** | 被 Deployment 管理 | `kind: Deployment` YAML(内含 template) | **Deployment 管**,自动重启/扩缩/更新 |
+| **部署 MySQL** | 被 StatefulSet 管理 | `kind: StatefulSet` YAML | **StatefulSet 管** |
+| **部署日志 Agent** | 被 DaemonSet 管理 | `kind: DaemonSet` YAML | **DaemonSet 管** |
+| **运行一次性任务** | 被 Job 管理 | `kind: Job` YAML | **Job 管** |
+
+**面试回答模板**:
+> "Pod 是 K8S 的最小运行单元。绝大多数情况下我们不会直接创建 Pod,而是通过 Deployment 等工作负载来间接管理。Deployment 的 `template` 字段就是 Pod 的定义模板,Deployment Controller 会根据这个模板自动创建和管理 Pod。Deployment 通过 `selector` 标签选择器来识别哪些 Pod 是自己管理的。"
+
+#### 独立 Pod vs Deployment 管理 Pod 的完整对比
+
+```yaml
+# ====== 方式一:独立 Pod(你手动管理一切) ======
+apiVersion: v1
+kind: Pod
+metadata:
+ name: my-nginx # 直接给 Pod 起名
+ labels:
+ app: nginx
+spec:
+ containers:
+ - name: nginx
+ image: nginx:1.25
+ ports:
+ - containerPort: 80
+# 问题:
+# ❌ Pod 挂了没人重启
+# ❌ 只能运行 1 个实例
+# ❌ 没有滚动更新
+# ❌ 手动扩缩(不可能)
+
+# ====== 方式二:Deployment(K8S 帮你管一切) ======
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: nginx-deployment # Deployment 的名字(不是 Pod 的)
+spec:
+ replicas: 3 # 要 3 个 Pod(不是 1 个!)
+ selector: # 用标签找"我的 Pod"
+ matchLabels:
+ app: nginx
+ template: # ⭐ Pod 模板(和上面的 Pod YAML 的 spec 基本一样)
+ metadata:
+ labels:
+ app: nginx # Pod 的标签(必须匹配 selector)
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.25
+ ports:
+ - containerPort: 80
+# 优势:
+# ✅ Pod 挂了自动重建
+# ✅ 自动维护 3 个副本
+# ✅ 滚动更新 nginx:1.25 → nginx:1.26
+# ✅ kubectl scale 一条命令扩缩
+```
+
+#### Deployment 创建 Pod 的完整过程
+
+```mermaid
+flowchart TB
+ A["你执行 kubectl apply -f deployment.yaml"] --> B["1. API Server 保存 Deployment 对象到 etcd"]
+ B --> C["2. Deployment Controller 发现:
有人创建了一个 replicas=3 的 Deployment
selector 要求 app=nginx 的 Pod
当前 0 个 Pod 匹配
差 3 个,我来创建!"]
+ C --> D["3. Deployment Controller 自动创建一个 ReplicaSet
(ReplicaSet 是 Deployment 的中间层,通常不直接操作)"]
+ D --> E["4. ReplicaSet Controller 根据 template 创建 3 个 Pod
→ Pod 名自动生成: nginx-deployment-abc1234-5xyz
→ Pod 自动打上 template.metadata.labels"]
+ E --> F["5. Scheduler 调度 Pod 到具体的 Node"]
+ F --> G["6. kubelet 在 Node 上拉取镜像、启动容器"]
+ G --> H["7. 你执行 kubectl get pods 看到的是这 3 个 Pod,
不是 Deployment 本身"]
+
+ note1["实际层级关系:
Deployment
└── ReplicaSet (自动创建,通常不直接操作)
└── Pod (实际运行的)
└── Pod
└── Pod"]
+```
+
+> **图 2-4:Deployment 创建 Pod 的完整过程**
+
+#### 一句话总结 Deployment 和 Pod 的关系
+
+> **Deployment 是"图纸+管理者",Pod 是"产品"。Deployment 的 `template` 是生产 Pod 的模具,`selector` 是质检标准(通过标签检查产品是否合格),`replicas` 是产量要求。K8S 的 Controller 就是工厂,不断检查实际产量是否达标。**
+
+### 2.4 Pod YAML 核心字段(必背)
+
+下面是独立 Pod 的完整 YAML,注意:**Deployment 的 `template` 部分(`template.metadata` + `template.spec`)和这个 Pod YAML 的结构几乎完全一样**。
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: my-app
+ labels: # 标签:用于选择器匹配
+ app: my-app
+ env: production
+ annotations: # 注解:存储元数据,不可用于选择
+ description: "Main application pod"
+spec: # ← 这部分和 Deployment 的 template.spec 完全一样
+ containers:
+ - name: app
+ image: nginx:1.25 # 镜像
+ imagePullPolicy: IfNotPresent # Always/Never/IfNotPresent
+ ports:
+ - containerPort: 80
+ env: # 环境变量
+ - name: DB_HOST
+ value: "mysql-service"
+ resources: # 资源限制(⭐面试高频)
+ requests: # 调度依据(保证最少给这么多)
+ cpu: "100m" # 100 millicore = 0.1 核
+ memory: "128Mi"
+ limits: # 硬限制(超过会 OOM Kill / CPU 限流)
+ cpu: "500m"
+ memory: "256Mi"
+ livenessProbe: # 存活探针:挂了就重启容器
+ httpGet:
+ path: /healthz
+ port: 80
+ initialDelaySeconds: 10 # 首次探测延迟
+ periodSeconds: 5 # 探测间隔
+ readinessProbe: # 就绪探针:没准备好就不接入流量
+ httpGet:
+ path: /ready
+ port: 80
+ initialDelaySeconds: 5
+ periodSeconds: 3
+ restartPolicy: Always # Always/OnFailure/Never
+```
+
+### 2.5 探针类型(面试必考)
+
+| 探针 | 作用 | 失败后果 |
+|------|------|---------|
+| **livenessProbe** | 容器是否存活 | 重启容器 |
+| **readinessProbe** | 是否可以接收流量 | 从 Service Endpoints 中移除 |
+| **startupProbe** | 容器是否启动完成 | 未通过则 liveness/readiness 不生效 |
+
+三种探测方式:
+```yaml
+# 1. HTTP 探测
+livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8080
+
+# 2. TCP 探测
+livenessProbe:
+ tcpSocket:
+ port: 3306
+
+# 3. 命令探测
+livenessProbe:
+ exec:
+ command:
+ - cat
+ - /tmp/healthy
+```
+
+### 2.6 工作负载类型对比
+
+| 类型 | 使用场景 | 特点 | restartPolicy |
+|------|---------|------|---------------|
+| **Deployment** | 无状态应用(Web服务、API) | 滚动更新、回滚、扩缩容 | Always |
+| **StatefulSet** | 有状态应用(数据库、MQ) | 稳定网络标识、有序部署/删除、持久存储 | Always |
+| **DaemonSet** | 每个节点运行一个(日志收集、监控) | 新节点自动加入,节点删除自动清理 | Always |
+| **Job** | 一次性任务(数据处理、ML训练、迁移脚本) | 运行到完成即退出,支持并行/重试/超时 | Never / OnFailure |
+| **CronJob** | 定时任务(备份、报表、清理) | Cron 表达式调度,管理 Job 的生命周期 | Never / OnFailure |
+
+> **⭐ 关键区别**:Deployment 的 Pod 退出后会被重建(维持副本数),Job 的 Pod 完成后不会重建。这就是 `restartPolicy` 必须设为 `Never` 或 `OnFailure` 的原因——告诉 kubelet 任务完成就不再重启。
+
+**ECS → K8S 迁移参考:**
+| AWS ECS | K8S 对应 | 补充工具 |
+|---------|---------|---------|
+| ECS Service | Deployment | — |
+| ECS Task(一次性) | Job | — |
+| ECS Task + SQS Trigger | Job + KEDA ScaledJob | KEDA(见 2.9 节) |
+| ECS Scheduled Task | CronJob | — |
+
+### 2.7 Deployment YAML 详解(最重要的资源)
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: nginx-deployment
+ labels:
+ app: nginx
+spec:
+ replicas: 3 # 副本数
+ selector: # ⭐ 必须!选择器匹配 Pod
+ matchLabels:
+ app: nginx
+ strategy: # 更新策略
+ type: RollingUpdate # RollingUpdate / Recreate
+ rollingUpdate:
+ maxSurge: 1 # 滚动更新时最多多出几个 Pod
+ maxUnavailable: 0 # 滚动更新时最多不可用几个 Pod
+ template: # Pod 模板(和 Pod YAML 的 spec 部分一样)
+ metadata:
+ labels:
+ app: nginx # ⭐ 必须和 selector.matchLabels 匹配!
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.25
+ ports:
+ - containerPort: 80
+ resources:
+ requests:
+ cpu: "100m"
+ memory: "64Mi"
+ limits:
+ cpu: "500m"
+ memory: "256Mi"
+```
+
+**面试考点:RollingUpdate 策略**
+```
+旧版本: [Pod1] [Pod2] [Pod3]
+ ↓
+新版本: [Pod1] [Pod2] [Pod3-new] ← maxSurge=1, maxUnavailable=0
+ ↓
+ [Pod1] [Pod2-new] [Pod3-new]
+ ↓
+ [Pod1-new] [Pod2-new] [Pod3-new] ← 完成
+```
+
+### 2.8 Job 与 CronJob 深度讲解(批处理任务)
+
+> **类比 AWS ECS**:如果你在 AWS 上用 ECS Task 运行一次性的 heavy 任务,K8S 的 `Job` 就是对等概念。每个 Job 会起一个独立容器,运行完自动退出并可以自动清理。
+
+#### Job 核心概念
+
+Job 负责管理"运行到完成"的任务,与 Deployment 的核心区别:
+
+| 对比项 | Deployment | Job |
+|--------|-----------|-----|
+| 目的 | 持续运行的服务 | 运行到完成的任务 |
+| Pod 退出后 | 自动重建(保持副本数) | 不重建(任务完成) |
+| `restartPolicy` | Always | Never 或 OnFailure |
+| 使用场景 | Web 服务、API | 数据处理、ML 训练、迁移脚本 |
+
+#### Job YAML 完整示例
+
+```yaml
+apiVersion: batch/v1
+kind: Job
+metadata:
+ name: data-process-job
+ namespace: default
+spec:
+ # ===== 核心控制字段 =====
+ completions: 1 # 需要成功完成的 Pod 数量(默认 1)
+ parallelism: 1 # 同时运行的 Pod 数量(默认 1)
+ backoffLimit: 3 # 失败重试次数(超过后 Job 标记为失败)
+ activeDeadlineSeconds: 600 # 整个 Job 的超时时间(秒)
+ ttlSecondsAfterFinished: 300 # 完成后 5 分钟自动清理(包括 Pod 日志)
+
+ # ===== Pod 模板 =====
+ template:
+ metadata:
+ labels:
+ app: data-processor
+ job-type: batch
+ spec:
+ restartPolicy: Never # ⭐ Job 中必须设为 Never 或 OnFailure
+ containers:
+ - name: worker
+ image: python:3.11
+ command: ["python", "/app/process.py"]
+ env:
+ - name: TASK_ID
+ value: "job-20260401-001"
+ - name: INPUT_BUCKET
+ value: "s3://my-bucket/input/"
+ resources:
+ requests:
+ cpu: "2" # 重型任务申请足够资源
+ memory: "4Gi"
+ limits:
+ cpu: "4"
+ memory: "8Gi"
+ volumeMounts:
+ - name: workdir
+ mountPath: /tmp/work
+ volumes:
+ - name: workdir
+ emptyDir: {}
+```
+
+#### restartPolicy 的选择
+
+```
+restartPolicy: Never
+ - Pod 失败 → 启动新的 Pod(不复用)
+ - 直到 backoffLimit 次失败后,Job 标记为 Failed
+ - 适合:幂等任务、需要干净环境的任务
+
+restartPolicy: OnFailure
+ - Pod 失败 → 在同一个 Pod 内重启容器
+ - 适合:轻量级任务、希望复用环境的任务
+```
+
+#### 并行 Job 模式(处理大量任务)
+
+**模式一:固定完成数(completions > parallelism)**
+```yaml
+spec:
+ completions: 10 # 总共要完成 10 个任务
+ parallelism: 3 # 每次并行 3 个 Pod
+ # K8S 自动调度:先起 3 个,某个完成后立刻补一个,直到完成 10 个
+```
+
+**模式二:WorkQueue 模式(消费队列)**
+```yaml
+spec:
+ completions: 10
+ parallelism: 5
+ completionMode: Indexed # ⭐ 每个 Pod 有唯一的 JOB_COMPLETION_INDEX 环境变量
+ # Pod 0 → 处理任务 0
+ # Pod 1 → 处理任务 1
+ # ...
+```
+Pod 内通过 `$JOB_COMPLETION_INDEX` 知道自己该处理哪个分片:
+```python
+import os
+shard_id = int(os.environ["JOB_COMPLETION_INDEX"])
+# 从队列或存储中取第 shard_id 个任务
+```
+
+#### 动态创建 Job(程序化触发)
+
+这是与 ECS `RunTask` 最接近的模式:你的调度服务通过 K8S API 动态创建 Job:
+
+```python
+# Python 示例(使用 kubernetes client-go 的 Python 版)
+from kubernetes import client, config
+
+config.load_incluster_config() # 在 K8S 内部运行时
+# config.load_kube_config() # 本地调试时
+
+batch_v1 = client.BatchV1Api()
+
+def create_task_job(task_id: str, input_data: str):
+ job = client.V1Job(
+ api_version="batch/v1",
+ kind="Job",
+ metadata=client.V1ObjectMeta(
+ name=f"task-{task_id}",
+ labels={"task-id": task_id}
+ ),
+ spec=client.V1JobSpec(
+ ttl_seconds_after_finished=300,
+ backoff_limit=3,
+ template=client.V1PodTemplateSpec(
+ spec=client.V1PodSpec(
+ restart_policy="Never",
+ containers=[client.V1Container(
+ name="worker",
+ image="your-worker:latest",
+ env=[client.V1EnvVar(name="TASK_ID", value=task_id),
+ client.V1EnvVar(name="INPUT", value=input_data)]
+ )]
+ )
+ )
+ )
+ )
+ batch_v1.create_namespaced_job(namespace="default", body=job)
+```
+
+#### CronJob(定时任务)
+
+```yaml
+apiVersion: batch/v1
+kind: CronJob
+metadata:
+ name: daily-report
+spec:
+ schedule: "0 2 * * *" # 每天凌晨 2 点(Cron 表达式)
+ concurrencyPolicy: Forbid # Forbid(禁止并发) / Allow / Replace
+ successfulJobsHistoryLimit: 3 # 保留最近 3 个成功的 Job 记录
+ failedJobsHistoryLimit: 1 # 保留最近 1 个失败的 Job 记录
+ startingDeadlineSeconds: 60 # 错过触发时间后 60 秒内还允许补跑
+ jobTemplate:
+ spec:
+ template:
+ spec:
+ restartPolicy: OnFailure
+ containers:
+ - name: reporter
+ image: your-reporter:latest
+ command: ["python", "generate_report.py"]
+```
+
+**Cron 表达式速查:**
+```
+┌───────────── 分钟 (0-59)
+│ ┌───────────── 小时 (0-23)
+│ │ ┌───────────── 日 (1-31)
+│ │ │ ┌───────────── 月 (1-12)
+│ │ │ │ ┌───────────── 星期几 (0-6, 0=周日)
+│ │ │ │ │
+* * * * *
+
+0 2 * * * 每天凌晨 2 点
+*/15 * * * * 每 15 分钟
+0 9 * * 1 每周一上午 9 点
+0 0 1 * * 每月 1 号零点
+```
+
+---
+
+### 2.9 任务调度架构:一任务一容器的正确姿势
+
+> **核心问题**:业务系统发起一个重型任务,需要为它独立起一个容器运行。CPU/内存 Auto Scaling(HPA)解决不了这个问题,业务系统也不应该直接调 K8S API。那正确的架构是什么?
+
+#### 为什么 HPA 解决不了这个问题
+
+这是一个常见的认知误区,必须澄清:
+
+| 维度 | HPA(水平 Pod 自动伸缩) | 任务级伸缩 |
+|------|----------------------|----------|
+| **伸缩对象** | 扩/缩 Deployment 的**副本数** | 为每个任务**独立创建**一个 Pod |
+| **触发条件** | CPU/内存使用率超过阈值 | 有新任务进队列 |
+| **Pod 生命周期** | Pod 持续运行,处理多个请求 | Pod 处理完一个任务后退出 |
+| **任务隔离** | 多个任务共享同一批 Pod | 每个任务有自己独占的 Pod |
+| **适用场景** | Web 服务、无状态 API | ML 训练、视频转码、数据处理 |
+
+**HPA 的本质**:你有 3 个 Worker Pod,CPU 高了变成 6 个——但这 6 个 Pod 仍然是竞争同一个队列,没有"任务和容器 1:1 绑定"的概念。
+
+**一任务一容器的本质**:不是扩副本,而是**动态创建 Job**——每个 Job 有自己独立的环境、资源、生命周期,完成后自动销毁。
+
+---
+
+#### 架构分层原则:业务系统不应直接调 K8S API
+
+```mermaid
+flowchart LR
+ subgraph Wrong["❌ 错误做法(强耦合)"]
+ A1["业务系统"] -->|"直接调用"| K8S["K8S API"]
+ K8S -->|"创建 Job"| J["Job"]
+ end
+
+ subgraph Correct["✅ 正确做法(解耦)"]
+ A2["业务系统"] -->|"写入"| MQ["消息队列 / 数据库"]
+ MQ -->|"调度层"| Dispatcher["调度层"]
+ Dispatcher -->|"K8S API"| K8S2["K8S API"]
+ K8S2 -->|"创建 Job"| J2["Job"]
+ end
+```
+
+> **图 2-5:业务系统与 K8S 交互的正确架构**
+
+**为什么业务系统不能直接调 K8S API?**
+1. **职责混乱**:业务系统需要知道 K8S 集群地址、ServiceAccount 权限、Job YAML 结构——把基础设施细节泄漏到业务层
+2. **强依赖**:K8S 集群迁移、升级,业务系统跟着改
+3. **权限风险**:给业务系统 K8S API 权限,意味着一旦业务系统被攻破,集群暴露
+4. **可观测性差**:任务状态分散在 K8S Event 里,业务侧看不到
+
+---
+
+#### 三种架构模式
+
+根据业务系统的交互方式,有三种成熟方案:
+
+---
+
+##### 模式 A:消息队列 + KEDA ScaledJob(推荐,消息队列场景)
+
+```mermaid
+flowchart LR
+ A["业务系统"] -->|"写消息"| MQ["SQS/Kafka/Redis"]
+ MQ -->|"KEDA 监听"| KEDA["KEDA"]
+ KEDA -->|"自动创建 Job"| TaskPod["任务 Pod"]
+ KEDA -.->|"(K8S 集群内的 KEDA 组件,
业务系统完全无感 K8S)"| note["⚠️"]
+```
+
+> **图 2-6:消息队列 + KEDA ScaledJob 架构**
+
+**KEDA 是 CNCF 项目,本质是 K8S 集群内的一个 Controller**,它盯着外部队列的深度,自动帮你创建/销毁 Job。业务系统只写队列,和 K8S 零耦合。
+
+**安装 KEDA:**
+```bash
+helm repo add kedacore https://kedacore.github.io/charts
+helm install keda kedacore/keda --namespace keda --create-namespace
+```
+
+**ScaledJob 配置(SQS 示例):**
+```yaml
+apiVersion: keda.sh/v1alpha1
+kind: ScaledJob
+metadata:
+ name: heavy-task-scaledjob
+ namespace: default
+spec:
+ # ===== Job 模板 =====
+ jobTargetRef:
+ template:
+ spec:
+ restartPolicy: Never
+ containers:
+ - name: worker
+ image: your-heavy-worker:latest
+ env:
+ - name: AWS_REGION
+ value: "ap-east-1"
+ resources:
+ requests:
+ cpu: "4"
+ memory: "8Gi"
+ limits:
+ cpu: "8"
+ memory: "16Gi"
+
+ # ===== 扩缩策略 =====
+ pollingInterval: 5 # 每 5 秒检查队列
+ maxReplicaCount: 20 # 最大并发 Job 数
+ scalingStrategy:
+ strategy: "accurate" # 队列 N 条消息 → 起 N 个 Job
+
+ # ===== 触发器(SQS)=====
+ triggers:
+ - type: aws-sqs-queue
+ metadata:
+ queueURL: https://sqs.ap-east-1.amazonaws.com/123/task-queue
+ queueLength: "1" # 1 条消息 = 1 个 Job
+ awsRegion: "ap-east-1"
+ authenticationRef:
+ name: keda-aws-creds
+---
+# AWS 认证
+apiVersion: keda.sh/v1alpha1
+kind: TriggerAuthentication
+metadata:
+ name: keda-aws-creds
+spec:
+ secretTargetRef:
+ - parameter: awsAccessKeyID
+ name: aws-secret
+ key: AWS_ACCESS_KEY_ID
+ - parameter: awsSecretAccessKey
+ name: aws-secret
+ key: AWS_SECRET_ACCESS_KEY
+```
+
+**其他常用触发源:**
+```yaml
+# Kafka(按 consumer lag 触发)
+- type: kafka
+ metadata:
+ bootstrapServers: kafka:9092
+ consumerGroup: heavy-task-group
+ topic: heavy-tasks
+ lagThreshold: "1" # lag 每增加 1 → 起 1 个 Job
+
+# Redis List
+- type: redis
+ metadata:
+ address: redis:6379
+ listName: task-queue
+ listLength: "1"
+
+# RabbitMQ
+- type: rabbitmq
+ metadata:
+ host: amqp://user:pass@rabbitmq:5672/
+ queueName: heavy-tasks
+ queueLength: "1"
+```
+
+---
+
+##### 模式 B:Dispatcher 服务(推荐,数据库记录场景)
+
+业务系统只写一行数据库记录(如 `status=pending`),由一个专职的 **Dispatcher 微服务**轮询 DB,发现新任务后调用 K8S API 创建 Job,并负责维护任务状态回写。
+
+```mermaid
+flowchart TB
+ A["业务系统"] -->|"INSERT"| DB["tasks 表 (status=pending)"]
+ DB --> B["Dispatcher 服务(K8S 内的一个 Deployment)"]
+ B --> C1["轮询 DB,发现 pending 任务"]
+ B --> C2["调用 K8S API 创建 Job"]
+ B --> C3["更新 tasks 表 status=running"]
+ B --> C4["监听 Job 完成事件,回写 status=done/failed"]
+ C2 --> J["Job Pod(处理任务)"]
+ J --> DB2["任务完成,向 DB 写入结果"]
+```
+
+> **图 2-7:Dispatcher 服务架构**
+
+**Dispatcher 实现思路(Python 伪代码):**
+```python
+# dispatcher.py —— 作为 K8S Deployment 运行,有 K8S API 权限
+import time
+from kubernetes import client, config
+from db import get_pending_tasks, update_task_status
+
+config.load_incluster_config() # 在 K8S 内自动加载 ServiceAccount 权限
+batch_v1 = client.BatchV1Api()
+
+def dispatch_loop():
+ while True:
+ tasks = get_pending_tasks(limit=10) # 每次取最多 10 个待处理任务
+ for task in tasks:
+ # 防止重复调度:先更新状态,再创建 Job(乐观锁)
+ updated = update_task_status(task.id,
+ old_status="pending",
+ new_status="dispatching")
+ if not updated:
+ continue # 已被其他 Dispatcher 实例抢走,跳过
+
+ job = build_job(task)
+ try:
+ batch_v1.create_namespaced_job("default", job)
+ update_task_status(task.id, "dispatching", "running",
+ job_name=f"task-{task.id}")
+ except Exception as e:
+ update_task_status(task.id, "dispatching", "pending") # 回滚
+
+ time.sleep(3) # 轮询间隔 3 秒
+
+def build_job(task):
+ return client.V1Job(
+ metadata=client.V1ObjectMeta(name=f"task-{task.id}"),
+ spec=client.V1JobSpec(
+ ttl_seconds_after_finished=300,
+ backoff_limit=2,
+ template=client.V1PodTemplateSpec(
+ spec=client.V1PodSpec(
+ restart_policy="Never",
+ containers=[client.V1Container(
+ name="worker",
+ image="your-worker:latest",
+ env=[
+ client.V1EnvVar("TASK_ID", task.id),
+ client.V1EnvVar("DB_URL", task.db_url),
+ ],
+ resources=client.V1ResourceRequirements(
+ requests={"cpu": "2", "memory": "4Gi"},
+ limits={"cpu": "4", "memory": "8Gi"},
+ )
+ )]
+ )
+ )
+ )
+ )
+```
+
+**Dispatcher 的 ServiceAccount 配置(最小权限原则):**
+```yaml
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: task-dispatcher
+ namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: job-creator
+ namespace: default
+rules:
+- apiGroups: ["batch"]
+ resources: ["jobs"]
+ verbs: ["create", "get", "list", "watch", "delete"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: dispatcher-job-creator
+ namespace: default
+subjects:
+- kind: ServiceAccount
+ name: task-dispatcher
+roleRef:
+ kind: Role
+ name: job-creator
+ apiGroup: rbac.authorization.k8s.io
+---
+# Dispatcher Deployment
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: task-dispatcher
+spec:
+ replicas: 1 # 通常单副本避免重复调度(或用选主机制)
+ selector:
+ matchLabels:
+ app: task-dispatcher
+ template:
+ metadata:
+ labels:
+ app: task-dispatcher
+ spec:
+ serviceAccountName: task-dispatcher # ⭐ 绑定最小权限 SA
+ containers:
+ - name: dispatcher
+ image: your-dispatcher:latest
+ resources:
+ requests:
+ cpu: "100m"
+ memory: "128Mi"
+```
+
+> **注意**:Dispatcher 通常只跑一个副本。如果需要高可用多副本,任务状态更新必须使用**乐观锁**(如上方的 `old_status=pending` 条件更新)或**分布式锁**,防止同一任务被多个 Dispatcher 重复调度。
+
+---
+
+##### 模式 C:Argo Workflows(复杂 DAG 工作流场景)
+
+当任务有依赖关系(步骤 A 完成后才能执行步骤 B/C,或者需要分支、并行、重试逻辑),推荐 **Argo Workflows**。
+
+```mermaid
+flowchart LR
+ A["业务系统"] -->|"提交 Workflow 定义"| Argo["Argo Server(K8S 内)"]
+ Argo -->|"按 DAG 顺序创建 Pod"| Pods["Pod"]
+```
+
+> **图 2-8:Argo Workflows 架构**
+
+
+```yaml
+# 示例:视频处理 Workflow(下载 → 转码 → 上传,可并行多路转码)
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+ generateName: video-process-
+spec:
+ entrypoint: video-pipeline
+ templates:
+ - name: video-pipeline
+ dag:
+ tasks:
+ - name: download
+ template: download-step
+ - name: transcode-hd
+ dependencies: [download] # 依赖 download 完成
+ template: transcode-step
+ arguments:
+ parameters: [{name: quality, value: "1080p"}]
+ - name: transcode-sd
+ dependencies: [download] # 和 transcode-hd 并行
+ template: transcode-step
+ arguments:
+ parameters: [{name: quality, value: "480p"}]
+ - name: upload
+ dependencies: [transcode-hd, transcode-sd] # 等两路都完成
+ template: upload-step
+
+ - name: download-step
+ container:
+ image: downloader:latest
+ resources:
+ requests: {cpu: "1", memory: "2Gi"}
+
+ - name: transcode-step
+ inputs:
+ parameters: [{name: quality}]
+ container:
+ image: ffmpeg:latest
+ command: ["ffmpeg", "-i", "input.mp4", "-q:v", "{{inputs.parameters.quality}}", "output.mp4"]
+ resources:
+ requests: {cpu: "4", memory: "8Gi"}
+
+ - name: upload-step
+ container:
+ image: uploader:latest
+```
+
+---
+
+#### 方案选择指南
+
+| 业务系统的动作 | 推荐方案 | 核心优势 |
+|---|---|---|
+| 往 **SQS/Kafka/RabbitMQ/Redis** 写一条消息 | **KEDA ScaledJob** | 零代码调度,K8S 原生,业务完全解耦 |
+| 往 **数据库** 写一行记录(status=pending) | **Dispatcher 服务** | 灵活、可定制,状态双向同步 |
+| 触发**多步骤有依赖**的任务链 | **Argo Workflows** | DAG 编排,可视化,内置重试/超时 |
+| **定时触发**(不需要外部触发) | **CronJob** | K8S 原生,无需额外组件 |
+
+> **架构原则总结**:业务系统只负责**表达意图**(写队列 / 写 DB),调度层负责**如何实现**(什么时候起 Pod、起几个、用多少资源)。这两层的职责必须清晰分离。
+
+---
+
+### 2.10 常用 kubectl 命令速查
+
+```bash
+# 查看资源
+kubectl get pods -n k8s-training
+kubectl get deployment -n k8s-training
+kubectl get all -n k8s-training
+
+# 查看详情
+kubectl describe pod -n k8s-training
+kubectl logs -n k8s-training
+kubectl logs -f -n k8s-training # 实时日志
+
+# 部署
+kubectl apply -f deployment.yaml
+kubectl delete -f deployment.yaml
+
+# 扩缩
+kubectl scale deployment nginx --replicas=5 -n k8s-training
+
+# 滚动更新
+kubectl set image deployment/nginx nginx=nginx:1.26 -n k8s-training
+
+# 回滚
+kubectl rollout status deployment/nginx -n k8s-training # 查看更新状态
+kubectl rollout history deployment/nginx -n k8s-training # 查看历史
+kubectl rollout undo deployment/nginx -n k8s-training # 回滚到上一版本
+
+# 进入容器
+kubectl exec -it -- /bin/bash -n k8s-training
+
+# 端口转发(调试用)
+kubectl port-forward svc/nginx 8080:80 -n k8s-training
+```
+
+---
+
+## 📝 习题
+
+### 选择题
+
+**Q1.** Pod 中多个容器共享什么?
+- A) CPU 和内存
+- B) 网络命名空间和存储卷
+- C) 环境变量
+- D) 以上全部
+
+**Q2.** livenessProbe 和 readinessProbe 的区别是?
+- A) 没有区别
+- B) livenessProbe 失败重启容器,readinessProbe 失败移除流量
+- C) readinessProbe 失败重启容器,livenessProbe 失败移除流量
+- D) 只能用 HTTP 探测方式
+
+**Q3.** 以下哪个适合使用 StatefulSet 部署?
+- A) Nginx Web 服务
+- B) Spring Boot API
+- C) MySQL 主从集群
+- D) 日志收集 Agent
+
+**Q4.** Deployment 的 `maxSurge: 1, maxUnavailable: 0` 含义是?
+- A) 最多多出1个 Pod,最多0个不可用
+- B) 最多多出1个不可用的 Pod
+- C) 每次只更新1个 Pod
+- D) 更新时不允许任何 Pod 停止
+
+**Q5.** 资源限制中 `requests` 和 `limits` 的区别?
+- A) 没有区别
+- B) requests 是调度依据,limits 是运行时硬限制
+- C) requests 是硬限制,limits 是调度依据
+- D) requests 用于 CPU,limits 用于内存
+
+### 简答题
+
+**Q6.** Deployment YAML 中的 `selector.matchLabels` 和 `template.metadata.labels` 是什么关系?如果不匹配会怎样?
+
+**Q7.** 如果你在集群中手动创建了一个标签为 `app: nginx` 的独立 Pod,它会不会被某个 Deployment 管理?为什么?
+
+**Q8.** 请解释 DaemonSet 的使用场景,并举例说明。
+
+**Q9.** 请描述 `kubectl rollout undo` 回滚的过程。
+
+**Q10.** 直接创建 Pod(`kind: Pod`)和通过 Deployment 创建 Pod 有什么区别?分别适用于什么场景?
+
+**Q11(⭐ 高频)** Job 中 `restartPolicy` 为什么必须设为 `Never` 或 `OnFailure`?`backoffLimit` 和 `activeDeadlineSeconds` 分别控制什么?
+
+**Q12(⭐⭐ 架构题)** 业务系统有一个 heavy 任务,每个任务需要独立容器运行,CPU/内存很高。有人说"用 HPA 扩容就行了",这个说法对吗?为什么?正确的做法是什么?
+
+**Q13(⭐ 架构题)** 业务系统发起任务时,应该直接调用 K8S API 创建 Job 吗?如果不应该,中间应该有什么层?分别适用于什么场景(消息队列场景 vs 数据库记录场景)?
+
+### 动手题
+
+**Q11.** 请编写一个 Deployment YAML,要求:
+- 3 副本
+- 镜像:`nginx:1.25`
+- 资源限制:CPU 100m~500m,内存 64Mi~256Mi
+- HTTP 存活探针:路径 `/`,端口 80
+- 标签:`app=nginx, tier=frontend`
+
+---
+
+## ✅ 参考答案
+
+### A1. B) 网络命名空间和存储卷
+同一 Pod 内的容器共享网络命名空间(相同 IP,通过 localhost 通信)和挂载的存储卷。CPU/内存是各自配置的。
+
+### A2. B) livenessProbe 失败重启容器,readinessProbe 失败移除流量
+- **livenessProbe**:容器"死了" → K8S 重启它
+- **readinessProbe**:容器"还没准备好" → 从 Service 负载均衡中暂时移除,流量不会打到它
+
+### A3. C) MySQL 主从集群
+StatefulSet 提供稳定的网络标识(Pod 名称不变)和稳定的持久存储,适合有状态应用。Nginx/Spring Boot 用 Deployment,日志 Agent 用 DaemonSet。
+
+### A4. A) 最多多出1个 Pod,最多0个不可用
+`maxSurge: 1` = 更新过程中最多比期望多1个 Pod;`maxUnavailable: 0` = 更新过程中不允许任何 Pod 不可用。这是最保守的更新策略,保证零停机。
+
+### A5. B) requests 是调度依据,limits 是运行时硬限制
+- **requests**:Scheduler 调度 Pod 时参考,确保 Node 有足够剩余资源
+- **limits**:运行时硬限制,CPU 超限会限流(throttle),内存超限会 OOM Kill
+
+### A6. selector.matchLabels 和 template.metadata.labels 的关系
+
+**必须匹配!** 这是 Deployment 工作机制的核心:
+
+```
+Deployment Controller 的工作循环:
+1. 用 selector.matchLabels 去 K8S 里找所有匹配的 Pod
+2. 数一下找到了几个
+3. 如果少于 replicas → 用 template 创建新的 Pod(新 Pod 会自动打上 template.metadata.labels)
+4. 如果多于 replicas → 删除多余的 Pod
+5. 重复
+
+如果 selector 和 template.labels 不匹配:
+ → selector 说"我要找 app=nginx 的 Pod"
+ → template 创建出来的 Pod 标签却是 app=web
+ → Controller 找不到自己创建的 Pod(标签不匹配)
+ → Controller 觉得"0 个 Pod,我想要 3 个"→ 再创建 3 个
+ → 新的 3 个也不匹配 → 再创建 → 无限循环!
+ → 所以 K8S API Server 直接在校验阶段拒绝这种配置
+```
+
+**简单记:selector 是"找人的条件",template.labels 是"新人身上的标签"。条件和新人的标签必须一致,否则 Deployment 认不出自己创建的 Pod。**
+
+### A7. 手动创建的 Pod 会不会被 Deployment 管理?
+
+**会被!** 但不推荐这样做。
+
+```
+Deployment: selector = {app: nginx}, replicas = 3
+你手动: kubectl apply -f pod.yaml (labels: {app: nginx})
+
+结果:
+ Deployment Controller 发现集群中有 4 个 app=nginx 的 Pod
+ 但 replicas 只要求 3 个
+ → Deployment 会删除"多余的"那个(可能是你手动创建的)
+ → 你手动创建的 Pod 随时可能被 Deployment 清理掉!
+```
+
+**结论**:不要在 Deployment 管理的范围内手动创建标签匹配的 Pod,它会被当作"多余的副本"删掉。
+
+### A8. DaemonSet 使用场景
+DaemonSet 确保**每个(或特定的)Node 上都运行一个 Pod 副本**。常见场景:
+- **日志收集**:Fluentd/Filebeat,每个节点收集日志
+- **监控**:Prometheus Node Exporter,每个节点采集指标
+- **网络插件**:Calico/Flannel 的 Agent
+- **存储守护进程**:每个节点上的存储守护进程
+
+新节点加入集群时,DaemonSet 自动在新节点上创建 Pod。
+
+### A9. kubectl rollout undo 回滚过程
+```
+1. K8S 维护 Deployment 的 ControllerRevision(历史版本记录)
+2. kubectl rollout undo 时,Controller 将旧版本的 Pod 模板恢复
+3. 创建新的 ReplicaSet 使用旧版本模板
+4. 滚动更新机制将当前 Pod 逐步替换为旧版本
+5. 回滚过程和更新过程一样是渐进的
+
+# 回滚到指定版本
+kubectl rollout undo deployment/nginx --to-revision=2
+```
+
+### A10. 独立 Pod vs Deployment 管理 Pod
+
+| 对比项 | 独立 Pod (`kind: Pod`) | Deployment 管理 Pod |
+|--------|----------------------|-------------------|
+| Pod 挂了 | 没人管,永久丢失 | 自动重建新 Pod |
+| 副本数 | 永远只有 1 个 | replicas 控制,随意扩缩 |
+| 更新 | 删了重建,有停机时间 | 滚动更新,零停机 |
+| 你需要写 | 完整的 Pod YAML | Deployment YAML(内含 template) |
+| 生命周期 | **你自己管** | **K8S 管你** |
+| 适用场景 | 临时调试、一次性任务、CI 测试 | 生产部署的所有无状态应用 |
+
+### A11. Job 的 restartPolicy 和控制字段
+
+**为什么必须设 `Never` 或 `OnFailure`**:
+Deployment 的默认 `restartPolicy: Always` 意味着"容器退出就重启",这对长期服务是对的。但 Job 的容器正常退出(exit code 0)代表任务完成,不应该被重启。如果用 `Always`,任务完成后会被反复重启,变成死循环。
+
+- `restartPolicy: Never`:失败时起新 Pod(日志保留在失败 Pod 上,便于排查)
+- `restartPolicy: OnFailure`:失败时在原 Pod 重启容器(节省资源)
+
+**`backoffLimit` vs `activeDeadlineSeconds`**:
+| 字段 | 含义 | 区别 |
+|------|------|------|
+| `backoffLimit: 3` | 最多失败重试 3 次,超过则 Job 标为 Failed | 按**次数**限制 |
+| `activeDeadlineSeconds: 600` | Job 整体超时 600 秒,超时则强制终止 | 按**时间**限制 |
+
+两者可以同时设置,任意一个触发都会终止 Job。
+
+### A12. HPA 能解决"一任务一容器"问题吗?
+
+**不能,这是两个完全不同的维度。**
+
+HPA 的本质是"扩 Deployment 的副本数"。假设你有 3 个 Worker Pod,CPU 高了 HPA 扩到 6 个——这 6 个 Pod 仍然是一个 Worker Pool,竞争同一个队列,没有任何任务隔离。
+
+| | HPA | 任务级伸缩(Job per task)|
+|--|-----|---|
+| 扩容结果 | 更多 Worker Pod | 更多独立 Job |
+| 任务隔离 | 无(多任务共享 Pod) | 完全隔离(1任务1Pod)|
+| 触发条件 | CPU/内存使用率 | 队列消息数 / DB pending 记录数 |
+| Pod 退出 | 异常(会被重建) | 正常(任务完成标志)|
+
+**正确方案**:用 `Job`(而非 Deployment)承载任务,每个任务创建一个独立的 Job。
+
+### A13. 业务系统应该直接调 K8S API 吗?
+
+**不应该。** 原因:
+1. 职责混乱:业务代码需要知道 K8S 集群地址、Job YAML 格式、ServiceAccount 权限
+2. 强依赖:集群迁移时业务系统一起改
+3. 权限风险:业务系统被攻破则集群暴露
+
+**正确做法**:在业务系统和 K8S 之间加一个**调度层**,业务系统只表达意图。
+
+| 业务系统的动作 | 调度层方案 |
+|---|---|
+| 往 **MQ(SQS/Kafka)** 写消息 | **KEDA ScaledJob**:监听队列,自动为每条消息创建 Job,业务完全无感知 K8S |
+| 往 **数据库** 写 pending 记录 | **Dispatcher 服务**:专职微服务轮询 DB,发现 pending 任务后调 K8S API 创建 Job,并负责状态回写 |
+| 复杂多步骤工作流 | **Argo Workflows**:业务提交 Workflow 定义,引擎负责按 DAG 调度 Pod |
+
+架构原则:**业务系统表达意图(写队列/写DB),调度层决定如何实现(何时起Pod、起几个、资源多少)。**
+
+### A14. Deployment YAML(原 A11)
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: nginx-deployment
+ namespace: k8s-training
+ labels: # ① Deployment 自身的标签(不影响 Pod 选择)
+ app: nginx
+ tier: frontend
+spec:
+ replicas: 3 # 要维护 3 个 Pod 副本
+ selector: # ② 选择器:用标签找"我管的 Pod"
+ matchLabels:
+ app: nginx # 找标签包含 app=nginx 的 Pod
+ tier: frontend
+ strategy:
+ type: RollingUpdate
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 0
+ template: # ③⭐ Pod 模板:这就是"隐含的 Pod 定义"
+ metadata:
+ labels: # ④ 新 Pod 的标签(必须和 ② selector 匹配!)
+ app: nginx
+ tier: frontend # Deployment 用这个标签来认领自己创建的 Pod
+ spec: # ⑤ 下面就是 Pod 的 spec,和独立 Pod YAML 的 spec 一模一样
+ containers:
+ - name: nginx
+ image: nginx:1.25
+ ports:
+ - containerPort: 80
+ resources:
+ requests:
+ cpu: "100m"
+ memory: "64Mi"
+ limits:
+ cpu: "500m"
+ memory: "256Mi"
+ livenessProbe:
+ httpGet:
+ path: /
+ port: 80
+ initialDelaySeconds: 10
+ periodSeconds: 5
+# 部署后,kubectl get pods 会看到 3 个自动创建的 Pod:
+# nginx-deployment-abc1234-5xyz1
+# nginx-deployment-abc1234-5xyz2
+# nginx-deployment-abc1234-5xyz3
+# 每个都自动带有 labels: {app: nginx, tier: frontend}
+```
+
+---
+
+## 🔧 实操练习
+
+```bash
+export KUBECONFIG=~/.kube/config
+kubectl create namespace k8s-training
+
+# 部署 Nginx Deployment
+kubectl apply -f - <10.244.1.5"] <-->|"直接通信"| CNI["CNI 插件
(Calico等)"]
+ CNI <--> PodB["Pod B
10.244.2.3"]
+
+ note1["K8S 网络三大要求:
1. 所有 Pod 之间可以直接通信(不经过 NAT)
2. 所有 Node 和所有 Pod 之间可以直接通信
3. Pod 看到自己的 IP 和其他 Pod 的 IP 是一样的"]
+```
+
+> **图 3-1:K8S 网络模型**
+
+### 3.2 为什么需要 Service?
+
+**问题**:Pod IP 是不稳定的!
+- Pod 每次重建 IP 都会变
+- 水平扩缩时 Pod 数量会变
+- 客户端不可能记住一堆会变的 IP
+
+**解决**:Service 提供稳定的 VIP(虚拟 IP)和 DNS 名称
+
+```mermaid
+flowchart TB
+ SVC["Service
ClusterIP
10.0.0.100"] -->|"负载均衡"| P1["Pod 1
.1.5"]
+ SVC --> P2["Pod 2
.2.3"]
+ SVC --> P3["Pod 3
.2.4"]
+
+ note1["Pod 3 挂了?Service 自动从 Endpoints 移除
新 Pod 加入?Service 自动加入 Endpoints"]
+```
+
+> **图 3-2:Service 工作原理**
+
+### 3.3 Service 四种类型(面试必背)
+
+| 类型 | 说明 | 使用场景 |
+|------|------|---------|
+| **ClusterIP** | 默认值,仅集群内部可访问 | 内部服务通信(微服务间) |
+| **NodePort** | 每个节点开放一个端口 | 开发测试、简单外部访问 |
+| **LoadBalancer** | 云厂商创建外部负载均衡 | 生产环境对外暴露服务 |
+| **ExternalName** | DNS 级别的 CNAME 映射 | 引用外部服务(如 RDS) |
+
+#### ClusterIP Service(最常用)
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: nginx-service
+ namespace: k8s-training
+spec:
+ type: ClusterIP # 默认值
+ selector: # 通过 label 选择后端 Pod
+ app: nginx
+ ports:
+ - port: 80 # Service 端口
+ targetPort: 80 # Pod 端口
+ protocol: TCP
+```
+
+```bash
+# 集群内部访问方式:
+# 1. Service IP
+curl http://10.0.0.100:80
+
+# 2. DNS(推荐)⚡
+curl http://nginx-service.k8s-training.svc.cluster.local
+
+# 3. 同 namespace 内可简写
+curl http://nginx-service
+```
+
+#### NodePort Service
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: nginx-nodeport
+spec:
+ type: NodePort
+ selector:
+ app: nginx
+ ports:
+ - port: 80 # Service 端口
+ targetPort: 80 # Pod 端口
+ nodePort: 30080 # 节点端口(30000-32767)
+```
+
+```
+外部请求 → NodeIP:30080 → kube-proxy(iptables) → Pod:80
+```
+
+#### LoadBalancer Service
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: nginx-lb
+spec:
+ type: LoadBalancer
+ selector:
+ app: nginx
+ ports:
+ - port: 80
+ targetPort: 80
+```
+
+> 云厂商(AWS/GCP/阿里云)会自动创建一个 ELB/SLB 并分配公网 IP。Sealos 环境也支持。
+
+### 3.4 DNS 服务发现(面试高频)
+
+K8S 内置 DNS 服务(CoreDNS),自动为 Service 创建 DNS 记录:
+
+```
+完整 FQDN 格式:
+..svc.cluster.local
+
+示例:
+nginx-service.k8s-training.svc.cluster.local
+
+简写规则:
+- 同 namespace: nginx-service
+- 跨 namespace: nginx-service.k8s-training
+- 完整: nginx-service.k8s-training.svc.cluster.local
+```
+
+**面试题**:Pod 内 `nginx-service` 是如何解析到 Service ClusterIP 的?
+> Pod 的 `/etc/resolv.conf` 中配置了 `nameserver` 指向 CoreDNS Service IP,`search` 配置了 namespace 和 `svc.cluster.local`。所以 `nginx-service` 会先尝试 `nginx-service.k8s-training.svc.cluster.local`。
+
+### 3.5 Headless Service
+
+将 `clusterIP: None`,DNS 直接解析到 Pod IP:
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: mysql-headless
+spec:
+ clusterIP: None # Headless!
+ selector:
+ app: mysql
+ ports:
+ - port: 3306
+```
+
+```
+# 普通 Service DNS 解析:
+mysql-service → 10.0.0.100 (ClusterIP)
+
+# Headless Service DNS 解析:
+mysql-headless → 10.244.1.5, 10.244.2.3, 10.244.2.4 (直接是 Pod IP 列表)
+```
+
+**使用场景**:StatefulSet 需要每个 Pod 有独立的 DNS 记录:
+```
+mysql-0.mysql-headless.namespace.svc.cluster.local → 10.244.1.5
+mysql-1.mysql-headless.namespace.svc.cluster.local → 10.244.2.3
+```
+
+### 3.6 Ingress(7层路由)
+
+Service 是 4层(TCP/UDP),Ingress 是 7层(HTTP/HTTPS)。
+
+Ingress 后端的 Service 一般使用 ClusterIP,因为 Ingress Controller 运行在集群内部,通过 ClusterIP 访问后端 Pod,而 NodePort 或 LoadBalancer 是用来暴露 Ingress Controller 本身的。
+
+在生产环境中,Ingress Controller 通常通过 LoadBalancer 类型的 Service 暴露,云厂商会提供外部 IP,将外部流量引入集群,再由 Ingress Controller 根据 Ingress 规则转发到后端 ClusterIP Service。
+
+```mermaid
+flowchart TB
+ User["互联网用户"] -->|"请求"| Ingress["Ingress
Controller
(Nginx/Traefik)"]
+
+ Ingress -->|"HTTP 路由规则"| Rules["Ingress 资源
host: api.example.com → api-svc
host: www.example.com → web-svc
/api/* → api-svc
/* → web-svc"]
+
+ Rules --> apiSVC["api-svc
ClusterIP"]
+ Rules --> webSVC["web-svc
ClusterIP"]
+```
+
+> **图 3-3:Ingress 工作原理**
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: app-ingress
+ namespace: k8s-training
+ annotations:
+ nginx.ingress.kubernetes.io/rewrite-target: /
+spec:
+ ingressClassName: nginx # 指定 Ingress Controller
+ rules:
+ - host: app.example.com # 域名
+ http:
+ paths:
+ - path: /api
+ pathType: Prefix
+ backend:
+ service:
+ name: api-service # 得先建 service 一般是clusterip,也可以是其他的,但是没必要
+ port:
+ number: 80
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: web-service
+ port:
+ number: 80
+ tls: # HTTPS 配置
+ - hosts:
+ - app.example.com
+ secretName: tls-secret
+```
+
+**Ingress vs Service LoadBalancer(面试对比)**:
+| 对比项 | LoadBalancer Service | Ingress |
+|--------|---------------------|---------|
+| 层级 | 4层(TCP) | 7层(HTTP) |
+| 成本 | 每个 Service 一个 LB | 多个 Service 共享一个 LB |
+| 路由能力 | 无 | 基于域名、路径路由 |
+| TLS | 自己处理 | 统一管理证书 |
+
+### 3.7 NetworkPolicy(网络策略)
+
+默认 K8S 中所有 Pod 之间网络是互通的。NetworkPolicy 可以限制流量:
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: api-policy
+ namespace: k8s-training
+spec:
+ podSelector:
+ matchLabels:
+ app: api
+ policyTypes:
+ - Ingress
+ ingress:
+ - from:
+ - podSelector:
+ matchLabels:
+ app: web # 只允许 label=web 的 Pod 访问
+ ports:
+ - port: 8080
+```
+
+---
+
+## 📝 习题
+
+### 选择题
+
+**Q1.** K8S 中 Service 的默认类型是?
+- A) NodePort
+- B) LoadBalancer
+- C) ClusterIP
+- D) ExternalName
+
+**Q2.** 同一 namespace 下的 Pod 如何通过 DNS 访问名为 `user-service` 的 Service?
+- A) user-service.k8s-training.svc.cluster.local
+- B) user-service
+- C) A 和 B 都可以
+- D) 需要直接用 Service IP
+
+**Q3.** NodePort 端口的默认范围是?
+- A) 1-1024
+- B) 3000-4000
+- C) 30000-32767
+- D) 80-443
+
+**Q4.** Headless Service 的 clusterIP 设置为什么?
+- A) 0.0.0.0
+- B) None
+- C) 空
+- D) 127.0.0.1
+
+**Q5.** Ingress 工作在哪一层?
+- A) 第2层(数据链路层)
+- B) 第3层(网络层)
+- C) 第4层(传输层)
+- D) 第7层(应用层)
+
+### 简答题
+
+**Q6.** Service 和 Pod 之间是怎么关联的?Service 如何感知 Pod 的变化?
+
+**Q7.** 请解释 Ingress 和 Service 的关系。有了 Service 为什么还需要 Ingress?
+
+**Q8.** 什么场景下需要用 Headless Service?
+
+### 动手题
+
+**Q9.** 请为上一章的 Nginx Deployment 创建:
+1. 一个 ClusterIP Service
+2. 一个 NodePort Service
+3. 通过端口转发测试 Service 是否正常工作
+
+---
+
+## ✅ 参考答案
+
+### A1. C) ClusterIP
+不指定 type 时默认是 ClusterIP,只能在集群内部访问。
+
+### A2. C) A 和 B 都可以
+同 namespace 下可以用短名称 `user-service`,K8S DNS 会自动补全。
+
+### A3. C) 30000-32767
+NodePort 默认范围是 30000-32767,可以通过 kube-apiserver 的 `--service-node-port-range` 参数修改。
+
+### A4. B) None
+`clusterIP: None` 表示 Headless Service,DNS 直接返回 Pod IP 而不是 ClusterIP。
+
+### A5. D) 第7层(应用层)
+Ingress 工作在 HTTP/HTTPS 层,可以基于域名和路径做路由。
+
+### A6. Service 与 Pod 的关联
+```
+1. Service 创建时定义 selector(标签选择器)
+2. K8S Endpoints Controller 持续监控匹配 selector 的 Pod
+3. 将匹配的 Pod IP:Port 写入 Service 的 Endpoints 对象
+4. kube-proxy 监听 Endpoints 变化,更新 iptables/IPVS 规则
+5. 流量到达 Service VIP 时,由 iptables/IPVS 负载均衡到 Endpoints 中的 Pod
+
+# 查看自动关联的 Endpoints
+kubectl get endpoints nginx-service
+```
+
+### A7. Ingress 和 Service 的关系
+**Service 是 4 层负载均衡**:只按 IP:Port 转发,不知道 HTTP 域名和路径。
+**Ingress 是 7 层路由**:
+- 一个 Ingress 可以根据域名/路径路由到多个 Service
+- 统一管理 TLS 证书
+- 共享一个外部 IP(而不是每个 Service 一个 LoadBalancer)
+
+简单说:**Service 是内部负载均衡,Ingress 是外部流量入口的路由器**。
+
+### A8. Headless Service 使用场景
+1. **StatefulSet**:需要每个 Pod 有稳定、独立的 DNS 记录(如 `mysql-0.mysql`)
+2. **自行负载均衡**:客户端自己决定连接哪个 Pod(如自定义负载均衡算法)
+3. **直接 Pod 发现**:需要知道每个 Pod 的具体 IP
+
+### A9. Service YAML + 测试
+
+```yaml
+# ClusterIP Service
+apiVersion: v1
+kind: Service
+metadata:
+ name: nginx-clusterip
+ namespace: k8s-training
+spec:
+ type: ClusterIP
+ selector:
+ app: nginx
+ ports:
+ - port: 80
+ targetPort: 80
+
+---
+# NodePort Service
+apiVersion: v1
+kind: Service
+metadata:
+ name: nginx-nodeport
+ namespace: k8s-training
+spec:
+ type: NodePort
+ selector:
+ app: nginx
+ ports:
+ - port: 80
+ targetPort: 80
+ nodePort: 30080
+```
+
+```bash
+# 测试
+kubectl port-forward svc/nginx-clusterip 8080:80 -n k8s-training
+# 然后另一个终端:
+curl http://localhost:8080
+```
+
+---
+
+## 🔧 实操练习
+
+```bash
+export KUBECONFIG=~/.kube/config
+kubectl create namespace k8s-training
+
+# 1. 部署应用
+kubectl apply -f - < 1. Secret 存储的值是 **base64 编码**(不是加密!只是编码)
+> 2. Secret 在传输和存储时可以启用 **加密 at rest**(etcd 加密)
+> 3. Secret 访问需要 **RBAC 权限控制**
+> 4. Secret 有大小限制(1MB)
+> 5. 生产环境建议使用 **External Secrets Operator** 或 Vault
+
+#### Secret YAML
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ name: app-secret
+ namespace: k8s-training
+type: Opaque # 通用类型
+data:
+ # ⚠️ data 字段的值必须是 base64 编码
+ username: YWRtaW4= # echo -n "admin" | base64
+ password: cEBzc3cwcmQ= # echo -n "p@ssw0rd" | base64
+---
+# stringData 字段可以直接写明文(K8S 自动编码)
+apiVersion: v1
+kind: Secret
+metadata:
+ name: app-secret
+ namespace: k8s-training
+type: Opaque
+stringData: # ⭐ 明文,方便使用
+ username: admin
+ password: p@ssw0rd
+```
+
+#### Secret 常见类型
+
+| 类型 | 用途 |
+|------|------|
+| `Opaque` | 通用(密码、Key) |
+| `kubernetes.io/dockerconfigjson` | Docker 镜像仓库凭证 |
+| `kubernetes.io/tls` | TLS 证书 |
+| `kubernetes.io/basic-auth` | Basic Auth |
+
+#### Docker 镜像仓库凭证(拉私有镜像必备)
+
+```bash
+# 创建镜像仓库凭证
+kubectl create secret docker-registry regcred \
+ --docker-server=registry.example.com \
+ --docker-username=admin \
+ --docker-password=pass123 \
+ -n k8s-training
+
+# 在 Pod 中引用
+spec:
+ imagePullSecrets:
+ - name: regcred
+ containers:
+ - name: app
+ image: registry.example.com/myapp:v1
+```
+
+### 4.4 PV / PVC 存储模型
+
+**为什么需要 PV/PVC?**
+- Pod 是临时的,重建后数据丢失
+- 需要持久化存储(数据库、文件上传等)
+- 不同云厂商/存储类型差异大,需要抽象
+
+```
+管理员创建 PV ──┐
+ ├──→ 用户创建 PVC 绑定 ──→ Pod 使用 PVC
+存储类供应 PV ──┘
+```
+
+#### PersistentVolume(PV)
+
+```yaml
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ name: mysql-pv
+spec:
+ capacity:
+ storage: 10Gi
+ accessModes:
+ - ReadWriteOnce # 单节点读写
+ persistentVolumeReclaimPolicy: Retain
+ storageClassName: manual # 定义了一个名称
+ # hostPath 是最简单的,生产用 NFS/Ceph/云盘
+ hostPath:
+ path: /data/mysql
+ type: DirectoryOrCreate
+```
+
+#### PersistentVolumeClaim(PVC)
+
+```yaml
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: mysql-pvc
+ namespace: k8s-training
+spec:
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 5Gi # 申请 5Gi
+ storageClassName: manual # 和上面定义的名称符合
+```
+
+#### Pod 挂载 PVC
+
+```yaml
+spec:
+ containers:
+ - name: mysql
+ image: mysql:8.0
+ volumeMounts:
+ - name: mysql-data
+ mountPath: /var/lib/mysql # MySQL 数据目录
+ volumes:
+ - name: mysql-data
+ persistentVolumeClaim:
+ claimName: mysql-pvc # 引用 PVC
+```
+
+#### accessModes 类型
+
+| 模式 | 缩写 | 说明 |
+|------|------|------|
+| ReadWriteOnce | RWO | 单节点读写(最常用) |
+| ReadOnlyMany | ROX | 多节点只读 |
+| ReadWriteMany | RWX | 多节点读写(如 NFS) |
+
+#### 回收策略
+
+| 策略 | 说明 |
+|------|------|
+| Retain | PVC 删除后保留数据,需手动清理 |
+| Delete | PVC 删除时自动删除 PV 和数据 |
+| Recycle | 已废弃,执行 rm -rf |
+
+### 4.5 StorageClass(动态供应)
+
+生产环境推荐使用 StorageClass,自动创建 PV:
+
+```yaml
+apiVersion: storage.k8s.io/v1
+kind: StorageClass
+metadata:
+ name: fast-ssd
+provisioner: kubernetes.io/aws-ebs # 云厂商 provisioner
+parameters:
+ type: gp3
+ fsType: ext4
+reclaimPolicy: Delete
+volumeBindingMode: WaitForFirstConsumer # 延迟绑定,调度后再创建
+```
+
+```yaml
+# PVC 指定 storageClassName,自动创建 PV
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: dynamic-pvc
+spec:
+ accessModes:
+ - ReadWriteOnce
+ storageClassName: fast-ssd # ⭐ 使用 StorageClass
+ resources:
+ requests:
+ storage: 20Gi
+```
+
+### 4.6 emptyDir 和 hostPath
+
+```yaml
+# emptyDir:Pod 临时存储,Pod 删除后数据丢失
+# 用途:容器间共享数据、缓存
+volumes:
+- name: cache
+ emptyDir: {}
+
+# hostPath:挂载 Node 本地目录
+# 用途:开发测试,生产不推荐(Pod 漂移到其他 Node 数据丢失)
+volumes:
+- name: host-data
+ hostPath:
+ path: /var/data
+ type: DirectoryOrCreate
+```
+
+---
+
+## 📝 习题
+
+### 选择题
+
+**Q1.** ConfigMap 中 data 字段的值是什么格式?
+- A) 明文
+- B) base64 编码
+- C) AES 加密
+- D) MD5 哈希
+
+**Q2.** Secret 中存储的密码是安全的吗?
+
+- A) 是,Secret 自动加密
+- B) 不是,默认只 base64 编码,需要额外配置 etcd 加密
+- C) Secret 不存储敏感信息
+- D) 取决于 Pod 的权限
+
+**Q3.** PV 的 accessModes 中 `ReadWriteOnce` 表示?
+- A) 多个节点可以读写
+- B) 只有一个节点可以读写
+- C) 所有节点只读
+- D) 只能写一次
+
+**Q4.** Pod 删除后,哪种 Volume 的数据会保留?
+- A) emptyDir
+- B) hostPath
+- C) persistentVolumeClaim
+- D) B 和 C 都可能
+
+**Q5.** 动态存储供应需要什么?
+- A) 手动创建 PV
+- B) StorageClass + Storage Provisioner
+- C) ConfigMap
+- D) Secret
+
+### 简答题
+
+**Q6.** 请比较 ConfigMap 和 Secret 的区别。
+
+**Q7.** 请解释 PV、PVC、StorageClass 三者的关系。
+
+**Q8.** 为什么生产环境不建议使用 hostPath?
+
+---
+
+## ✅ 参考答案
+
+### A1. A) 明文
+ConfigMap 的 data 字段直接存明文字符串。而 Secret 的 data 字段是 base64 编码。
+
+### A2. B) 不是,默认只 base64 编码
+base64 不是加密,只是编码,任何人都能解码。要安全需要:
+1. 配置 etcd 加密(EncryptionConfiguration)
+2. 启用 RBAC 限制 Secret 访问
+3. 考虑使用 Vault/External Secrets
+
+### A3. B) 只有一个节点可以读写
+ReadWriteOnce = RWO = 单节点读写。这是最常见的模式。
+
+### A4. D) B 和 C 都可能
+- emptyDir:Pod 删除数据丢失 ❌
+- hostPath:数据在 Node 本地,Pod 删除但文件还在 ✅
+- PVC:数据在持久化存储中,Pod 删除不影响 ✅
+
+### A5. B) StorageClass + Storage Provisioner
+StorageClass 定义存储类型,Provisioner 负责自动创建 PV。PVC 指定 storageClassName 后自动完成供应。
+
+### A6. ConfigMap vs Secret
+
+| 对比项 | ConfigMap | Secret |
+|--------|-----------|--------|
+| 存储内容 | 非敏感配置 | 敏感信息 |
+| 数据格式 | 明文 | base64 编码 |
+| 大小限制 | 1MB | 1MB |
+| 安全性 | 普通 RBAC | 需要更严格的 RBAC |
+| etcd 加密 | 通常不需要 | 建议启用 |
+| 使用方式 | 环境变量/文件 | 环境变量/文件/imagePullSecrets |
+| 生产建议 | 可以 | 用 Vault/External Secrets |
+
+### A7. PV、PVC、StorageClass 的关系
+
+```
+StorageClass:存储"类型"的模板(如 SSD、HDD)
+ ↓ 定义
+Provisioner:自动创建 PV 的插件
+ ↓ 创建
+PersistentVolume(PV):集群级别的存储资源(类似"磁盘")
+ ↓ 被绑定
+PersistentVolumeClaim(PVC):用户对存储的请求(类似"申请单")
+ ↓ 被使用
+Pod:通过 volumeMounts 挂载 PVC
+
+静态供应:管理员手动创建 PV → PVC 绑定 PV
+动态供应:PVC 指定 StorageClass → Provisioner 自动创建 PV
+```
+
+### A8. 不建议 hostPath 的原因
+1. **数据不安全**:Node 故障时数据丢失
+2. **调度问题**:Pod 漂移到其他 Node 后找不到原来的数据
+3. **安全风险**:可以访问宿主机文件系统
+4. **隔离性差**:多 Pod 可能冲突
+5. **不适合集群环境**:违反了 Pod 的可移植性
+
+---
+
+## 🔧 实操练习
+
+```bash
+export KUBECONFIG=~/.kube/config
+kubectl create namespace k8s-training
+
+# 1. 创建 ConfigMap
+kubectl create configmap app-config \
+ --from-literal=APP_ENV=production \
+ --from-literal=LOG_LEVEL=info \
+ -n k8s-training
+
+# 2. 创建 Secret
+kubectl create secret generic app-secret \
+ --from-literal=username=admin \
+ --from-literal=password=MySecret123 \
+ -n k8s-training
+
+# 3. 查看创建的资源
+kubectl get configmap,secret -n k8s-training
+
+# 4. 查看 Secret 的值(解码)
+kubectl get secret app-secret -o jsonpath='{.data.password}' -n k8s-training | base64 -d
+echo
+
+# 5. 创建使用 ConfigMap 和 Secret 的 Pod
+kubectl apply -f - <
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ include "myapp.fullname" . }}
+ labels:
+ {{- include "myapp.labels" . | nindent 4 }}
+spec:
+ replicas: {{ .Values.replicaCount }}
+ selector:
+ matchLabels:
+ {{- include "myapp.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ labels:
+ {{- include "myapp.selectorLabels" . | nindent 8 }}
+ spec:
+ containers:
+ - name: {{ .Chart.Name }}
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ ports:
+ - containerPort: 80
+ env:
+ {{- range $key, $val := .Values.config }}
+ - name: {{ $key }}
+ value: {{ $val | quote }}
+ {{- end }}
+ resources:
+ {{- toYaml .Values.resources | nindent 10 }}
+```
+
+**模板语法要点**:
+
+```go
+{{ .Values.xxx }} # 引用 values.yaml 的值
+{{ .Chart.Name }} # 引用 Chart 元数据
+{{ include "func" . }} # 调用 _helpers.tpl 中定义的函数
+{{- ... | nindent 4 }} # 缩进4格
+{{- range $key, $val := . }} # 循环
+{{- if .Values.xxx }} # 条件判断
+```
+
+### 5.6 Helm 常用命令
+
+```bash
+# === 仓库管理 ===
+helm repo add bitnami https://charts.bitnami.com/bitnami
+helm repo update
+helm search repo nginx
+
+# === 创建 Chart ===
+helm create myapp # 生成标准 Chart 结构
+
+# === 安装/部署 ===
+# 使用默认值
+helm install myapp ./myapp -n k8s-training
+
+# 使用自定义 values
+helm install myapp ./myapp -n k8s-training \
+ -f values-prod.yaml
+
+# 覆盖单个值
+helm install myapp ./myapp -n k8s-training \
+ --set replicaCount=3 \
+ --set image.tag=1.26
+
+# === 查看 ===
+helm list -n k8s-training # 查看已安装的 Release
+helm status myapp -n k8s-training # 查看状态
+helm get values myapp -n k8s-training # 查看当前 values
+
+# === 升级 ===
+helm upgrade myapp ./myapp -n k8s-training \
+ --set image.tag=1.26
+
+# === 回滚 ===
+helm rollback myapp 1 -n k8s-training # 回滚到 revision 1
+helm history myapp -n k8s-training # 查看历史
+
+# === 卸载 ===
+helm uninstall myapp -n k8s-training
+
+# === 调试 ===
+helm template myapp ./myapp # 本地渲染模板(不部署)
+helm lint ./myapp # 检查 Chart 语法
+helm diff upgrade myapp ./myapp # 查看变更(需要 helm-diff 插件)
+```
+
+### 5.7 使用公共 Chart 部署
+
+```bash
+# 安装 Bitnami Nginx
+helm repo add bitnami https://charts.bitnami.com/bitnami
+helm install my-nginx bitnami/nginx -n k8s-training \
+ --set replicaCount=2 \
+ --set service.type=ClusterIP
+
+# 安装 MySQL
+helm install my-db bitnami/mysql -n k8s-training \
+ --set auth.rootPassword=MyRootPassword123 \
+ --set primary.persistence.size=5Gi
+```
+
+### 5.8 多环境配置管理
+
+```bash
+# 目录结构
+myapp/
+├── values.yaml # 默认值
+├── values-dev.yaml # 开发环境
+├── values-staging.yaml # 预发布环境
+└── values-prod.yaml # 生产环境
+
+# 部署到不同环境
+helm install myapp-dev ./myapp -n dev -f values-dev.yaml
+helm install myapp-prod ./myapp -n prod -f values-prod.yaml
+```
+
+**values-prod.yaml 示例**:
+```yaml
+replicaCount: 3
+
+image:
+ tag: "1.0.0"
+
+resources:
+ requests:
+ cpu: "500m"
+ memory: "512Mi"
+ limits:
+ cpu: "2000m"
+ memory: "2Gi"
+
+ingress:
+ enabled: true
+ host: app.example.com
+```
+
+---
+
+## 📝 习题
+
+### 选择题
+
+**Q1.** Helm 中,一个 Chart 的运行实例称为什么?
+- A) Chart
+- B) Release
+- C) Template
+- D) Package
+
+**Q2.** `helm rollback myapp 1` 的作用是?
+- A) 删除 myapp
+- B) 回滚 myapp 到 revision 1
+- C) 更新 myapp 到版本1
+- D) 查看 myapp 的历史
+
+**Q3.** Helm 模板中 `{{ .Values.replicaCount }}` 的作用是?
+- A) 定义 replicaCount 的值
+- B) 引用 values.yaml 中 replicaCount 的值
+- C) 创建一个名为 replicaCount 的资源
+- D) 删除 replicaCount 配置
+
+**Q4.** `helm template` 命令的作用是?
+- A) 部署应用到 K8S
+- B) 创建新的 Chart
+- C) 本地渲染模板,查看最终 YAML(不部署)
+- D) 从仓库下载 Chart
+
+**Q5.** 如何覆盖 values.yaml 中的单个值?
+- A) `helm install --value key=value`
+- B) `helm install --set key=value`
+- C) `helm install --override key=value`
+- D) `helm install --env key=value`
+
+### 简答题
+
+**Q6.** Helm 相比直接 `kubectl apply` 有什么优势?
+
+**Q7.** 请描述 Helm 的 Chart、Release、Repository 三者的关系。
+
+**Q8.** 如何实现多环境(dev/prod)的 Helm 配置管理?
+
+### 动手题
+
+**Q9.** 请创建一个 Helm Chart,部署一个 Nginx 应用,要求:
+- Chart 名称:webapp
+- 支持自定义副本数、镜像标签、Service 类型
+- 包含 Deployment 和 Service 模板
+- 支持 ConfigMap 配置注入
+
+---
+
+## ✅ 参考答案
+
+### A1. B) Release
+Chart 是包模板,Release 是 Chart 的一个部署实例。同一个 Chart 可以安装多次,每次产生不同的 Release。
+
+### A2. B) 回滚 myapp 到 revision 1
+rollback 会回滚到指定的历史版本(revision),之前的版本会被保留在 history 中。
+
+### A3. B) 引用 values.yaml 中 replicaCount 的值
+`{{ .Values.xxx }}` 是 Go 模板语法,引用 values.yaml 中的配置值。
+
+### A4. C) 本地渲染模板,查看最终 YAML(不部署)
+`helm template` 非常有用,可以在不部署的情况下看到渲染后的 K8S YAML,用于调试模板。
+
+### A5. B) `helm install --set key=value`
+`--set` 用于覆盖 values.yaml 中的值。也可以用 `-f values-override.yaml` 覆盖多个值。
+
+### A6. Helm 的优势
+| 特性 | kubectl apply | Helm |
+|------|--------------|------|
+| 部署多个资源 | 多条命令 | 一条命令 |
+| 参数化 | 手动修改 YAML | values.yaml |
+| 多环境 | 多份 YAML | 多个 values 文件 |
+| 升级/回滚 | 手动管理 | 内置支持 |
+| 版本管理 | 自己用 Git | 内置 history |
+| 共享/复用 | 复制粘贴 | Chart 仓库 |
+
+### A7. Chart、Release、Repository 的关系
+- **Repository**:Chart 的远程仓库(集中存放)
+- **Chart**:从 Repository 下载的"应用包"(包含模板和默认配置)
+- **Release**:Chart 在集群上的一个运行实例(一次 `helm install` = 一个 Release)
+
+```
+Repository (仓库) → 包含多个 Chart (包)
+Chart (包) → 可以创建多个 Release (实例)
+Release (实例) → 运行在 K8S 集群中
+```
+
+### A8. 多环境配置管理
+```
+# 方案1:多 values 文件(推荐)
+values.yaml # 公共默认值
+values-dev.yaml # 开发环境覆盖
+values-prod.yaml # 生产环境覆盖
+
+helm install -f values.yaml -f values-dev.yaml ./chart
+helm install -f values.yaml -f values-prod.yaml ./chart
+
+# 方案2:--set 覆盖
+helm install --set replicaCount=1 ./chart # dev
+helm install --set replicaCount=3 ./chart # prod
+
+# 方案3:--set-file 引用环境变量文件
+helm install --set-file config.env=env/dev.env ./chart
+```
+
+### A9. Helm Chart 实现参见实操练习部分。
+
+---
+
+## 🔧 实操练习
+
+```bash
+export KUBECONFIG=~/.kube/config
+kubectl create namespace k8s-training
+
+# 1. 创建 Chart
+helm create webapp
+cd webapp
+
+# 2. 修改 values.yaml
+cat > values.yaml <<'EOF'
+replicaCount: 2
+
+image:
+ repository: nginx
+ tag: "1.25"
+ pullPolicy: IfNotPresent
+
+service:
+ type: ClusterIP
+ port: 80
+
+resources:
+ requests:
+ cpu: "50m"
+ memory: "32Mi"
+ limits:
+ cpu: "200m"
+ memory: "128Mi"
+
+config:
+ APP_ENV: production
+ LOG_LEVEL: info
+EOF
+
+# 3. 简化 templates/deployment.yaml(用 Helm 默认生成的即可)
+# 4. 简化 templates/service.yaml(用 Helm 默认生成的即可)
+
+# 5. 本地渲染检查
+helm template mywebapp ./webapp
+
+# 6. 安装
+helm install mywebapp ./webapp -n k8s-training
+
+# 7. 查看状态
+helm status mywebapp -n k8s-training
+helm list -n k8s-training
+kubectl get all -n k8s-training
+
+# 8. 升级(改副本数)
+helm upgrade mywebapp ./webapp -n k8s-training --set replicaCount=5
+kubectl get pods -n k8s-training
+
+# 9. 回滚
+helm rollback mywebapp 1 -n k8s-training
+
+# 10. 查看 history
+helm history mywebapp -n k8s-training
+
+# ⚠️ 清理!
+helm uninstall mywebapp -n k8s-training
+cd ..
+rm -rf webapp
+kubectl delete namespace k8s-training
+```
+
+---
+
+**上一章**:[第4章:ConfigMap、Secret 与存储](./04-config-storage.md) | **下一章**:[第6章:部署实战](./06-practice-project.md) | **进阶**:[第7章:调度与弹性伸缩](./07-scheduling-autoscaling.md)
diff --git a/docs/posts/k8s-training/06-practice-project.md b/docs/posts/k8s-training/06-practice-project.md
new file mode 100644
index 0000000..0d789b5
--- /dev/null
+++ b/docs/posts/k8s-training/06-practice-project.md
@@ -0,0 +1,32 @@
+---
+title: "第6章:综合部署实战"
+date: "2026-05-15"
+category: "k8s"
+tags:
+ - "k8s"
+ - "AI生成"
+---
+
+# 第6章:综合部署实战
+
+> 实战项目使用 Helm Chart 打包一个完整的应用部署系统,覆盖前面 5 章的所有核心知识点。
+
+→ **请前往 [实战项目目录](./practice/README.md) 开始练习**
+
+## 本实战覆盖的知识点
+
+| 知识点 | 说明 |
+|--------|------|
+| Namespace | 多租户资源隔离 |
+| Deployment | Pod 管理、副本数、资源限制 |
+| Liveness/Readiness Probe | 容器健康检测 |
+| RollingUpdate | 零停机更新策略 |
+| Service (ClusterIP) | 内部负载均衡 |
+| ConfigMap / Secret | 配置与敏感信息管理 |
+| Ingress / PVC | HTTP 路由与持久化存储(可选) |
+| Helm Chart | 包管理、模板化配置 |
+| DNS 服务发现 | CoreDNS 自动解析 |
+
+---
+
+**上一章**:[第5章:Helm 包管理](./05-helm.md) | **下一章**:[第7章:调度与弹性伸缩](./07-scheduling-autoscaling.md) | **实战**:[practice/README.md](./practice/README.md)
diff --git a/docs/posts/k8s-training/07-scheduling-autoscaling.md b/docs/posts/k8s-training/07-scheduling-autoscaling.md
new file mode 100644
index 0000000..3c76aed
--- /dev/null
+++ b/docs/posts/k8s-training/07-scheduling-autoscaling.md
@@ -0,0 +1,946 @@
+---
+title: "第7章:调度与弹性伸缩"
+date: "2026-05-15"
+category: "k8s"
+tags:
+ - "k8s"
+ - "AI生成"
+---
+
+# 第7章:调度与弹性伸缩
+
+## 🎯 学习目标
+- 理解 Scheduler 的调度决策机制
+- 掌握 nodeSelector、nodeAffinity、podAffinity、podAntiAffinity 的使用场景和区别
+- 掌握 Taint/Toleration(污点与容忍度)
+- 掌握 HPA(水平自动扩缩)和 VPA(垂直自动扩缩)
+- 能在面试中清晰回答调度和弹性伸缩相关问题
+
+---
+
+## 📖 学习要点
+
+### 7.1 调度概述
+
+回顾第1章,Scheduler 的职责是**决定 Pod 运行在哪个 Node 上**。调度是一个两步过程:
+
+```mermaid
+flowchart TB
+ subgraph Step1["第一步:过滤(Filtering / Predicate)"]
+ direction TB
+ F1["排除不满足条件的 Node:"]
+ F2["❌ 资源不够(CPU/内存 requests 超出 Node 可分配)"]
+ F3["❌ nodeSelector 不匹配"]
+ F4["❌ nodeAffinity 不满足"]
+ F5["❌ Taint(污点)未被 Toleration 容忍"]
+ F6["❌ PVC 存储不可用"]
+ F7["❌ Pod 反亲和性冲突"]
+ F8["→ 剩下的叫\"可调度节点\"(Feasible Nodes)"]
+ end
+
+ subgraph Step2["第二步:打分(Scoring / Priority)"]
+ direction TB
+ S1["给每个可调度节点打分,选最高的:"]
+ S2["✅ 资源均衡(选剩余资源最均衡的)"]
+ S3["✅ podAffinity(靠近或远离某些 Pod)"]
+ S4["✅ 数据本地性(PV 和 Pod 在同一 Node)"]
+ S5["→ 选得分最高的 Node → 绑定(Binding)"]
+ end
+
+ Step1 --> Step2
+```
+
+> **图 7-1:调度流程(过滤 + 打分)**
+
+**面试回答模板**:
+> "K8S 的调度分两步:先过滤排除不满足条件的 Node,再打分选最优的。过滤条件包括资源请求、节点选择器、亲和性、污点容忍等。打分考虑资源均衡、亲和性权重、数据本地性等因素。"
+
+---
+
+### 7.2 节点选择:nodeSelector → nodeAffinity
+
+#### 7.2.1 nodeSelector(最简单)
+
+通过标签精确匹配 Node:
+
+```yaml
+# 先给 Node 打标签
+# kubectl label nodes node-1 disktype=ssd
+
+apiVersion: v1
+kind: Pod
+metadata:
+ name: nginx
+spec:
+ nodeSelector:
+ disktype: ssd # 只有标签包含 disktype=ssd 的 Node 才能调度
+ containers:
+ - name: nginx
+ image: nginx:1.25
+```
+
+**局限**:只能精确匹配,不能表达"或"、"非"等逻辑。
+
+#### 7.2.2 nodeAffinity(节点亲和性)⭐ 面试高频
+
+比 nodeSelector 更强大,支持丰富的表达式:
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: app-deployment
+spec:
+ template:
+ spec:
+ affinity:
+ nodeAffinity:
+ # 硬性要求:必须满足,否则 Pod 无法调度(Pending)
+ requiredDuringSchedulingIgnoredDuringExecution:
+ nodeSelectorTerms:
+ - matchExpressions:
+ - key: kubernetes.io/arch
+ operator: In # 值在列表中
+ values: ["amd64"]
+ - key: disktype
+ operator: NotIn # 值不在列表中
+ values: ["hdd"]
+
+ # 软性偏好:尽量满足,不满足也能调度到其他 Node
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 80 # 权重 1-100
+ preference:
+ matchExpressions:
+ - key: node-type
+ operator: In
+ values: ["high-memory"]
+ - weight: 20
+ preference:
+ matchExpressions:
+ - key: zone
+ operator: In
+ values: ["cn-beijing-a"]
+ containers:
+ - name: app
+ image: myapp:1.0
+```
+
+#### operator 支持的运算符
+
+| operator | 含义 | 示例 |
+|----------|------|------|
+| `In` | 值在列表中 | `values: ["ssd", "nvme"]` |
+| `NotIn` | 值不在列表中 | `values: ["hdd"]` |
+| `Exists` | 标签存在(不关心值) | 不需要 values |
+| `DoesNotExist` | 标签不存在 | 不需要 values |
+| `Gt` | 值大于(数字) | `values: ["8"]` |
+| `Lt` | 值小于(数字) | `values: ["4"]` |
+
+#### 硬亲和 vs 软亲和(面试必问)
+
+| 类型 | 关键词 | 不满足时 |
+|------|--------|---------|
+| **硬亲和**(必须) | `requiredDuringScheduling...` | Pod 永远 Pending |
+| **软亲和**(偏好) | `preferredDuringScheduling...` | 仍然调度,只是不优先 |
+
+> `IgnoredDuringExecution` 的含义:Pod 已经运行后,即使 Node 标签变了,**不会**驱逐 Pod。这是默认行为。
+
+**面试回答模板**:
+> "nodeAffinity 分为 required(硬)和 preferred(软)。required 不满足 Pod 会 Pending,preferred 只是偏好。operator 支持 In、NotIn、Exists、DoesNotExist、Gt、Lt 六种。后缀 IgnoredDuringExecution 表示 Pod 运行后 Node 标签变了不会驱逐。"
+
+---
+
+### 7.3 Pod 亲和与反亲和(⭐⭐⭐ 面试高频)
+
+#### 7.3.1 podAffinity(Pod 亲和性)
+
+**控制 Pod 和 Pod 之间的调度关系**——"让我的 Pod 靠近某些 Pod"。
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: frontend
+spec:
+ template:
+ spec:
+ affinity:
+ podAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchLabels:
+ app: cache # 找标签为 app=cache 的 Pod
+ topologyKey: kubernetes.io/hostname # 在同一 Node 上
+ # topologyKey: topology.kubernetes.io/zone # 在同一可用区
+ containers:
+ - name: frontend
+ image: myapp:1.0
+```
+
+**场景**:前端应用需要和缓存部署在同一 Node,减少网络延迟。
+
+#### 7.3.2 podAntiAffinity(Pod 反亲和性)
+
+**让 Pod 远离某些 Pod**——"不要把我的 Pod 和某些 Pod 放在一起"。
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: api-server
+spec:
+ replicas: 3
+ template:
+ spec:
+ affinity:
+ podAntiAffinity:
+ # 硬反亲和:每个 Pod 必须在不同 Node
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchLabels:
+ app: api-server
+ topologyKey: kubernetes.io/hostname
+
+ # 软反亲和:尽量分布在不同可用区(权重分摊)
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 100
+ podAffinityTerm:
+ labelSelector:
+ matchLabels:
+ app: api-server
+ topologyKey: topology.kubernetes.io/zone
+ containers:
+ - name: api
+ image: myapp:1.0
+```
+
+#### topologyKey 是什么?
+
+`topologyKey` 定义了"靠近"的范围(拓扑域):
+
+| topologyKey | 含义 | 效果 |
+|-------------|------|------|
+| `kubernetes.io/hostname` | 同一个 Node | Pod 必须在同一/不同 Node |
+| `topology.kubernetes.io/zone` | 同一个可用区 | Pod 在同一/不同 AZ |
+| `topology.kubernetes.io/region` | 同一个地域 | Pod 在同一/不同地域 |
+
+#### nodeAffinity vs podAffinity 对比(面试必背)
+
+| 对比项 | nodeAffinity | podAffinity |
+|--------|-------------|-------------|
+| 关注点 | Pod → Node 的关系 | Pod → Pod 的关系 |
+| 基于什么选择 | Node 的标签 | 已有 Pod 的标签 |
+| 典型场景 | Pod 必须在 SSD Node / GPU Node | 前端靠近缓存 / API 分散部署 |
+| 是否需要 topologyKey | ❌ 不需要 | ✅ 必须指定 |
+
+**面试回答模板**:
+> "podAntiAffinity 常用来实现高可用——同一个 Deployment 的多个副本分散在不同 Node 或不同可用区,避免单节点故障导致服务不可用。硬反亲和保证分布,软反亲和只是偏好。topologyKey 定义拓扑域范围,hostname 是节点级别,zone 是可用区级别。"
+
+---
+
+### 7.4 污点与容忍度(Taint & Toleration)⭐ 面试高频
+
+#### 7.4.1 概念
+
+```
+Taint(污点):打在 Node 上的"排斥标记",排斥不想被调度的 Pod
+Toleration(容忍度):Pod 声明"我能容忍某些污点"
+```
+
+**类比**:
+- Node 污点 = 房间门上的"闲人免进"牌子
+- Pod 容忍度 = "我有通行证,可以进"
+
+```bash
+# 给 Node 打污点
+kubectl taint nodes node-1 dedicated=gpu:NoSchedule
+# kubectl taint nodes key=value:effect
+```
+
+#### 7.4.2 Taint 效果(effect)
+
+| effect | 含义 | Pod 没有 Toleration 时 |
+|--------|------|----------------------|
+| **NoSchedule** | 新 Pod 不会被调度到此 Node | ✅ 不调度(已有的不受影响) |
+| **PreferNoSchedule** | 尽量不调度到此 Node | ⚠️ 尽量不调度,实在没地方也能调度 |
+| **NoExecute** | 新不调度 + 已有的 Pod 如果没 Toleration 会被驱逐 | 🚫 不调度 + 驱逐已有 Pod |
+
+#### 7.4.3 YAML 示例
+
+```yaml
+# Pod 声明容忍度
+apiVersion: v1
+kind: Pod
+metadata:
+ name: gpu-task
+spec:
+ tolerations:
+ # 精确匹配:容忍 dedicated=gpu:NoSchedule
+ - key: "dedicated"
+ operator: "Equal"
+ value: "gpu"
+ effect: "NoSchedule"
+
+ # 存在即容忍:容忍所有 effect 为 NoSchedule 的 dedicated 污点
+ - key: "dedicated"
+ operator: "Exists"
+ effect: "NoSchedule"
+
+ # 容忍所有污点(危险!慎用)
+ - operator: "Exists"
+ containers:
+ - name: cuda-app
+ image: cuda-app:1.0
+```
+
+#### 7.4.4 常见使用场景
+
+**场景1:Master 节点隔离**
+```bash
+# K8S 默认给 Master 节点打污点
+kubectl taint nodes master-node node-role.kubernetes.io/control-plane:NoSchedule
+
+# 只有声明了 Toleration 的系统 Pod(如 CoreDNS)才能运行在 Master 上
+```
+
+**场景2:GPU 专用节点**
+```bash
+# 给 GPU Node 打污点
+kubectl taint nodes gpu-node-1 hardware=gpu:NoSchedule
+
+# 只有 GPU 任务 Pod 声明容忍度才能调度上去
+```
+
+**场景3:节点故障自动驱逐(NoExecute)**
+```bash
+# Node 不健康时自动打 NoExecute 污点
+# 没有容忍度的 Pod 会被驱逐到其他 Node
+# 配合 tolerationSeconds 可以延迟驱逐:
+ tolerations:
+ - key: "node.kubernetes.io/not-ready"
+ operator: "Exists"
+ effect: "NoExecute"
+ tolerationSeconds: 300 # 等待 300 秒再驱逐(给 Node 恢复时间)
+```
+
+#### 7.4.5 Taint vs nodeAffinity(面试对比)
+
+| 对比项 | Taint(污点) | nodeAffinity(节点亲和性) |
+|--------|-------------|--------------------------|
+| 方向 | Node 排斥 Pod | Pod 选择 Node |
+| 谁定义规则 | Node 上配置 | Pod 上配置 |
+| 作用 | "我不欢迎你" | "我想去你那里" |
+| 类比 | 房间挂牌子"闲人免进" | 人拿钥匙"我能进这个房间" |
+| 典型场景 | Master 隔离、GPU 专用 | Pod 必须在 SSD Node |
+
+> 两者可以**组合使用**:Node 打 Taint 限制谁能来,Pod 用 nodeAffinity 表达想去哪。
+
+**面试回答模板**:
+> "Taint 是 Node 上的排斥标记,Toleration 是 Pod 上的容忍声明。三个 effect:NoSchedule 不调度新 Pod,PreferNoSchedule 尽量不调度,NoExecute 还会驱逐已有 Pod。常见用途是 Master 节点隔离和专用节点。和 nodeAffinity 的区别是方向相反——Taint 是 Node 排斥 Pod,Affinity 是 Pod 选择 Node。"
+
+---
+
+### 7.5 水平自动扩缩 HPA(⭐⭐⭐ 面试必考)
+
+#### 7.5.1 什么是 HPA?
+
+HPA(Horizontal Pod Autoscaler)根据指标**自动调整 Pod 副本数**。
+
+```
+没有 HPA:
+ 手动 kubectl scale → 流量来了来不及 → 手动扩容有延迟
+
+有 HPA:
+ CPU 超过 80% → 自动扩到 5 个副本
+ CPU 降到 30% → 自动缩回 2 个副本
+ 完全自动,无需人工干预!
+```
+
+```mermaid
+flowchart TB
+ Metrics["Metrics Server
(采集 Pod 的 CPU/内存指标)"] --> HPA["HPA Controller
(每 15-30 秒检查一次)"]
+ HPA -->|"期望副本数 = ceil(当前指标值 / 目标指标值)"| Calc["计算"]
+ Calc -->|"例:目标 CPU = 50%,
当前 4 Pod 共 240%
期望副本 = ceil(240/50) = 5
不够 → 扩容到 5 个"| Deploy["调整 Deployment 的 replicas"]
+```
+
+> **图 7-2:HPA 工作流程**
+
+#### 7.5.2 前提条件
+
+```bash
+# 1. 必须安装 Metrics Server(采集资源指标)
+# 大多数云集群默认已安装,检查:
+kubectl top pods # 能显示数据说明已安装
+
+# 如果未安装(如 Sealos):
+kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
+
+# 2. Pod 必须配置 resources.requests(HPA 基于 requests 计算百分比)
+# 没有 requests 的 Pod 其指标会被忽略
+```
+
+#### 7.5.3 HPA YAML 示例
+
+```yaml
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: app-hpa
+ namespace: k8s-training
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: app-deployment # ⭐ 控制哪个 Deployment
+ minReplicas: 2 # 最少 2 个副本
+ maxReplicas: 10 # 最多 10 个副本
+ metrics:
+ # 指标1:CPU 利用率
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: 70 # 目标:平均 CPU 利用率 70%
+
+ # 指标2:内存利用率
+ - type: Resource
+ resource:
+ name: memory
+ target:
+ type: Utilization
+ averageUtilization: 80 # 目标:平均内存利用率 80%
+
+ # 指标3:自定义指标(需要 Prometheus Adapter)
+ # - type: Pods
+ # pods:
+ # metric:
+ # name: http_requests_per_second
+ # target:
+ # type: AverageValue
+ # averageValue: "1000"
+
+ behavior: # 扩缩行为控制(可选,但推荐配置)
+ scaleDown:
+ stabilizationWindowSeconds: 300 # 缩容稳定窗口:5分钟内不重复缩容
+ policies:
+ - type: Percent
+ value: 10 # 每次最多缩容 10%
+ periodSeconds: 60
+ scaleUp:
+ stabilizationWindowSeconds: 0 # 扩容不等待(立即响应)
+ policies:
+ - type: Percent
+ value: 100 # 每次最多扩容 100%(翻倍)
+ periodSeconds: 15
+ - type: Pods
+ value: 4 # 或每次最多增加 4 个
+ periodSeconds: 15
+ selectPolicy: Max # 多个策略取最大值
+```
+
+#### 7.5.4 快速创建 HPA
+
+```bash
+# 命令行快速创建(基于 CPU)
+kubectl autoscale deployment app-deployment \
+ --cpu-percent=70 \
+ --min=2 --max=10 \
+ -n k8s-training
+
+# 查看 HPA 状态
+kubectl get hpa -n k8s-training
+# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
+# app-hpa Deployment/app-deployment 45%/70% 2 10 3 5m
+
+# 查看 HPA 事件(了解扩缩原因)
+kubectl describe hpa app-hpa -n k8s-training
+```
+
+#### 7.5.5 HPA 扩缩公式
+
+```
+期望副本数 = ceil(当前所有 Pod 的指标总量 / 目标指标值)
+
+示例:
+ Deployment 有 3 个 Pod,CPU requests 都是 100m
+ 目标 CPU 利用率 = 70%
+ 当前 3 个 Pod 的 CPU 使用率:80%、60%、50%
+
+ 当前平均利用率 = (80 + 60 + 50) / 3 = 63.3%(低于 70%,不扩容)
+
+ 如果负载增加,3 个 Pod 的 CPU 使用率变为:90%、85%、80%:
+ 当前平均利用率 = (90 + 85 + 80) / 3 = 85%(高于 70%)
+ 期望副本数 = ceil(3 × 85 / 70) = ceil(3.64) = 4 → 扩容到 4 个
+```
+
+#### 7.5.6 HPA 注意事项(面试加分)
+
+```
+1. Pod 必须配置 resources.requests
+ → HPA 基于 requests 计算百分比,没配 requests 的 Pod 不参与计算
+
+2. 冷启动问题
+ → 扩容需要等 Pod 启动并注册到 Service(readinessProbe 通过)
+ → 配置 startupProbe 和合理的 readinessProbe 可以加速
+
+3. 避免抖动
+ → 配置 behavior.scaleDown.stabilizationWindowSeconds(默认 5 分钟)
+ → 避免频繁扩缩
+
+4. 多指标 HPA
+ → 取所有指标计算出的最大副本数(最坏的指标决定扩多少)
+
+5. HPA 与 PDB(PodDisruptionBudget)配合
+ → PDB 限制最大不可用 Pod 数,HPA 缩容时不会违反 PDB
+```
+
+---
+
+### 7.6 VPA 与 Cluster Autoscaler(了解即可)
+
+#### 7.6.1 VPA(Vertical Pod Autoscaler)
+
+HPA 是水平扩缩(加/减 Pod 数量),VPA 是**垂直扩缩**(调整单个 Pod 的 CPU/内存 requests)。
+
+```
+HPA: 2 个 Pod(1核2G)→ 4 个 Pod(1核2G) ← 加副本
+VPA: 2 个 Pod(1核2G)→ 2 个 Pod(2核4G) ← 加资源
+```
+
+| 对比 | HPA | VPA |
+|------|-----|-----|
+| 扩缩方式 | 增减副本数 | 调整 requests/limits |
+| 适用场景 | 无状态应用 | 资源需求波动大 |
+| 注意 | 需要 Metrics Server | 需要 VPA Controller |
+| **重要** | **可与 VPA 共存(某些模式)** | **默认会重启 Pod 生效** |
+
+> 面试中了解 VPA 的概念即可,知道它和 HPA 的区别就行。大多数场景用 HPA。
+
+#### 7.6.2 Cluster Autoscaler(CA)
+
+HPA/VPA 调整 Pod 级别,CA 调整**节点级别**——自动增减 Node 数量。
+
+```mermaid
+flowchart TB
+ subgraph L1["Layer 1: HPA(Pod 级别)"]
+ L1A["流量增大,Pod 不够 → 增加副本"]
+ end
+
+ subgraph L2["Layer 2: VPA(Pod 级别)"]
+ L2A["Pod 资源不够 → 增加单 Pod 的资源"]
+ end
+
+ subgraph L3["Layer 3: Cluster Autoscaler(Node 级别)"]
+ L3A["所有 Node 的资源都被 Pod 占满了"]
+ L3A --> L3B["Pod 处于 Pending 状态"]
+ L3B --> L3C["CA 自动向云厂商申请新 Node 加入集群"]
+ L3C --> L3D["Pod 调度到新 Node"]
+ L3D --> L3E["反过来,Node 利用率低 → CA 缩减 Node"]
+ end
+```
+
+> **图 7-3:三级自动伸缩体系**
+
+> CA 是云厂商级别的能力(AWS/阿里云/GCP),在 Sealos 这种托管平台上不适用。面试中知道有这个东西就行。
+
+---
+
+### 7.7 调度场景综合案例
+
+#### 场景:部署高可用 API 服务
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: api-server
+ namespace: production
+spec:
+ replicas: 6
+ selector:
+ matchLabels:
+ app: api-server
+ template:
+ metadata:
+ labels:
+ app: api-server
+ spec:
+ # 1. 容忍 Master 节点的污点(如果允许调度到 Master)
+ # tolerations:
+ # - key: "node-role.kubernetes.io/control-plane"
+ # operator: "Exists"
+ # effect: "NoSchedule"
+
+ affinity:
+ # 2. 硬反亲和:每个副本必须在不同 Node(高可用基础)
+ podAntiAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchLabels:
+ app: api-server
+ topologyKey: kubernetes.io/hostname
+
+ # 3. 软亲和:尽量分布在不同可用区(跨 AZ 容灾)
+ podAntiAffinity:
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 100
+ podAffinityTerm:
+ labelSelector:
+ matchLabels:
+ app: api-server
+ topologyKey: topology.kubernetes.io/zone
+
+ # 4. 软亲和:靠近缓存 Pod(减少延迟)
+ podAffinity:
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 50
+ podAffinityTerm:
+ labelSelector:
+ matchLabels:
+ app: redis-cache
+ topologyKey: kubernetes.io/hostname
+
+ # 5. 节点亲和:必须部署在 x86 架构上
+ nodeAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ nodeSelectorTerms:
+ - matchExpressions:
+ - key: kubernetes.io/arch
+ operator: In
+ values: ["amd64"]
+
+ containers:
+ - name: api
+ image: myapp:2.0
+ resources:
+ requests:
+ cpu: "200m"
+ memory: "256Mi"
+ limits:
+ cpu: "1000m"
+ memory: "1Gi"
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8080
+ initialDelaySeconds: 10
+ periodSeconds: 5
+ readinessProbe:
+ httpGet:
+ path: /ready
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 3
+---
+# 6. HPA:根据 CPU 自动扩缩
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: api-server-hpa
+ namespace: production
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: api-server
+ minReplicas: 6
+ maxReplicas: 20
+ metrics:
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: 70
+```
+
+---
+
+## 📝 习题
+
+### 选择题
+
+**Q1.** nodeAffinity 中 `requiredDuringSchedulingIgnoredDuringExecution` 的含义是?
+- A) 调度时尽量满足,运行后标签变了会驱逐 Pod
+- B) 调度时必须满足,运行后标签变了不驱逐 Pod
+- C) 调度和运行时都必须满足
+- D) 仅在运行时检查
+
+**Q2.** 以下哪个不是 Taint 的 effect 类型?
+- A) NoSchedule
+- B) PreferNoSchedule
+- C) NoExecute
+- D) NoTolerate
+
+**Q3.** podAntiAffinity 的 `topologyKey: kubernetes.io/hostname` 表示什么?
+- A) 所有 Pod 必须在同一 Node
+- B) 同一 Deployment 的 Pod 不能在同一 Node
+- C) Pod 必须调度到特定主机名的 Node
+- D) Node 的 hostname 必须匹配
+
+**Q4.** HPA 根据什么指标自动扩缩?
+- A) Node 的 CPU 利用率
+- B) Deployment 所有 Pod 的平均 CPU 利用率
+- C) 集群总 CPU 使用量
+- D) Service 的 QPS
+
+**Q5.** 以下哪种情况下 Pod 会被驱逐(Evicted)?
+- A) Node 有 NoSchedule 污点,Pod 没有对应 Toleration
+- B) Node 有 NoExecute 污点,Pod 没有对应 Toleration
+- C) Node 有 PreferNoSchedule 污点,Pod 没有对应 Toleration
+- D) Pod 没有配置 nodeAffinity
+
+### 简答题
+
+**Q6.** 请解释 nodeAffinity 和 podAffinity 的区别,分别适用什么场景?
+
+**Q7.** Taint(污点)和 nodeAffinity(节点亲和性)有什么区别?
+
+**Q8.** HPA 的工作原理是什么?扩容公式怎么计算?
+
+**Q9.** 为什么 HPA 要求 Pod 必须配置 resources.requests?
+
+### 动手题
+
+**Q10.** 请编写一个 Deployment + HPA 的 YAML,要求:
+- Deployment: 3 副本,Nginx 镜像,CPU requests=100m, limits=500m
+- HPA: 目标 CPU 利用率 70%,最小 2 副本,最大 10 副本
+- podAntiAffinity: 硬性要求,副本不能在同一 Node
+
+---
+
+## ✅ 参考答案
+
+### A1. B) 调度时必须满足,运行后标签变了不驱逐 Pod
+- `required` = 必须满足
+- `DuringScheduling` = 调度时检查
+- `IgnoredDuringExecution` = 运行后不检查(不驱逐)
+
+### A2. D) NoTolerate
+三种 effect:NoSchedule、PreferNoSchedule(也称 SoftNoSchedule)、NoExecute。没有 NoTolerate。
+
+### A3. B) 同一 Deployment 的 Pod 不能在同一 Node
+`podAntiAffinity` + `topologyKey: hostname` = 同标签的 Pod 不能在同一 hostname 的 Node 上。
+
+### A4. B) Deployment 所有 Pod 的平均 CPU 利用率
+HPA 监控 scaleTargetRef 指定的 Deployment 下所有 Pod 的平均指标值。
+
+### A5. B) Node 有 NoExecute 污点,Pod 没有对应 Toleration
+只有 NoExecute 会驱逐已有 Pod。NoSchedule 只阻止新 Pod 调度。
+
+### A6. nodeAffinity vs podAffinity
+```
+nodeAffinity(节点亲和性):
+ - Pod 对 Node 的偏好
+ - 基于 Node 的标签
+ - 例:Pod 必须调度到 SSD Node
+ - 不需要 topologyKey
+
+podAffinity(Pod 亲和性):
+ - Pod 对其他 Pod 的偏好
+ - 基于已有 Pod 的标签
+ - 例:前端 Pod 靠近缓存 Pod
+ - 必须指定 topologyKey
+```
+
+### A7. Taint vs nodeAffinity
+```
+Taint(污点):Node 主动排斥 Pod
+ - 配置在 Node 上
+ - "我不欢迎你"
+ - 没有容忍度的 Pod 不能调度
+
+nodeAffinity(节点亲和性):Pod 主动选择 Node
+ - 配置在 Pod 上
+ - "我想去你那里"
+ - 没有匹配的 Node Pod 不会调度
+
+类比:
+ Taint = 房间门上贴"闲人免进"
+ nodeAffinity = 人说"我想进标有SSD的房间"
+```
+
+### A8. HPA 工作原理
+```
+1. Metrics Server 采集每个 Pod 的 CPU/内存使用数据
+2. HPA Controller 每 15-30 秒检查一次
+3. 计算公式:期望副本数 = ceil(当前所有 Pod 指标总量 / 目标指标值)
+4. 如果期望 > maxReplicas → 扩到 maxReplicas
+5. 如果期望 < minReplicas → 保持 minReplicas
+6. 如果期望 > 当前副本数 → 扩容
+7. 如果期望 < 当前副本数 → 等待稳定窗口后缩容
+
+扩缩行为可以通过 behavior 字段精细控制。
+```
+
+### A9. HPA 需要 requests 的原因
+```
+HPA 基于指标"百分比"计算:
+ CPU 利用率 = 实际使用量 / requests
+
+如果没配 requests:
+ - 无法计算百分比(分母为零)
+ - HPA 会忽略该 Pod 的指标
+ - 导致扩缩不准确
+
+例:requests.cpu = 100m,实际使用 70m → 利用率 70%
+```
+
+### A10. Deployment + HPA YAML
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: nginx-hpa-demo
+ namespace: k8s-training
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ affinity:
+ podAntiAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchLabels:
+ app: nginx
+ topologyKey: kubernetes.io/hostname
+ containers:
+ - name: nginx
+ image: nginx:1.25
+ ports:
+ - containerPort: 80
+ resources:
+ requests:
+ cpu: "100m"
+ memory: "64Mi"
+ limits:
+ cpu: "500m"
+ memory: "256Mi"
+---
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: nginx-hpa
+ namespace: k8s-training
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: nginx-hpa-demo
+ minReplicas: 2
+ maxReplicas: 10
+ metrics:
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: 70
+ behavior:
+ scaleDown:
+ stabilizationWindowSeconds: 300
+```
+
+---
+
+## 🔧 实操练习
+
+```bash
+export KUBECONFIG=~/.kube/config
+kubectl create namespace k8s-training
+
+# 1. 查看 Node 标签(了解 Node 信息)
+kubectl get nodes --show-labels
+
+# 2. 给 Node 打标签(模拟不同节点类型)
+kubectl label nodes disktype=ssd --overwrite
+
+# 3. 部署带 nodeSelector 的 Pod
+kubectl apply -f - < | grep -A5 Taints
+
+# 6. 测试 podAntiAffinity(需要多 Node 环境)
+kubectl apply -f - < disktype-
+```
+
+---
+
+**上一章**:[第6章:综合部署实战](./06-practice-project.md) | **下一章**:[第8章:安全与权限](./08-security.md)
diff --git a/docs/posts/k8s-training/08-security.md b/docs/posts/k8s-training/08-security.md
new file mode 100644
index 0000000..25e9bbc
--- /dev/null
+++ b/docs/posts/k8s-training/08-security.md
@@ -0,0 +1,1083 @@
+---
+title: "第8章:安全与权限"
+date: "2026-05-15"
+category: "k8s"
+tags:
+ - "k8s"
+ - "AI生成"
+---
+
+# 第8章:安全与权限
+
+## 🎯 学习目标
+- 理解 K8S 安全架构的四大层面
+- 掌握 RBAC 的核心概念和配置(⭐ 面试高频)
+- 理解 NetworkPolicy 网络隔离策略
+- 了解 SecurityContext 容器安全上下文
+- 能在面试中清晰回答安全相关问题
+
+---
+
+## 📖 学习要点
+
+### 8.1 K8S 安全架构全景
+
+K8S 安全分为四个层面:
+
+```mermaid
+flowchart TB
+ subgraph Security["K8S 安全四层模型"]
+ A["① 认证(Authentication)
\"你是谁?\"
→ Token、证书、OIDC、ServiceAccount"]
+ B["② 授权(Authorization / RBAC)
\"你能做什么?\"
→ Role、ClusterRole、RoleBinding"]
+ C["③ 准入控制(Admission Control)
\"你的请求安全吗?\"
→ LimitRanger、ResourceQuota、PodSecurity"]
+ D["④ 网络策略(Network Policy)
\"谁能和谁通信?\"
→ NetworkPolicy、Service Mesh"]
+ end
+
+ Request["用户请求"] --> Auth["①认证"] --> Authz["②授权"] --> Admission["③准入控制"] --> etcd["写入 etcd"]
+```
+
+> **图 8-1:K8S 安全四层模型**
+
+---
+
+### 8.2 RBAC(⭐⭐⭐ 面试必考)
+
+RBAC = Role-Based Access Control(基于角色的访问控制),是 K8S 的授权机制。
+
+#### 8.2.1 核心概念
+
+```
+四个核心对象:
+
+Role → 定义"能做什么"(Namespace 级别)
+ClusterRole → 定义"能做什么"(集群级别)
+
+RoleBinding → 把 Role 绑定到"谁"
+ClusterRoleBinding → 把 ClusterRole 绑定到"谁"
+
+绑定对象("谁"):
+ User → 外部用户(通过证书/OIDC 认证)
+ Group → 用户组
+ ServiceAccount → Pod 的身份(最常用!)
+```
+
+```mermaid
+flowchart LR
+ Role["Role
(权限规则)"] -->|"绑定到"| RB["RoleBinding
(角色绑定)"]
+ RB -->|"授予"| SA["ServiceAccount(Pod 身份)
或 User / Group(用户/用户组)"]
+```
+
+> **图 8-2:RBAC 核心概念**
+
+#### 8.2.2 Role 和 ClusterRole
+
+```yaml
+# Role:Namespace 级别的权限
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: developer
+ namespace: production # ⭐ 只在 production namespace 生效
+rules:
+- apiGroups: [""] # "" = core API group(Pod、Service 等)
+ resources: ["pods", "services", "configmaps"]
+ verbs: ["get", "list", "watch", "create", "update"]
+- apiGroups: ["apps"] # apps API group(Deployment、StatefulSet 等)
+ resources: ["deployments"]
+ verbs: ["get", "list", "watch", "create", "update", "patch"]
+- apiGroups: [""]
+ resources: ["secrets"] # Secret 单独限制
+ verbs: ["get", "list"] # 只允许查看,不允许创建/删除
+```
+
+```yaml
+# ClusterRole:集群级别的权限
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: cluster-reader # ⭐ 全集群生效
+rules:
+- apiGroups: [""]
+ resources: ["nodes", "namespaces", "pods"]
+ verbs: ["get", "list", "watch"] # 只读权限
+```
+
+#### 8.2.3 RoleBinding 和 ClusterRoleBinding
+
+```yaml
+# RoleBinding:把 Role 绑定到 ServiceAccount
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: developer-binding
+ namespace: production
+subjects:
+- kind: ServiceAccount # 绑定给 ServiceAccount
+ name: app-sa # ServiceAccount 名称
+ namespace: production
+# - kind: User # 也可以绑定给外部用户
+# name: zhangsan
+# - kind: Group # 或用户组
+# name: developers
+roleRef:
+ kind: Role # 引用的 Role
+ name: developer # Role 名称
+ apiGroup: rbac.authorization.k8s.io
+```
+
+```yaml
+# ClusterRoleBinding:把 ClusterRole 绑定到所有 Namespace 的 SA
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: read-all-binding
+subjects:
+- kind: ServiceAccount
+ name: monitor-sa
+ namespace: monitoring
+roleRef:
+ kind: ClusterRole
+ name: cluster-reader
+ apiGroup: rbac.authorization.k8s.io
+```
+
+#### 8.2.4 ServiceAccount(Pod 的身份)
+
+```yaml
+# 每个 Namespace 默认有一个 default ServiceAccount
+# 建议为每个应用创建独立的 SA
+
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: app-sa
+ namespace: production
+---
+# 在 Pod 中使用 ServiceAccount
+apiVersion: v1
+kind: Pod
+metadata:
+ name: my-app
+ namespace: production
+spec:
+ serviceAccountName: app-sa # ⭐ 指定 SA(不指定则用 default)
+ containers:
+ - name: app
+ image: myapp:1.0
+```
+
+#### 8.2.5 内置 ClusterRole(⭐ 面试常考)
+
+K8S 自带了几个常用的 ClusterRole,不需要自己创建:
+
+| ClusterRole | 权限范围 | 典型用途 |
+|-------------|----------|----------|
+| `cluster-admin` | **全集群所有权限** | 集群管理员(慎用!) |
+| `admin` | Namespace 内几乎所有权限(含 RBAC) | Namespace 负责人 |
+| `edit` | Namespace 内读写权限(不含 RBAC) | 普通开发者 |
+| `view` | Namespace 内只读权限 | 只读观察者 |
+
+```bash
+# 给开发者 devuser 授予 production namespace 的 edit 权限
+kubectl create rolebinding devuser-edit \
+ --clusterrole=edit \
+ --user=devuser \
+ --namespace=production
+
+# 给监控 SA 授予全集群只读(ClusterRoleBinding)
+kubectl create clusterrolebinding monitor-view \
+ --clusterrole=view \
+ --serviceaccount=monitoring:prometheus-sa
+```
+
+> ⚠️ 重要区别:
+> - `kubectl create rolebinding --clusterrole=edit -n production` → ClusterRole + **RoleBinding** = 只在 production 生效
+> - `kubectl create clusterrolebinding --clusterrole=edit` → ClusterRole + **ClusterRoleBinding** = 全集群生效
+
+#### 8.2.6 RoleBinding 引用 ClusterRole(⭐ 常见混淆点)
+
+这是一个非常实用但容易混淆的用法:**ClusterRole 可以被 RoleBinding 引用**,此时权限仅在 RoleBinding 所在的 Namespace 生效。
+
+```
+场景:你希望在每个 Namespace 里都有相同的权限规则(如"只读 Pod"),
+ 但不想在每个 Namespace 里都创建一模一样的 Role。
+
+解决方案:
+ 1. 创建一个 ClusterRole(定义规则)
+ 2. 在每个 Namespace 里用 RoleBinding 引用这个 ClusterRole
+```
+
+```yaml
+# ClusterRole 定义规则(集群级别,共享使用)
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: pod-reader
+rules:
+- apiGroups: [""]
+ resources: ["pods", "pods/log"]
+ verbs: ["get", "list", "watch"]
+---
+# RoleBinding 在 team-a namespace 引用 ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: team-a-pod-reader
+ namespace: team-a # ⭐ 只在 team-a 生效
+subjects:
+- kind: ServiceAccount
+ name: app-sa
+ namespace: team-a
+roleRef:
+ kind: ClusterRole # ⭐ 引用的是 ClusterRole(而不是 Role)
+ name: pod-reader
+ apiGroup: rbac.authorization.k8s.io
+```
+
+```mermaid
+flowchart LR
+ subgraph Table["RBAC 组合对比表"]
+ direction TB
+ H["组合方式"]:::header --> A["roleRef 类型"]:::header --> B["binding 类型"]:::header --> C["作用范围"]:::header
+ R1["常规用法"] --> R1A["Role"] --> R1B["RoleBinding"] --> R1C["同 Namespace"]
+ R2["跨 NS 复用"] --> R2A["ClusterRole"] --> R2B["RoleBinding"] --> R2C["⭐ 仅 RoleBinding 所在 NS"]
+ R3["全集群授权"] --> R3A["ClusterRole"] --> R3B["ClusterRoleBinding"] --> R3C["全集群"]
+ end
+
+ classDef header fill:#f96
+```
+
+> **图 8-3:RBAC 组合对比**
+
+#### 8.2.7 聚合 ClusterRole(Aggregated ClusterRole)
+
+通过标签聚合多个 ClusterRole,实现权限的动态扩展:
+
+```yaml
+# 聚合 ClusterRole:自动合并所有带 rbac.example.com/aggregate-to-monitoring=true 标签的 ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: monitoring
+aggregationRule:
+ clusterRoleSelectors:
+ - matchLabels:
+ rbac.example.com/aggregate-to-monitoring: "true"
+rules: [] # 聚合 ClusterRole 的 rules 由 controller 自动填充,这里不需要手写
+
+---
+# 被聚合的 ClusterRole(打上标签即可自动加入)
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: monitoring-endpoints
+ labels:
+ rbac.example.com/aggregate-to-monitoring: "true" # ⭐ 关键标签
+rules:
+- apiGroups: [""]
+ resources: ["endpoints", "pods", "services"]
+ verbs: ["get", "list", "watch"]
+```
+
+> 💡 内置 ClusterRole(`admin`、`edit`、`view`)都使用了聚合机制。安装 CRD 时,运营商可以通过打标签的方式自动扩展这些内置权限,而无需修改原有 ClusterRole。
+
+#### 8.2.8 API Group 详解(⭐ 写 Role 必须掌握)
+
+RBAC rules 中的 `apiGroups` 决定了 `resources` 属于哪个 API Group:
+
+```
+常用 API Group 对应关系:
+
+apiGroups: [""] # core group(空字符串)
+ resources: pods, services, configmaps, secrets, nodes,
+ namespaces, endpoints, persistentvolumeclaims, events
+
+apiGroups: ["apps"]
+ resources: deployments, replicasets, statefulsets, daemonsets
+
+apiGroups: ["batch"]
+ resources: jobs, cronjobs
+
+apiGroups: ["autoscaling"]
+ resources: horizontalpodautoscalers
+
+apiGroups: ["networking.k8s.io"]
+ resources: ingresses, networkpolicies
+
+apiGroups: ["rbac.authorization.k8s.io"]
+ resources: roles, rolebindings, clusterroles, clusterrolebindings
+
+apiGroups: ["storage.k8s.io"]
+ resources: storageclasses, volumeattachments
+
+apiGroups: ["apiextensions.k8s.io"]
+ resources: customresourcedefinitions (CRDs)
+```
+
+```bash
+# 查询某个资源属于哪个 API Group
+kubectl api-resources | grep deployment
+# NAME SHORTNAMES APIVERSION NAMESPACED KIND
+# deployments deploy apps/v1 true Deployment
+# ^^^^
+# 这里 apps 就是 apiGroup
+```
+
+```yaml
+# 示例:给 Dispatcher 服务授予创建 Job 的权限
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: job-creator
+ namespace: processing
+rules:
+- apiGroups: ["batch"] # ⭐ Job 属于 batch group
+ resources: ["jobs"]
+ verbs: ["create", "get", "list", "watch", "delete"]
+- apiGroups: [""] # ⭐ Pod 日志属于 core group
+ resources: ["pods", "pods/log"]
+ verbs: ["get", "list", "watch"]
+```
+
+#### 8.2.9 subresources(子资源)权限
+
+某些操作需要单独授权**子资源**:
+
+```yaml
+rules:
+# Pod 本身的增删查
+- apiGroups: [""]
+ resources: ["pods"]
+ verbs: ["get", "list", "watch", "create", "delete"]
+
+# Pod 日志(pods/log 是子资源)
+- apiGroups: [""]
+ resources: ["pods/log"] # ⭐ 子资源格式:资源/子资源
+ verbs: ["get"]
+
+# Pod exec(远程执行命令)
+- apiGroups: [""]
+ resources: ["pods/exec"] # ⭐ kubectl exec 需要此权限
+ verbs: ["create"]
+
+# Deployment 扩缩容(scale 子资源)
+- apiGroups: ["apps"]
+ resources: ["deployments/scale"] # ⭐ kubectl scale 需要此权限
+ verbs: ["update"]
+```
+
+```
+常见子资源速查:
+ pods/log → kubectl logs
+ pods/exec → kubectl exec
+ pods/portforward → kubectl port-forward
+ pods/status → 更新 Pod 状态
+ deployments/scale → kubectl scale
+ services/proxy → kubectl proxy
+```
+
+#### 8.2.10 RBAC 调试与审计(⭐ 必会命令)
+
+```bash
+# ===== 权限检查 =====
+
+# 检查当前用户是否有某个权限
+kubectl auth can-i create pods
+kubectl auth can-i list secrets -n production
+
+# 检查指定 SA 是否有权限(impersonation)
+kubectl auth can-i list pods \
+ --as=system:serviceaccount:production:app-sa \
+ -n production
+
+# 列出当前用户所有权限(K8S 1.14+)
+kubectl auth can-i --list -n production
+
+# 列出指定 SA 的所有权限
+kubectl auth can-i --list \
+ --as=system:serviceaccount:monitoring:prometheus-sa \
+ -n monitoring
+
+# ===== 查看 RBAC 资源 =====
+
+# 查看所有 Role 和 RoleBinding
+kubectl get roles,rolebindings -n production
+
+# 查看 ClusterRole 的详细规则
+kubectl describe clusterrole edit
+
+# 查看谁绑定了某个 ClusterRole
+kubectl get clusterrolebindings -o wide | grep cluster-admin
+
+# 找出某个 SA 被绑定到哪些 Role/ClusterRole
+kubectl get rolebindings,clusterrolebindings -A \
+ -o jsonpath='{range .items[*]}{.metadata.name}{" → "}{range .subjects[*]}{.kind}/{.name}{" "}{end}{"\n"}{end}' \
+ | grep "app-sa"
+
+# ===== 权限问题排查流程 =====
+# 1. 查看错误信息(通常是 403 Forbidden)
+kubectl get pods -n production
+# Error: pods is forbidden: User "xxx" cannot list resource "pods" ...
+
+# 2. 确认当前身份
+kubectl auth whoami # K8S 1.28+
+kubectl config current-context
+
+# 3. 检查权限
+kubectl auth can-i list pods --as=system:serviceaccount:production:app-sa -n production
+
+# 4. 找到对应的 SA 和 RoleBinding
+kubectl describe rolebinding -n production | grep -A5 "app-sa"
+
+# 5. 修复:添加缺失权限
+kubectl edit role -n production
+```
+
+#### 8.2.11 生产级 RBAC 架构(多团队场景)
+
+**场景**:一个 K8S 集群,team-a 和 team-b 各有自己的 Namespace,SRE 团队负责全集群运维。
+
+```mermaid
+flowchart TB
+ subgraph Cluster["K8S 集群"]
+ subgraph NS_A["Namespace: team-a"]
+ SA_A1["app-sa"]
+ SA_A2["ci-sa"]
+ SA_A3["mon-sa"]
+ SA_A1 --> RB_A1["RoleBinding"]
+ SA_A2 --> RB_A2["RoleBinding"]
+ SA_A3 --> RB_A3["RoleBinding"]
+ RB_A1 -.-> CR_E["ClusterRole: edit"]
+ RB_A2 -.-> CR_E
+ RB_A3 -.-> CR_V["ClusterRole: view"]
+ end
+
+ subgraph NS_B["Namespace: team-b"]
+ SA_B1["app-sa"]
+ SA_B2["ci-sa"]
+ SA_B1 --> RB_B1["RoleBinding"]
+ SA_B2 --> RB_B2["RoleBinding"]
+ RB_B1 -.-> CR_E
+ RB_B2 -.-> CR_E
+ end
+
+ subgraph ClusterLevel["集群级别(ClusterRoleBinding)"]
+ SRE["sre-team"] -.-> CR_ADMIN["ClusterRole: cluster-admin"]
+ MON["monitoring-sa"] -.-> CR_V
+ ING["ingress-controller-sa"] -.-> CR_ING["ClusterRole: ingress-viewer"]
+ end
+ end
+```
+
+> **图 8-4:多团队 RBAC 架构**
+
+```yaml
+# 完整示例:为 team-a 的 CI/CD 流水线配置 RBAC
+
+# 1. 创建 ServiceAccount
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: ci-deployer
+ namespace: team-a
+---
+# 2. 创建 Role(只允许 CI/CD 需要的操作)
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: ci-deployer-role
+ namespace: team-a
+rules:
+# 允许管理 Deployment
+- apiGroups: ["apps"]
+ resources: ["deployments", "replicasets"]
+ verbs: ["get", "list", "watch", "create", "update", "patch"]
+# 允许管理 ConfigMap(不含 Secret)
+- apiGroups: [""]
+ resources: ["configmaps", "services"]
+ verbs: ["get", "list", "watch", "create", "update", "patch"]
+# 只允许查看 Pod(不允许创建/删除)
+- apiGroups: [""]
+ resources: ["pods", "pods/log"]
+ verbs: ["get", "list", "watch"]
+# 允许查看 Job 状态(部署后验证)
+- apiGroups: ["batch"]
+ resources: ["jobs"]
+ verbs: ["get", "list", "watch"]
+# 注意:❌ 不授予 secrets 权限(CI 环境不需要读敏感信息)
+# 注意:❌ 不授予 rolebindings 权限(防止 CI 提升自身权限)
+---
+# 3. RoleBinding 绑定
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: ci-deployer-binding
+ namespace: team-a
+subjects:
+- kind: ServiceAccount
+ name: ci-deployer
+ namespace: team-a
+roleRef:
+ kind: Role
+ name: ci-deployer-role
+ apiGroup: rbac.authorization.k8s.io
+```
+
+#### 8.2.12 RBAC 最佳实践(面试加分)
+
+```
+✅ 最小权限原则
+ → 每个 ServiceAccount 只授予必需的权限
+ → 不要直接用 default SA
+ → 定期审计(kubectl auth can-i --list)
+
+✅ 分离读写权限
+ → developer Role: 可以 get/list/create/update
+ → viewer Role: 只能 get/list/watch
+
+✅ 限制 Secret 访问
+ → 大部分应用不需要访问 Secret
+ → 把 Secret 权限单独抽到一个 Role
+ → CI/CD SA 通常不需要读 Secret(通过注入而不是读取)
+
+✅ 使用命名规范
+ → Role/Binding: -(如 dev-readonly)
+ → ServiceAccount: -sa(如 api-sa, ci-deployer)
+
+✅ 优先使用内置 ClusterRole
+ → view/edit/admin 已覆盖大多数场景
+ → 通过 RoleBinding 引用 ClusterRole,避免重复定义
+
+✅ 使用 RoleBinding + ClusterRole 代替重复的 Role
+ → 多个 Namespace 需要相同规则时,创建一个 ClusterRole,
+ 在每个 Namespace 用 RoleBinding 引用
+
+❌ 不要使用 ClusterRoleBinding 授予非必要权限
+ → ClusterRoleBinding 是全集群级别的
+ → 能用 RoleBinding 的就不要用 ClusterRoleBinding
+
+❌ 不要授予通配符权限
+ → resources: ["*"] 或 verbs: ["*"] 等于放弃最小权限
+ → cluster-admin 只给极少数人
+
+❌ 防止权限提升(Privilege Escalation)
+ → 不要给应用授予 create rolebindings/clusterrolebindings 权限
+ → 否则应用可以给自己提权
+```
+
+**面试回答模板**:
+> "RBAC 是 K8S 的授权机制,通过 Role/ClusterRole 定义权限规则,通过 RoleBinding/ClusterRoleBinding 绑定到 User/Group/ServiceAccount。有几个关键点容易混淆:一是 ClusterRole 可以被 RoleBinding 引用,此时权限只在该 Namespace 生效,这是多 Namespace 复用权限规则的常用方式;二是 API Group 要写对,Deployment 在 apps 组,Job 在 batch 组;三是子资源需要单独授权,比如 pods/exec 和 pods/log。生产实践中我们遵循最小权限原则,每个应用独立 SA,用 `kubectl auth can-i --list` 做定期审计。"
+
+---
+
+### 8.3 NetworkPolicy(网络策略)
+
+#### 8.3.1 为什么需要 NetworkPolicy?
+
+```
+默认情况下:
+ K8S 中所有 Pod 之间网络完全互通(零信任反面)
+
+有了 NetworkPolicy:
+ 可以精确控制:
+ → 哪些 Pod 可以被访问
+ → 哪些 Pod 可以访问外部
+ → 允许哪些端口和协议
+```
+
+> ⚠️ NetworkPolicy 需要 CNI 插件支持(Calico、Cilium 支持,Flannel 默认不支持)。
+
+#### 8.3.2 NetworkPolicy 示例
+
+**示例1:只允许前端 Pod 访问 API Pod**
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: api-allow-frontend
+ namespace: production
+spec:
+ podSelector:
+ matchLabels:
+ app: api # 这个 Policy 作用于 label=app:api 的 Pod
+ policyTypes:
+ - Ingress # 控制入站流量
+ ingress:
+ - from:
+ - podSelector:
+ matchLabels:
+ app: frontend # 只允许 label=app:frontend 的 Pod
+ ports:
+ - port: 8080 # 只允许 8080 端口
+ protocol: TCP
+```
+
+**效果**:
+```
+frontend Pod → api Pod:8080 ✅ 允许
+database Pod → api Pod:8080 ❌ 拒绝
+任何 Pod → api Pod:3306 ❌ 拒绝(只开了 8080)
+api Pod → 外部 ✅ 允许(出站默认不受限,除非设 Egress)
+```
+
+**示例2:禁止 Pod 访问外部网络**
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: deny-external
+ namespace: production
+spec:
+ podSelector:
+ matchLabels:
+ app: sensitive-app
+ policyTypes:
+ - Egress # 控制出站流量
+ egress:
+ - to:
+ - podSelector: {} # 只允许访问同 namespace 内的 Pod
+ ports:
+ - port: 53 # DNS
+ protocol: UDP
+ - to: # 允许访问特定 Service
+ - namespaceSelector:
+ matchLabels:
+ name: production
+```
+
+**示例3:完全隔离 Namespace**
+
+```yaml
+# 默认拒绝所有入站流量(白名单模式)
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: deny-all-ingress
+ namespace: production
+spec:
+ podSelector: {} # 作用于所有 Pod(空选择器 = 所有)
+ policyTypes:
+ - Ingress
+ # ingress 为空 = 拒绝所有入站
+```
+
+#### 8.3.3 NetworkPolicy 要点总结
+
+```
+1. 选择器(podSelector)
+ → 空 {} = 该 namespace 的所有 Pod
+ → 有条件 = 只有匹配标签的 Pod
+
+2. 策略类型(policyTypes)
+ → Ingress:入站(谁可以访问我)
+ → Egress:出站(我可以访问谁)
+ → 不指定 Ingress = 不限制入站
+ → 不指定 Egress = 不限制出站
+
+3. 规则模式
+ → 白名单模式:先 deny-all,再逐条允许(推荐)
+ → 黑名单模式:逐条拒绝(不推荐,容易遗漏)
+
+4. DNS 注意事项
+ → 即使限制了出站,也要放行 UDP 53 端口(DNS)
+ → 否则 Pod 无法解析域名
+```
+
+---
+
+### 8.4 SecurityContext(安全上下文)
+
+控制 Pod 和容器的安全属性:
+
+#### 8.4.1 Pod 级别
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: secure-pod
+spec:
+ securityContext:
+ runAsNonRoot: true # ⭐ 强制容器以非 root 运行(面试高频)
+ runAsUser: 1000 # 以 UID 1000 运行
+ runAsGroup: 3000 # 以 GID 3000 运行
+ fsGroup: 2000 # 挂载的 Volume 文件属组
+ seccompProfile: # Seccomp 安全配置文件
+ type: RuntimeDefault
+ containers:
+ - name: app
+ image: myapp:1.0
+ securityContext:
+ allowPrivilegeEscalation: false # ⭐ 禁止提权(面试高频)
+ readOnlyRootFilesystem: true # 根文件系统只读
+ capabilities:
+ drop: ["ALL"] # 丢弃所有 Linux capabilities
+ add: ["NET_BIND_SERVICE"] # 只添加必需的
+```
+
+#### 8.4.2 核心安全配置(面试记忆表)
+
+| 配置 | 作用 | 推荐值 |
+|------|------|--------|
+| `runAsNonRoot` | 禁止以 root 运行 | `true` |
+| `allowPrivilegeEscalation` | 禁止 setuid 提权 | `false` |
+| `readOnlyRootFilesystem` | 根文件系统只读 | `true`(需要时挂载 writable volume) |
+| `capabilities.drop: ["ALL"]` | 丢弃所有 Linux capabilities | 推荐 |
+| `seccompProfile` | 系统调用过滤 | `RuntimeDefault` |
+
+#### 8.4.3 PodSecurity admission(Pod 安全准入)
+
+K8S 1.25+ 使用 Pod Security Standards 替代了旧的 PodSecurityPolicy:
+
+| 级别 | 说明 | 策略 |
+|------|------|------|
+| **Privileged** | 无限制 | 不推荐 |
+| **Baseline** | 基本安全(防止提权) | 最小安全要求 |
+| **Restricted** | 严格安全 | 推荐 |
+
+```yaml
+# 在 Namespace 上应用安全标准
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: production
+ labels:
+ pod-security.kubernetes.io/enforce: restricted # 强制执行
+ pod-security.kubernetes.io/audit: restricted # 审计记录
+ pod-security.kubernetes.io/warn: restricted # 告警
+```
+
+---
+
+### 8.5 ResourceQuota(资源配额)
+
+限制 Namespace 的总资源用量,防止一个团队占用所有资源:
+
+```yaml
+apiVersion: v1
+kind: ResourceQuota
+metadata:
+ name: team-quota
+ namespace: team-a
+spec:
+ hard:
+ # 计算资源限制
+ requests.cpu: "10" # 该 namespace 所有 Pod 的 CPU requests 总和不超过 10 核
+ requests.memory: "20Gi" # 内存 requests 不超过 20Gi
+ limits.cpu: "20" # CPU limits 不超过 20 核
+ limits.memory: "40Gi" # 内存 limits 不超过 40Gi
+
+ # 对象数量限制
+ pods: "50" # 最多 50 个 Pod
+ services: "10" # 最多 10 个 Service
+ persistentvolumeclaims: "20" # 最多 20 个 PVC
+ configmaps: "50" # 最多 50 个 ConfigMap
+ secrets: "50" # 最多 50 个 Secret
+```
+
+```bash
+# 查看配额使用情况
+kubectl describe resourcequota team-quota -n team-a
+
+# 输出示例:
+# Name: team-quota
+# Resource Used Hard
+# -------- ---- ----
+# limits.cpu 3 20
+# limits.memory 6Gi 40Gi
+# pods 5 50
+# services 3 10
+```
+
+---
+
+### 8.6 LimitRange(默认资源限制)
+
+为没有配置 resources 的 Pod 设置默认值:
+
+```yaml
+apiVersion: v1
+kind: LimitRange
+metadata:
+ name: default-limits
+ namespace: production
+spec:
+ limits:
+ - type: Container
+ default: # 默认 limits(没设 limits 的 Pod 自动获得)
+ cpu: "500m"
+ memory: "256Mi"
+ defaultRequest: # 默认 requests(没设 requests 的 Pod 自动获得)
+ cpu: "100m"
+ memory: "64Mi"
+ max: # 最大限制(不能超过)
+ cpu: "2000m"
+ memory: "2Gi"
+ min: # 最小限制(不能低于)
+ cpu: "50m"
+ memory: "32Mi"
+```
+
+**面试回答模板**:
+> "LimitRange 是 namespace 级别的默认资源限制,为没有配置 resources 的 Pod 自动填充 default 和 defaultRequest。ResourceQuota 是 namespace 级别的总量限制。两者配合使用:LimitRange 确保每个 Pod 有合理配置,ResourceQuota 确保整个 namespace 不超标。"
+
+---
+
+## 📝 习题
+
+### 选择题
+
+**Q1.** RBAC 中,定义集群级别权限的对象是?
+- A) Role
+- B) RoleBinding
+- C) ClusterRole
+- D) ServiceAccount
+
+**Q2.** Pod 默认使用哪个 ServiceAccount?
+- A) admin-sa
+- B) default
+- C) kube-system
+- D) 无 ServiceAccount
+
+**Q3.** 以下哪个 NetworkPolicy 配置会拒绝所有入站流量?
+- A) `podSelector: {app: nginx}`,不设置 ingress
+- B) `podSelector: {}`,设置 `policyTypes: [Ingress]`,ingress 为空
+- C) `podSelector: {app: nginx}`,设置 `policyTypes: [Egress]`
+- D) 设置 `ingress: [{from: [{podSelector: {}}]}]`
+
+**Q4.** `runAsNonRoot: true` 的作用是?
+- A) 容器以 root 用户运行
+- B) 容器不能以 root 用户运行(如果镜像默认是 root 会启动失败)
+- C) 禁止容器访问网络
+- D) 限制容器的内存使用
+
+**Q5.** ResourceQuota 和 LimitRange 的区别是?
+- A) ResourceQuota 限制单个 Pod,LimitRange 限制整个 Namespace
+- B) ResourceQuota 限制整个 Namespace 的总量,LimitRange 为单个 Pod 设置默认值
+- C) 两者完全一样
+- D) ResourceQuota 限制网络,LimitRange 限制存储
+
+**Q6.** 用 `RoleBinding` 引用 `ClusterRole`,权限生效范围是?
+- A) 全集群
+- B) 仅 ClusterRole 所在的 Namespace
+- C) 仅 RoleBinding 所在的 Namespace
+- D) 两个 Namespace 都生效
+
+**Q7.** 以下哪个操作需要 `pods/exec` 权限?
+- A) `kubectl get pods`
+- B) `kubectl logs mypod`
+- C) `kubectl exec -it mypod -- bash`
+- D) `kubectl delete pod mypod`
+
+### 简答题
+
+**Q8.** 请简述 RBAC 的四个核心对象及其作用。
+
+**Q9.** 为什么不应该直接使用 default ServiceAccount?最佳实践是什么?
+
+**Q10.** NetworkPolicy 默认行为是什么?如何实现白名单安全策略?
+
+**Q11.** 请解释 SecurityContext 中 `allowPrivilegeEscalation: false` 的作用。
+
+**Q12.** 你需要给 Dispatcher 服务(在 processing namespace)配置 RBAC,使其能够在 processing namespace 内创建和查看 Job,但不能删除 Job,也不能操作其他资源。请写出 ServiceAccount、Role、RoleBinding 的 YAML。
+
+---
+
+## ✅ 参考答案
+
+### A1. C) ClusterRole
+Role 是 Namespace 级别,ClusterRole 是集群级别。
+
+### A2. B) default
+每个 Namespace 自动创建一个 default ServiceAccount。建议为每个应用创建独立 SA。
+
+### A3. B) `podSelector: {}`,设置 `policyTypes: [Ingress]`,ingress 为空
+空 podSelector = 所有 Pod。policyTypes 指定 Ingress 但 ingress 规则为空 = 拒绝所有入站。
+
+### A4. B) 容器不能以 root 用户运行
+如果镜像的 USER 是 root(UID 0),设置 `runAsNonRoot: true` 后 Pod 会启动失败。
+
+### A5. B) ResourceQuota 限制整个 Namespace 的总量,LimitRange 为单个 Pod 设置默认值
+
+### A6. C) 仅 RoleBinding 所在的 Namespace
+RoleBinding 引用 ClusterRole 时,权限范围由 Binding 决定,只在 RoleBinding 所在的 Namespace 生效。这是 ClusterRole 跨 Namespace 复用权限规则的常见方式,而非扩大权限范围。
+
+### A7. C) `kubectl exec -it mypod -- bash`
+- `kubectl get pods` → 需要 `pods` 的 `list` 权限
+- `kubectl logs` → 需要 `pods/log` 子资源的 `get` 权限
+- `kubectl exec` → 需要 `pods/exec` 子资源的 `create` 权限
+- `kubectl delete pod` → 需要 `pods` 的 `delete` 权限
+
+### A8. RBAC 四个核心对象
+```
+Role:Namespace 级别的权限规则(能访问哪些 API、执行哪些操作)
+ClusterRole:集群级别的权限规则(也可被 RoleBinding 引用,只在该 NS 生效)
+RoleBinding:将 Role/ClusterRole 绑定到 User/Group/ServiceAccount(Namespace 内生效)
+ClusterRoleBinding:将 ClusterRole 绑定到主体(全集群生效)
+```
+
+### A9. 不使用 default SA 的原因
+```
+1. default SA 权限不可控——不同版本的 K8S 对 default SA 的权限策略不同
+2. 无法区分应用——多个 Pod 用同一个 SA,审计时无法区分
+3. 最小权限原则——每个应用应该只有必要的权限
+
+最佳实践:
+ - 每个应用创建独立 ServiceAccount
+ - 通过 RoleBinding 授予最小必要权限
+ - 在 Pod spec 中显式指定 serviceAccountName
+ - 定期用 kubectl auth can-i --list 审计权限
+```
+
+### A10. NetworkPolicy 默认行为
+```
+默认:所有 Pod 之间网络完全互通(无限制)
+
+白名单安全策略实现:
+ 1. 先创建一个"拒绝所有入站"的 NetworkPolicy
+ 2. 再逐条创建"允许特定来源"的 NetworkPolicy
+
+ # 步骤1:默认拒绝
+ podSelector: {}, policyTypes: [Ingress], ingress: []
+
+ # 步骤2:允许前端访问 API
+ podSelector: {app: api}, ingress: [{from: [{podSelector: {app: frontend}}]}]
+```
+
+### A11. allowPrivilegeEscalation: false
+```
+allowPrivilegeEscalation 控制容器是否可以"提权":
+
+ false → 容器进程不能通过 setuid/setgid 提升权限
+ true → 允许提权(默认值)
+
+为什么重要?
+ - 即使以普通用户运行(runAsUser: 1000),
+ 如果进程有 setuid 位,仍然可以提权到 root
+ - 设置为 false + runAsNonRoot: true = 双重保险
+
+这是 CIS K8S Benchmark 的必检项。
+```
+
+### A12. Dispatcher RBAC 配置
+```yaml
+# ServiceAccount
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: dispatcher-sa
+ namespace: processing
+---
+# Role:只允许创建和查看 Job
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: dispatcher-role
+ namespace: processing
+rules:
+- apiGroups: ["batch"]
+ resources: ["jobs"]
+ verbs: ["create", "get", "list", "watch"] # ❌ 不包含 delete
+- apiGroups: [""]
+ resources: ["pods", "pods/log"] # 查看 Job 创建的 Pod 日志
+ verbs: ["get", "list", "watch"]
+---
+# RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: dispatcher-binding
+ namespace: processing
+subjects:
+- kind: ServiceAccount
+ name: dispatcher-sa
+ namespace: processing
+roleRef:
+ kind: Role
+ name: dispatcher-role
+ apiGroup: rbac.authorization.k8s.io
+
+# 验证:
+# kubectl auth can-i create jobs --as=system:serviceaccount:processing:dispatcher-sa -n processing
+# → yes
+# kubectl auth can-i delete jobs --as=system:serviceaccount:processing:dispatcher-sa -n processing
+# → no
+```
+
+---
+
+## 🔧 实操练习
+
+```bash
+export KUBECONFIG=~/.kube/config
+kubectl create namespace k8s-training
+
+# 1. 创建 ServiceAccount
+kubectl create sa app-sa -n k8s-training
+
+# 2. 创建 Role(只允许操作 Pod)
+kubectl apply -f - < # 详细描述(含 Events)
+kubectl get pod -o yaml # 完整 YAML
+
+# 编辑资源(直接打开编辑器)
+kubectl edit deployment nginx -n k8s-training
+# 等同于:kubectl get → 修改 → kubectl apply
+
+# 查看资源变更(diff)
+kubectl diff -f deployment.yaml # 查看本地 YAML 和集群差异
+
+# 打补丁(局部更新)
+kubectl patch deployment nginx -p '{"spec":{"replicas":5}}' -n k8s-training
+
+# 临时注入(调试用,不修改原始 YAML)
+kubectl set image deployment/nginx nginx=nginx:1.26 -n k8s-training
+kubectl set resources deployment/nginx -c nginx --limits=cpu=1,memory=1Gi -n k8s-training
+```
+
+#### 9.1.4 调试命令
+
+```bash
+# 查看日志
+kubectl logs -n k8s-training
+kubectl logs -c # 多容器时指定容器
+kubectl logs --previous # 上一次崩溃的日志(CrashLoopBackOff 必备)
+kubectl logs -l app=nginx # 按标签查看多个 Pod 日志
+kubectl logs -f # 实时跟踪(类似 tail -f)
+kubectl logs --tail=100 # 最近 100 行
+kubectl logs --since=1h # 最近 1 小时
+
+# 进入容器
+kubectl exec -it -- /bin/bash
+kubectl exec -it -- /bin/sh # Alpine 等轻量镜像
+
+# 端口转发
+kubectl port-forward svc/nginx 8080:80 # 本地 8080 → Service 80
+kubectl port-forward pod/nginx 8080:80 # 直接转发到 Pod
+
+# 临时运行 Pod(调试用)
+kubectl run debug --image=busybox:1.36 --rm -it --restart=Never -- sh
+kubectl run curl-debug --image=curlimages/curl --rm -it --restart=Never -- curl http://nginx-svc
+
+# 文件拷贝
+kubectl cp :/path/file ./local-file # 从 Pod 拷出
+kubectl cp ./local-file :/path/file # 拷入 Pod
+
+# 查看 RBAC 权限
+kubectl auth can-i create deployments -n k8s-training
+kubectl auth can-i create deployments --as=system:serviceaccount:k8s-training:app-sa -n k8s-training
+kubectl auth can-i --list --as=system:serviceaccount:k8s-training:app-sa -n k8s-training
+```
+
+---
+
+### 9.2 Pod 生命周期与状态转换
+
+```mermaid
+stateDiagram-v2
+ [*] --> Pending: kubectl apply
+ Pending --> Running: Scheduler 分配 Node
+ Running --> Succeeded: 正常退出
+ Running --> Failed: 探针失败
+ Running --> Unknown: Node 不可达
+ Unknown --> Running: Node 恢复
+
+ note right of Pending: 资源已创建,等待调度
+ note right of Running: 容器已启动(Running 或 Running+Ready)
+ note right of Succeeded: 用于 Job/CronJob(正常退出)
+ note right of Failed: 容器启动失败
+ note right of Unknown: Node 不可达
+```
+
+> **图 9-1:Pod 生命周期状态转换图**
+
+**面试关键:理解 Pending 不一定是异常,可能是正常的调度等待。**
+
+---
+
+### 9.3 常见异常排查(⭐⭐⭐ 面试必考)
+
+#### 9.3.1 排查万能公式
+
+```
+第一步:看状态
+ kubectl get pods
+
+第二步:看详情
+ kubectl describe pod
+ → 重点看 Events 部分(最底部)
+
+第三步:看日志
+ kubectl logs
+ kubectl logs --previous # CrashLoopBackOff 时看上次日志
+
+第四步:看指标
+ kubectl top pod
+```
+
+#### 9.3.2 Pending
+
+```
+原因排查:
+ kubectl describe pod
+ → Events: 0/3 nodes available, ...
+
+常见原因:
+ ❌ 资源不足:Node 的 CPU/内存 requests 已满
+ ❌ nodeSelector 没有匹配的 Node
+ ❌ nodeAffinity 不满足
+ ❌ Taint 不被 Toleration 容忍
+ ❌ PVC 未绑定(PVC Pending 导致 Pod Pending)
+ ❌ podAntiAffinity 冲突(没有足够的 Node)
+
+解决:
+ → kubectl describe node 查看资源使用情况
+ → kubectl get nodes --show-labels 检查标签
+ → kubectl describe node | grep Taints 检查污点
+ → kubectl get pvc 检查 PVC 状态
+```
+
+#### 9.3.3 CrashLoopBackOff
+
+```
+原因排查:
+ kubectl logs --previous # ⭐ 关键!看崩溃前的日志
+ kubectl describe pod
+
+常见原因:
+ ❌ 应用启动失败(配置错误、依赖不可用)
+ ❌ livenessProbe 配置不合理(应用正常但探针判断失败)
+ ❌ OOM Kill(内存超过 limits)
+ ❌ 命令/参数错误
+
+解决:
+ → kubectl logs --previous 看崩溃原因
+ → kubectl describe pod 看 State.Last State.Terminated(含退出码和原因)
+ → 检查 livenessProbe 配置(initialDelaySeconds 是否够)
+ → 检查 resources.limits.memory 是否太小
+```
+
+#### 9.3.4 ImagePullBackOff / ErrImagePull
+
+```
+原因排查:
+ kubectl describe pod
+ → Events: Failed to pull image ...
+
+常见原因:
+ ❌ 镜像名/标签错误
+ ❌ 私有仓库无凭证(需要 imagePullSecrets)
+ ❌ 网络不通(无法访问镜像仓库)
+ ❌ 镜像不存在(tag 错误)
+
+解决:
+ → 检查 image 字段拼写和 tag
+ → kubectl create secret docker-registry 创建凭证
+ → kubectl apply -f deployment.yaml 更新 imagePullSecrets
+ → 在 Node 上手动 docker pull 测试连通性
+```
+
+#### 9.3.5 Running 但 Not Ready
+
+```
+原因排查:
+ kubectl describe pod
+
+常见原因:
+ ❌ readinessProbe 失败(应用还没准备好)
+ ❌ 应用启动慢(Java/Spring Boot 等需要预热)
+ ❌ 依赖服务不可用(数据库连接失败)
+
+解决:
+ → 检查 readinessProbe 配置
+ → 增加 initialDelaySeconds
+ → 配置 startupProbe(给慢启动应用缓冲)
+ → kubectl logs 查看应用日志
+```
+
+#### 9.3.6 OOMKilled
+
+```
+原因排查:
+ kubectl describe pod
+ → State.Last State.Terminated.Reason: OOMKilled
+ → State.Last State.Terminated.OOMKilled: true
+
+常见原因:
+ ❌ limits.memory 设置太小
+ ❌ 应用存在内存泄漏
+ ❌ JVM -Xmx 设置接近 limits
+
+解决:
+ → 增大 resources.limits.memory
+ → JVM 应用:-Xmx < limits.memory * 0.8
+ → 分析内存使用:kubectl top pod → 持续观察
+```
+
+**面试回答模板**:
+> "排查 Pod 问题我一般遵循四步:先 kubectl get pods 看状态,再 describe 看 Events,然后 logs 看日志,最后 top 看资源。最常见的是 CrashLoopBackOff,我会先用 `--previous` 看上次崩溃日志,再结合 describe 中的退出码和终止原因判断。如果是 Pending 则检查资源、亲和性、污点容忍和 PVC 状态。"
+
+---
+
+### 9.4 Events 事件系统
+
+Events 是 K8S 中记录"发生了什么"的审计日志,是排查问题的第一入口。
+
+```bash
+# 查看所有事件
+kubectl get events -n k8s-training
+
+# 查看事件详情(按时间排序,最新的在上面)
+kubectl get events -n k8s-training --sort-by='.lastTimestamp'
+
+# 只看 Warning 事件
+kubectl get events -n k8s-training --field-selector type=Warning
+
+# 只看某个 Pod 的事件
+kubectl get events -n k8s-training --field-selector involvedObject.name=
+
+# 持续监控事件
+kubectl get events -n k8s-training -w
+
+# 查看事件详情
+kubectl describe pod # Events 在输出最底部
+```
+
+Events 的常见类型:
+```
+Normal 类型:
+ Pulling → 正在拉取镜像
+ Pulled → 镜像拉取完成
+ Created → 容器已创建
+ Started → 容器已启动
+ Scheduled → Pod 已调度到 Node
+ Killing → 正在终止容器
+
+Warning 类型:
+ FailedScheduling → 调度失败(资源不足、亲和性不满足)
+ FailedAttachVolume → 挂载 Volume 失败
+ FailedMount → Mount 失败
+ InsufficientCPU → CPU 不够
+ InsufficientMemory → 内存不够
+ BackOff → 容器启动失败,正在退避重试
+```
+
+> Events 有 TTL(默认 1 小时),不会永久保存。长期事件监控需要使用日志系统。
+
+---
+
+### 9.5 日志体系
+
+#### 9.5.1 容器日志
+
+```
+K8S 中容器日志的标准输出:
+ stdout → 容器标准输出
+ stderr → 容器错误输出
+ → kubelet 自动采集,kubectl logs 可查看
+ → 日志文件存储在 Node 的 /var/log/containers/
+```
+
+```bash
+# 基本日志
+kubectl logs
+
+# 多容器 Pod
+kubectl logs -c
+
+# 之前的容器(崩溃后重启,旧容器日志丢失除非用 --previous)
+kubectl logs --previous
+
+# 跟踪日志
+kubectl logs -f
+
+# 多 Pod 日志聚合(按标签)
+kubectl logs -l app=nginx --all-containers=true
+```
+
+#### 9.5.2 集群组件日志
+
+```bash
+# Node 上的组件日志
+# kubelet 日志(在 Node 上)
+journalctl -u kubelet -f
+
+# kube-proxy 日志
+journalctl -u kube-proxy -f
+
+# Master 组件日志(在 Master Node 上)
+journalctl -u kube-apiserver -f
+journalctl -u kube-scheduler -f
+journalctl -u kube-controller-manager -f
+
+# 或者通过 Pod 查看(组件以静态 Pod 运行时)
+kubectl logs -n kube-system kube-apiserver-
+kubectl logs -n kube-system etcd-
+```
+
+#### 9.5.3 集中式日志方案
+
+```mermaid
+flowchart LR
+ subgraph EFK["方案1:EFK Stack(传统)"]
+ F1["Fluentd
(采集)"] --> E1["Elasticsearch
(存储/搜索)"]
+ E1 --> K1["Kibana
(可视化)"]
+ end
+
+ subgraph PLG["方案2:PLG Stack(更现代)"]
+ P1["Promtail
(采集)"] --> L1["Loki
(存储)"]
+ L1 --> G1["Grafana
(可视化)"]
+ end
+
+ subgraph Cloud["方案3:云厂商托管"]
+ C1["阿里云 SLS / AWS CloudWatch / 腾讯云 CLS"]
+ end
+```
+
+> **图 9-2:集中式日志方案**
+
+---
+
+### 9.6 监控告警概览
+
+#### 9.6.1 Prometheus + Grafana(行业标准)
+
+```mermaid
+flowchart TB
+ subgraph Prometheus["Prometheus
(采集、存储、告警)"]
+ P["Prometheus
(数据源)"]
+ end
+
+ P --> G["Grafana
(面板)"]
+ P --> A["AlertManager
(告警通知)"]
+
+ subgraph Targets["采集目标"]
+ K["kubelet
(Node/Pod 指标)"]
+ KSM["kube-state-metrics
(K8S 资源状态)"]
+ NE["node-exporter
(Node 硬件指标)"]
+ BB["blackbox-exporter
(探针)"]
+ APP["应用自带 /metrics 端点"]
+ end
+
+ K & KSM & NE & BB & APP --> P
+```
+
+> **图 9-3:Prometheus + Grafana 监控体系架构**
+
+#### 9.6.2 关键监控指标
+
+```
+Node 级别:
+ → CPU 使用率、内存使用率、磁盘 IO、网络流量
+ → kubelet 状态
+
+Pod 级别:
+ → CPU/Memory 使用率 vs requests/limits
+ → Pod 重启次数
+ → OOM Kill 次数
+
+集群级别:
+ → Pod 数量、Deployment 状态
+ → PV 使用率
+ → 证书过期时间
+ → etcd 集群健康
+
+业务级别:
+ → HTTP 请求量 / 错误率 / 延迟(RED 指标)
+ → 消息队列积压
+ → 数据库连接池
+```
+
+---
+
+### 9.7 集群维护操作
+
+#### 9.7.1 节点维护(cordon / drain)
+
+```bash
+# 标记 Node 不可调度(不再调度新 Pod,已有 Pod 不受影响)
+kubectl cordon
+
+# 驱逐 Node 上的所有 Pod(维护前使用)
+kubectl drain --ignore-daemonsets --delete-emptydir-data
+
+# cordon + drain 的典型场景:
+# 1. Node 需要升级/维护
+# 2. 缩容集群
+# 3. 节点硬件故障
+
+# 恢复
+kubectl uncordon
+```
+
+#### 9.7.2 版本升级策略
+
+```
+K8S 版本号:v1.28.3
+ → 1 = 大版本
+ → 28 = 小版本
+ → 3 = 补丁版本
+
+升级规则(面试了解):
+ → 小版本只能升级 +1(1.27 → 1.28,不能跳到 1.29)
+ → 大版本不能跳(不能直接从 1.26 → 1.28)
+ → 控制面先升级,再逐个升级 Worker
+
+多云/托管集群(EKS/ACK/GKE):
+ → 控制面升级由云厂商管理
+ → Worker Node 升级通过滚动替换
+```
+
+#### 9.7.3 备份与恢复
+
+```
+etcd 备份(集群最重要的备份):
+ # 备份 etcd 快照
+ ETCDCTL_API=3 etcdctl snapshot save snapshot.db \
+ --endpoints=https://127.0.0.1:2379 \
+ --cacert=/etc/kubernetes/pki/etcd/ca.crt \
+ --cert=/etc/kubernetes/pki/etcd/server.crt \
+ --key=/etc/kubernetes/pki/etcd/server.key
+
+ # 恢复(⚠️ 慎重!)
+ ETCDCTL_API=3 etcdctl snapshot restore snapshot.db
+
+资源清单备份(YAML 级别):
+ # 导出所有资源为 YAML
+ kubectl get all -A -o yaml > cluster-backup.yaml
+
+ # 或使用 Velero(专业备份工具)
+ velero backup create daily-backup --include-namespaces=production
+```
+
+---
+
+### 9.8 常见运维场景
+
+#### 场景1:滚动更新卡住
+
+```bash
+# 现象:kubectl rollout status 一直不完成
+
+# 排查
+kubectl get pods # 看是否有旧 Pod 没退出
+kubectl describe deployment # 看 Events 和 Conditions
+
+# 常见原因
+# 1. 新 Pod 的 readinessProbe 不通过 → 旧 Pod 不会被终止
+# 2. 旧 Pod 的 preStop hook 阻塞
+# 3. 资源不足,新 Pod Pending
+
+# 强制回滚
+kubectl rollout undo deployment/
+```
+
+#### 场景2:Namespace 删除卡住
+
+```bash
+# 现象:kubectl delete ns 一直 Terminating
+
+# 原因:有资源仍在使用该 Namespace
+
+# 解决:强制删除(最后手段)
+kubectl patch namespace -p '{"metadata":{"finalizers":null}}' --type=merge
+```
+
+#### 场景3:Pod 频繁重启
+
+```bash
+# 排查步骤
+kubectl get pod -o yaml | grep -A5 restartCount # 看重启次数
+kubectl describe pod | grep -A10 "Last State" # 看上次的终止原因
+kubectl logs --previous # 看崩溃日志
+
+# 常见原因:
+# OOMKilled → 增大 limits.memory
+# Exit Code 137 → 被 SIGKILL(通常是 OOM)
+# Exit Code 1 → 应用错误(看日志)
+# Exit Code 0 → 正常退出(可能是探针配置问题)
+# Exit Code 139 → 段错误(程序崩溃)
+```
+
+---
+
+## 📝 习题
+
+### 选择题
+
+**Q1.** Pod 处于 CrashLoopBackOff 状态,首先应该用什么命令排查?
+- A) `kubectl describe node`
+- B) `kubectl logs --previous`
+- C) `kubectl top pods`
+- D) `kubectl get events`
+
+**Q2.** `kubectl cordon ` 的作用是?
+- A) 驱逐 Node 上的所有 Pod
+- B) 标记 Node 不可调度(不影响已有 Pod)
+- C) 删除 Node
+- D) 重启 Node
+
+**Q3.** Pod 处于 Pending 状态,最不可能的原因是?
+- A) Node 资源不足
+- B) 镜像拉取失败
+- C) PVC 未绑定
+- D) podAntiAffinity 冲突
+
+**Q4.** `kubectl drain ` 的作用是?
+- A) 清空 Node 上的所有日志
+- B) 安全驱逐 Node 上的所有 Pod(DaemonSet 除外)
+- C) 删除 Node
+- D) 暂停 Node
+
+**Q5.** Events 的默认保留时间是?
+- A) 永久
+- B) 1 小时
+- C) 24 小时
+- D) 7 天
+
+### 简答题
+
+**Q6.** 请描述 Pod 常见异常状态(Pending、CrashLoopBackOff、ImagePullBackOff、OOMKilled)的排查步骤。
+
+**Q7.** `kubectl describe pod ` 的输出中,哪些部分对排查问题最有价值?
+
+**Q8.** 生产环境的日志方案应该考虑哪些方面?
+
+### 场景题
+
+**Q9.** 用户反馈服务不可用,你收到告警。请描述从接到告警到定位问题的完整排查流程。
+
+**Q10.** 一个 Deployment 的滚动更新卡住了,`kubectl rollout status` 一直显示等待。请列出排查步骤。
+
+---
+
+## ✅ 参考答案
+
+### A1. B) `kubectl logs --previous`
+CrashLoopBackOff 意味着容器反复崩溃重启,`--previous` 可以看到上一次崩溃前的日志。
+
+### A2. B) 标记 Node 不可调度(不影响已有 Pod)
+cordon 只是设置 `Unschedulable: true`,已有 Pod 继续运行。drain 才会驱逐 Pod。
+
+### A3. B) 镜像拉取失败
+镜像拉取失败会导致 ImagePullBackOff/ErrImagePull,不是 Pending。Pending 的常见原因是资源不足、亲和性不满足、PVC 未绑定等调度问题。
+
+### A4. B) 安全驱逐 Node 上的所有 Pod(DaemonSet 除外)
+drain = cordon(标记不可调度)+ 驱逐 Pod。通常需要 `--ignore-daemonsets` 和 `--delete-emptydir-data`。
+
+### A5. B) 1 小时
+Events 默认 TTL 为 1 小时(`--event-ttl=1h`),不是永久保存。
+
+### A6. 常见异常排查步骤
+```
+Pending:
+ 1. kubectl describe pod → Events 部分
+ 2. 检查 "0/N nodes available"
+ 3. 检查资源:kubectl describe node → Allocatable
+ 4. 检查亲和性:nodeSelector/nodeAffinity/podAntiAffinity
+ 5. 检查污点:kubectl describe node | grep Taints
+ 6. 检查 PVC:kubectl get pvc
+
+CrashLoopBackOff:
+ 1. kubectl logs --previous(看崩溃日志)
+ 2. kubectl describe pod → Last State.Terminated
+ 3. 检查退出码和原因(OOMKilled、Error、Completed)
+ 4. 检查 livenessProbe 配置
+
+ImagePullBackOff:
+ 1. kubectl describe pod → Events
+ 2. 检查 image 拼写和 tag
+ 3. 检查 imagePullSecrets
+ 4. 在 Node 上手动 docker pull 测试
+
+OOMKilled:
+ 1. kubectl describe pod → Last State.Terminated
+ 2. 增大 limits.memory
+ 3. JVM 应用:-Xmx < limits * 0.8
+```
+
+### A7. describe 输出中最有价值的部分
+```
+1. Events(最底部)
+ → 记录了 Pod 的完整事件链
+ → Warning 类型的事件是排查关键
+
+2. Conditions
+ → PodScheduled:是否已调度
+ → Initialized:Init 容器是否完成
+ → Ready:是否就绪
+ → ContainersReady:容器是否就绪
+
+3. Last State.Terminated(如果 Pod 重启过)
+ → Exit Code(退出码)
+ → Reason(OOMKilled、Error 等)
+ → Finished At(终止时间)
+
+4. Status.ContainerStatuses
+ → State:当前状态(Running/Waiting/Terminated)
+ → Last Termination State:上次终止信息
+ → Restart Count:重启次数
+```
+
+### A8. 生产环境日志方案
+```
+需求:
+ 1. 集中采集:所有 Pod 日志统一采集
+ 2. 结构化存储:方便搜索和分析
+ 3. 可视化:Dashboard 查看日志
+ 4. 告警:错误日志触发告警
+ 5. 保留策略:按合规要求保留(如 30 天)
+
+方案选型:
+ - EFK(Elasticsearch + Fluentd + Kibana):功能全面但资源消耗大
+ - PLG(Promtail + Loki + Grafana):轻量,与 Grafana 生态集成好
+ - 云厂商托管(阿里云 SLS / AWS CloudWatch):免运维
+
+最佳实践:
+ - 应用日志写 stdout/stderr(不要写文件)
+ - 使用结构化 JSON 日志格式
+ - 设置合理的日志级别(生产 info/warn/error)
+ - 敏感信息脱敏
+```
+
+### A9. 服务不可用排查流程
+```
+1. 确认影响范围
+ → kubectl get pods -A -o wide | grep -v Running
+ → 是单个 Pod 还是大规模故障?
+
+2. 检查 Service 和 Endpoints
+ → kubectl get svc -n
+ → kubectl get endpoints
+ → Endpoints 是否有健康的 Pod IP?
+
+3. 检查 Pod 状态
+ → kubectl get pods -n
+ → 有没有 Pending/CrashLoopBackOff 的 Pod?
+
+4. 如果有异常 Pod
+ → kubectl describe pod → 看 Events
+ → kubectl logs [--previous]
+
+5. 检查 Node 状态
+ → kubectl get nodes
+ → 有没有 NotReady 的 Node?
+
+6. 检查资源
+ → kubectl top nodes
+ → kubectl top pods -n
+
+7. 快速止血
+ → 如果是单个 Pod 问题:kubectl rollout restart deployment/
+ → 如果是配置问题:kubectl rollout undo deployment/
+ → 如果是资源问题:临时扩容或清理无用资源
+```
+
+### A10. 滚动更新卡住排查
+```
+1. kubectl rollout status deployment/ # 确认卡在哪个阶段
+
+2. kubectl get pods # 查看新/旧 Pod 状态
+ → 新 Pod 是否 Running?
+ → 新 Pod 是否 Ready?
+ → 旧 Pod 是否在 Terminating?
+
+3. 如果新 Pod Not Ready:
+ → kubectl logs (检查应用启动)
+ → 检查 readinessProbe(initialDelaySeconds 够不够)
+ → 检查新 Pod 是否有资源
+
+4. 如果旧 Pod 一直 Terminating:
+ → 检查 preStop hook(是否有 sleep 阻塞)
+ → 检查 terminationGracePeriodSeconds
+ → kubectl describe pod
+
+5. 强制处理:
+ → kubectl rollout undo deployment/ # 回滚
+ → 或手动删除旧 Pod:kubectl delete pod --grace-period=0 --force
+```
+
+---
+
+## 🔧 实操练习
+
+```bash
+export KUBECONFIG=~/.kube/config
+kubectl create namespace k8s-training
+
+# 1. 部署一个应用
+kubectl apply -f - </dev/null | head -5
+
+# 6. 练习资源查看
+kubectl top pods -n k8s-training # 如果 Metrics Server 已安装
+
+# ⚠️ 清理!
+kubectl delete namespace k8s-training
+```
+
+---
+
+**上一章**:[第8章:安全与权限](./08-security.md) | **下一章**:[第10章:面试高频题](./10-interview-questions.md)
diff --git a/docs/posts/k8s-training/10-interview-questions.md b/docs/posts/k8s-training/10-interview-questions.md
new file mode 100644
index 0000000..c77bbb5
--- /dev/null
+++ b/docs/posts/k8s-training/10-interview-questions.md
@@ -0,0 +1,780 @@
+---
+title: "第10章:K8S 面试高频题 + 答案模板"
+date: "2026-05-15"
+category: "k8s"
+tags:
+ - "k8s"
+ - "AI生成"
+---
+
+# 第10章:K8S 面试高频题 + 答案模板
+
+## 🎯 使用说明
+- ⭐ = 必考题(每场面试几乎必问)
+- 面试时按"核心概念 → 深入细节 → 实际经验"的结构回答
+- 每道题都有【一句话回答】和【展开回答】,根据时间选择
+
+---
+
+## 一、K8S 架构篇
+
+### ⭐ Q1: 请简述 Kubernetes 的架构
+
+**一句话回答**:K8S 采用 Master-Worker 架构,Master 节点负责管理和调度(API Server、Scheduler、Controller Manager、etcd),Worker 节点运行容器(kubelet、kube-proxy)。
+
+**展开回答**:
+```mermaid
+flowchart TB
+ subgraph Master["Master 节点(控制面)"]
+ API["API Server
唯一入口,认证/授权/准入控制,所有组件通过它通信"]
+ etcd["etcd
分布式KV存储,保存集群所有状态数据"]
+ Scheduler["Scheduler
调度器,决定Pod运行在哪个Node"]
+ CM["Controller Manager
控制器管理器,维护集群状态"]
+ end
+
+ subgraph Worker["Worker 节点(数据面)"]
+ kubelet["kubelet
每个Node上的代理,管理容器生命周期"]
+ kubeproxy["kube-proxy
维护网络规则,实现Service负载均衡"]
+ end
+```
+
+**加分点**:提到"所有组件通过 API Server 通信而非直接访问 etcd"体现了对安全和解耦的理解。
+
+---
+
+### ⭐ Q2: Pod 从创建到运行的完整流程
+
+**一句话回答**:kubectl → API Server(认证+写入etcd)→ Scheduler(调度)→ kubelet(创建容器)→ Running。
+
+**展开回答**:
+```
+1. kubectl 发送请求到 API Server
+2. API Server 进行认证、授权、准入控制
+3. 将 Pod 配置写入 etcd(此时状态为 Pending)
+4. Scheduler 通过 Watch 机制感知未绑定的 Pod
+5. Scheduler 执行调度算法,选择最优 Node
+6. 将绑定信息(nodeName)写入 etcd
+7. 目标 Node 的 kubelet 感知到分配给自己的 Pod
+8. kubelet 调用 CRI(容器运行时接口)拉取镜像
+9. 创建容器,配置网络和存储
+10. 执行健康检查,向 API Server 汇报状态
+11. Pod 变为 Running
+```
+
+---
+
+### Q3: etcd 挂了会怎样?
+
+**回答要点**:
+- Master 功能完全失效(无法创建/更新/删除资源)
+- 已有 Pod 继续运行(kubelet 直接管理容器)
+- 自愈、调度、扩缩等所有控制功能停止
+- **类比**:etcd 是大脑,大脑罢工但手脚还能动
+
+---
+
+## 二、Pod 与工作负载篇
+
+### ⭐ Q4: 什么是 Pod?为什么 K8S 不直接管理容器?
+
+**一句话回答**:Pod 是 K8S 最小调度单元,包含一个或多个共享网络和存储的容器。
+
+**为什么需要 Pod**:
+1. 有些应用天然需要多个进程协作(如主容器 + 日志收集 sidecar)
+2. 同 Pod 容器共享 IP 和端口空间,通过 localhost 通信
+3. Pod 是调度的原子单位——同 Pod 的容器一定在同一 Node
+4. 同 Pod 的容器共享存储卷(Volume)
+
+---
+
+### ⭐ Q5: livenessProbe、readinessProbe、startupProbe 的区别
+
+**一句话回答**:liveness 探测"死了就重启",readiness 探测"没准备好就不接流量",startup 给慢启动应用缓冲时间。
+
+```
+livenessProbe(存活探针):
+ 失败 → 重启容器
+ 场景:应用死锁、无限循环
+ 类比:心跳监测
+
+readinessProbe(就绪探针):
+ 失败 → 从 Service Endpoints 中移除(不接收流量)
+ 场景:应用启动中加载缓存、预热中
+ 类比:门面检查
+
+startupProbe(启动探针):
+ 存在时,前几次 liveness/readiness 失败不会触发动作
+ 场景:Java/Spring Boot 等启动慢的应用
+ 作用:给应用足够的启动时间
+```
+
+---
+
+### ⭐ Q6: requests 和 limits 的区别
+
+**一句话回答**:requests 是调度依据(保证最少给多少),limits 是运行时硬限制(最多能用多少)。
+
+```
+requests(请求值):
+ - Scheduler 调度 Pod 时参考,确保 Node 有足够剩余资源
+ - Node 的可分配资源 = 总资源 - 已分配的 requests
+ - 不设置 requests = 默认等于 limits
+
+limits(限制值):
+ - CPU 超过 limits → 限流(throttle),不会 OOM
+ - 内存超过 limits → OOM Kill 容器
+ - 不设置 limits = 可以使用 Node 的所有剩余资源(危险!)
+
+最佳实践:
+ ✅ requests 和 limits 都设置
+ ✅ requests ≤ limits
+ ✅ CPU: limits / requests ≈ 2~4(允许突发)
+ ✅ 内存: requests ≈ limits(避免 OOM)
+```
+
+---
+
+### ⭐ Q7: Deployment、StatefulSet、DaemonSet 的区别
+
+**一句话回答**:Deployment 无状态,StatefulSet 有状态,DaemonSet 每个节点一个。
+
+| 特性 | Deployment | StatefulSet | DaemonSet |
+|------|-----------|-------------|-----------|
+| 场景 | Web、API | DB、MQ | 日志、监控 |
+| Pod 名称 | 随机(*-abc123) | 固定有序(mysql-0,1,2) | 固定 |
+| 网络标识 | 不稳定 | 稳定 DNS | 不稳定 |
+| 存储共享 | 不保证 PVC 绑定 | 每个Pod独立PVC | 不需要 |
+| 扩缩 | 随机并行 | 有序(逆序删除) | 不扩缩 |
+| Headless SVC | 不需要 | 需要 | 不需要 |
+
+---
+
+## 三、Service 与网络篇
+
+### ⭐ Q8: Service 的类型有哪些?
+
+**回答**:
+```
+1. ClusterIP(默认)
+ - 仅集群内部可访问
+ - 用于微服务间通信
+ - 分配虚拟IP,通过 kube-proxy(iptables/IPVS)负载均衡
+
+2. NodePort
+ - 每个Node开放端口(30000-32767)
+ - 外部通过 NodeIP:NodePort 访问
+ - 适合开发测试
+
+3. LoadBalancer
+ - 云厂商创建外部LB(AWS ELB/阿里云SLB)
+ - 自动分配公网IP
+ - 生产环境对外暴露服务
+
+4. ExternalName
+ - DNS级CNAME映射
+ - 引用集群外服务
+```
+
+---
+
+### ⭐ Q9: Pod 之间如何通信?
+
+**回答**:
+```
+1. 同 Pod 内容器:通过 localhost(共享网络命名空间)
+2. 同 Node Pod 间:通过 cni0 网桥(如 Calico 的虚拟网卡)
+3. 跨 Node Pod 间:通过 CNI 插件的隧道或路由方案
+ - Calico:BGP 路由(推荐)
+ - Flannel:VXLAN 隧道
+ - Cilium:eBPF(性能最优)
+
+关键规则:
+- 每个Pod都有独立IP
+- Pod间通信不经过NAT
+- 所有Node和所有Pod间可以直接通信
+```
+
+---
+
+### Q10: Headless Service 是什么?什么场景用?
+
+**回答**:
+```
+clusterIP: None,DNS直接返回Pod IP而不是ClusterIP。
+
+使用场景:
+1. StatefulSet:需要每个Pod有稳定独立的DNS记录
+ mysql-0.mysql-headless → 10.244.1.5
+ mysql-1.mysql-headless → 10.244.2.3
+
+2. 自定义负载均衡:客户端自己选择连接哪个Pod
+```
+
+---
+
+## 四、配置与存储篇
+
+### ⭐ Q11: ConfigMap 和 Secret 的区别
+
+**一句话回答**:ConfigMap 存非敏感配置(明文),Secret 存敏感信息(base64编码,但不是加密)。
+
+```
+ConfigMap:
+ - 明文存储
+ - 环境变量、配置文件
+ - 例:DB地址、日志级别
+
+Secret:
+ - base64编码存储(注意:不是加密!)
+ - 支持etcd加密(需要配置EncryptionConfiguration)
+ - 需要严格RBAC控制
+ - 例:密码、Token、证书
+ - 特殊类型:dockerconfigjson(镜像仓库凭证)、tls(证书)
+
+面试加分:提到 base64 不等于加密,生产用 Vault/External Secrets
+```
+
+---
+
+### Q12: PV、PVC、StorageClass 的关系
+
+**一句话回答**:StorageClass 定义存储类型,PVC 申请存储,PV 提供存储。动态供应时 PVC + StorageClass 自动创建 PV。
+
+```
+静态供应:
+ 管理员手动创建 PV → PVC 绑定 PV → Pod 使用 PVC
+
+动态供应(推荐):
+ PVC 指定 StorageClass → Provisioner 自动创建 PV → Pod 使用 PVC
+
+accessModes:
+ - ReadWriteOnce (RWO): 单节点读写,最常用
+ - ReadOnlyMany (ROX): 多节点只读
+ - ReadWriteMany (RWX): 多节点读写(NFS等)
+
+reclaimPolicy:
+ - Retain: 保留数据,手动清理
+ - Delete: 自动删除
+```
+
+---
+
+## 五、Helm 篇
+
+### ⭐ Q13: 什么是 Helm?有什么优势?
+
+**一句话回答**:Helm 是 K8S 的包管理器,将多个 K8S 资源打包为一个 Chart,支持模板化配置、版本管理和一键部署。
+
+```
+核心优势:
+1. 一条命令部署(vs kubectl apply 多个文件)
+2. 模板化配置(values.yaml 参数化)
+3. 多环境管理(values-dev.yaml / values-prod.yaml)
+4. 内置升级/回滚(helm upgrade / rollback)
+5. 版本历史(helm history)
+6. 生态丰富(公共 Chart 仓库)
+
+核心概念:
+- Chart: K8S 应用的包(模板 + 默认值)
+- Release: Chart 的一个运行实例
+- Repository: Chart 仓库
+- Values: 自定义配置值
+```
+
+---
+
+## 六、调度与弹性伸缩篇(⭐ 新增)
+
+### ⭐ Q14: nodeAffinity 和 podAffinity 的区别?
+
+**一句话回答**:nodeAffinity 控制 Pod 和 Node 的关系(Pod 选择 Node),podAffinity 控制 Pod 和 Pod 的关系(Pod 靠近/远离其他 Pod)。
+
+```
+nodeAffinity(节点亲和性):
+ - 关注:Pod → Node
+ - 基于:Node 的标签
+ - 不需要 topologyKey
+ - 场景:Pod 必须调度到 SSD Node / GPU Node
+
+podAffinity / podAntiAffinity(Pod 亲和/反亲和):
+ - 关注:Pod → Pod
+ - 基于:已有 Pod 的标签
+ - 必须指定 topologyKey
+ - 场景:前端靠近缓存 / API 副本分散部署(高可用)
+
+topologyKey 常用值:
+ - kubernetes.io/hostname → 同一 Node
+ - topology.kubernetes.io/zone → 同一可用区
+
+硬 vs 软:
+ - required = 必须满足,不满足 Pending
+ - preferred = 偏好满足,不满足也能调度
+```
+
+---
+
+### ⭐ Q15: Taint(污点)和 Toleration(容忍度)是什么?
+
+**一句话回答**:Taint 是 Node 上的排斥标记,Toleration 是 Pod 声明"我能容忍某些污点"。
+
+```
+三个 effect:
+ NoSchedule → 新 Pod 不调度(已有不受影响)
+ PreferNoSchedule → 尽量不调度
+ NoExecute → 不调度 + 驱逐不匹配的已有 Pod
+
+典型场景:
+ - Master 节点隔离(默认有 control-plane:NoSchedule 污点)
+ - GPU 专用节点(只有 GPU 任务能调度)
+ - Node 故障自动驱逐(node.kubernetes.io/not-ready:NoExecute)
+
+Taint vs nodeAffinity:
+ - Taint = Node 排斥 Pod("闲人免进")
+ - nodeAffinity = Pod 选择 Node("我想进这房间")
+ - 两者可组合使用
+```
+
+---
+
+### ⭐ Q16: HPA 的工作原理是什么?
+
+**一句话回答**:HPA(Horizontal Pod Autoscaler)根据 Pod 的 CPU/内存等指标自动调整 Deployment 的副本数。
+
+```
+工作流程:
+ 1. Metrics Server 采集 Pod 的资源使用数据
+ 2. HPA Controller 每 15-30 秒检查
+ 3. 计算期望副本数 = ceil(当前指标总量 / 目标指标值)
+ 4. 调整 Deployment 的 replicas
+
+前提条件:
+ - 必须安装 Metrics Server
+ - Pod 必须配置 resources.requests(HPA 基于此计算百分比)
+
+扩缩行为控制(behavior):
+ scaleDown.stabilizationWindowSeconds → 缩容稳定窗口(避免抖动)
+ scaleUp → 扩容策略
+
+面试加分:
+ - HPA 是"水平"扩缩(加减副本数),VPA 是"垂直"扩缩(调整 requests)
+ - Cluster Autoscaler 是节点级别(加减 Node)
+ - 三者组成完整的三级自动伸缩体系
+```
+
+---
+
+## 七、安全篇(⭐ 新增)
+
+### ⭐ Q17: RBAC 是什么?核心概念?
+
+**一句话回答**:RBAC 是 K8S 的授权机制,通过 Role 定义权限、RoleBinding 绑定到主体(User/Group/ServiceAccount)。
+
+```
+四个核心对象:
+ Role → Namespace 级别权限规则
+ ClusterRole → 集群级别权限规则(也可被 RoleBinding 引用)
+ RoleBinding → 绑定 Role/ClusterRole 到主体(Namespace 内)
+ ClusterRoleBinding → 绑定 ClusterRole 到主体(全集群)
+
+主体(subjects):
+ User → 外部用户
+ Group → 用户组
+ ServiceAccount → Pod 的身份(最常用)
+
+最佳实践:
+ ✅ 每个应用创建独立 ServiceAccount
+ ✅ 最小权限原则(只授予必要的 API 操作)
+ ✅ Secret 访问权限单独控制
+ ✅ 能用 RoleBinding 就不用 ClusterRoleBinding
+```
+
+---
+
+### ⭐ Q17-A: RoleBinding 引用 ClusterRole 和 ClusterRoleBinding 有什么区别?
+
+**一句话回答**:权限范围由 Binding 决定,不由 Role 决定。RoleBinding 无论引用 Role 还是 ClusterRole,都只在 RoleBinding 所在的 Namespace 生效。
+
+```
+三种组合方式:
+
+① Role + RoleBinding(同 Namespace)
+ 作用范围:RoleBinding 所在 Namespace
+
+② ClusterRole + RoleBinding(跨 NS 复用规则)
+ 作用范围:⭐ 仅 RoleBinding 所在 Namespace
+ 优点:ClusterRole 作为"规则模板",避免每个 NS 重复定义相同的 Role
+
+③ ClusterRole + ClusterRoleBinding(全集群)
+ 作用范围:整个集群
+
+面试加分回答:
+ "内置的 ClusterRole(如 view/edit/admin)就是利用这个机制复用的。
+ 给某个团队的 SA 授予某个 Namespace 的 edit 权限时,
+ 我会用 RoleBinding 引用内置 ClusterRole:edit,而非自己写 Role,
+ 这样既复用了内置权限规则,又不扩大到全集群。"
+```
+
+---
+
+### ⭐ Q17-B: 写 Role 时如何确定 apiGroups 填什么?
+
+**一句话回答**:用 `kubectl api-resources` 查资源所属的 API Version,取 `/` 前的部分就是 apiGroup。
+
+```
+核心速查表:
+ apiGroups: [""] → Pod、Service、ConfigMap、Secret、Node、
+ PVC、Namespace、Endpoints、Events
+ apiGroups: ["apps"] → Deployment、StatefulSet、DaemonSet、ReplicaSet
+ apiGroups: ["batch"] → Job、CronJob
+ apiGroups: ["autoscaling"] → HorizontalPodAutoscaler
+ apiGroups: ["networking.k8s.io"] → Ingress、NetworkPolicy
+ apiGroups: ["rbac.authorization.k8s.io"] → Role、RoleBinding、
+ ClusterRole、ClusterRoleBinding
+ apiGroups: ["storage.k8s.io"] → StorageClass
+
+验证命令:
+ kubectl api-resources | grep -E "^deployments"
+ # NAME SHORTNAMES APIVERSION NAMESPACED KIND
+ # deployments deploy apps/v1 true Deployment
+ # ↑
+ # apiGroup = "apps"
+```
+
+---
+
+### Q17-C: 子资源(subresource)权限是什么?常见的有哪些?
+
+**一句话回答**:子资源是某个资源的特定操作入口,需要单独授权,格式为 `resources: ["父资源/子资源"]`。
+
+```
+常见子资源速查:
+ pods/log → kubectl logs(需要 get 权限)
+ pods/exec → kubectl exec(需要 create 权限)
+ pods/portforward → kubectl port-forward(需要 create 权限)
+ pods/status → 更新 Pod 状态
+ deployments/scale → kubectl scale(需要 update 权限)
+
+错误示例(常见坑):
+ 你授权了 pods 的 get 权限,但 kubectl logs 仍然报 403。
+ 原因:logs 是子资源,需要单独授权 pods/log。
+
+正确配置:
+ rules:
+ - apiGroups: [""]
+ resources: ["pods"] # Pod 本体
+ verbs: ["get", "list"]
+ - apiGroups: [""]
+ resources: ["pods/log"] # 日志子资源(单独授权)
+ verbs: ["get"]
+ - apiGroups: [""]
+ resources: ["pods/exec"] # exec 子资源(单独授权)
+ verbs: ["create"]
+```
+
+---
+
+### Q17-D: 如何排查 RBAC 权限问题?
+
+**一句话回答**:用 `kubectl auth can-i` 验证权限,用 `describe` 查找绑定关系。
+
+```
+排查步骤:
+
+1. 确认报错类型
+ Error: pods is forbidden: User "system:serviceaccount:ns:app-sa"
+ cannot list resource "pods" in API group "" in the namespace "ns"
+
+2. 验证权限
+ kubectl auth can-i list pods \
+ --as=system:serviceaccount:ns:app-sa -n ns
+ # → no
+
+3. 列出该 SA 的所有权限
+ kubectl auth can-i --list \
+ --as=system:serviceaccount:ns:app-sa -n ns
+
+4. 找到所有绑定该 SA 的 RoleBinding
+ kubectl get rolebindings,clusterrolebindings -A \
+ -o wide | grep "app-sa"
+
+5. 查看 Role/ClusterRole 的规则
+ kubectl describe role -n ns
+
+6. 添加缺失权限
+ kubectl edit role -n ns
+```
+
+---
+
+### Q18: NetworkPolicy 的作用?默认行为是什么?
+
+**回答**:
+```
+作用:控制 Pod 之间的网络流量(入站 Ingress / 出站 Egress)
+
+默认行为:
+ - 没有任何 NetworkPolicy → 所有 Pod 之间网络完全互通
+
+创建 NetworkPolicy 后:
+ - 只影响匹配 podSelector 的 Pod
+ - 只影响 policyTypes 中指定的方向(Ingress/Egress)
+ - 规则是叠加的(多个 Policy 取并集)
+
+注意:
+ - 需要支持 NetworkPolicy 的 CNI(Calico、Cilium 支持,Flannel 不支持)
+ - 限制出站时别忘了放行 DNS(UDP 53)
+ - 推荐白名单模式:先 deny-all,再逐条允许
+```
+
+---
+
+### Q19: Pod 安全最佳实践有哪些?
+
+**回答**:
+```
+SecurityContext:
+ ✅ runAsNonRoot: true → 禁止 root 运行
+ ✅ allowPrivilegeEscalation: false → 禁止提权
+ ✅ readOnlyRootFilesystem: true → 根文件系统只读
+ ✅ drop ALL capabilities → 丢弃所有 Linux capabilities
+ ✅ seccompProfile: RuntimeDefault → 系统调用过滤
+
+PodSecurity Standards:
+ - Baseline:基本安全(防止提权)
+ - Restricted:严格安全(推荐生产使用)
+
+镜像安全:
+ ✅ 使用非 root 基础镜像
+ ✅ 定期扫描镜像漏洞(Trivy)
+ ✅ 使用最小化基础镜像(distroless/Alpine)
+ ✅ 固定镜像 tag(不用 latest)
+
+RBAC:
+ ✅ 不使用 default ServiceAccount
+ ✅ 每个 Pod 使用独立 SA
+ ✅ 最小权限原则
+```
+
+---
+
+## 八、实战场景篇
+
+### ⭐ Q20: 如何实现零停机部署?
+
+**回答**:
+```
+1. Deployment 滚动更新策略
+ strategy:
+ type: RollingUpdate
+ rollingUpdate:
+ maxSurge: 1 # 允许最多多1个Pod
+ maxUnavailable: 0 # 不允许任何Pod不可用
+
+2. readinessProbe 保护
+ - 新Pod通过readinessProbe后才接入流量
+ - 老Pod在新Pod就绪前继续服务
+
+3. 优雅关闭
+ - preStop hook: sleep 10(等待连接排空)
+ - terminationGracePeriodSeconds: 30
+
+4. Ingress/Service 层面
+ - 多个版本并行运行
+ - 金丝雀发布(Canary)
+
+5. 健康检查端点
+ - 确保应用真正就绪后才报告healthy
+```
+
+---
+
+### ⭐ Q21: Pod 常见异常状态怎么排查?
+
+**回答**:
+```
+通用排查公式:
+ kubectl get pods → 看状态
+ kubectl describe pod → 看 Events
+ kubectl logs [--previous] → 看日志
+ kubectl top pod → 看资源
+
+Pending → 资源不足 / 亲和性不满足 / PVC 未绑定
+ → kubectl describe pod → Events: 0/N nodes available
+
+CrashLoopBackOff → 应用崩溃反复重启
+ → kubectl logs --previous(看上次崩溃日志)
+ → kubectl describe pod → Last State.Terminated
+
+ImagePullBackOff → 镜像拉取失败
+ → kubectl describe pod → Events: Failed to pull image
+ → 检查 imagePullSecrets
+
+OOMKilled → 内存超限
+ → kubectl describe pod → Last State: OOMKilled
+ → 增大 limits.memory
+
+Running 但 Not Ready → readinessProbe 未通过
+ → 检查 readinessProbe 配置
+ → 增加 initialDelaySeconds
+ → 配置 startupProbe
+```
+
+---
+
+### Q22: 如何进行容器资源调优?
+
+**回答**:
+```
+1. 资源估算
+ - 压测确定应用的实际需求
+ - 使用 VPA(Vertical Pod Autoscaler)辅助推荐
+
+2. CPU 建议
+ - requests: 正常负载的 70-80%
+ - limits: requests 的 2-4 倍
+ - 利用 CPU 的可压缩性(超限只会限流,不会杀死)
+
+3. 内存建议
+ - requests ≈ limits(避免 OOM Kill)
+ - 预留 20-30% buffer
+ - 内存不可压缩,超限直接 OOM Kill
+
+4. JVM 应用特别注意
+ - -Xmx 应小于 limits.memory 的 80%
+ - 留出堆外内存空间
+
+5. 监控
+ - 使用 Prometheus + Grafana 监控资源使用率
+ - 设置 HPA(水平自动扩缩)
+```
+
+---
+
+### Q23: K8S 中如何实现蓝绿部署和金丝雀发布?
+
+**蓝绿部署**:
+```
+1. 部署 v2 Deployment(使用不同 label)
+2. 更新 Service selector 指向 v2
+3. 如果出问题,Service selector 切回 v1
+
+特点:切换即时,但需要双倍资源
+```
+
+**金丝雀发布**(Canary):
+```
+1. v2 Deployment 只有 1 个副本
+2. 逐步增加 v2 比例(10% → 50% → 100%)
+3. 观察错误率和性能指标
+4. 有问题立即回滚
+
+工具:Flagger、Argo Rollouts、Istio
+```
+
+---
+
+## 九、进阶题
+
+### Q24: K8S 的控制循环(Reconcile Loop)是什么?
+
+**回答**:
+```
+控制循环是 K8S 控制器模式的核心:
+1. 控制器观察(Watch)API Server 中的资源状态
+2. 对比"期望状态"(YAML中定义)和"实际状态"(集群中真实的)
+3. 如果不一致,执行动作使实际状态趋近期望状态
+4. 循环重复
+
+类比:恒温器——你设定 25°C(期望状态),恒温器不断检测室温(实际状态),低于 25°C 就开暖气。
+
+每个控制器都有自己的控制循环:
+- Deployment Controller:对比期望副本数 vs 实际 Pod 数
+- Node Controller:监控 Node 健康状态
+- Service Controller:维护 Endpoints
+```
+
+---
+
+### Q25: Pod 一直处于 Pending,如何排查?
+
+**回答**:
+```
+1. kubectl describe pod
+2. 查看 Events 部分:
+ - "Insufficient cpu" → Node CPU 不足
+ - "Insufficient memory" → Node 内存不足
+ - "didn't match Pod's node affinity" → 亲和性不满足
+ - "node(s) had taints that the pod didn't tolerate" → 污点不匹配
+ - "persistentvolumeclaim not found/not bound" → PVC 问题
+
+3. 逐一检查:
+ → kubectl describe node → 看 Allocatable vs Capacity
+ → kubectl get nodes --show-labels → 检查标签
+ → kubectl describe node | grep Taints → 检查污点
+ → kubectl get pvc → 检查 PVC 状态
+ → 检查 podAntiAffinity 是否有足够的 Node
+```
+
+---
+
+### Q26: etcd 和 API Server 的关系?为什么所有组件通过 API Server 通信?
+
+**回答**:
+```
+关系:
+ - etcd = 数据库(存储集群所有状态)
+ - API Server = 统一入口(唯一操作 etcd 的组件)
+
+为什么所有组件通过 API Server 通信而非直接访问 etcd:
+ 1. 安全:etcd 不暴露给外部,通过 API Server 做认证/授权
+ 2. 解耦:组件不需要知道 etcd 的拓扑和配置
+ 3. 审计:所有操作经过 API Server,可以记录审计日志
+ 4. 一致性:API Server 提供乐观并发控制(resourceVersion)
+ 5. 变更通知:组件通过 Watch API Server 而非 Watch etcd
+```
+
+---
+
+### Q27: 滚动更新卡住了怎么排查?
+
+**回答**:
+```
+1. kubectl rollout status deployment/ → 确认卡在哪
+2. kubectl get pods → 看新/旧 Pod 状态
+
+新 Pod Not Ready?
+ → readinessProbe 未通过
+ → 检查 initialDelaySeconds 是否够
+ → kubectl logs
+
+旧 Pod 一直 Terminating?
+ → preStop hook 阻塞
+ → 应用没处理 SIGTERM 信号
+ → terminationGracePeriodSeconds 不够
+
+解决:
+ → kubectl rollout undo deployment/ → 回滚
+ → 修复问题后重新触发更新
+```
+
+---
+
+## 🎯 模拟面试(JD:能使用 K8S 进行部署)
+
+### 面试官可能的问题顺序
+
+1. **自我介绍后**:你用过 K8S 吗?简单说说 K8S 的架构。
+2. **深入基础**:Pod 和容器的关系?什么是探针?
+3. **资源管理**:requests 和 limits?资源不够会怎样?
+4. **网络通信**:Service 的类型?Pod 之间怎么通信?
+5. **配置管理**:怎么管理配置?ConfigMap 和 Secret 区别?
+6. **调度**:Pod 怎么被调度到 Node?亲和性和污点了解吗?
+7. **弹性伸缩**:怎么自动扩缩?HPA 怎么配?
+8. **实际部署**:你平时怎么部署应用到 K8S?用过 Helm 吗?
+9. **安全**:RBAC 了解吗?怎么控制 Pod 权限?
+10. **故障排查**:Pod 起不来你怎么排查?什么流程?
+11. **进阶**:怎么实现零停机部署?滚动更新策略?
+
+---
+
+**上一章**:[第9章:运维与故障排查](./09-operations.md) | **实战**:[第6章:部署实战](./06-practice-project.md)
diff --git a/docs/posts/k8s-training/index.md b/docs/posts/k8s-training/index.md
new file mode 100644
index 0000000..f47d878
--- /dev/null
+++ b/docs/posts/k8s-training/index.md
@@ -0,0 +1,112 @@
+---
+title: "K8S 培训 - 从零到面试通关"
+date: "2026-05-15"
+category: "k8s"
+tags:
+ - "k8s"
+ - "AI生成"
+---
+
+# K8S 培训 - 从零到面试通关
+
+## 📋 培训大纲
+
+| 章节 | 主题 | 核心知识点 |
+|------|------|-----------|
+| [第1章](./01-k8s-architecture.md) | K8S 核心概念与架构 | Master/Worker 组件、资源对象、声明式API |
+| [第2章](./02-pod-and-workloads.md) | Pod 与工作负载管理 | Pod生命周期、探针、Deployment/StatefulSet/DaemonSet |
+| [第3章](./03-service-networking.md) | Service 与网络 | 4种Service类型、DNS服务发现、Ingress |
+| [第4章](./04-config-storage.md) | ConfigMap、Secret 与存储 | 配置管理、PV/PVC、StorageClass |
+| [第5章](./05-helm.md) | Helm 包管理 | Chart、Release、values模板化 |
+| [第6章](./practice/README.md) | 综合部署实战 | Helm Chart + 一键部署脚本 |
+| [第7章](./07-scheduling-autoscaling.md) | 调度与弹性伸缩 | nodeAffinity、podAffinity/AntiAffinity、Taint/Toleration、HPA/VPA |
+| [第8章](./08-security.md) | 安全与权限 | RBAC、NetworkPolicy、SecurityContext、ResourceQuota |
+| [第9章](./09-operations.md) | 运维与故障排查 | kubectl 高级用法、Events、日志体系、排障流程 |
+| [第10章](./10-interview-questions.md) | 面试高频题 | 27道必考题 + 答案模板 + 模拟面试流程 |
+
+## 🎯 学习路径
+
+```
+第1天:第1章 + 第2章(架构 + Pod) → 理解 K8S 是什么
+第2天:第3章 + 第4章(网络 + 存储) → 掌握核心资源
+第3天:第5章 + 第6章实战(Helm + 实操部署) → 学会工具化部署
+第4天:第7章 + 第8章(调度 + 安全) → 掌握进阶能力
+第5天:第9章 + 第10章(运维 + 面试题) → 冲刺面试
+```
+
+## 📌 章节关系图
+
+```mermaid
+flowchart LR
+ subgraph Basic["基础篇(第1-4章)"]
+ B1["1. 架构"]
+ B2["2. Pod/工作负载"]
+ B3["3. Service/网络"]
+ B4["4. 配置/存储"]
+ end
+
+ subgraph Tools["工具篇(第5-6章)"]
+ T1["5. Helm 包管理"]
+ T2["6. 综合实战部署"]
+ end
+
+ subgraph Advanced["进阶篇(第7-10章)"]
+ A1["7. 调度与弹性伸缩"]
+ A2["8. 安全与权限"]
+ A3["9. 运维与故障排查"]
+ A4["10. 面试高频题"]
+ end
+
+ Basic -->|"→"| Tools
+ Tools -->|"→"| Advanced
+```
+
+> **学习路径图**
+
+## 🔧 环境准备
+
+```bash
+# 设置集群连接
+export KUBECONFIG=~/.kube/config.yaml
+
+# 创建训练 namespace
+kubectl create namespace k8s-training
+
+# 练习完清理
+kubectl delete namespace k8s-training
+```
+
+## ⚠️ 重要提醒
+
+**Sealos 是付费集群!** 所有实操练习完成后务必清理资源。
+
+- 每章练习完后执行 `kubectl delete namespace k8s-training`
+- 实战项目完成后执行 `./practice/cleanup.sh`
+
+## 📚 面试能力自测
+
+学习完成后,检查以下问题是否都能回答:
+
+### 基础必会(JD:能使用 K8S 进行部署)
+
+- [ ] 能画出 K8S 架构图(Master + Worker)
+- [ ] 能说出 Pod 创建的完整流程
+- [ ] 能手写 Deployment YAML(含探针和资源限制)
+- [ ] 能解释 4 种 Service 类型的区别和使用场景
+- [ ] 能说出 ConfigMap 和 Secret 的区别
+- [ ] 能说出 requests 和 limits 的区别
+- [ ] 能说出 liveness/readiness/startup 探针的区别
+- [ ] 能解释 RollingUpdate 策略
+- [ ] 能使用 Helm 部署和回滚应用
+- [ ] 能排查 Pod 常见异常(Pending/CrashLoopBackOff)
+
+### 进阶加分(面试脱颖而出)
+
+- [ ] 能解释 nodeAffinity 和 podAffinity 的区别和使用场景
+- [ ] 能解释 Taint/Toleration 的工作机制和三种 effect
+- [ ] 能配置 HPA 实现自动扩缩
+- [ ] 能说出 RBAC 的四个核心对象
+- [ ] 能解释 NetworkPolicy 的白名单安全策略
+- [ ] 能说出 Pod 安全最佳实践(SecurityContext)
+- [ ] 能说出三级自动伸缩体系(HPA/VPA/CA)
+- [ ] 能描述完整的故障排查流程
diff --git a/docs/posts/k8s-training/practice/README.md b/docs/posts/k8s-training/practice/README.md
new file mode 100644
index 0000000..cf535dc
--- /dev/null
+++ b/docs/posts/k8s-training/practice/README.md
@@ -0,0 +1,119 @@
+---
+title: "K8S培训综合实战项目"
+date: "2026-05-15"
+category: "k8s"
+tags:
+ - "k8s"
+ - "AI生成"
+---
+
+# K8S培训综合实战项目
+
+## 项目概述
+
+这是一个覆盖 K8S 所有核心知识点的完整部署系统,使用 Helm Chart 打包,一条命令部署。
+
+## 涉及的知识点清单
+
+| 知识点 | 对应文件 | 说明 |
+|--------|---------|------|
+| Namespace | deploy.sh | 多租户资源隔离 |
+| Deployment | templates/deployment.yaml | Pod 管理、副本数、资源限制 |
+| Liveness Probe | templates/deployment.yaml | 容器存活检测 |
+| Readiness Probe | templates/deployment.yaml | 流量就绪检测 |
+| RollingUpdate | templates/deployment.yaml | 零停机更新策略 |
+| Service (ClusterIP) | templates/service.yaml | 内部负载均衡 |
+| Endpoints | 自动生成 | Service 与 Pod 的关联 |
+| ConfigMap | templates/configmap.yaml | 非敏感配置管理 |
+| Secret | templates/secret.yaml | 敏感信息管理(base64) |
+| Ingress | templates/ingress.yaml | HTTP 路由(可选) |
+| PVC | templates/pvc.yaml | 持久化存储(可选) |
+| Helm Chart | Chart.yaml + values.yaml | 包管理、模板化 |
+| Helm Install/Upgrade/Rollback | deploy.sh | 生命周期管理 |
+| DNS 服务发现 | deploy.sh 验证步骤 | CoreDNS 自动解析 |
+| 资源限制 (requests/limits) | values.yaml | Pod 调度和运行时资源控制 |
+
+## 使用方法
+
+### 1. 部署
+
+```bash
+cd k8s-training/practice
+chmod +x deploy.sh cleanup.sh
+./deploy.sh
+```
+
+### 2. 手动操作练习
+
+```bash
+export KUBECONFIG=~/.kube/config
+NS=k8s-training
+
+# 查看 Pod
+kubectl get pods -n $NS
+
+# 查看日志
+kubectl logs -f deployment/training-app -n $NS
+
+# 端口转发测试
+kubectl port-forward svc/training-app 8080:80 -n $NS
+# 另一个终端访问:curl http://localhost:8080
+
+# 扩缩容
+kubectl scale deployment training-app --replicas=5 -n $NS
+
+# 进入 Pod
+kubectl exec -it deployment/training-app -n $NS -- sh
+
+# 查看 ConfigMap
+kubectl get configmap training-app-config -o yaml -n $NS
+
+# 查看 Secret(解码)
+kubectl get secret training-app-secret -o jsonpath='{.data.DB_PASSWORD}' -n $NS | base64 -d
+```
+
+### 3. Helm 操作练习
+
+```bash
+# 查看渲染后的 YAML
+helm template training-app ./helm-chart -n $NS
+
+# 升级(修改值)
+helm upgrade training-app ./helm-chart -n $NS --set replicaCount=5
+
+# 查看历史
+helm history training-app -n $NS
+
+# 回滚
+helm rollback training-app 1 -n $NS
+
+# 查看 Release 状态
+helm status training-app -n $NS
+```
+
+### 4. 清理(⚠️ 必做!付费集群!)
+
+```bash
+./cleanup.sh
+```
+
+## 目录结构
+
+```
+practice/
+├── helm-chart/
+│ ├── Chart.yaml # Chart 元数据
+│ ├── values.yaml # 默认配置(副本数、镜像、资源限制等)
+│ └── templates/
+│ ├── _helpers.tpl # 模板辅助函数
+│ ├── deployment.yaml # Deployment + 探针 + 资源限制
+│ ├── service.yaml # ClusterIP Service
+│ ├── configmap.yaml # ConfigMap
+│ ├── secret.yaml # Secret
+│ ├── ingress.yaml # Ingress(可选)
+│ ├── pvc.yaml # PVC(可选)
+│ └── NOTES.txt # 安装后提示
+├── deploy.sh # 一键部署 & 验证脚本
+├── cleanup.sh # 清理脚本
+└── README.md # 本文件
+```
diff --git a/docs/posts/k8s-training/practice/cleanup.sh b/docs/posts/k8s-training/practice/cleanup.sh
new file mode 100755
index 0000000..1fe1365
--- /dev/null
+++ b/docs/posts/k8s-training/practice/cleanup.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+# ===========================================
+# K8S培训综合实战 - 清理脚本
+# ===========================================
+
+set -e
+
+KUBECONFIG_PATH="${KUBECONFIG:-$HOME/.kube/config}"
+NS="k8s-training"
+RELEASE_NAME="training-app"
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+info() { echo -e "${GREEN}[INFO]${NC} $1"; }
+ok() { echo -e "${GREEN}[OK]${NC} $1"; }
+warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
+
+export KUBECONFIG=$KUBECONFIG_PATH
+
+echo "================================================"
+echo " 🔧 清理 K8S 培训资源"
+echo "================================================"
+echo ""
+
+# 1. 卸载 Helm Release
+info "卸载 Helm Release: $RELEASE_NAME"
+if helm list -n $NS 2>/dev/null | grep -q $RELEASE_NAME; then
+ helm uninstall $RELEASE_NAME -n $NS
+ ok "Release 已卸载"
+else
+ warn "Release 不存在,跳过"
+fi
+
+# 2. 删除 Namespace(会删除该 NS 下所有资源)
+info "删除 Namespace: $NS"
+if kubectl get ns $NS > /dev/null 2>&1; then
+ kubectl delete namespace $NS --timeout=60s
+ ok "Namespace 已删除"
+else
+ warn "Namespace 不存在,跳过"
+fi
+
+# 3. 确认清理干净
+echo ""
+info "验证清理结果:"
+REMAINING=$(kubectl get all -n $NS 2>&1 || true)
+if echo "$REMAINING" | grep -q "No resources"; then
+ ok "所有资源已清理干净 ✅"
+elif echo "$REMAINING" | grep -qi "not found"; then
+ ok "Namespace 已完全删除 ✅"
+else
+ warn "可能还有残留资源,请手动检查:"
+ echo "$REMAINING"
+fi
+
+echo ""
+echo "================================================"
+ok "清理完成!"
+echo "================================================"
diff --git a/docs/posts/k8s-training/practice/deploy.sh b/docs/posts/k8s-training/practice/deploy.sh
new file mode 100755
index 0000000..c75fbc0
--- /dev/null
+++ b/docs/posts/k8s-training/practice/deploy.sh
@@ -0,0 +1,289 @@
+#!/bin/bash
+# ===========================================
+# K8S培训综合实战 - 一键部署 & 验证脚本
+# 覆盖所有知识点:
+# 1. Namespace 隔离
+# 2. Deployment(Pod + 资源限制 + 探针 + 滚动更新)
+# 3. Service(ClusterIP + DNS服务发现)
+# 4. ConfigMap(非敏感配置)
+# 5. Secret(敏感信息)
+# 6. Helm 包管理(安装/升级/回滚)
+# ===========================================
+
+set -e
+
+KUBECONFIG_PATH="${KUBECONFIG:-$HOME/.kube/config}"
+NS="k8s-training"
+RELEASE_NAME="training-app"
+CHART_DIR="./helm-chart"
+
+# 颜色输出
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+CYAN='\033[0;36m'
+NC='\033[0m' # No Color
+
+info() { echo -e "${BLUE}[INFO]${NC} $1"; }
+ok() { echo -e "${GREEN}[OK]${NC} $1"; }
+warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
+fail() { echo -e "${RED}[FAIL]${NC} $1"; }
+step() { echo -e "\n${CYAN}📌 $1${NC}"; }
+
+echo "================================================"
+echo " K8S培训综合实战 - 部署 & 验证"
+echo "================================================"
+echo ""
+
+# 检查 KUBECONFIG
+info "检查集群连接..."
+export KUBECONFIG=$KUBECONFIG_PATH
+if kubectl cluster-info > /dev/null 2>&1; then
+ ok "集群连接成功"
+else
+ warn "无法获取集群信息,但继续尝试..."
+fi
+
+# ==========================================
+# 步骤0:权限检查
+# ==========================================
+step "步骤0:权限检查"
+echo " 知识点:RBAC(基于角色的访问控制)"
+echo ""
+
+CAN_CREATE_NS=$(kubectl auth can-i create namespaces 2>/dev/null || echo "no")
+CAN_CREATE_POD=$(kubectl auth can-i create pods -n $NS 2>/dev/null || echo "no")
+CAN_CREATE_DEPLOY=$(kubectl auth can-i create deployments -n $NS 2>/dev/null || echo "no")
+
+if [ "$CAN_CREATE_NS" = "no" ] && [ "$CAN_CREATE_POD" = "no" ]; then
+ warn "当前用户没有创建 namespace 和 pod 的权限"
+ echo ""
+ echo " 当前用户:$(kubectl config view --minify 2>/dev/null | grep 'namespace\|user' || echo 'unknown')"
+ echo ""
+ info "解决方案:"
+ echo " 1. 使用完整的 kubeconfig 文件(含管理员凭证)"
+ echo " 2. 联系集群管理员授予相应权限"
+ echo " 3. 使用 minikube/kind 搭建本地练习集群"
+ echo ""
+ info "本地练习集群推荐:"
+ echo " # macOS"
+ echo " brew install kind"
+ echo " kind create cluster --name k8s-training"
+ echo " export KUBECONFIG=\$(kind get kubeconfig-path --name k8s-training)"
+ echo ""
+ echo " # 或使用 minikube"
+ echo " brew install minikube"
+ echo " minikube start --cpus 2 --memory 4096"
+ echo ""
+ echo " # 或使用 Docker Desktop 自带的 K8S"
+ echo " # 设置 → Kubernetes → Enable Kubernetes"
+ echo ""
+
+ # 即使没有实际集群权限,仍然展示学习内容
+ info "虽然无法实际部署,但可以查看所有模板和配置:"
+ echo ""
+ info "📋 Helm Chart 模板渲染(知识点:helm template):"
+ if [ -d "$CHART_DIR" ]; then
+ helm template $RELEASE_NAME $CHART_DIR --namespace $NS 2>/dev/null || echo " (需要安装 helm)"
+ fi
+ echo ""
+ ok "培训材料已准备就绪,建议在本地 K8S 集群上实操"
+ exit 0
+fi
+
+ok "权限检查通过,继续部署..."
+
+# ==========================================
+# 步骤1:创建 Namespace
+# 知识点:多租户隔离
+# ==========================================
+step "步骤1:创建 Namespace"
+echo " 知识点:Namespace 用于多租户隔离、资源分组"
+echo ""
+
+if kubectl get ns $NS > /dev/null 2>&1; then
+ warn "Namespace $NS 已存在,跳过创建"
+else
+ kubectl create namespace $NS
+ ok "Namespace $NS 创建成功"
+fi
+
+# ==========================================
+# 步骤2:使用 Helm 安装应用
+# 知识点:Helm Chart 一键部署
+# ==========================================
+step "步骤2:Helm 安装应用"
+echo " 知识点:Helm = K8S 包管理器,一条命令部署所有资源"
+echo " 将创建:Deployment + Service + ConfigMap + Secret"
+echo ""
+
+cd "$(dirname "$0")"
+
+# 先渲染模板看看(知识点:helm template)
+info "渲染 Helm 模板..."
+helm template $RELEASE_NAME $CHART_DIR --namespace $NS > /tmp/training-rendered.yaml
+ok "模板渲染成功,查看渲染结果: cat /tmp/training-rendered.yaml"
+
+# 安装
+if helm list -n $NS 2>/dev/null | grep -q $RELEASE_NAME; then
+ warn "Release $RELEASE_NAME 已存在,执行升级..."
+ helm upgrade $RELEASE_NAME $CHART_DIR -n $NS
+ ok "Release 升级成功"
+else
+ helm install $RELEASE_NAME $CHART_DIR -n $NS
+ ok "Release $RELEASE_NAME 安装成功"
+fi
+
+# 等待 Pod 就绪
+info "等待 Pod 就绪..."
+kubectl wait --for=condition=ready pod \
+ -l app.kubernetes.io/instance=$RELEASE_NAME \
+ -n $NS --timeout=120s 2>/dev/null || warn "Pod 等待超时,继续验证..."
+
+# ==========================================
+# 步骤3:验证 Deployment
+# ==========================================
+step "步骤3:验证 Deployment"
+echo " 知识点:Pod 副本数、资源限制、探针"
+echo ""
+
+PODS=$(kubectl get pods -n $NS -l app.kubernetes.io/instance=$RELEASE_NAME -o name 2>/dev/null || true)
+if [ -z "$PODS" ]; then
+ warn "没有找到 Pod,可能部署失败"
+else
+ POD_COUNT=$(echo "$PODS" | wc -l | tr -d ' ')
+ info "Pod 数量: $POD_COUNT(期望: 2)"
+ [ "$POD_COUNT" -eq 2 ] && ok "副本数正确" || warn "副本数不完全匹配"
+
+ echo ""
+ info "Pod 状态:"
+ kubectl get pods -n $NS -l app.kubernetes.io/instance=$RELEASE_NAME
+
+ echo ""
+ FIRST_POD=$(echo "$PODS" | head -1)
+ info "资源配置(知识点:requests 和 limits):"
+ kubectl get $FIRST_POD -n $NS -o jsonpath='CPU Request: {.spec.containers[0].resources.requests.cpu}
+CPU Limit: {.spec.containers[0].resources.limits.cpu}
+Mem Request: {.spec.containers[0].resources.requests.memory}
+Mem Limit: {.spec.containers[0].resources.limits.memory}
+'
+ echo ""
+fi
+
+# ==========================================
+# 步骤4:验证 Service
+# ==========================================
+step "步骤4:验证 Service"
+echo " 知识点:ClusterIP Service、标签选择器、Endpoints"
+echo ""
+
+info "Service 信息:"
+kubectl get svc -n $NS -l app.kubernetes.io/instance=$RELEASE_NAME 2>/dev/null || warn "未找到 Service"
+
+echo ""
+info "Endpoints(Service 如何关联 Pod):"
+kubectl get endpoints -n $NS -l app.kubernetes.io/instance=$RELEASE_NAME 2>/dev/null || warn "未找到 Endpoints"
+
+# ==========================================
+# 步骤5:验证 ConfigMap
+# ==========================================
+step "步骤5:验证 ConfigMap"
+echo " 知识点:非敏感配置外部化、配置注入"
+echo ""
+
+info "ConfigMap 内容:"
+kubectl get configmap ${RELEASE_NAME}-config -n $NS -o yaml 2>/dev/null | grep -A 20 "^data:" || \
+ kubectl get configmap -n $NS -o yaml 2>/dev/null || warn "未找到 ConfigMap"
+
+# ==========================================
+# 步骤6:验证 Secret
+# ==========================================
+step "步骤6:验证 Secret"
+echo " 知识点:敏感信息管理(base64编码,非加密!)"
+echo ""
+
+info "Secret keys(隐藏值):"
+kubectl get secret ${RELEASE_NAME}-secret -n $NS -o jsonpath='{.data}' 2>/dev/null | python3 -c "
+import sys, json
+data = json.load(sys.stdin)
+for k in data:
+ print(f' {k}: [HIDDEN - base64 encoded]')
+" 2>/dev/null || kubectl get secret ${RELEASE_NAME}-secret -n $NS 2>/dev/null || warn "未找到 Secret"
+
+info "解码验证(知识点:base64 不是加密):"
+DB_HOST=$(kubectl get secret ${RELEASE_NAME}-secret -n $NS -o jsonpath='{.data.DB_HOST}' 2>/dev/null || true)
+if [ -n "$DB_HOST" ]; then
+ echo -n " DB_HOST 解码: " && echo "$DB_HOST" | base64 -d 2>/dev/null && echo
+ DB_PASS=$(kubectl get secret ${RELEASE_NAME}-secret -n $NS -o jsonpath='{.data.DB_PASSWORD}' 2>/dev/null)
+ echo -n " DB_PASSWORD 解码: " && echo "$DB_PASS" | base64 -d 2>/dev/null && echo
+fi
+warn "⚠️ base64 只是编码不是加密!生产环境需要 etcd 加密或使用 Vault"
+
+# ==========================================
+# 步骤7:DNS 服务发现测试
+# ==========================================
+step "步骤7:DNS 服务发现测试"
+echo " 知识点:CoreDNS 自动为 Service 创建 DNS 记录"
+echo ""
+
+info "创建临时 Pod 测试 DNS 解析..."
+kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never -n $NS -- \
+ sh -c "nslookup ${RELEASE_NAME}.${NS}.svc.cluster.local 2>/dev/null || echo 'DNS test completed'" 2>/dev/null || \
+ warn "DNS 测试跳过(可能权限不足)"
+
+# ==========================================
+# 步骤8:滚动更新 & 回滚演示
+# ==========================================
+step "步骤8:滚动更新 & 回滚"
+echo " 知识点:RollingUpdate 策略、maxSurge、maxUnavailable"
+echo ""
+
+info "执行滚动更新(修改镜像版本 nginx:1.25 → nginx:1.26)..."
+# 记录升级前的版本号,回滚时用它,避免写死 revision 1
+CURRENT_REVISION=$(helm history $RELEASE_NAME -n $NS 2>/dev/null | tail -1 | awk '{print $1}')
+helm upgrade $RELEASE_NAME $CHART_DIR -n $NS --set image.tag=1.26
+info "等待更新完成..."
+sleep 10
+helm history $RELEASE_NAME -n $NS
+
+echo ""
+info "回滚到上一版本(revision $CURRENT_REVISION)..."
+helm rollback $RELEASE_NAME $CURRENT_REVISION -n $NS
+sleep 10
+ok "回滚完成"
+
+echo ""
+info "版本历史:"
+helm history $RELEASE_NAME -n $NS
+
+# ==========================================
+# 验证总结
+# ==========================================
+echo ""
+echo "================================================"
+echo " ✅ 验证总结"
+echo "================================================"
+echo ""
+echo " 已验证的知识点:"
+echo " ✅ Namespace - 多租户资源隔离"
+echo " ✅ Deployment - Pod 管理、副本数、资源限制"
+echo " ✅ Liveness Probe - 存活探针"
+echo " ✅ Readiness Probe - 就绪探针"
+echo " ✅ Service - 内部负载均衡 (ClusterIP)"
+echo " ✅ Endpoints - Service 与 Pod 的关联"
+echo " ✅ ConfigMap - 非敏感配置管理"
+echo " ✅ Secret - 敏感信息管理(base64编码)"
+echo " ✅ DNS 服务发现 - CoreDNS 自动解析"
+echo " ✅ Helm Install - 一键部署"
+echo " ✅ Helm Upgrade - 滚动更新"
+echo " ✅ Helm Rollback - 版本回滚"
+echo " ✅ Helm History - 版本历史"
+echo " ✅ RollingUpdate - 零停机更新策略"
+echo ""
+echo "================================================"
+
+echo "清理命令:"
+echo " cd $(pwd)"
+echo " ./cleanup.sh"
+echo ""
diff --git a/docs/posts/k8s-training/practice/helm-chart/Chart.yaml b/docs/posts/k8s-training/practice/helm-chart/Chart.yaml
new file mode 100644
index 0000000..bfdd700
--- /dev/null
+++ b/docs/posts/k8s-training/practice/helm-chart/Chart.yaml
@@ -0,0 +1,11 @@
+apiVersion: v2
+name: training-app
+description: K8S培训综合实战 - 覆盖所有知识点的完整部署系统
+type: application
+version: 0.1.0
+appVersion: "1.0.0"
+keywords:
+ - k8s-training
+ - practice
+maintainers:
+ - name: devops-team
diff --git a/docs/posts/k8s-training/practice/helm-chart/templates/NOTES.txt b/docs/posts/k8s-training/practice/helm-chart/templates/NOTES.txt
new file mode 100644
index 0000000..58cfaea
--- /dev/null
+++ b/docs/posts/k8s-training/practice/helm-chart/templates/NOTES.txt
@@ -0,0 +1,23 @@
+🎉 训练应用部署成功!
+
+应用信息:
+ Release名称: {{ .Release.Name }}
+ Namespace: {{ .Release.Namespace }}
+ 副本数: {{ .Values.replicaCount }}
+ 镜像: {{ .Values.image.repository }}:{{ .Values.image.tag }}
+
+访问方式:
+ 集群内部:http://{{ include "training-app.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}
+
+{{- if .Values.ingress.enabled }}
+ 外部访问:http://{{ .Values.ingress.host }}
+{{- end }}
+
+常用命令:
+ 查看Pod: kubectl get pods -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }}
+ 查看日志: kubectl logs -f deployment/{{ include "training-app.fullname" . }} -n {{ .Release.Namespace }}
+ 端口转发: kubectl port-forward svc/{{ include "training-app.fullname" . }} 8080:80 -n {{ .Release.Namespace }}
+ 查看配置: kubectl get configmap {{ include "training-app.fullname" . }}-config -o yaml -n {{ .Release.Namespace }}
+ 查看密钥: kubectl get secret {{ include "training-app.fullname" . }}-secret -o yaml -n {{ .Release.Namespace }}
+
+⚠️ 记得练习完清理资源:helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }}
diff --git a/docs/posts/k8s-training/practice/helm-chart/templates/_helpers.tpl b/docs/posts/k8s-training/practice/helm-chart/templates/_helpers.tpl
new file mode 100644
index 0000000..28e50f6
--- /dev/null
+++ b/docs/posts/k8s-training/practice/helm-chart/templates/_helpers.tpl
@@ -0,0 +1,29 @@
+{{/*
+通用标签
+*/}}
+{{- define "training-app.labels" -}}
+app.kubernetes.io/name: {{ .Chart.Name }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
+{{- end }}
+
+{{/*
+选择器标签
+*/}}
+{{- define "training-app.selectorLabels" -}}
+app.kubernetes.io/name: {{ .Chart.Name }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+完整名称
+*/}}
+{{- define "training-app.fullname" -}}
+{{- if contains .Chart.Name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
diff --git a/docs/posts/k8s-training/practice/helm-chart/templates/configmap.yaml b/docs/posts/k8s-training/practice/helm-chart/templates/configmap.yaml
new file mode 100644
index 0000000..0ddd61d
--- /dev/null
+++ b/docs/posts/k8s-training/practice/helm-chart/templates/configmap.yaml
@@ -0,0 +1,14 @@
+# ===========================================
+# ConfigMap 模板
+# 知识点:非敏感配置外部化、配置与代码分离
+# ===========================================
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ include "training-app.fullname" . }}-config
+ labels:
+ {{- include "training-app.labels" . | nindent 4 }}
+data:
+{{- range $key, $val := .Values.config }}
+ {{ $key }}: {{ $val | quote }}
+{{- end }}
diff --git a/docs/posts/k8s-training/practice/helm-chart/templates/deployment.yaml b/docs/posts/k8s-training/practice/helm-chart/templates/deployment.yaml
new file mode 100644
index 0000000..0f50349
--- /dev/null
+++ b/docs/posts/k8s-training/practice/helm-chart/templates/deployment.yaml
@@ -0,0 +1,74 @@
+# ===========================================
+# Deployment 模板
+# 知识点:Pod、副本、资源限制、探针、滚动更新
+# ===========================================
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ include "training-app.fullname" . }}
+ labels:
+ {{- include "training-app.labels" . | nindent 4 }}
+spec:
+ replicas: {{ .Values.replicaCount }}
+ {{- with .Values.strategy }}
+ strategy:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+ selector:
+ matchLabels:
+ {{- include "training-app.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ labels:
+ {{- include "training-app.selectorLabels" . | nindent 8 }}
+ annotations:
+ checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
+ checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
+ spec:
+ containers:
+ - name: {{ .Chart.Name }}
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ ports:
+ - name: http
+ containerPort: {{ .Values.service.targetPort }}
+ protocol: TCP
+ # ========== 知识点:环境变量注入 ==========
+ # ConfigMap 注入(非敏感配置)
+ env:
+ {{- range $key, $val := .Values.config }}
+ - name: {{ $key }}
+ valueFrom:
+ configMapKeyRef:
+ name: {{ include "training-app.fullname" $ }}-config
+ key: {{ $key }}
+ {{- end }}
+ # Secret 注入(敏感信息)
+ {{- range $key, $val := .Values.secrets }}
+ - name: {{ $key }}
+ valueFrom:
+ secretKeyRef:
+ name: {{ include "training-app.fullname" $ }}-secret
+ key: {{ $key }}
+ {{- end }}
+ # ========== 知识点:资源限制 ==========
+ resources:
+ {{- toYaml .Values.resources | nindent 10 }}
+ # ========== 知识点:存活探针 ==========
+ livenessProbe:
+ {{- toYaml .Values.livenessProbe | nindent 10 }}
+ # ========== 知识点:就绪探针 ==========
+ readinessProbe:
+ {{- toYaml .Values.readinessProbe | nindent 10 }}
+ # ========== 知识点:持久化存储挂载 ==========
+ {{- if .Values.persistence.enabled }}
+ volumeMounts:
+ - name: data
+ mountPath: /app/data
+ {{- end }}
+ {{- if .Values.persistence.enabled }}
+ volumes:
+ - name: data
+ persistentVolumeClaim:
+ claimName: {{ include "training-app.fullname" . }}-pvc
+ {{- end }}
diff --git a/docs/posts/k8s-training/practice/helm-chart/templates/ingress.yaml b/docs/posts/k8s-training/practice/helm-chart/templates/ingress.yaml
new file mode 100644
index 0000000..d90b69d
--- /dev/null
+++ b/docs/posts/k8s-training/practice/helm-chart/templates/ingress.yaml
@@ -0,0 +1,27 @@
+# ===========================================
+# Ingress 模板
+# 知识点:7层HTTP路由、域名路由、TLS
+# ===========================================
+{{- if .Values.ingress.enabled }}
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: {{ include "training-app.fullname" . }}
+ labels:
+ {{- include "training-app.labels" . | nindent 4 }}
+ annotations:
+ nginx.ingress.kubernetes.io/rewrite-target: /
+spec:
+ ingressClassName: {{ .Values.ingress.className }}
+ rules:
+ - host: {{ .Values.ingress.host }}
+ http:
+ paths:
+ - path: {{ .Values.ingress.path }}
+ pathType: {{ .Values.ingress.pathType }}
+ backend:
+ service:
+ name: {{ include "training-app.fullname" . }}
+ port:
+ number: {{ .Values.service.port }}
+{{- end }}
diff --git a/docs/posts/k8s-training/practice/helm-chart/templates/pvc.yaml b/docs/posts/k8s-training/practice/helm-chart/templates/pvc.yaml
new file mode 100644
index 0000000..325a9e3
--- /dev/null
+++ b/docs/posts/k8s-training/practice/helm-chart/templates/pvc.yaml
@@ -0,0 +1,21 @@
+# ===========================================
+# PVC 模板
+# 知识点:持久化存储、动态供应
+# ===========================================
+{{- if .Values.persistence.enabled }}
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: {{ include "training-app.fullname" . }}-pvc
+ labels:
+ {{- include "training-app.labels" . | nindent 4 }}
+spec:
+ accessModes:
+ - {{ .Values.persistence.accessMode }}
+ resources:
+ requests:
+ storage: {{ .Values.persistence.size }}
+ {{- if .Values.persistence.storageClassName }}
+ storageClassName: {{ .Values.persistence.storageClassName }}
+ {{- end }}
+{{- end }}
diff --git a/docs/posts/k8s-training/practice/helm-chart/templates/secret.yaml b/docs/posts/k8s-training/practice/helm-chart/templates/secret.yaml
new file mode 100644
index 0000000..6a693f2
--- /dev/null
+++ b/docs/posts/k8s-training/practice/helm-chart/templates/secret.yaml
@@ -0,0 +1,16 @@
+# ===========================================
+# Secret 模板
+# 知识点:敏感信息管理、base64 编码
+# 注意:stringData 中的值 K8S 会自动 base64 编码
+# ===========================================
+apiVersion: v1
+kind: Secret
+metadata:
+ name: {{ include "training-app.fullname" . }}-secret
+ labels:
+ {{- include "training-app.labels" . | nindent 4 }}
+type: Opaque
+stringData:
+{{- range $key, $val := .Values.secrets }}
+ {{ $key }}: {{ $val | quote }}
+{{- end }}
diff --git a/docs/posts/k8s-training/practice/helm-chart/templates/service.yaml b/docs/posts/k8s-training/practice/helm-chart/templates/service.yaml
new file mode 100644
index 0000000..1d84c7d
--- /dev/null
+++ b/docs/posts/k8s-training/practice/helm-chart/templates/service.yaml
@@ -0,0 +1,19 @@
+# ===========================================
+# Service 模板
+# 知识点:ClusterIP Service、标签选择器、端口映射
+# ===========================================
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ include "training-app.fullname" . }}
+ labels:
+ {{- include "training-app.labels" . | nindent 4 }}
+spec:
+ type: {{ .Values.service.type }}
+ selector:
+ {{- include "training-app.selectorLabels" . | nindent 4 }}
+ ports:
+ - name: http
+ port: {{ .Values.service.port }}
+ targetPort: {{ .Values.service.targetPort }}
+ protocol: TCP
diff --git a/docs/posts/k8s-training/practice/helm-chart/values.yaml b/docs/posts/k8s-training/practice/helm-chart/values.yaml
new file mode 100644
index 0000000..a17f3db
--- /dev/null
+++ b/docs/posts/k8s-training/practice/helm-chart/values.yaml
@@ -0,0 +1,87 @@
+# ===========================================
+# K8S培训综合实战 - values.yaml
+# 覆盖所有知识点:
+# - Deployment(Pod、资源限制、探针、滚动更新)
+# - Service(ClusterIP)
+# - ConfigMap(非敏感配置)
+# - Secret(敏感信息)
+# - Ingress(HTTP路由)
+# ===========================================
+
+# 全局配置
+global:
+ namespace: k8s-training
+
+# Deployment 配置
+replicaCount: 2
+
+image:
+ repository: nginx
+ tag: "1.25"
+ pullPolicy: IfNotPresent
+
+# 资源限制
+resources:
+ requests:
+ cpu: "100m"
+ memory: "64Mi"
+ limits:
+ cpu: "500m"
+ memory: "256Mi"
+
+# 滚动更新策略
+strategy:
+ type: RollingUpdate
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 0
+
+# 存活探针
+livenessProbe:
+ httpGet:
+ path: /
+ port: 80
+ initialDelaySeconds: 10
+ periodSeconds: 5
+
+# 就绪探针
+readinessProbe:
+ httpGet:
+ path: /
+ port: 80
+ initialDelaySeconds: 5
+ periodSeconds: 3
+
+# Service 配置
+service:
+ type: ClusterIP
+ port: 80
+ targetPort: 80
+
+# ConfigMap 配置(非敏感)
+config:
+ APP_ENV: production
+ LOG_LEVEL: info
+ TZ: Asia/Shanghai
+
+# Secret 配置(敏感)- 注意:生产环境应使用外部 Secret 管理
+secrets:
+ DB_HOST: mysql-service
+ DB_PORT: "3306"
+ DB_USER: app_user
+ DB_PASSWORD: ChangeMe123!
+
+# Ingress 配置
+ingress:
+ enabled: false
+ className: "nginx"
+ host: app.example.com
+ path: /
+ pathType: Prefix
+
+# PVC 配置(可选持久化存储)
+persistence:
+ enabled: false
+ accessMode: ReadWriteOnce
+ size: 1Gi
+ storageClassName: ""
diff --git a/package-lock.json b/package-lock.json
index 6e3e4b4..8681392 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,7 +7,8 @@
"name": "nchj.github.io",
"devDependencies": {
"gray-matter": "^4.0.3",
- "vitepress": "^1.6.4"
+ "vitepress": "^1.6.4",
+ "vitepress-plugin-mermaid": "^2.0.17"
}
},
"node_modules/@algolia/abtesting": {
@@ -268,6 +269,21 @@
"node": ">= 14.0.0"
}
},
+ "node_modules/@antfu/install-pkg": {
+ "version": "1.1.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
+ "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "package-manager-detector": "^1.3.0",
+ "tinyexec": "^1.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
@@ -318,6 +334,22 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@braintree/sanitize-url": {
+ "version": "7.1.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz",
+ "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@chevrotain/types": {
+ "version": "11.1.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@chevrotain/types/-/types-11.1.2.tgz",
+ "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true
+ },
"node_modules/@docsearch/css": {
"version": "3.8.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/@docsearch/css/-/css-3.8.2.tgz",
@@ -777,6 +809,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@iconify/utils": {
+ "version": "3.1.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@iconify/utils/-/utils-3.1.3.tgz",
+ "integrity": "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@antfu/install-pkg": "^1.1.0",
+ "@iconify/types": "^2.0.0",
+ "import-meta-resolve": "^4.2.0"
+ }
+ },
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://repo.huaweicloud.com/repository/npm/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
@@ -784,6 +829,42 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@mermaid-js/mermaid-mindmap": {
+ "version": "9.3.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@mermaid-js/mermaid-mindmap/-/mermaid-mindmap-9.3.0.tgz",
+ "integrity": "sha512-IhtYSVBBRYviH1Ehu8gk69pMDF8DSRqXBRDMWrEfHoaMruHeaP2DXA3PBnuwsMaCdPQhlUUcy/7DBLAEIXvCAw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@braintree/sanitize-url": "^6.0.0",
+ "cytoscape": "^3.23.0",
+ "cytoscape-cose-bilkent": "^4.1.0",
+ "cytoscape-fcose": "^2.1.0",
+ "d3": "^7.0.0",
+ "khroma": "^2.0.0",
+ "non-layered-tidy-tree-layout": "^2.0.2"
+ }
+ },
+ "node_modules/@mermaid-js/mermaid-mindmap/node_modules/@braintree/sanitize-url": {
+ "version": "6.0.4",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz",
+ "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@mermaid-js/parser": {
+ "version": "1.1.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@mermaid-js/parser/-/parser-1.1.1.tgz",
+ "integrity": "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@chevrotain/types": "~11.1.1"
+ }
+ },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.60.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
@@ -1260,6 +1341,321 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/d3": {
+ "version": "7.4.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3/-/d3-7.4.3.tgz",
+ "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/d3-axis": "*",
+ "@types/d3-brush": "*",
+ "@types/d3-chord": "*",
+ "@types/d3-color": "*",
+ "@types/d3-contour": "*",
+ "@types/d3-delaunay": "*",
+ "@types/d3-dispatch": "*",
+ "@types/d3-drag": "*",
+ "@types/d3-dsv": "*",
+ "@types/d3-ease": "*",
+ "@types/d3-fetch": "*",
+ "@types/d3-force": "*",
+ "@types/d3-format": "*",
+ "@types/d3-geo": "*",
+ "@types/d3-hierarchy": "*",
+ "@types/d3-interpolate": "*",
+ "@types/d3-path": "*",
+ "@types/d3-polygon": "*",
+ "@types/d3-quadtree": "*",
+ "@types/d3-random": "*",
+ "@types/d3-scale": "*",
+ "@types/d3-scale-chromatic": "*",
+ "@types/d3-selection": "*",
+ "@types/d3-shape": "*",
+ "@types/d3-time": "*",
+ "@types/d3-time-format": "*",
+ "@types/d3-timer": "*",
+ "@types/d3-transition": "*",
+ "@types/d3-zoom": "*"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-array/-/d3-array-3.2.2.tgz",
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-axis": {
+ "version": "3.0.6",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-axis/-/d3-axis-3.0.6.tgz",
+ "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-brush": {
+ "version": "3.0.6",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-brush/-/d3-brush-3.0.6.tgz",
+ "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-chord": {
+ "version": "3.0.6",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-chord/-/d3-chord-3.0.6.tgz",
+ "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-contour": {
+ "version": "3.0.6",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-contour/-/d3-contour-3.0.6.tgz",
+ "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-dispatch": {
+ "version": "3.0.7",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz",
+ "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-drag": {
+ "version": "3.0.7",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+ "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-dsv": {
+ "version": "3.0.7",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
+ "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-fetch": {
+ "version": "3.0.7",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
+ "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/d3-dsv": "*"
+ }
+ },
+ "node_modules/@types/d3-force": {
+ "version": "3.0.10",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-force/-/d3-force-3.0.10.tgz",
+ "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-format": {
+ "version": "3.0.4",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-format/-/d3-format-3.0.4.tgz",
+ "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-geo": {
+ "version": "3.1.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-geo/-/d3-geo-3.1.0.tgz",
+ "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-hierarchy": {
+ "version": "3.1.7",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
+ "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-polygon": {
+ "version": "3.0.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
+ "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-quadtree": {
+ "version": "3.0.6",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
+ "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-random": {
+ "version": "3.0.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-random/-/d3-random-3.0.3.tgz",
+ "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-selection": {
+ "version": "3.0.11",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-selection/-/d3-selection-3.0.11.tgz",
+ "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.8",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-shape/-/d3-shape-3.1.8.tgz",
+ "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-time-format": {
+ "version": "4.0.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
+ "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/d3-transition": {
+ "version": "3.0.9",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-transition/-/d3-transition-3.0.9.tgz",
+ "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-zoom": {
+ "version": "3.0.8",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+ "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/d3-interpolate": "*",
+ "@types/d3-selection": "*"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://repo.huaweicloud.com/repository/npm/@types/estree/-/estree-1.0.8.tgz",
@@ -1267,6 +1663,14 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/@types/hast": {
"version": "3.0.4",
"resolved": "https://repo.huaweicloud.com/repository/npm/@types/hast/-/hast-3.0.4.tgz",
@@ -1312,6 +1716,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true
+ },
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://repo.huaweicloud.com/repository/npm/@types/unist/-/unist-3.0.3.tgz",
@@ -1333,6 +1746,18 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/@upsetjs/venn.js": {
+ "version": "2.0.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/@upsetjs/venn.js/-/venn.js-2.0.0.tgz",
+ "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "optionalDependencies": {
+ "d3-selection": "^3.0.0",
+ "d3-transition": "^3.0.1"
+ }
+ },
"node_modules/@vitejs/plugin-vue": {
"version": "5.2.4",
"resolved": "https://repo.huaweicloud.com/repository/npm/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
@@ -1688,6 +2113,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/copy-anything": {
"version": "4.0.5",
"resolved": "https://repo.huaweicloud.com/repository/npm/copy-anything/-/copy-anything-4.0.5.tgz",
@@ -1704,6 +2139,16 @@
"url": "https://github.com/sponsors/mesqueeb"
}
},
+ "node_modules/cose-base": {
+ "version": "1.0.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/cose-base/-/cose-base-1.0.3.tgz",
+ "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "layout-base": "^1.0.0"
+ }
+ },
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://repo.huaweicloud.com/repository/npm/csstype/-/csstype-3.2.3.tgz",
@@ -1711,68 +2156,656 @@
"dev": true,
"license": "MIT"
},
- "node_modules/dequal": {
- "version": "2.0.3",
- "resolved": "https://repo.huaweicloud.com/repository/npm/dequal/-/dequal-2.0.3.tgz",
- "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "node_modules/cytoscape": {
+ "version": "3.33.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/cytoscape/-/cytoscape-3.33.3.tgz",
+ "integrity": "sha512-Gej7U+OKR+LZ8kvX7rb2HhCYJ0IhvEFsnkud4SB1PR+BUY/TsSO0dmOW59WEVLu51b1Rm+gQRKoz4bLYxGSZ2g==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">=0.10"
}
},
- "node_modules/devlop": {
- "version": "1.1.0",
- "resolved": "https://repo.huaweicloud.com/repository/npm/devlop/-/devlop-1.1.0.tgz",
- "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "node_modules/cytoscape-cose-bilkent": {
+ "version": "4.1.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
+ "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "dequal": "^2.0.0"
+ "cose-base": "^1.0.0"
},
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
+ "peerDependencies": {
+ "cytoscape": "^3.2.0"
}
},
- "node_modules/emoji-regex-xs": {
- "version": "1.0.0",
- "resolved": "https://repo.huaweicloud.com/repository/npm/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz",
- "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/entities": {
- "version": "7.0.1",
- "resolved": "https://repo.huaweicloud.com/repository/npm/entities/-/entities-7.0.1.tgz",
- "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+ "node_modules/cytoscape-fcose": {
+ "version": "2.2.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
+ "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
"dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.12"
+ "license": "MIT",
+ "dependencies": {
+ "cose-base": "^2.2.0"
},
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
+ "peerDependencies": {
+ "cytoscape": "^3.2.0"
}
},
- "node_modules/esbuild": {
- "version": "0.21.5",
- "resolved": "https://repo.huaweicloud.com/repository/npm/esbuild/-/esbuild-0.21.5.tgz",
- "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "node_modules/cytoscape-fcose/node_modules/cose-base": {
+ "version": "2.2.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/cose-base/-/cose-base-2.2.0.tgz",
+ "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
"dev": true,
- "hasInstallScript": true,
"license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
+ "dependencies": {
+ "layout-base": "^2.0.0"
+ }
+ },
+ "node_modules/cytoscape-fcose/node_modules/layout-base": {
+ "version": "2.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/layout-base/-/layout-base-2.0.1.tgz",
+ "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/d3": {
+ "version": "7.9.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3/-/d3-7.9.0.tgz",
+ "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "3",
+ "d3-axis": "3",
+ "d3-brush": "3",
+ "d3-chord": "3",
+ "d3-color": "3",
+ "d3-contour": "4",
+ "d3-delaunay": "6",
+ "d3-dispatch": "3",
+ "d3-drag": "3",
+ "d3-dsv": "3",
+ "d3-ease": "3",
+ "d3-fetch": "3",
+ "d3-force": "3",
+ "d3-format": "3",
+ "d3-geo": "3",
+ "d3-hierarchy": "3",
+ "d3-interpolate": "3",
+ "d3-path": "3",
+ "d3-polygon": "3",
+ "d3-quadtree": "3",
+ "d3-random": "3",
+ "d3-scale": "4",
+ "d3-scale-chromatic": "3",
+ "d3-selection": "3",
+ "d3-shape": "3",
+ "d3-time": "3",
+ "d3-time-format": "4",
+ "d3-timer": "3",
+ "d3-transition": "3",
+ "d3-zoom": "3"
},
"engines": {
"node": ">=12"
+ }
+ },
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "internmap": "1 - 2"
},
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.21.5",
- "@esbuild/android-arm": "0.21.5",
- "@esbuild/android-arm64": "0.21.5",
- "@esbuild/android-x64": "0.21.5",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-axis": {
+ "version": "3.0.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-axis/-/d3-axis-3.0.0.tgz",
+ "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-brush": {
+ "version": "3.0.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-brush/-/d3-brush-3.0.0.tgz",
+ "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "3",
+ "d3-transition": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-chord": {
+ "version": "3.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-chord/-/d3-chord-3.0.1.tgz",
+ "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-contour": {
+ "version": "4.0.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-contour/-/d3-contour-4.0.2.tgz",
+ "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "delaunator": "5"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dispatch": {
+ "version": "3.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+ "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-drag": {
+ "version": "3.0.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-drag/-/d3-drag-3.0.0.tgz",
+ "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-selection": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dsv": {
+ "version": "3.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-dsv/-/d3-dsv-3.0.1.tgz",
+ "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "commander": "7",
+ "iconv-lite": "0.6",
+ "rw": "1"
+ },
+ "bin": {
+ "csv2json": "bin/dsv2json.js",
+ "csv2tsv": "bin/dsv2dsv.js",
+ "dsv2dsv": "bin/dsv2dsv.js",
+ "dsv2json": "bin/dsv2json.js",
+ "json2csv": "bin/json2dsv.js",
+ "json2dsv": "bin/json2dsv.js",
+ "json2tsv": "bin/json2dsv.js",
+ "tsv2csv": "bin/dsv2dsv.js",
+ "tsv2json": "bin/dsv2json.js"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-fetch": {
+ "version": "3.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-fetch/-/d3-fetch-3.0.1.tgz",
+ "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-dsv": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-force": {
+ "version": "3.0.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-force/-/d3-force-3.0.0.tgz",
+ "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-quadtree": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-format/-/d3-format-3.1.2.tgz",
+ "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-geo": {
+ "version": "3.1.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-geo/-/d3-geo-3.1.1.tgz",
+ "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.5.0 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-hierarchy": {
+ "version": "3.1.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
+ "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-polygon": {
+ "version": "3.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-polygon/-/d3-polygon-3.0.1.tgz",
+ "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-quadtree": {
+ "version": "3.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
+ "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-random": {
+ "version": "3.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-random/-/d3-random-3.0.1.tgz",
+ "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-sankey": {
+ "version": "0.12.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-sankey/-/d3-sankey-0.12.3.tgz",
+ "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "d3-array": "1 - 2",
+ "d3-shape": "^1.2.0"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "internmap": "^1.0.0"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/d3-path": {
+ "version": "1.0.9",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-path/-/d3-path-1.0.9.tgz",
+ "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true
+ },
+ "node_modules/d3-sankey/node_modules/d3-shape": {
+ "version": "1.3.7",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-shape/-/d3-shape-1.3.7.tgz",
+ "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "d3-path": "1"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/internmap": {
+ "version": "1.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/internmap/-/internmap-1.0.1.tgz",
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-interpolate": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-selection": {
+ "version": "3.0.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-selection/-/d3-selection-3.0.0.tgz",
+ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-transition": {
+ "version": "3.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-transition/-/d3-transition-3.0.1.tgz",
+ "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-dispatch": "1 - 3",
+ "d3-ease": "1 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "d3-selection": "2 - 3"
+ }
+ },
+ "node_modules/d3-zoom": {
+ "version": "3.0.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/d3-zoom/-/d3-zoom-3.0.0.tgz",
+ "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "2 - 3",
+ "d3-transition": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dagre-d3-es": {
+ "version": "7.0.14",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz",
+ "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "d3": "^7.9.0",
+ "lodash-es": "^4.17.21"
+ }
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.20",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/dayjs/-/dayjs-1.11.20.tgz",
+ "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/delaunator": {
+ "version": "5.1.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/delaunator/-/delaunator-5.1.0.tgz",
+ "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "robust-predicates": "^3.0.2"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/dompurify": {
+ "version": "3.4.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/dompurify/-/dompurify-3.4.3.tgz",
+ "integrity": "sha512-VVwJidIJcp1hpg2OMXML3ZVRPYSZiq4aX7qBh83BSIpOaRDqI+qxhXjjIWnpzkOXhmp0L81lnoME1mnCc9H48A==",
+ "dev": true,
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "peer": true,
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
+ "node_modules/emoji-regex-xs": {
+ "version": "1.0.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz",
+ "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/entities": {
+ "version": "7.0.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/entities/-/entities-7.0.1.tgz",
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-toolkit": {
+ "version": "1.46.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/es-toolkit/-/es-toolkit-1.46.1.tgz",
+ "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "workspaces": [
+ "docs",
+ "benchmarks"
+ ]
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
"@esbuild/darwin-arm64": "0.21.5",
"@esbuild/darwin-x64": "0.21.5",
"@esbuild/freebsd-arm64": "0.21.5",
@@ -1869,6 +2902,14 @@
"node": ">=6.0"
}
},
+ "node_modules/hachure-fill": {
+ "version": "0.5.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/hachure-fill/-/hachure-fill-0.5.2.tgz",
+ "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/hast-util-to-html": {
"version": "9.0.5",
"resolved": "https://repo.huaweicloud.com/repository/npm/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
@@ -1925,6 +2966,41 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/import-meta-resolve": {
+ "version": "4.2.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
+ "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/is-extendable/-/is-extendable-0.1.1.tgz",
@@ -1962,6 +3038,41 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/katex": {
+ "version": "0.16.46",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/katex/-/katex-0.16.46.tgz",
+ "integrity": "sha512-WHy4Coo+bGZyH7NwJKHkS04YFsFcarWbAEOAC3EMndzdN6VSZqklLLIgfxzyaW9jDoeGYJX9SWbJPKpecox0Uw==",
+ "dev": true,
+ "funding": [
+ "https://opencollective.com/katex",
+ "https://github.com/sponsors/katex"
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "commander": "^8.3.0"
+ },
+ "bin": {
+ "katex": "cli.js"
+ }
+ },
+ "node_modules/katex/node_modules/commander": {
+ "version": "8.3.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/commander/-/commander-8.3.0.tgz",
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/khroma": {
+ "version": "2.1.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/khroma/-/khroma-2.1.0.tgz",
+ "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==",
+ "dev": true
+ },
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://repo.huaweicloud.com/repository/npm/kind-of/-/kind-of-6.0.3.tgz",
@@ -1972,6 +3083,21 @@
"node": ">=0.10.0"
}
},
+ "node_modules/layout-base": {
+ "version": "1.0.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/layout-base/-/layout-base-1.0.2.tgz",
+ "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash-es": {
+ "version": "4.18.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/lodash-es/-/lodash-es-4.18.1.tgz",
+ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://repo.huaweicloud.com/repository/npm/magic-string/-/magic-string-0.30.21.tgz",
@@ -1989,6 +3115,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/marked": {
+ "version": "16.4.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/marked/-/marked-16.4.2.tgz",
+ "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 20"
+ }
+ },
"node_modules/mdast-util-to-hast": {
"version": "13.2.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
@@ -2011,6 +3151,37 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/mermaid": {
+ "version": "11.15.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/mermaid/-/mermaid-11.15.0.tgz",
+ "integrity": "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@braintree/sanitize-url": "^7.1.1",
+ "@iconify/utils": "^3.0.2",
+ "@mermaid-js/parser": "^1.1.1",
+ "@types/d3": "^7.4.3",
+ "@upsetjs/venn.js": "^2.0.0",
+ "cytoscape": "^3.33.1",
+ "cytoscape-cose-bilkent": "^4.1.0",
+ "cytoscape-fcose": "^2.2.0",
+ "d3": "^7.9.0",
+ "d3-sankey": "^0.12.3",
+ "dagre-d3-es": "7.0.14",
+ "dayjs": "^1.11.19",
+ "dompurify": "^3.3.1",
+ "es-toolkit": "^1.45.1",
+ "katex": "^0.16.25",
+ "khroma": "^2.1.0",
+ "marked": "^16.3.0",
+ "roughjs": "^4.6.6",
+ "stylis": "^4.3.6",
+ "ts-dedent": "^2.2.0",
+ "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0"
+ }
+ },
"node_modules/micromark-util-character": {
"version": "2.1.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
@@ -2138,6 +3309,14 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/non-layered-tidy-tree-layout": {
+ "version": "2.0.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
+ "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/oniguruma-to-es": {
"version": "3.1.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz",
@@ -2150,6 +3329,22 @@
"regex-recursion": "^6.0.2"
}
},
+ "node_modules/package-manager-detector": {
+ "version": "1.6.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/package-manager-detector/-/package-manager-detector-1.6.0.tgz",
+ "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/path-data-parser": {
+ "version": "0.1.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/path-data-parser/-/path-data-parser-0.1.0.tgz",
+ "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/perfect-debounce": {
"version": "1.0.0",
"resolved": "https://repo.huaweicloud.com/repository/npm/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
@@ -2164,6 +3359,26 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/points-on-curve": {
+ "version": "0.2.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/points-on-curve/-/points-on-curve-0.2.0.tgz",
+ "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/points-on-path": {
+ "version": "0.2.1",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/points-on-path/-/points-on-path-0.2.1.tgz",
+ "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "path-data-parser": "0.1.0",
+ "points-on-curve": "0.2.0"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.8",
"resolved": "https://repo.huaweicloud.com/repository/npm/postcss/-/postcss-8.5.8.tgz",
@@ -2249,6 +3464,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/robust-predicates": {
+ "version": "3.0.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/robust-predicates/-/robust-predicates-3.0.3.tgz",
+ "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==",
+ "dev": true,
+ "license": "Unlicense"
+ },
"node_modules/rollup": {
"version": "4.60.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/rollup/-/rollup-4.60.1.tgz",
@@ -2294,6 +3516,34 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/roughjs": {
+ "version": "4.6.6",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/roughjs/-/roughjs-4.6.6.tgz",
+ "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "hachure-fill": "^0.5.2",
+ "path-data-parser": "^0.1.0",
+ "points-on-curve": "^0.2.0",
+ "points-on-path": "^0.2.1"
+ }
+ },
+ "node_modules/rw": {
+ "version": "1.3.3",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/rw/-/rw-1.3.3.tgz",
+ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/search-insights": {
"version": "2.17.3",
"resolved": "https://repo.huaweicloud.com/repository/npm/search-insights/-/search-insights-2.17.3.tgz",
@@ -2396,6 +3646,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/stylis": {
+ "version": "4.4.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/stylis/-/stylis-4.4.0.tgz",
+ "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/superjson": {
"version": "2.2.6",
"resolved": "https://repo.huaweicloud.com/repository/npm/superjson/-/superjson-2.2.6.tgz",
@@ -2416,6 +3674,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/tinyexec": {
+ "version": "1.1.2",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/tinyexec/-/tinyexec-1.1.2.tgz",
+ "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/trim-lines": {
"version": "3.0.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/trim-lines/-/trim-lines-3.0.1.tgz",
@@ -2427,6 +3696,17 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/ts-dedent": {
+ "version": "2.2.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/ts-dedent/-/ts-dedent-2.2.0.tgz",
+ "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.10"
+ }
+ },
"node_modules/unist-util-is": {
"version": "6.0.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/unist-util-is/-/unist-util-is-6.0.1.tgz",
@@ -2500,6 +3780,21 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/uuid": {
+ "version": "14.0.0",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/uuid/-/uuid-14.0.0.tgz",
+ "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "uuid": "dist-node/bin/uuid"
+ }
+ },
"node_modules/vfile": {
"version": "6.0.3",
"resolved": "https://repo.huaweicloud.com/repository/npm/vfile/-/vfile-6.0.3.tgz",
@@ -2632,6 +3927,20 @@
}
}
},
+ "node_modules/vitepress-plugin-mermaid": {
+ "version": "2.0.17",
+ "resolved": "https://repo.huaweicloud.com/repository/npm/vitepress-plugin-mermaid/-/vitepress-plugin-mermaid-2.0.17.tgz",
+ "integrity": "sha512-IUzYpwf61GC6k0XzfmAmNrLvMi9TRrVRMsUyCA8KNXhg/mQ1VqWnO0/tBVPiX5UoKF1mDUwqn5QV4qAJl6JnUg==",
+ "dev": true,
+ "license": "MIT",
+ "optionalDependencies": {
+ "@mermaid-js/mermaid-mindmap": "^9.3.0"
+ },
+ "peerDependencies": {
+ "mermaid": "10 || 11",
+ "vitepress": "^1.0.0 || ^1.0.0-alpha"
+ }
+ },
"node_modules/vue": {
"version": "3.5.32",
"resolved": "https://repo.huaweicloud.com/repository/npm/vue/-/vue-3.5.32.tgz",
diff --git a/package.json b/package.json
index 46adedd..66e42ed 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
},
"devDependencies": {
"gray-matter": "^4.0.3",
- "vitepress": "^1.6.4"
+ "vitepress": "^1.6.4",
+ "vitepress-plugin-mermaid": "^2.0.17"
}
}