diff --git a/CLAUDE.md b/CLAUDE.md
index b5c16ae..6dfe9fb 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -44,7 +44,8 @@ create time: YYYY-MM-DD HH:mm
|------|------|------|
| `technical/` | 技术知识点 | Go、Redis、K8s 等 |
| `projects/` | 开发项目记录 | 具体参与的模块或系统 |
-| `weekly/` | 周/日报 | 按周归档的工作总结 |
+| `weekly/` | 周报 | 按周归档的工作总结 |
+| `weekly/daily/` | 日报 | 按日期归档的每日记录 |
- 文件名统一使用 **kebab-case**(小写 + 短横线),如 `redis-hash-internals.md`
- 日期类文件在文件名中包含日期,如 `2026-w25-weekly-report.md`
@@ -134,11 +135,13 @@ status: active | completed | on-hold # 可选状态字段
---
-### weekly/ — 周报/日报
+### weekly/ — 周报与日报
-侧重**结构化总结与快速回顾**。
+侧重**结构化总结与快速回顾**。周报和日报分开管理。
-#### 结构模板
+#### 周报
+
+路径:`weekly/2026-w25-weekly-report.md`
```markdown
---
@@ -163,11 +166,57 @@ week: W25 # 第几周
- 待办事项 & 下周计划
```
+#### 日报
+
+路径:`weekly/daily/2026-07-07-daily.md`,模板:`weekly/daily/template.md`
+
+日报为**下班前**提交的当日总结,结构:今日完成 → 卡点 → 明日计划。新建日报时复制模板并填入当日日期。
+
+```markdown
+---
+tags: [daily]
+create time: YYYY-MM-DD HH:mm
+---
+
+# 2026-07-07 日报
+
+## 今日完成
+- [ ] 完成事项
+
+## GitHub Issues
+> 当日无关联 Issue
+
+
+
+## 卡点 / 阻塞
+> 当前无卡点
+
+
+
+## 明日计划
+- [ ] 计划事项
+
+## 备注
+```
+
#### 写作要求
- 简洁条目式,不展开长段落
- 每条完成后打 `[x]`,未完成保留 `[ ]`
-- 「问题 / 阻塞」板块用于向上同步风险,务必诚实标注
-- 日期按周归档,文件名格式:`2026-w25-weekly-report.md`
+- 「今日完成」需体现对目标的影响,不是流水账
+- 卡点必须注明跟进人和预计反馈时间,无卡点时保留「当前无卡点」占位
+- GitHub Issues 板块按状态分组(进行中 / 待 Review / 已关闭),无 Issue 时保留「当日无关联 Issue」占位
+- 周报「问题 / 阻塞」板块用于向上同步风险,务必诚实标注
+- 周报文件名格式:`2026-w25-weekly-report.md`
+- 日报文件名格式:`2026-07-07-daily.md`,按日期自然排序
---
diff --git a/technical/k8s/k8s-01-overview.md b/technical/k8s/k8s-01-overview.md
new file mode 100644
index 0000000..1831312
--- /dev/null
+++ b/technical/k8s/k8s-01-overview.md
@@ -0,0 +1,562 @@
+---
+tags: [k8s, devops, container-orchestration]
+create time: 2026-07-07 15:48
+---
+
+# Kubernetes 概述
+
+## 概述
+
+Kubernetes(简称 K8s)是一个开源的**容器编排平台**,由 Google 内部的 Borg 系统演化而来,于 2014 年开源,目前由 CNCF(Cloud Native Computing Foundation)托管。它的核心使命是:**让部署、扩展和管理容器化应用变得自动化且可预测**。简单来说,Docker 解决了「如何打包一个应用」的问题,而 K8s 解决的是「如何在成百上千台机器上高效地运行和管理这些应用」的问题。
+
+## 为什么需要 K8s
+
+### 从 Docker 单机到集群编排
+
+让我们先回顾一下容器技术的演进路径:
+
+1. **裸机/虚拟机时代**:应用直接部署在物理机或 VM 上,手动管理依赖、端口和资源分配。
+2. **Docker 单机时代**:容器解决了环境一致性和依赖打包的问题,但单机资源有限,且面临应用故障恢复、滚动更新等挑战。
+3. **集群编排时代**:当你的服务需要跨多台机器运行时,你需要一个「大脑」来决定:把容器调度到哪台机器?挂了怎么重启?流量怎么分配?——这就是 K8s 存在的意义。
+
+> [!tip] 一个类比
+> 如果 Docker 集装箱标准化了「货物打包」,那 K8s 就是自动化码头——它决定集装箱该放哪艘船、航线怎么规划、遇到风暴如何重新调度。
+
+### Docker Compose vs Docker Swarm vs Kubernetes
+
+| 特性 | Docker Compose | Docker Swarm | Kubernetes |
+|------|---------------|-------------|------------|
+| **定位** | 单机多容器编排 | 轻量级集群编排 | 企业级容器编排平台 |
+| **规模** | 单主机 | 中小规模集群 | 大规模生产环境 |
+| **学习曲线** | 低 | 中 | 高 |
+| **自动扩缩容** | 不支持 | 基础支持 | HPA / VPA / Cluster Autoscaler |
+| **自愈能力** | 无 | 基础(重启策略) | 强(Liveness/Readiness Probe + 控制器) |
+| **滚动更新** | 不支持 | 支持 | 细粒度控制(maxUnavailable / maxSurge) |
+| **生态** | 仅 Docker 生态 | 仅 Docker 生态 | CNCF 生态,插件丰富 |
+| **服务发现** | 容器名 DNS | 内置 DNS | CoreDNS + Service 资源 |
+| **适用场景** | 本地开发、测试 | 快速上手小集群 | 生产环境首选 |
+
+> [!info] 为什么 Swarm 没能打败 K8s?
+> Docker Swarm 的设计哲学是「简单优先」,但现实世界的生产需求远比想象中复杂。K8s 的声明式 API、丰富的控制器模型、以及 CNCF 生态的飞轮效应,最终让它成为事实标准。
+
+## 核心架构
+
+K8s 采用经典的 **Master-Worker 架构**(官方文档中 Master 节点现在更常称为 Control Plane)。
+
+### 架构总览
+
+```mermaid
+graph TB
+ subgraph ControlPlane["Control Plane(控制平面)"]
+ API["API Server
集群的唯一入口"]
+ etcd["etcd
分布式 KV 存储"]
+ sched["Scheduler
Pod 调度决策"]
+ cm["Controller Manager
维持期望状态"]
+ end
+
+ subgraph Worker1["Worker Node 1"]
+ kubelet1["kubelet
管理 Pod 生命周期"]
+ proxy1["kube-proxy
网络规则管理"]
+ cr1["Container Runtime
containerd / CRI-O"]
+ P1a["Pod A"]
+ P1b["Pod B"]
+ end
+
+ subgraph Worker2["Worker Node 2"]
+ kubelet2["kubelet"]
+ proxy2["kube-proxy"]
+ cr2["Container Runtime"]
+ P2a["Pod C"]
+ P2b["Pod D"]
+ end
+
+ subgraph Client["用户 / CI/CD"]
+ kubectl["kubectl / SDK"]
+ end
+
+ kubectl -->|"REST API"| API
+ API <-->|"读写"| etcd
+ API -->|"监听事件"| sched
+ API -->|"监听事件"| cm
+ kubelet1 -->|"汇报状态"| API
+ kubelet2 -->|"汇报状态"| API
+ kubelet1 --> cr1
+ kubelet2 --> cr2
+ cr1 --> P1a & P1b
+ cr2 --> P2a & P2b
+ proxy1 -.->|"Service 流量"| P1a & P1b
+ proxy2 -.->|"Service 流量"| P2a & P2b
+```
+
+### Control Plane 组件详解
+
+#### API Server(kube-apiserver)
+
+API Server 是整个集群的**唯一入口**,所有组件之间的通信都通过它进行。它承担的核心职责:
+
+- **RESTful API 网关**:暴露 K8s API,所有 `kubectl` 操作最终都到达这里
+- **认证与授权**:RBAC、ServiceAccount、Webhook Token 等
+- **准入控制(Admission Control)**:在资源持久化之前进行校验和修改(如 MutatingWebhook、ValidatingWebhook)
+- **Watch 机制**:其他组件通过 Watch API Server 获取资源变更事件
+
+> [!tip] 为什么所有组件都要通过 API Server 通信?
+> 这是 K8s 的一个重要设计决策——**中心化 API 网关**保证了统一的认证、授权、审计和数据校验逻辑。如果各组件直接读写 etcd,安全策略将难以实施。
+
+#### etcd
+
+etcd 是一个高可用的分布式 KV 存储,是 K8s 的「唯一数据源」。所有集群状态(Pod、Service、ConfigMap 等资源对象)都持久化在 etcd 中。
+
+- 基于 **Raft 共识算法**保证数据一致性
+- 生产环境通常部署 3 或 5 个节点(奇数个,便于选举)
+- **Watch 机制**:支持高效地监听 key 的变更
+
+> [!warning] etcd 是整个集群的 Single Point of Truth
+> 如果 etcd 数据丢失且没有备份,整个集群状态将不可恢复。生产环境务必做好 etcd 的定期备份。
+
+#### Scheduler(kube-scheduler)
+
+Scheduler 负责将新创建的 Pod **绑定到合适的 Node** 上。调度过程分为两个阶段:
+
+1. **过滤(Filtering)**:排除不满足条件的 Node(如资源不足、Taint 不匹配、亲和性规则冲突)
+2. **打分(Scoring)**:对剩余 Node 进行评分,选择得分最高的
+
+常见调度策略:
+
+- **资源请求与限制**:基于 Pod 的 `requests` 和 `limits`
+- **亲和性与反亲和性**:`nodeAffinity`、`podAffinity`、`podAntiAffinity`
+- **Taint 和 Toleration**:驱逐不兼容的 Pod
+- **拓扑分布约束**:`topologySpreadConstraints` 跨可用区均匀分布
+
+#### Controller Manager(kube-controller-manager)
+
+Controller Manager 是一组控制器的集合,每个控制器负责一种资源类型的「调谐循环(Reconciliation Loop)」。核心思想:**持续比较「期望状态」和「实际状态」,并采取行动使实际状态趋近期望状态**。
+
+常见控制器:
+
+| 控制器 | 职责 |
+|--------|------|
+| Deployment Controller | 管理 ReplicaSet 的创建/更新 |
+| ReplicaSet Controller | 确保指定数量的 Pod 副本运行 |
+| Node Controller | 监控 Node 心跳,处理节点故障 |
+| Job Controller | 管理一次性任务的 Pod |
+| ServiceAccount Controller | 为新 Namespace 创建默认 ServiceAccount |
+
+### Worker Node 组件详解
+
+#### kubelet
+
+kubelet 是运行在每个 Worker Node 上的**代理进程**,负责:
+
+- 从 API Server 获取分配给本节点的 Pod Spec
+- 调用 Container Runtime 拉取镜像、启动容器
+- 执行健康检查(Liveness Probe、Readiness Probe、Startup Probe)
+- 向 API Server 汇报节点和 Pod 状态
+- 管理 Volume 的挂载和卸载
+
+> [!info] kubelet 不是通过 API Server 启动容器的
+> kubelet 通过 **CRI(Container Runtime Interface)** 与容器运行时交互,而非直接调用 Docker CLI。
+
+#### kube-proxy
+
+kube-proxy 负责在每个节点上维护**网络规则**,实现 Service 的负载均衡。它有三种工作模式:
+
+| 模式 | 实现方式 | 性能 | 推荐度 |
+|------|---------|------|--------|
+| iptables | Linux iptables 规则 | 中等(规则数线性增长) | 默认模式 |
+| IPVS | Linux IPVS(内核级 LB) | 高(哈希表查找) | 大规模集群推荐 |
+| nftables | nftables 规则 | 高 | K8s 1.29+ 实验性 |
+
+#### Container Runtime
+
+Container Runtime 是真正执行容器生命周期操作的组件。K8s 通过 **CRI(Container Runtime Interface)** 标准化了与运行时的交互方式:
+
+- **containerd**:目前最主流的选择,Docker 的核心运行时组件被独立出来
+- **CRI-O**:Red Hat 主导,专为 K8s 设计的轻量级运行时
+- **Docker**:自 K8s 1.24 起不再直接支持(dockershim 已移除),但 containerd 仍可使用
+
+## 关键概念
+
+### Pod
+
+Pod 是 K8s 中**最小的可部署单元**,而不是容器。一个 Pod 可以包含一个或多个容器,它们:
+
+- 共享同一个 Network Namespace(即共享 IP 和端口空间)
+- 可以通过 Volume 共享存储
+- 总是被调度到同一个 Node 上
+
+> [!tip] 为什么需要 Pod 而不是直接管理容器?
+> 现实中有些应用需要「伴生容器(Sidecar)」——比如日志收集器、Service Mesh 代理。Pod 将这些紧密耦合的容器打包在一起,共享网络和存储,比独立容器更优雅。
+
+```yaml
+# 一个包含 Sidecar 的 Pod 示例
+apiVersion: v1
+kind: Pod
+metadata:
+ name: app-with-logging
+ labels:
+ app: my-app # 用于 Service 选择和筛选
+ env: production
+spec:
+ containers:
+ - name: app
+ image: my-app:v1.2.3
+ ports:
+ - containerPort: 8080
+ resources:
+ requests:
+ cpu: "100m" # 0.1 核
+ memory: "128Mi"
+ limits:
+ cpu: "500m" # 最多 0.5 核
+ memory: "512Mi"
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8080
+ initialDelaySeconds: 10
+ periodSeconds: 5
+ readinessProbe:
+ httpGet:
+ path: /ready
+ port: 8080
+ periodSeconds: 3
+ - name: log-collector
+ image: fluentd:v1.16
+ volumeMounts:
+ - name: log-volume
+ mountPath: /var/log/app
+ volumes:
+ - name: log-volume
+ emptyDir: {} # Pod 内共享的临时目录
+```
+
+**Pod 生命周期中的几个关键阶段**:
+
+| Phase | 含义 |
+|-------|------|
+| Pending | 已被 API Server 接受,但尚未调度或镜像未拉取 |
+| Running | 至少一个容器正在运行 |
+| Succeeded | 所有容器正常退出(退出码 0) |
+| Failed | 至少一个容器异常退出 |
+| Unknown | 无法获取 Pod 状态(通常是 Node 通信故障) |
+
+### Node
+
+Node 是 K8s 集群中的**工作机器**(物理机或虚拟机)。每个 Node 包含运行 Pod 所需的服务(kubelet、kube-proxy、Container Runtime)。
+
+查看 Node 信息:
+
+```bash
+# 查看集群所有节点
+kubectl get nodes -o wide
+
+# 查看某个节点的详细信息(资源、条件、运行的 Pod)
+kubectl describe node
+```
+
+Node 有几个重要的**条件(Conditions)**:
+
+| Condition | 说明 |
+|-----------|------|
+| Ready | 节点健康且准备好接受 Pod |
+| MemoryPressure | 节点内存不足 |
+| DiskPressure | 节点磁盘空间不足 |
+| PIDPressure | 节点进程数过多 |
+| NetworkUnavailable | 节点网络未正确配置 |
+
+### Namespace
+
+Namespace 是 K8s 中的**逻辑隔离机制**,用于在同一物理集群中划分多个虚拟集群。适用于:
+
+- 多团队共享集群(`team-a`、`team-b`)
+- 环境隔离(`dev`、`staging`、`production`)
+- 资源配额管理(每个 Namespace 设置独立的 ResourceQuota)
+
+```yaml
+# 创建一个 Namespace
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: team-backend
+ labels:
+ team: backend
+ env: production
+---
+# 在该 Namespace 下设置资源配额
+apiVersion: v1
+kind: ResourceQuota
+metadata:
+ name: team-backend-quota
+ namespace: team-backend
+spec:
+ hard:
+ requests.cpu: "10" # 总 CPU 请求上限 10 核
+ requests.memory: "20Gi" # 总内存请求上限 20Gi
+ limits.cpu: "20"
+ limits.memory: "40Gi"
+ pods: "50" # 最多 50 个 Pod
+```
+
+K8s 默认创建的 Namespace:
+
+| Namespace | 用途 |
+|-----------|------|
+| `default` | 未指定 Namespace 时的默认值 |
+| `kube-system` | K8s 系统组件(CoreDNS、kube-proxy 等) |
+| `kube-public` | 公共资源(集群信息等) |
+| `kube-node-lease` | 节点心跳 Lease 对象 |
+
+```bash
+# 在指定 Namespace 下操作
+kubectl get pods -n team-backend
+
+# 设置默认 Namespace(避免每次都输入 -n)
+kubectl config set-context --current --namespace=team-backend
+```
+
+> [!warning] Namespace 不提供网络隔离
+> 默认情况下,不同 Namespace 的 Pod 可以互相通信。如需网络隔离,需要配合 **NetworkPolicy** 实现。
+
+### Label & Selector
+
+Label 是附加在 K8s 对象上的**键值对**,是 K8s 组织和筛选资源的核心机制。你可能会问:「为什么 K8s 不用目录/文件夹的方式来组织资源?」——因为资源之间的关系是多对多的,一个 Pod 可能同时属于某个应用、某个环境、某个团队,Label 的灵活性远超树形结构。
+
+```yaml
+# Label 的常见命名约定
+metadata:
+ labels:
+ app.kubernetes.io/name: my-app # 应用名称
+ app.kubernetes.io/version: "1.2.3" # 版本
+ app.kubernetes.io/component: frontend # 组件
+ app.kubernetes.io/part-of: platform # 所属系统
+ app.kubernetes.io/managed-by: helm # 管理工具
+ env: production # 环境
+ team: backend # 团队
+```
+
+**Selector 的三种用法**:
+
+```bash
+# 等值选择器
+kubectl get pods -l app=my-app,env=production
+
+# 集合选择器
+kubectl get pods -l 'env in (production, staging)'
+kubectl get pods -l 'env notin (dev)'
+
+# 否定选择器
+kubectl get pods -l 'app!=my-app'
+```
+
+在资源定义中使用 Selector(以 Service 为例):
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: my-app-svc
+spec:
+ selector:
+ app: my-app # 匹配所有带 app=my-app 标签的 Pod
+ env: production
+ ports:
+ - port: 80
+ targetPort: 8080
+```
+
+### Annotation
+
+Annotation 用于存储**非标识性的附加信息**,这些信息不会被 K8s API 用于筛选,但对工具、库和运维人员很有用。典型的 Annotation 内容:
+
+```yaml
+metadata:
+ annotations:
+ # 记录维护者信息
+ owner: "backend-team@company.com"
+ # Git 提交信息(CI/CD 注入)
+ git-commit: "abc123f"
+ git-branch: "main"
+ # 构建时间
+ build-time: "2026-07-07T15:00:00Z"
+ # Ingress 配置(Nginx Ingress Controller 使用)
+ nginx.ingress.kubernetes.io/proxy-body-size: "50m"
+ nginx.ingress.kubernetes.io/ssl-redirect: "true"
+ # Prometheus 自动发现
+ prometheus.io/scrape: "true"
+ prometheus.io/port: "9090"
+```
+
+> [!tip] Label vs Annotation 如何选择?
+> 问自己一个问题:「我需要根据这个信息来筛选或分组资源吗?」—— 如果是,用 Label;如果只是附加的描述性信息,用 Annotation。Label 有长度和字符限制,Annotation 则更宽松。
+
+## K8s 与 Docker 的关系
+
+### 常见误解
+
+很多初学者会问:「K8s 是不是要替代 Docker?」答案是:**不完全是**。准确地说:
+
+- K8s 替代的是 Docker Swarm(编排层面的竞争)
+- K8s **不再直接使用 Docker Engine** 作为容器运行时(自 1.24 起)
+- 但你仍然可以**用 Docker 构建镜像**,K8s 运行的是 OCI 标准镜像,与构建工具无关
+
+### 容器运行时的演进
+
+```mermaid
+timeline
+ title K8s 容器运行时演进
+ 2014-2016 : Docker 是唯一选择
+ : kubelet 直接调用 Docker API
+ 2017 : CRI 标准引入
+ : dockershim 作为适配层
+ 2018 : containerd 从 Docker 拆分
+ : CRI-O 项目成熟
+ 2020 : dockershim 弃用公告
+ 2022-04 : K8s 1.24 移除 dockershim
+ : containerd 成为主流
+ 2024+ : CRI-O 广泛使用
+ : Kata / gVisor 沙箱运行时兴起
+```
+
+**CRI(Container Runtime Interface)** 是 K8s 定义的一套 gRPC 接口规范,任何实现了 CRI 的运行时都可以被 kubelet 使用:
+
+| 运行时 | 特点 | 适用场景 |
+|--------|------|---------|
+| containerd | Docker 核心组件独立版,功能全面 | 通用场景,最广泛使用 |
+| CRI-O | 专为 K8s 设计,轻量级 | Red Hat / OpenShift 生态 |
+| Kata Containers | 轻量级 VM,安全隔离 | 多租户、不可信工作负载 |
+| gVisor (runsc) | 用户态内核沙箱 | 安全敏感场景 |
+
+> [!info] 理解 CRI 的分层
+> kubelet → CRI (gRPC) → containerd → runc → Linux Kernel(namespaces, cgroups)
+>
+> 每一层都有明确的职责边界。containerd 负责镜像管理和容器生命周期,runc 负责实际创建 Linux 容器。
+
+### 为什么移除 dockershim?
+
+移除 dockershim 的主要原因:
+
+1. **维护成本**:dockershim 是 kubelet 内部的一层额外抽象,增加代码复杂度
+2. **性能开销**:kubelet → dockershim → Docker daemon → containerd,调用链过长
+3. **Docker 的「额外功能」**:Docker Engine 包含很多 K8s 不需要的组件(如 Docker Swarm、docker build 等),但作为运行时又必须加载整个 daemon
+4. **CRI 标准成熟**:containerd 和 CRI-O 直接实现 CRI,不再需要中间层
+
+## 部署方式对比
+
+| 方案 | 类型 | 节点数 | 生产就绪 | 学习成本 | 适用场景 |
+|------|------|--------|---------|---------|---------|
+| **minikube** | 单节点本地集群 | 1 | 否 | 低 | 本地开发和学习,支持 Addons |
+| **kind** | Docker 容器模拟 Node | 1-N | 否 | 低 | CI/CD 测试、多节点本地模拟 |
+| **kubeadm** | 生产级部署工具 | N | 是 | 中高 | 自建集群、对基础设施有完全控制 |
+| **EKS** | AWS 托管 | N | 是 | 中 | AWS 生态用户 |
+| **AKS** | Azure 托管 | N | 是 | 中 | Azure 生态用户 |
+| **GKE** | Google Cloud 托管 | N | 是 | 中 | GCP 生态用户,Autopilot 模式 |
+| **ACK** | 阿里云托管 | N | 是 | 中 | 国内云生态用户 |
+
+> [!tip] 如何选择?
+> - **学习阶段**:minikube 或 kind(零成本,快速启动)
+> - **CI/CD**:kind(轻量,测试完即销毁)
+> - **小团队生产**:托管服务(减少运维负担)
+> - **大规模/定制需求**:kubeadm 或 Cluster API
+
+## 常见陷阱与最佳实践
+
+### 陷阱
+
+1. **不设置 Resource Requests/Limits**
+ ```yaml
+ # 反面教材:Pod 没有资源声明
+ containers:
+ - name: app
+ image: my-app:v1
+ ```
+ 后果:调度器无法做出合理决策,某个 Pod 可能吃掉整个节点的资源,导致其他 Pod 被 OOM Kill。
+
+2. **把所有东西塞进一个 Pod**
+ Pod 中的容器应该有紧密的生命周期耦合。如果两个应用可以独立部署、独立扩缩容,它们应该在不同的 Pod 中。
+
+3. **忽略 Liveness/Readiness Probe**
+ 没有 Probe 的 Pod,K8s 无法知道它是否真的健康。一个「Running」状态的 Pod,内部应用可能已经死锁。
+
+4. **在生产环境使用 `latest` 标签**
+ ```yaml
+ # 反面教材
+ image: my-app:latest
+ ```
+ 你无法确定每次部署用的是哪个版本,回滚更是噩梦。始终使用明确的版本标签(如 `v1.2.3` 或 Git SHA)。
+
+5. **直接操作集群资源而不使用 GitOps**
+ 手动 `kubectl apply` 的变更不可追溯、不可审计。使用 Helm / Kustomize + ArgoCD / Flux 进行声明式管理。
+
+### 最佳实践
+
+1. **声明式 > 命令式**:始终用 YAML 定义资源,而不是 `kubectl run`/`kubectl create` 的命令式操作。声明式配置可版本控制、可审计、可复现。
+
+2. **合理使用 Namespace**:按团队或环境划分 Namespace,配合 ResourceQuota 和 LimitRange 进行资源管控。
+
+3. **Pod 反亲和性确保高可用**:
+ ```yaml
+ spec:
+ affinity:
+ podAntiAffinity:
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 100
+ podAffinityTerm:
+ labelSelector:
+ matchExpressions:
+ - key: app
+ operator: In
+ values: ["my-app"]
+ topologyKey: kubernetes.io/hostname
+ ```
+ 这确保同一应用的多个副本不会被调度到同一个 Node 上。
+
+4. **使用 `imagePullPolicy` 控制镜像拉取策略**:
+ - `Always`:总是拉取(适用于 `latest` 标签,但不推荐用 latest)
+ - `IfNotPresent`:本地没有才拉取(推荐用于明确版本标签)
+ - `Never`:从不拉取(仅用于本地开发)
+
+5. **优雅终止(Graceful Shutdown)**:
+ ```yaml
+ spec:
+ terminationGracePeriodSeconds: 30
+ containers:
+ - name: app
+ lifecycle:
+ preStop:
+ exec:
+ command: ["/bin/sh", "-c", "sleep 5"]
+ ```
+ `preStop` 中的 `sleep 5` 给 kube-proxy 更新规则留出时间,避免请求被发送到正在终止的 Pod。
+
+6. **LimitRange 设置默认值**:
+ ```yaml
+ apiVersion: v1
+ kind: LimitRange
+ metadata:
+ name: default-limits
+ namespace: team-backend
+ spec:
+ limits:
+ - default:
+ cpu: "500m"
+ memory: "256Mi"
+ defaultRequest:
+ cpu: "100m"
+ memory: "128Mi"
+ type: Container
+ ```
+ 防止单个未配置资源限制的容器拖垮整个节点。
+
+## 延伸阅读
+
+- [Kubernetes 官方文档](https://kubernetes.io/zh-cn/docs/home/) — 最权威的参考
+- [Kubernetes 官方教程](https://kubernetes.io/zh-cn/docs/tutorials/) — 动手入门
+- [CNCF 技术雷达](https://www.cncf.io/tech-radars/) — 云原生技术选型参考
+- [Kelsey Hightower - Kubernetes The Hard Way](https://github.com/kelseyhightower/kubernetes-the-hard-way) — 手动从零搭建 K8s 集群,深入理解每个组件
+- 《Kubernetes in Action (第二版)》 — Marko Lukša 著,深入原理的经典教材
+
+## 关联笔记
+
+- [[k8s/k8s-workload-resources]] — Deployment、StatefulSet、DaemonSet 等工作负载详解
+- [[k8s/k8s-networking]] — Service、Ingress、NetworkPolicy 网络模型
+- [[k8s/k8s-storage]] — PV、PVC、StorageClass 存储体系
diff --git a/technical/k8s/k8s-02-architecture.md b/technical/k8s/k8s-02-architecture.md
new file mode 100644
index 0000000..c9f8055
--- /dev/null
+++ b/technical/k8s/k8s-02-architecture.md
@@ -0,0 +1,695 @@
+---
+tags: [k8s, architecture, devops]
+create time: 2026-07-07 15:48
+---
+
+# Kubernetes 架构深度解析
+
+## 概述
+
+Kubernetes 采用经典的**控制平面 + 数据平面**分层架构。控制平面(Control Plane)负责全局决策——调度、编排、状态收敛;数据平面(Data Plane)由每个节点上的 Agent 组成,负责 Pod 的实际运行与网络转发。本文从 API Server、etcd、Scheduler、Controller Manager 四大控制平面组件,到 kubelet、kube-proxy、Container Runtime 三大数据平面组件,逐一拆解其内部机制、设计动机与协作关系,最后通过一次完整的 `kubectl apply` 请求流转,串联所有组件的工作链路。
+
+---
+
+## 控制平面(Control Plane)
+
+控制平面是 Kubernetes 集群的"大脑",所有声明式状态的写入、决策与协调均在此完成。
+
+### API Server(kube-apiserver)
+
+API Server 是整个集群的**唯一入口**(Single Source of Truth)。所有组件——kubectl、kubelet、Controller Manager、Scheduler——都只和 API Server 通信,绝不直接操作 etcd。
+
+#### 为什么这样设计?
+
+- **统一鉴权**:所有请求经过同一套认证/授权/准入链,避免各组件自行实现安全逻辑。
+- **解耦存储**:API Server 作为 etcd 的代理层,后端存储引擎可替换(理论层面),上层组件无需感知。
+- **可观测性**:集中入口便于审计日志、请求速率限制等横向关注点的统一治理。
+
+#### 请求处理链(Request Pipeline)
+
+一次 API 请求经过以下阶段:
+
+```
+Client Request
+ → Authentication(认证)
+ → Authorization(授权)
+ → Admission Control(准入控制,Mutating → Validating)
+ → Schema Validation(对象校验)
+ → etcd Write(持久化)
+```
+
+| 阶段 | 作用 | 常见实现 |
+|------|------|----------|
+| Authentication | 确认"你是谁" | X.509 Client Cert、ServiceAccount Token、OIDC、Webhook Token |
+| Authorization | 确认"你能做什么" | RBAC(主流)、ABAC、Webhook |
+| Mutating Admission | 修改对象(注入 sidecar、设置默认值) | MutatingWebhookConfiguration |
+| Validating Admission | 拒绝非法对象(资源限额、策略校验) | ValidatingAdmissionPolicy、OPA/Gatekeeper |
+| Schema Validation | 校验字段类型与必填项 | 内置 OpenAPI Schema |
+
+> [!info] RBAC 的三个核心概念
+> - **Role / ClusterRole**:定义一组权限(动词 + 资源)。
+> - **RoleBinding / ClusterRoleBinding**:将 Role 绑定到 Subject(User、Group、ServiceAccount)。
+> - **聚合 ClusterRole**:通过 Label Selector 合并多个 ClusterRole,扩展权限时无需修改原角色。
+
+#### Watch 机制
+
+API Server 支持 **Watch**——客户端可以长轮询监听资源变更,而非反复 List 轮询。其实现原理:
+
+1. 客户端发送 `GET /api/v1/pods?watch=true&resourceVersion=12345`。
+2. API Server 从 etcd 的 Watch 流中获取变更事件,经序列化后通过 HTTP chunked transfer / HTTP/2 stream 推送给客户端。
+3. 如果客户端的 `resourceVersion` 过旧(已被 API Server compaction),返回 `410 Gone`,客户端需重新 List。
+
+> [!tip] Informer 机制
+> Kubernetes client-go 库中的 Informer 封装了 Watch + 本地缓存(Indexer)+ 事件分发(EventHandler)的完整流程。所有 Controller 内部都依赖 Informer 来感知资源变化,避免直接对 API Server 发起大量请求。
+
+#### 与 etcd 的交互
+
+- **写操作**:API Server 是唯一向 etcd 写入数据的组件。
+- **读操作**:其他组件(Scheduler、Controller Manager)通过 API Server 读取,API Server 内部有 `apiserver cache` 层优化读性能。
+- **一致性保证**:API Server 使用 etcd 的 MVCC(Multi-Version Concurrency Control)实现乐观并发控制,通过 `resourceVersion` 字段做冲突检测。
+
+---
+
+### etcd
+
+etcd 是 Kubernetes 的**唯一持久化存储**,存储了集群的所有状态数据:Pods、Services、ConfigMaps、Secrets、Nodes、Namespaces 等。
+
+#### Raft 一致性协议
+
+etcd 使用 Raft 协议保证分布式一致性。Raft 将节点分为三种角色:
+
+| 角色 | 职责 | 选举条件 |
+|------|------|----------|
+| Leader | 处理所有写请求,日志复制到 Follower | 获得多数节点投票 |
+| Follower | 接收 Leader 日志,被动同步 | 默认状态 |
+| Candidate | 发起选举 | Follower 在 election timeout 内未收到心跳 |
+
+**写入流程**:
+
+1. 客户端写请求发送至 Leader。
+2. Leader 将日志条目(Log Entry)追加到本地日志。
+3. Leader 将日志复制到所有 Follower。
+4. **多数派(majority)**确认后,Leader 提交(commit)该条目。
+5. Leader 通知 Follower 提交,状态机应用变更。
+
+> [!warning] etcd 集群节点数建议
+> 生产环境建议 **3 或 5 个节点**。3 节点容忍 1 个故障,5 节点容忍 2 个故障。偶数节点(如 4)不增加容错能力反而增加复制开销,不推荐。
+
+#### Key-Value 结构
+
+etcd 以扁平的 Key 空间存储数据,Kubernetes 的资源路径映射为:
+
+```
+/registry/pods/default/my-pod
+/registry/deployments/default/my-deploy
+/registry/services/kube-system/kube-dns
+/registry/minions/node-1
+```
+
+每个 Value 是对应资源对象的 **Protocol Buffer 编码**。
+
+#### Watch 特性
+
+etcd 原生支持基于 revision 的 Watch。API Server 的 Watch 机制正是建立在 etcd Watch 之上。etcd 使用 **MVCC** 保留历史版本,支持从任意 revision 回放事件,这也是 `resourceVersion` 机制的底层基础。
+
+#### 备份与恢复策略
+
+- **定期快照**:`etcdctl snapshot save` 创建一致性快照。
+- **自动 compaction**:通过 `--auto-compaction-retention` 配置历史版本保留时间,防止存储无限增长。
+- **恢复流程**:`etcdctl snapshot restore` 恢复数据到新目录 → 重启 etcd 进程。
+
+> [!tip] etcd 的磁盘性能至关重要
+> etcd 对磁盘延迟极其敏感。生产环境应使用 SSD,避免与其他 I/O 密集型应用共享磁盘。可以使用 `fio` 工具检测磁盘 IOPS 和延迟。
+
+---
+
+### Scheduler(kube-scheduler)
+
+Scheduler 负责为每个未绑定 Node 的 Pod 选择最优节点。其核心流程是**过滤(Filter)→ 打分(Score)**两阶段调度。
+
+#### 调度流程
+
+```
+Pod Created (spec.nodeName 为空)
+ → 1. Filtering(过滤阶段)
+ - 移除不满足硬性条件的节点
+ - 例:资源不足、NodeSelector 不匹配、Taint 无对应 Toleration
+ → 2. Scoring(打分阶段)
+ - 对剩余节点按策略打分
+ - 默认策略倾向于:资源均衡分布、亲和性匹配、数据局部性
+ → 3. Binding(绑定阶段)
+ - 选择得分最高的节点,写入 Pod.spec.nodeName
+ - API Server 通知对应节点的 kubelet
+```
+
+> [!info] 为什么是 Filter + Score 两阶段?
+> 过滤阶段是**约束满足问题(CSP)**,用排除法缩小候选集;打分阶段是**优化问题**,在可行解中找最优。两阶段分离使得每一步可以独立扩展和插件化。
+
+#### 亲和性与反亲和性
+
+| 类型 | 作用域 | 典型场景 |
+|------|--------|----------|
+| `nodeAffinity` | Pod → Node | 将 Pod 调度到特定标签的节点(如 GPU 节点) |
+| `podAffinity` | Pod → Pod | 将相关 Pod 调度到同一拓扑域(如日志收集器靠近应用) |
+| `podAntiAffinity` | Pod → Pod | 将同组 Pod 分散到不同节点(高可用) |
+
+亲和性规则分为两种强度:
+- **requiredDuringSchedulingIgnoredDuringExecution**:硬性要求,不满足则不调度。
+- **preferredDuringSchedulingIgnoredDuringExecution**:软性偏好,尽量满足。
+
+> [!tip] "IgnoredDuringExecution" 的含义
+> Pod 一旦调度到某节点,即使该节点后来不再满足亲和性条件,也不会被驱逐(除非配置了 `requiredDuringScheduling` 节点亲和)。这是为了避免频繁迁移造成抖动。
+
+#### Taint / Toleration
+
+- **Taint**:施加在 Node 上的排斥标记,格式 `key=value:effect`。
+- **Toleration**:Pod 上声明的容忍声明,允许 Pod 调度到带对应 Taint 的节点。
+
+Effect 有三种:
+
+| Effect | 含义 |
+|--------|------|
+| `NoSchedule` | 不允许新 Pod 调度到该节点(已运行的不受影响) |
+| `PreferNoSchedule` | 尽量不调度,但非强制 |
+| `NoExecute` | 新 Pod 不调度 + 已运行且无 Toleration 的 Pod 会被驱逐 |
+
+> [!warning] Master 节点的 Taint
+> 生产集群的 Master 节点通常带有 `node-role.kubernetes.io/control-plane:NoExecute` Taint,防止业务 Pod 消耗控制平面资源。如果需要在 Master 运行监控组件,需添加对应 Toleration。
+
+#### PriorityClass
+
+PriorityClass 定义 Pod 的调度优先级。当集群资源不足时,低优先级 Pod 可能被**抢占(Preemption)**——即 Scheduler 驱逐低优先级 Pod 为高优先级 Pod 腾出空间。
+
+```yaml
+apiVersion: scheduling.k8s.io/v1
+kind: PriorityClass
+metadata:
+ name: high-priority
+value: 1000000
+globalDefault: false
+description: "用于关键业务 Pod"
+```
+
+> [!info] 调度框架(Scheduling Framework)
+> Kubernetes 1.19+ 引入 Scheduling Framework,将调度过程拆分为多个扩展点(QueueSort、PreFilter、Filter、PostFilter、Score、Reserve、Permit、Bind、PostBind),插件可以在任意扩展点介入,极大提升了调度器的可扩展性。
+
+---
+
+### Controller Manager(kube-controller-manager)
+
+Controller Manager 是一组**控制器(Controller)**的集合进程,每个控制器负责一种资源类型的持续协调。
+
+#### 控制循环(Reconcile Loop)原理
+
+所有控制器遵循同一个模式——**声明式控制循环**:
+
+```mermaid
+graph LR
+ A[Observe: 观察当前状态] --> B[Diff: 比较期望状态与实际状态]
+ B --> C{存在差异?}
+ C -->|是| D[Act: 执行操作收敛差异]
+ D --> A
+ C -->|否| E[Wait: 等待下一次事件触发]
+ E --> A
+```
+
+这就是 Kubernetes 的核心哲学:**不要告诉系统"怎么做"(命令式),而是告诉它"要什么"(声明式),系统自行收敛到期望状态。**
+
+#### 常见控制器详解
+
+##### Deployment Controller
+
+- 监听 Deployment 对象的变更。
+- 当 `.spec.replicas` 或 `.spec.template` 发生变化时,创建或更新对应的 **ReplicaSet**。
+- 支持 **Rolling Update**(滚动更新):创建新版 ReplicaSet 逐步扩容,同时缩减旧版。
+- 支持 **Rollback**:通过 `kubectl rollout undo` 切换回旧版 ReplicaSet。
+
+**Deployment → ReplicaSet → Pod** 的层级关系:
+
+```
+Deployment (my-app)
+ ├── ReplicaSet (my-app-7d8b9f, revision 2) ← 当前版本
+ │ ├── Pod-1
+ │ ├── Pod-2
+ │ └── Pod-3
+ └── ReplicaSet (my-app-4a6c2e, revision 1) ← 旧版本(保留用于回滚)
+```
+
+##### ReplicaSet Controller
+
+- 确保指定数量的 Pod 副本始终运行。
+- 当 Pod 数量 < `replicas` 时创建新 Pod。
+- 当 Pod 数量 > `replicas` 时删除多余 Pod(优先删除处于异常状态的 Pod)。
+- 使用 **Label Selector** 匹配归属的 Pod。
+
+##### Node Controller
+
+- 监控每个 Node 的健康状态(通过 kubelet 上报的 `NodeStatus`)。
+- 当 Node 的 `Ready` 条件变为 `Unknown`(默认 40 秒未收到心跳)时,标记为 `NodeNotReady`。
+- 经过 `--pod-eviction-timeout`(默认 5 分钟)后,驱逐该节点上的所有 Pod,触发 Scheduler 重新调度。
+
+**Node 状态条件**:
+
+| Condition | 含义 |
+|-----------|------|
+| `Ready` | kubelet 心跳正常 |
+| `MemoryPressure` | 节点内存不足 |
+| `DiskPressure` | 节点磁盘不足 |
+| `PIDPressure` | 节点进程数过多 |
+| `NetworkUnavailable` | 网络插件未就绪 |
+
+##### Job Controller
+
+- 管理批处理任务的生命周期。
+- 确保 Job 中的 Pod 成功运行指定次数(`.spec.completions`)。
+- 支持三种模式:单 Pod Job、固定完成次数 Job、工作队列 Job。
+- 通过 `.spec.backoffLimit` 控制失败重试次数。
+- `ttlSecondsAfterFinished` 可在完成后自动清理 Job。
+
+> [!tip] 控制器如何避免重复处理?
+> Informer 维护一个本地缓存的 `resourceVersion`,控制器在处理完一个事件后将当前版本记录下来。下次事件如果版本相同则跳过。此外,控制器的 Worker Queue 会对同一资源进行去重合并。
+
+---
+
+## 数据平面(Data Plane)
+
+数据平面由集群中每个工作节点上的组件组成,负责 Pod 的实际创建、运行与网络通信。
+
+### kubelet
+
+kubelet 是运行在每个节点上的 **Node Agent**,是控制平面与数据平面之间的桥梁。
+
+#### Pod 生命周期管理
+
+kubelet 监听 API Server 中分配到本节点的 Pod,按照以下流程管理:
+
+```mermaid
+stateDiagram-v2
+ [*] --> Pending: API Server 创建 Pod
+ Pending --> ContainerCreating: kubelet 接收
+ ContainerCreating --> Running: 所有容器启动成功
+ Running --> Succeeded: 所有容器正常退出(Exit 0)
+ Running --> Failed: 容器异常退出
+ Running --> Running: 健康检查通过
+ Failed --> Running: 重启策略触发(RestartPolicy)
+ Succeeded --> [*]
+ Failed --> [*]: 超过重启次数上限
+```
+
+**Pod 阶段(Phase)**:
+
+| Phase | 含义 |
+|-------|------|
+| `Pending` | 已被 API Server 接受,但容器尚未全部创建 |
+| `Running` | 至少一个容器正在运行 |
+| `Succeeded` | 所有容器正常终止 |
+| `Failed` | 至少一个容器异常终止 |
+| `Unknown` | 无法获取 Pod 状态(通常是节点失联) |
+
+#### CRI 调用链
+
+kubelet 通过 **CRI(Container Runtime Interface)** 与容器运行时交互。CRI 是基于 gRPC 的标准接口:
+
+```
+kubelet
+ → CRI Client(内置)
+ → Runtime Service(管理容器生命周期:RunPodSandbox、CreateContainer、StartContainer、StopContainer)
+ → Image Service(管理镜像:PullImage、ListImages、RemoveImage)
+ → containerd(主流实现)
+```
+
+> [!info] 为什么引入 CRI?
+> Kubernetes 早期绑定 Docker,切换运行时需要重新编译 kubelet。CRI 作为抽象层,使得 kubelet 可以对接任何实现了 CRI 接口的运行时(containerd、CRI-O 等),实现了**运行时可插拔**。
+
+#### 健康检查执行
+
+kubelet 执行三类探针(Probe):
+
+| 探针类型 | 作用 | 失败后果 |
+|----------|------|----------|
+| **Liveness Probe** | 检测容器是否存活 | 重启容器 |
+| **Readiness Probe** | 检测容器是否就绪接收流量 | 从 Service Endpoint 中移除 |
+| **Startup Probe** | 检测容器是否启动完成 | 在 Startup Probe 成功前,Liveness/Readiness 不生效 |
+
+探针支持三种探测方式:
+
+- **httpGet**:发送 HTTP GET 请求,2xx/3xx 视为成功。
+- **tcpSocket**:尝试建立 TCP 连接,连接成功视为成功。
+- **exec**:在容器内执行命令,退出码为 0 视为成功。
+
+> [!warning] 没有 Readiness Probe 的后果
+> 如果不配置 Readiness Probe,容器一启动就会被加入 Service Endpoint,可能在应用初始化完成前就收到流量,导致请求失败。生产环境中**强烈建议为每个 Pod 配置 Readiness Probe**。
+
+---
+
+### kube-proxy
+
+kube-proxy 运行在每个节点上,负责实现 **Service** 的网络转发逻辑。
+
+#### Service 实现原理
+
+Service 是 Kubernetes 提供的**服务发现与负载均衡**抽象。一个 Service 通过 Label Selector 匹配一组后端 Pod,并分配一个稳定的虚拟 IP(ClusterIP)。
+
+当客户端访问 `ServiceIP:Port` 时,kube-proxy 负责将流量转发到后端 Pod。
+
+#### iptables 模式 vs IPVS 模式
+
+| 维度 | iptables 模式 | IPVS 模式 |
+|------|---------------|-----------|
+| **实现方式** | 使用 iptables 规则链做 DNAT | 使用内核 IPVS(IP Virtual Server)模块 |
+| **规则数量** | 随 Service/Endpoint 数量线性增长 | 使用 Hash 表,复杂度 O(1) |
+| **性能** | Service 数量 < 1000 时表现良好 | Service 数量 > 1000 时性能优势明显 |
+| **负载均衡算法** | 随机(概率相等) | 支持多种:rr、lc、dh、sh、sed、nq |
+| **连接追踪** | 依赖 conntrack 模块 | IPVS 自带连接追踪 |
+| **会话保持** | 不支持 | 支持(Client-IP 会话亲和) |
+| **内核要求** | 标准 Linux 内核 | 需要 IPVS 内核模块(`ip_vs`、`ip_vs_rr` 等) |
+| **适用场景** | 中小规模集群 | 大规模集群(Service 数量多) |
+
+> [!tip] nftables 模式(Kubernetes 1.29+)
+> Kubernetes 1.29 引入 nftables 代理模式作为 iptables 的替代。nftables 是 iptables 的继任者,提供更好的规则匹配性能和更简洁的语法。未来可能成为默认模式。
+
+**iptables 模式的转发链路**:
+
+```
+Client → KUBE-SERVICES Chain
+ → KUBE-SVC-XXXX (匹配 Service ClusterIP:Port)
+ → KUBE-SEP-YYYY (DNAT 到后端 Pod IP:Port,随机选择)
+ → Pod
+```
+
+**IPVS 模式的转发链路**:
+
+```
+Client → IPVS Virtual Server (Service ClusterIP:Port)
+ → IPVS Real Server (后端 Pod IP:Port,按算法选择)
+ → Pod
+```
+
+> [!info] ClusterIP、NodePort、LoadBalancer 三种 Service 类型的关系
+> - **ClusterIP**:分配集群内部虚拟 IP,kube-proxy 通过 iptables/IPVS 实现转发。
+> - **NodePort**:在 ClusterIP 基础上,在每个节点开放一个端口(30000-32767),外部可通过 `NodeIP:NodePort` 访问。
+> - **LoadBalancer**:在 NodePort 基础上,调用云厂商 API 创建外部负载均衡器,将流量转发到 NodePort。
+
+---
+
+### Container Runtime(containerd)
+
+containerd 是目前 Kubernetes 最主流的容器运行时,从 Docker 中剥离出来的**工业级容器运行时**。
+
+#### containerd 架构
+
+```mermaid
+graph TB
+ K[kubelet] -->|CRI gRPC| CRI[CRI Plugin]
+ CRI --> CT[Container Service]
+ CRI --> SS[Sandbox Service]
+ CRI --> IS[Image Service]
+ CT --> T[Task Manager]
+ T --> R[runc / youki]
+ IS --> CS[Content Store]
+ SS --> NS[Network Sandbox]
+```
+
+containerd 的核心模块:
+
+| 模块 | 职责 |
+|------|------|
+| **CRI Plugin** | 实现 CRI gRPC 接口,翻译 kubelet 请求 |
+| **Container Service** | 管理容器的元数据(创建、删除、查询) |
+| **Task Manager** | 管理容器进程的生命周期(start、stop、pause) |
+| **Content Store** | 管理镜像层(layer)的存储,支持 content-addressable 存储 |
+| **Snapshotter** | 管理容器文件系统快照(overlayfs、btrfs 等) |
+| **Events** | 发布容器生命周期事件 |
+
+#### CRI 接口规范
+
+CRI 定义了两组 gRPC Service:
+
+1. **RuntimeService**:管理 Pod Sandbox 和容器的生命周期。
+
+```protobuf
+service RuntimeService {
+ rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse);
+ rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse);
+ rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
+ rpc StartContainer(StartContainerRequest) returns (StartContainerResponse);
+ rpc StopContainer(StopContainerRequest) returns (StopContainerResponse);
+ rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse);
+ rpc ListContainers(ListContainersRequest) returns (ListContainersResponse);
+ rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse);
+ // ...
+}
+```
+
+2. **ImageService**:管理镜像的拉取、查询与删除。
+
+```protobuf
+service ImageService {
+ rpc PullImage(PullImageRequest) returns (PullImageResponse);
+ rpc ListImages(ListImagesRequest) returns (ListImagesResponse);
+ rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse);
+ rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse);
+}
+```
+
+> [!info] Pod Sandbox 是什么?
+> Pod Sandbox 是 CRI 中对 Pod 级别资源的抽象,包括网络命名空间、PID 命名空间等共享资源。containerd 中对应的是 `pause` 容器(也叫 infra 容器),它的唯一作用是持有命名空间,业务容器通过加入这些命名空间实现网络共享。
+
+---
+
+## 请求流转全过程
+
+从 `kubectl apply -f deployment.yaml` 到 Pod 变为 `Running` 状态,整个请求经过以下完整路径:
+
+```mermaid
+sequenceDiagram
+ actor User as 用户
+ participant kubectl as kubectl
+ participant API as API Server
+ participant etcd as etcd
+ participant DC as Deployment Controller
+ participant RSC as ReplicaSet Controller
+ participant SC as Scheduler
+ participant KL as kubelet
+ participant CRI as containerd
+ participant Pod as Pod
+
+ User->>kubectl: kubectl apply -f deploy.yaml
+ kubectl->>kubectl: 本地 YAML 校验
+ kubectl->>API: POST /apis/apps/v1/namespaces/{ns}/deployments
+
+ Note over API: Authentication (证书认证)
+ Note over API: Authorization (RBAC 鉴权)
+ Note over API: Admission Control (Mutating + Validating)
+
+ API->>etcd: 写入 Deployment 对象
+ etcd-->>API: OK
+
+ Note over API: 返回 201 Created 给 kubectl
+ API-->>kubectl: Deployment created
+ kubectl-->>User: deployment.apps/xxx created
+
+ Note over DC: Deployment Controller 通过 Watch 感知变更
+
+ DC->>API: 创建 ReplicaSet
+ API->>etcd: 写入 ReplicaSet 对象
+ etcd-->>API: OK
+
+ Note over RSC: ReplicaSet Controller 通过 Watch 感知变更
+
+ loop 创建 3 个 Pod (replicas=3)
+ RSC->>API: 创建 Pod (spec.nodeName 为空)
+ API->>etcd: 写入 Pod 对象
+ etcd-->>API: OK
+ end
+
+ Note over SC: Scheduler 通过 Watch 感知未调度的 Pod
+
+ loop 每个 Pod
+ SC->>SC: Filter: 过滤不满足条件的节点
+ SC->>SC: Score: 对候选节点打分
+ SC->>API: Bind Pod to Node
+ API->>etcd: 更新 Pod.spec.nodeName
+ etcd-->>API: OK
+ end
+
+ Note over KL: kubelet 通过 Watch 感知分配到本节点的 Pod
+
+ KL->>CRI: RunPodSandbox (创建 pause 容器)
+ CRI-->>KL: Sandbox ID
+
+ KL->>CRI: CreateContainer (创建业务容器)
+ CRI-->>KL: Container ID
+
+ KL->>CRI: StartContainer (启动业务容器)
+ CRI->>Pod: 容器进程启动
+ CRI-->>KL: OK
+
+ KL->>API: 更新 Pod Status 为 Running
+ API->>etcd: 更新 Pod Status
+ etcd-->>API: OK
+```
+
+### 关键阶段说明
+
+1. **kubectl 阶段**:kubectl 做本地 YAML 解析与校验,然后通过 REST API 发送给 API Server。
+2. **API Server 阶段**:经过完整的认证→授权→准入链后,将对象持久化到 etcd。
+3. **Deployment Controller 阶段**:Watch 到 Deployment 变更,创建对应的 ReplicaSet。
+4. **ReplicaSet Controller 阶段**:Watch 到 ReplicaSet 变更,创建指定数量的 Pod 对象(此时 Pod 尚未绑定节点)。
+5. **Scheduler 阶段**:Watch 到未调度的 Pod,执行 Filter + Score 两阶段调度,将结果写入 `Pod.spec.nodeName`。
+6. **kubelet 阶段**:目标节点的 kubelet Watch 到分配给自己的 Pod,通过 CRI 接口调用 containerd 创建和启动容器。
+7. **状态更新**:kubelet 将 Pod 状态上报给 API Server,完成整个流程。
+
+> [!tip] 整个过程大约需要几秒到几十秒
+> 时间取决于镜像大小、节点资源状况、网络速度等因素。可以通过 `kubectl describe pod` 查看每个阶段的时间戳,定位瓶颈所在。
+
+---
+
+## 高可用架构
+
+生产环境需要部署多 Master 节点以消除单点故障。
+
+### 多 Master 部署
+
+```mermaid
+graph TB
+ LB[负载均衡器
如 HAProxy / 云 LB]
+
+ subgraph Master 集群
+ M1[Master 1
API Server + Scheduler + Controller Manager]
+ M2[Master 2
API Server + Scheduler + Controller Manager]
+ M3[Master 3
API Server + Scheduler + Controller Manager]
+ end
+
+ subgraph etcd 集群
+ E1[etcd 1]
+ E2[etcd 2]
+ E3[etcd 3]
+ end
+
+ subgraph Worker 集群
+ W1[Node 1]
+ W2[Node 2]
+ W3[Node 3]
+ end
+
+ LB --> M1
+ LB --> M2
+ LB --> M3
+
+ M1 --- E1
+ M2 --- E2
+ M3 --- E3
+
+ E1 --- E2
+ E2 --- E3
+
+ W1 --> LB
+ W2 --> LB
+ W3 --> LB
+```
+
+### 组件高可用机制
+
+| 组件 | 高可用机制 | 说明 |
+|------|-----------|------|
+| **API Server** | 多实例 + 负载均衡 | 无状态组件,可水平扩展 |
+| **etcd** | Raft 共识协议 | 3/5 节点集群,容忍 (n-1)/2 故障 |
+| **Scheduler** | Leader Election | 多实例运行,仅 Leader 执行调度 |
+| **Controller Manager** | Leader Election | 多实例运行,仅 Leader 执行控制循环 |
+
+> [!info] Leader Election 机制
+> Scheduler 和 Controller Manager 使用 Kubernetes Lease 对象实现 Leader Election。每个实例启动时尝试获取 Lease,获取成功者成为 Leader 并定期续租。Leader 故障后,其他实例在 Lease 过期后重新竞选。
+
+### etcd 集群拓扑选择
+
+**堆叠式(Stacked)拓扑**:
+
+- etcd 与 Master 组件部署在同一节点。
+- 优点:部署简单,节点数少。
+- 缺点:Master 节点故障同时丢失 etcd 成员,可靠性较低。
+
+**外部式(External)etcd 拓扑**:
+
+- etcd 独立部署在专用节点上。
+- 优点:Master 故障不影响 etcd 集群,可靠性更高。
+- 缺点:节点数更多,运维复杂度增加。
+
+| 维度 | 堆叠式 | 外部式 |
+|------|--------|--------|
+| 节点数 | 3 Master = 3 etcd | 3 Master + 3 etcd = 6 |
+| 容错能力 | 1 节点故障 | 1 Master 故障 + 1 etcd 故障 |
+| 部署复杂度 | 低 | 高 |
+| 适用场景 | 测试/小规模生产 | 大规模生产 |
+
+### 负载均衡方案
+
+- **硬件 LB**:F5 等(传统数据中心)。
+- **软件 LB**:HAProxy + Keepalived(自建集群)、Nginx(四层代理)。
+- **云厂商 LB**:AWS NLB、阿里云 SLB、GCP Load Balancer(托管集群通常内置)。
+
+> [!warning] API Server 的负载均衡是四层(TCP),不是七层(HTTP)
+> 因为 API Server 使用 HTTP/2 + gRPC,七层负载均衡可能破坏长连接和流式请求。应使用 L4 负载均衡(如 HAProxy TCP mode、NLB)。
+
+---
+
+## 常见陷阱与最佳实践
+
+### 陷阱
+
+1. **etcd 磁盘性能不足导致集群卡顿**
+ - etcd 是延迟敏感型应用,磁盘 I/O 慢会导致 Leader 选举频繁、API Server 超时。
+ - 解决:使用 SSD,监控 `etcd_disk_wal_fsync_duration_seconds` 指标。
+
+2. **RBAC 权限配置不当**
+ - 过于宽松的 ClusterRole 绑定带来安全风险;过于严格导致组件无法工作。
+ - 解决:遵循最小权限原则,使用 `kubectl auth can-i` 验证权限。
+
+3. **未配置 Pod 资源请求(requests/limits)**
+ - 没有 requests,Scheduler 无法做出合理的调度决策。
+ - 没有 limits,单个 Pod 可能耗尽节点资源,影响其他 Pod。
+ - 解决:始终为容器设置 `resources.requests` 和 `resources.limits`。
+
+4. **kubelet 证书过期**
+ - kubeadm 默认签发的证书有效期为 1 年。
+ - 解决:设置证书自动轮换(`--rotate-certificates`),或使用外部 CA 签发长周期证书。
+
+5. **使用 `latest` 镜像标签**
+ - `latest` 标签不可追溯,回滚时无法确定之前的版本。
+ - 解决:始终使用明确的版本标签(如 `v1.2.3`)或镜像摘要(sha256)。
+
+### 最佳实践
+
+1. **控制平面组件监控**
+ - 监控 API Server 的请求延迟、错误率(`apiserver_request_duration_seconds`)。
+ - 监控 etcd 的 Leader 切换次数、提案提交延迟。
+ - 监控 Scheduler 的调度延迟与 Pending Pod 数量。
+
+2. **分离 Master 与 Worker 节点**
+ - Master 节点不运行业务 Pod(通过 Taint 机制)。
+ - 避免业务负载影响控制平面稳定性。
+
+3. **etcd 定期备份**
+ - 至少每日一次快照,保留 7 天。
+ - 备份存储在独立于集群的存储位置。
+ - 定期演练恢复流程。
+
+4. **合理设置 Pod Disruption Budget (PDB)**
+ - 在节点维护(drain)时,PDB 保证最少可用 Pod 数量。
+ - 示例:`minAvailable: 2` 确保滚动维护时至少 2 个 Pod 运行。
+
+5. **使用 Pod Priority and Preemption**
+ - 关键系统组件(如 CoreDNS、Ingress Controller)设置高优先级。
+ - 避免资源紧张时关键组件被驱逐。
+
+---
+
+## 延伸阅读
+
+- [[k8s-01-overview]] — Kubernetes 基础概念概览
+- Kubernetes 官方文档 - Architecture: https://kubernetes.io/docs/concepts/overview/components/
+- etcd 官方文档: https://etcd.io/docs/
+- Kubernetes Scheduler 源码: https://github.com/kubernetes/kubernetes/tree/master/pkg/scheduler
+- containerd 官方文档: https://containerd.io/docs/
+- 《Kubernetes in Action》第二版 — Marko Lukša
+- 《Programming Kubernetes》— Michael Hausenblas, Stefan Schimanski
diff --git a/technical/k8s/k8s-03-pod.md b/technical/k8s/k8s-03-pod.md
new file mode 100644
index 0000000..4253962
--- /dev/null
+++ b/technical/k8s/k8s-03-pod.md
@@ -0,0 +1,725 @@
+---
+tags: [k8s, pod, devops]
+create time: 2026-07-07 15:48
+---
+
+# Pod — Kubernetes 最小调度单元
+
+## 概述
+
+Pod 是 Kubernetes 中**最小的可部署和调度单元**。它不是一个容器,而是一组(一个或多个)共享网络和存储资源的容器的逻辑封装。为什么 Kubernetes 不直接调度容器,而是引入了 Pod 这一抽象层?因为在实际业务中,多个紧密协作的进程往往需要共享同一个 IP 地址、端口空间和卷挂载——比如一个 Web 应用容器和一个负责收集日志的 sidecar 容器。Pod 正是为了解决这种「亲密耦合进程协同运行」的需求而设计的。理解 Pod,是掌握 Kubernetes 一切上层抽象(Deployment、StatefulSet、DaemonSet 等)的基础。
+
+## Pod 的本质
+
+### 共享网络命名空间
+
+同一个 Pod 内的所有容器共享**同一个网络命名空间(Network Namespace)**,这意味着:
+
+- 它们拥有**相同的 IP 地址**
+- 它们可以**通过 localhost 互相通信**
+- 端口空间是共享的,因此同一 Pod 内的容器**不能绑定相同的端口**
+
+> [!question] 为什么不直接调度容器?
+> 因为单个容器通常只运行一个进程(这是容器设计的最佳实践)。但在很多场景下,多个进程需要紧密协作——比如 Nginx + 日志收集器、主应用 + 配置热加载 sidecar。如果让调度器去理解「哪些容器应该放在一起」,复杂度会急剧上升。Pod 将这个决策交给了用户:你来决定哪些容器属于同一个 Pod。
+
+### Pause 容器 — Pod 的「灵魂」
+
+每个 Kubernetes Pod 启动时,都会先创建一个特殊的**Pause 容器**(也叫 infra 容器),它是 Pod 的第一个容器,也是整个 Pod 的「根进程」。其他业务容器通过加入 Pause 容器的 Network Namespace 和 PID Namespace 来实现共享。
+
+```mermaid
+graph TB
+ subgraph Pod
+ P["Pause 容器
(infra container)"]
+ C1["业务容器 A"]
+ C2["业务容器 B
(sidecar)"]
+ C3["业务容器 C"]
+ end
+
+ P -->|"提供 Network NS"| C1
+ P -->|"提供 Network NS"| C2
+ P -->|"提供 Network NS"| C3
+ P -->|"提供 PID NS (可选)"| C1
+ P -->|"提供 PID NS (可选)"| C2
+ P -->|"提供 PID NS (可选)"| C3
+```
+
+Pause 容器的职责非常简单:
+
+1. **持有 Network Namespace** — 确保 Pod 的 IP 地址在所有容器生命周期内保持不变
+2. **充当 PID 1** — 回收僵尸进程(zombie reaping)
+3. **极轻量** — 通常只有几百 KB,使用 `registry.k8s.io/pause` 镜像
+
+> [!info] Pause 容器的代码只有几百行
+> 它的核心逻辑就是一个无限循环的 `pause()` 系统调用。正因为足够简单,它几乎不会出问题,可以稳定地为 Pod 内所有容器提供基础运行环境。
+
+### 容器与 Pod 的关系(1:N)
+
+一个 Pod 可以包含多个容器,典型模式如下:
+
+| 模式 | 说明 | 示例 |
+|------|------|------|
+| 单容器 Pod | 最常见的场景,一个 Pod 运行一个应用容器 | 一个 Go Web 服务 |
+| 多容器 Pod (sidecar) | 主容器 + 辅助容器,辅助容器扩展主容器功能 | Nginx + Filebeat 日志收集 |
+| 多容器 Pod (adapter) | 辅助容器将主容器的输出转换为标准格式 | 应用日志 → Fluentd 标准格式 |
+| 多容器 Pod (ambassador) | 辅助容器代理网络请求 | 应用 → Envoy Proxy → 外部服务 |
+
+```yaml
+# 一个多容器 Pod 的简单示例:Web 服务 + 日志收集 sidecar
+apiVersion: v1
+kind: Pod
+metadata:
+ name: web-with-logging
+spec:
+ containers:
+ - name: web
+ image: nginx:1.25
+ ports:
+ - containerPort: 80
+ volumeMounts:
+ - name: log-volume
+ mountPath: /var/log/nginx
+ - name: log-collector
+ image: fluent/fluent-bit:latest
+ volumeMounts:
+ - name: log-volume
+ mountPath: /var/log/nginx
+ readOnly: true
+ volumes:
+ - name: log-volume
+ emptyDir: {}
+```
+
+两个容器共享 `log-volume` 卷,Web 容器写入日志,Fluent Bit 容器读取并转发——这是经典的日志收集 sidecar 模式。
+
+## Pod 生命周期
+
+Pod 的生命周期由**阶段(Phase)**和**条件(Condition)**共同描述。从创建到销毁,Pod 经历以下主要阶段:
+
+```mermaid
+stateDiagram-v2
+ [*] --> Pending: kubectl apply / API 创建
+ Pending --> Running: 至少一个容器启动成功
+ Pending --> Failed: 拉取镜像失败 / 调度失败
+ Running --> Succeeded: 所有容器正常退出(exit 0)
+ Running --> Failed: 容器异常退出 / OOMKilled
+ Running --> Unknown: Node 失联, 无法获取状态
+ Succeeded --> [*]
+ Failed --> [*]
+ Unknown --> Running: Node 恢复通信
+ Unknown --> Failed: 确认 Pod 已丢失
+```
+
+### Init Containers
+
+Init Containers 是在**主容器启动之前**按顺序运行的容器。它们主要用于完成一些前置条件的检查和准备工作。
+
+**与主容器的区别:**
+
+| 特性 | Init Container | 主容器 (App Container) |
+|------|---------------|----------------------|
+| 执行顺序 | 严格按顺序依次执行 | 并行启动 |
+| 失败行为 | 失败后 Pod 重启(遵循 restartPolicy) | 失败后根据探针配置重启 |
+| 探针支持 | 不支持 liveness/readiness probe | 支持 |
+| 用途 | 前置条件检查、等待依赖、初始化配置 | 运行业务逻辑 |
+| 资源计算 | 独立于主容器的资源请求 | 作为整体调度依据 |
+
+> [!tip] 何时使用 Init Container?
+> 当你需要在主容器启动前完成某些「只执行一次」的任务时——比如等待数据库就绪、下载配置文件、执行数据库迁移等——Init Container 是最佳选择。
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: app-with-init
+spec:
+ initContainers:
+ # 第一个 Init Container:等待数据库就绪
+ - name: wait-for-db
+ image: busybox:1.36
+ command:
+ - sh
+ - -c
+ - |
+ until nc -z mysql-service 3306; do
+ echo "Waiting for MySQL..."
+ sleep 2
+ done
+ echo "MySQL is ready!"
+ # 第二个 Init Container:执行数据库迁移
+ - name: db-migration
+ image: myapp/migration:v1.2
+ env:
+ - name: DB_HOST
+ value: "mysql-service"
+ containers:
+ - name: app
+ image: myapp/api:v1.2
+ ports:
+ - containerPort: 8080
+```
+
+上述示例中,两个 Init Container **严格按顺序**执行:先等 MySQL 可达,再执行数据库迁移。只有两者都成功(exit 0)后,主容器 `app` 才会启动。
+
+### 容器探针
+
+Kubernetes 提供三种探针来监控容器的健康状态:
+
+| 探针类型 | 作用 | 失败后果 | 典型场景 |
+|---------|------|---------|---------|
+| `livenessProbe` | 检测容器是否**存活** | 杀死容器并重启 | 进程死锁、无限循环 |
+| `readinessProbe` | 检测容器是否**就绪**接收流量 | 从 Service 的 Endpoints 中移除 | 启动预热、临时过载 |
+| `startupProbe` | 检测容器是否**启动完成** | 杀死容器并重启 | 启动耗时长的应用(如 Java) |
+
+> [!warning] startupProbe 与 livenessProbe 的关系
+> 当配置了 `startupProbe` 时,在它成功之前,`livenessProbe` 和 `readinessProbe` 都不会执行。这是为了防止启动时间较长的应用被误判为不健康。`startupProbe` 的 `failureThreshold * periodSeconds` 应该大于应用的最大启动时间。
+
+**探针支持的检测方式:**
+
+- **httpGet** — 发送 HTTP GET 请求,2xx/3xx 视为成功
+- **tcpSocket** — 尝试 TCP 连接,端口可达视为成功
+- **exec** — 执行命令,exit code 0 视为成功
+- **grpc** — gRPC 健康检查协议(Kubernetes 1.24+)
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: probes-demo
+spec:
+ containers:
+ - name: app
+ image: myapp:v1
+ ports:
+ - containerPort: 8080
+
+ # 启动探针:最多给 60 秒启动时间 (10 次 x 6 秒)
+ startupProbe:
+ httpGet:
+ path: /healthz
+ port: 8080
+ failureThreshold: 10
+ periodSeconds: 6
+
+ # 存活探针:每 15 秒检测一次,连续 3 次失败则重启
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8080
+ initialDelaySeconds: 0 # startupProbe 成功后才开始
+ periodSeconds: 15
+ failureThreshold: 3
+ timeoutSeconds: 5
+
+ # 就绪探针:每 5 秒检测一次,失败则从 Endpoints 移除
+ readinessProbe:
+ httpGet:
+ path: /ready
+ port: 8080
+ periodSeconds: 5
+ failureThreshold: 3
+ timeoutSeconds: 3
+```
+
+> [!tip] 最佳实践
+> 生产环境中建议同时配置三种探针:`startupProbe` 保护慢启动应用、`livenessProbe` 检测死锁、`readinessProbe` 控制流量切换。`readinessProbe` 的检测频率可以比 `livenessProbe` 更高,因为它的失败不会触发重启。
+
+### Pod 阶段(Phase)与条件(Condition)
+
+**Pod Phase** 是对 Pod 在其生命周期中所处位置的高层概括:
+
+| Phase | 说明 |
+|-------|------|
+| `Pending` | Pod 已被 API Server 接受,但尚未调度或容器镜像未拉取完成 |
+| `Running` | Pod 已绑定到节点,且至少有一个容器处于运行状态 |
+| `Succeeded` | 所有容器正常退出(exit 0),不会再重启 |
+| `Failed` | 所有容器已终止,且至少有一个容器是非正常退出 |
+| `Unknown` | 无法获取 Pod 状态,通常是与 Node 的通信中断 |
+
+**Pod Conditions** 提供了更细粒度的状态信息,是一个数组,每个 Condition 包含以下字段:
+
+```yaml
+conditions:
+ - type: PodScheduled # 条件类型
+ status: "True" # True / False / Unknown
+ lastProbeTime: null # 上次探测时间
+ lastTransitionTime: "2026-07-07T07:00:00Z" # 上次状态转换时间
+ reason: "SchedulerCompletedSchedulingReason"
+ message: "Successfully assigned default/my-pod to node-1"
+```
+
+常见的 Condition Type:
+
+| Type | 说明 |
+|------|------|
+| `PodScheduled` | Pod 已被调度到某个节点 |
+| `Initialized` | 所有 Init Container 已成功完成 |
+| `ContainersReady` | 所有容器都已就绪 |
+| `Ready` | Pod 可以对外提供服务,被纳入 Service Endpoints |
+
+```mermaid
+graph LR
+ A["PodScheduled
status: True"] --> B["Initialized
status: True"]
+ B --> C["ContainersReady
status: True"]
+ C --> D["Ready
status: True"]
+```
+
+> [!info] Condition vs Phase
+> Phase 是一个全局性的、互斥的状态值(一个 Pod 在同一时刻只有一个 Phase)。而 Conditions 是一组独立的布尔标志,一个 Pod 可以同时有多个 Condition 为 True。Conditions 通常用于精确排查 Pod 启动过程中的具体卡点。
+
+## 资源管理
+
+### Requests 与 Limits
+
+Kubernetes 通过 `requests` 和 `limits` 两个维度来管理容器的资源:
+
+- **requests(请求量)** — 调度器依据此值决定 Pod 放到哪个节点。是容器运行所需的**最小**资源量
+- **limits(限制量)** — 容器可使用的**最大**资源量。超出限制时的行为取决于资源类型
+
+```yaml
+resources:
+ requests:
+ cpu: "250m" # 0.25 核
+ memory: "128Mi" # 128 MiB
+ limits:
+ cpu: "500m" # 0.5 核
+ memory: "256Mi" # 256 MiB
+```
+
+> [!question] CPU 超限和 Memory 超限的处理方式为什么不同?
+> CPU 是**可压缩资源(compressible)**——超出 limit 时会被节流(throttle),进程不会被杀死。而内存是**不可压缩资源(incompressible)**——超出 limit 时进程会被 OOMKilled。这是因为 CPU 可以通过时间片轮转来降级服务,但内存一旦不足,系统无法「回收」已分配的内存页面。
+
+### QoS(Quality of Service)三级分类
+
+Kubernetes 根据 requests 和 limits 的配置将 Pod 分为三个 QoS 等级:
+
+| QoS 等级 | 条件 | OOM 优先级 | 调度优先级 |
+|----------|------|-----------|-----------|
+| **Guaranteed** | 每个容器的 requests == limits | 最低(最后被杀) | 最高 |
+| **Burstable** | 至少一个容器设置了 requests,且 requests < limits | 中等 | 中等 |
+| **BestEffort** | 所有容器均未设置 requests 和 limits | 最高(最先被杀) | 最低 |
+
+```yaml
+# Guaranteed — requests 等于 limits
+containers:
+ - name: critical-app
+ resources:
+ requests:
+ cpu: "1"
+ memory: "1Gi"
+ limits:
+ cpu: "1"
+ memory: "1Gi"
+
+---
+# Burstable — requests 小于 limits
+containers:
+ - name: normal-app
+ resources:
+ requests:
+ cpu: "250m"
+ memory: "128Mi"
+ limits:
+ cpu: "1"
+ memory: "512Mi"
+
+---
+# BestEffort — 未设置任何 requests / limits
+containers:
+ - name: batch-job
+ image: busybox
+ # 未配置 resources 字段
+```
+
+> [!warning] 生产环境严禁 BestEffort
+> BestEffort Pod 在节点资源紧张时会被**最先驱逐**。在生产环境中,至少应为所有容器设置合理的 `requests`。
+
+### OOM(Out of Memory)行为
+
+当节点内存不足时,kubelet 会按照以下顺序选择 Pod 进行驱逐:
+
+1. **BestEffort** Pod(OOM Score 最高)
+2. **Burstable** Pod 中,实际内存使用量超过 requests 最多的
+3. **Guaranteed** Pod(OOM Score 最低,最后被杀)
+
+同时,Linux 内核的 OOM Killer 也会根据进程的 `oom_score_adj` 值来决定杀哪个进程。Kubernetes 为不同 QoS 等级的容器设置了不同的 `oom_score_adj`:
+
+| QoS 等级 | oom_score_adj |
+|----------|--------------|
+| Guaranteed | -998 |
+| Burstable | 2 ~ 1000(按比例) |
+| BestEffort | 1000 |
+
+### CPU 节流机制
+
+当容器的 CPU 使用超过 limit 时,Linux 内核的 CFS(Completely Fair Scheduler)会通过 **CFS Bandwidth Throttling** 机制限制容器的 CPU 时间片分配。
+
+表现症状:
+- 容器进程运行变慢,响应延迟升高
+- `container_cpu_cfs_throttled_periods_total` 指标持续增长
+- 应用程序的 P99 延迟出现周期性毛刺
+
+> [!tip] 避免 CPU 节流
+> 如果发现应用延迟升高且 CPU throttling 严重,考虑:① 提高 CPU limit;② 将 limit 设置为与 request 相同(Guaranteed QoS,不会被节流但调度更严格);③ 使用 `CPU Manager Policy: static` 获得独占 CPU 核心。
+
+## Sidecar 模式
+
+Sidecar 模式是 Pod 多容器设计中最常用的架构模式。通过在 Pod 中添加辅助容器(sidecar),可以在不修改主应用代码的前提下扩展其功能。
+
+### 日志收集
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: app-logging-sidecar
+spec:
+ containers:
+ - name: app
+ image: myapp:v2
+ volumeMounts:
+ - name: shared-logs
+ mountPath: /var/log/app
+ - name: log-shipper
+ image: fluent/fluent-bit:2.2
+ volumeMounts:
+ - name: shared-logs
+ mountPath: /var/log/app
+ readOnly: true
+ - name: fluentbit-config
+ mountPath: /fluent-bit/etc/
+ resources:
+ requests:
+ cpu: "50m"
+ memory: "64Mi"
+ limits:
+ cpu: "200m"
+ memory: "128Mi"
+ volumes:
+ - name: shared-logs
+ emptyDir: {}
+ - name: fluentbit-config
+ configMap:
+ name: fluentbit-config
+```
+
+### 服务网格代理(Service Mesh Proxy)
+
+这是 Istio、Linkerd 等服务网格的典型数据面模式——每个应用 Pod 旁注入一个 Envoy sidecar 代理:
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: app-with-proxy
+ annotations:
+ sidecar.istio.io/inject: "true"
+spec:
+ containers:
+ - name: app
+ image: myapp:v3
+ ports:
+ - containerPort: 8080
+ # 以下通常由 Istio 自动注入,这里手动展示
+ - name: istio-proxy
+ image: envoyproxy/envoy:v1.28
+ args:
+ - proxy
+ - sidecar
+ - --domain
+ - $(POD_NAMESPACE).svc.cluster.local
+ ports:
+ - containerPort: 15001 # Envoy 入口
+ - containerPort: 15006 # Envoy 出口
+ env:
+ - name: POD_NAME
+ valueFrom:
+ fieldRef:
+ fieldPath: metadata.name
+ - name: POD_NAMESPACE
+ valueFrom:
+ fieldRef:
+ fieldPath: metadata.namespace
+```
+
+### 配置热更新(Reloader Sidecar)
+
+某些应用不支持热加载配置文件,此时可以用一个 sidecar 监听 ConfigMap 变化并通知主容器:
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: app-config-reload
+spec:
+ containers:
+ - name: app
+ image: nginx:1.25
+ volumeMounts:
+ - name: config-volume
+ mountPath: /etc/nginx/conf.d
+ - name: config-reloader
+ image: jimmidyson/configmap-reload:v0.13
+ args:
+ - --volume-dir=/config
+ - --webhook-url=http://localhost:8080/-/reload
+ volumeMounts:
+ - name: config-volume
+ mountPath: /config
+ readOnly: true
+ volumes:
+ - name: config-volume
+ configMap:
+ name: nginx-config
+```
+
+## Pod 调度基础
+
+Kubernetes 调度器(kube-scheduler)负责将 Pod 分配到合适的节点上。Pod 本身可以通过以下字段影响调度决策:
+
+### nodeSelector
+
+最简单的调度约束,通过标签匹配将 Pod 调度到特定节点:
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: gpu-task
+spec:
+ nodeSelector:
+ gpu: "true" # 必须调度到带有 gpu=true 标签的节点
+ disktype: "ssd"
+ containers:
+ - name: ml-trainer
+ image: pytorch:v2
+```
+
+### nodeName
+
+直接指定节点名称,**跳过调度器**:
+
+```yaml
+spec:
+ nodeName: node-3 # 直接绑定到 node-3,不经过调度器
+```
+
+> [!warning] 慎用 nodeName
+> 使用 `nodeName` 会绕过调度器的资源检查、亲和性计算和污点容忍判断。如果目标节点资源不足,Pod 会一直处于 Pending 状态。一般仅用于测试或特殊运维场景。
+
+### 亲和性(Affinity)简介
+
+亲和性提供了比 `nodeSelector` 更丰富的调度规则:
+
+- **nodeAffinity** — 节点亲和性,支持硬性(required)和软性(preferred)规则
+- **podAffinity** — Pod 亲和性,将 Pod 调度到与其他 Pod 相近的位置
+- **podAntiAffinity** — Pod 反亲和性,将 Pod 调度到与其他 Pod 远离的位置
+
+```yaml
+affinity:
+ nodeAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution: # 硬性要求
+ nodeSelectorTerms:
+ - matchExpressions:
+ - key: kubernetes.io/arch
+ operator: In
+ values:
+ - amd64
+ preferredDuringSchedulingIgnoredDuringExecution: # 软性偏好
+ - weight: 80
+ preference:
+ matchExpressions:
+ - key: zone
+ operator: In
+ values:
+ - cn-east-1a
+```
+
+> [!info] 深入阅读
+> 调度器的完整机制(包括 Taint/Toleration、PriorityClass、拓扑分布约束等)将在 [[k8s-scheduler]] 中详细展开。
+
+## 静态 Pod(Static Pod)
+
+静态 Pod 是由 **kubelet 直接管理**的 Pod,不通过 API Server 创建。kubelet 会监视指定目录(默认为 `/etc/kubernetes/manifests/`)中的 YAML 文件,并自动创建和维护对应的 Pod。
+
+### 特点
+
+| 特性 | 静态 Pod | 普通 Pod |
+|------|---------|---------|
+| 管理者 | kubelet | API Server / Controller Manager |
+| 存储位置 | 节点本地文件系统 | etcd |
+| 是否出现在 API Server 中 | 是(kubelet 创建 mirror pod) | 是 |
+| 是否受控制器管理 | 否 | 是(Deployment、StatefulSet 等) |
+| 是否可被 kubectl 删除 | 否(需删除文件) | 是 |
+
+### 用途
+
+静态 Pod 最重要的用途是**部署控制平面组件**:
+
+```bash
+# kubeadm 部署的集群中,控制平面组件都是静态 Pod
+/etc/kubernetes/manifests/
+├── kube-apiserver.yaml
+├── kube-controller-manager.yaml
+├── kube-scheduler.yaml
+└── etcd.yaml
+```
+
+```yaml
+# /etc/kubernetes/manifests/kube-apiserver.yaml(简化示例)
+apiVersion: v1
+kind: Pod
+metadata:
+ name: kube-apiserver
+ namespace: kube-system
+ labels:
+ component: kube-apiserver
+spec:
+ containers:
+ - name: kube-apiserver
+ image: registry.k8s.io/kube-apiserver:v1.30.0
+ command:
+ - kube-apiserver
+ - --advertise-address=192.168.1.10
+ - --etcd-servers=https://127.0.0.1:2379
+ - --service-cluster-ip-range=10.96.0.0/12
+ ports:
+ - containerPort: 6443
+ hostNetwork: true
+```
+
+> [!info] 为什么控制平面用静态 Pod?
+> 这是一个「鸡生蛋」的问题:kube-apiserver 是 Kubernetes 集群的核心,如果 kube-apiserver 本身也依赖 API Server 来创建,那集群就无法启动。静态 Pod 由 kubelet 独立管理,不依赖 API Server,因此可以用来部署 API Server 本身。
+
+## 常见陷阱与最佳实践
+
+### 镜像拉取策略(imagePullPolicy)
+
+| 策略 | 行为 | 适用场景 |
+|------|------|---------|
+| `Always` | 每次创建 Pod 都拉取镜像 | 使用 `:latest` 标签时(默认策略) |
+| `IfNotPresent` | 本地不存在时才拉取 | 使用固定版本标签(如 `:v1.2.3`) |
+| `Never` | 从不拉取,仅使用本地镜像 | 离线环境或测试场景 |
+
+```yaml
+containers:
+ - name: app
+ image: myapp:v1.2.3 # 使用固定标签
+ imagePullPolicy: IfNotPresent # 推荐:避免不必要的拉取
+```
+
+> [!warning] :latest 标签陷阱
+> 当镜像标签为 `:latest` 或不指定标签时,`imagePullPolicy` 默认为 `Always`。这意味着每次 Pod 重建都会重新拉取镜像,带来不确定性。**生产环境必须使用固定版本标签**(如 `:v1.2.3` 或 SHA256 digest)。
+
+### 优雅终止(Graceful Shutdown)
+
+当 Kubernetes 删除一个 Pod 时,会经历以下流程:
+
+```mermaid
+sequenceDiagram
+ participant User as 用户/API
+ participant API as API Server
+ participant Kubelet as kubelet
+ participant App as 应用进程
+
+ User->>API: 删除 Pod
+ API->>Kubelet: 标记 Pod 为 Terminating
+ Note over API: 从 Service Endpoints 移除
+
+ Kubelet->>App: 执行 preStop hook
+ App-->>App: 执行清理逻辑
+ App-->>Kubelet: preStop 完成
+
+ Kubelet->>App: 发送 SIGTERM 信号
+ App-->>App: 开始优雅关闭
+
+ alt 在 terminationGracePeriodSeconds 内退出
+ App-->>Kubelet: 进程正常退出
+ else 超时未退出
+ Kubelet->>App: 发送 SIGKILL 强制杀死
+ end
+```
+
+**关键配置项:**
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: graceful-demo
+spec:
+ terminationGracePeriodSeconds: 60 # 默认 30s,给足够时间清理
+ containers:
+ - name: app
+ image: myapp:v1
+ lifecycle:
+ preStop:
+ exec:
+ command:
+ - /bin/sh
+ - -c
+ - |
+ # 1. 通知应用停止接收新请求
+ curl -s http://localhost:8080/admin/drain
+ # 2. 等待正在进行的请求处理完成
+ sleep 15
+ # 3. 关闭数据库连接池
+ echo "Shutting down gracefully..."
+```
+
+> [!tip] preStop 的妙用
+> preStop hook 在 SIGTERM 之前执行,可以用来做「通知式关闭」——比如先从注册中心注销、停止接收新连接、等待进行中的请求完成。配合 `terminationGracePeriodSeconds` 调大值,可以实现零请求丢失的优雅关闭。
+
+### PID 1 信号问题
+
+在 Linux 中,PID 1 进程有特殊行为:**默认不响应 SIGTERM 信号**(为了兼容 init 系统)。如果容器的入口进程是 shell 脚本,shell 不会将 SIGTERM 转发给子进程,导致容器无法优雅退出。
+
+**问题示例:**
+
+```dockerfile
+# 有问题的 Dockerfile
+FROM node:20
+COPY server.js .
+CMD node server.js # node 进程成为 PID 1,但不一定正确处理信号
+```
+
+**解决方案:**
+
+```dockerfile
+# 方案 1:使用 exec 形式(推荐)
+CMD ["node", "server.js"] # node 直接成为 PID 1
+
+# 方案 2:使用 tini 作为 init 进程
+RUN apt-get update && apt-get install -y tini
+ENTRYPOINT ["tini", "--"]
+CMD ["node", "server.js"]
+
+# 方案 3:在 Pod Spec 中设置(Kubernetes 1.28+)
+# 通过 feature gate: SidecarContainers
+```
+
+```yaml
+# 方案 3:Pod 级别的 init 进程设置
+apiVersion: v1
+kind: Pod
+metadata:
+ name: signal-demo
+spec:
+ # Kubernetes 1.28+ 支持设置 Pod 的 PID namespace
+ # 确保 PID 1 被正确处理
+ containers:
+ - name: app
+ image: myapp:v1
+```
+
+### 其他注意事项
+
+- **不要在同一 Pod 内运行多个不相关的服务** — 这违反了 Pod 的设计初衷,应该拆分为多个 Pod
+- **使用 Liveness Probe 要谨慎** — 错误的 liveness 探针配置会导致容器反复重启(restart loop),建议先用 readiness 验证探针逻辑
+- **设置资源 requests** — 不设 requests 的 Pod 会变成 BestEffort QoS,在节点压力下最先被驱逐
+- **避免使用 hostNetwork** — 除非必要(如 CNI 插件、Ingress Controller),否则不要使用 hostNetwork,它会占用宿主机端口并绕过 Kubernetes 网络策略
+
+## 延伸阅读
+
+- [[k8s-02-architecture]] — Kubernetes 整体架构,理解 Pod 在集群中的位置
+- [[k8s-04-deployment-and-replicaset]] — Deployment 如何管理 Pod 的副本与滚动更新
+- [[k8s-scheduler]] — 调度器完整机制,包括亲和性、污点容忍、拓扑分布等
diff --git a/technical/k8s/k8s-04-deployment-and-replicaset.md b/technical/k8s/k8s-04-deployment-and-replicaset.md
new file mode 100644
index 0000000..a0087b5
--- /dev/null
+++ b/technical/k8s/k8s-04-deployment-and-replicaset.md
@@ -0,0 +1,792 @@
+---
+tags: [k8s, deployment, replicaset, devops]
+create time: 2026-07-07 15:48
+---
+
+# Deployment 与 ReplicaSet
+
+## 概述
+
+Deployment 是 Kubernetes 中最常用的无状态工作负载控制器,它在 ReplicaSet 之上提供了声明式更新、滚动发布、版本回滚等高级能力。日常开发中,绝大多数无状态服务(Web API、微服务后端、定时任务 Worker)都通过 Deployment 来管理 Pod 的生命周期。理解 Deployment 与 ReplicaSet 的协作关系,是掌握 Kubernetes 应用交付模型的基础。
+
+## ReplicaSet 详解
+
+### 什么是 ReplicaSet
+
+ReplicaSet(简称 RS)的核心职责只有一件事:**确保指定数量的 Pod 副本始终处于运行状态**。如果某个 Pod 意外退出或被删除,RS 会立即创建新的 Pod 来弥补差额;如果 Pod 数量超过期望值,RS 会删除多余的 Pod。
+
+```yaml
+# 一个独立的 ReplicaSet 示例(通常不直接创建,由 Deployment 自动管理)
+apiVersion: apps/v1
+kind: ReplicaSet
+metadata:
+ name: nginx-rs
+ namespace: default
+spec:
+ replicas: 3 # 期望副本数
+ selector:
+ matchLabels:
+ app: nginx # Label Selector:通过标签匹配 Pod
+ template:
+ metadata:
+ labels:
+ app: nginx # Pod 必须携带此标签才能被 RS 管理
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.25
+ ports:
+ - containerPort: 80
+```
+
+### Label Selector 机制
+
+RS 通过 **Label Selector** 来发现和管理 Pod,而非维护一个固定的 Pod 列表。这意味着:
+
+- 手动创建的 Pod 只要标签匹配,也会被 RS 纳入管理(数量超出则删除,不足则新建)
+- 修改某个 Pod 的标签可以将其从 RS 管理中"摘除",这在调试时非常有用
+
+```bash
+# 将一个 Pod 从 RS 中移除(不删除 Pod,仅修改标签使其脱离管理)
+kubectl label pod nginx-rs-xxxx app=detached --overwrite
+
+# 此时 RS 会发现少了一个副本,自动新建一个 Pod 来补足
+```
+
+> [!tip] 调试技巧
+> 需要排查某个 Pod 问题时,先把它从 RS 中"摘出来"(修改标签),这样 RS 不会因为 Pod 异常而自动重建,你可以从容排查。
+
+### ReplicaSet vs ReplicationController
+
+ReplicationController(RC)是 RS 的前身,两者核心区别在于 Selector 语义:
+
+| 对比维度 | ReplicationController | ReplicaSet |
+|----------|----------------------|------------|
+| Selector 类型 | 仅支持等式(`env=prod`) | 支持等式 + 集合(`env in (prod, staging)`) |
+| 标签匹配灵活性 | 低 | 高 |
+| 推荐程度 | 已废弃,不建议使用 | 推荐使用 |
+| 滚动更新 | 需要 `kubectl rolling-update`(客户端执行) | 由 Deployment 在服务端协调 |
+
+RC 已经是遗留概念,新资源应统一使用 ReplicaSet。不过实践中你几乎不会直接创建 RS,而是通过 Deployment 间接管理。
+
+## Deployment 核心机制
+
+### 声明式管理
+
+Deployment 采用声明式模型:你只需描述"期望状态"(desired state),Kubernetes 控制面会持续将其与"实际状态"(actual state)做对比,自动执行必要的操作来弥合差距。这个循环叫做 **Reconcile Loop**(调谐循环)。
+
+```mermaid
+graph LR
+ A["用户提交 Deployment
(desired state)"] --> B["Deployment Controller"]
+ B --> C{"desired == actual ?"}
+ C -->|No| D["创建/删除/更新
ReplicaSet / Pod"]
+ D --> B
+ C -->|Yes| E["状态稳定
不做任何操作"]
+```
+
+工作流程:
+
+1. 用户通过 `kubectl apply` 提交 Deployment YAML,声明 `replicas: 3`、`image: nginx:1.25` 等期望状态
+2. Deployment Controller 观察到当前没有匹配的 RS,创建一个新的 RS
+3. RS Controller 观察到 Pod 数量为 0,创建 3 个 Pod
+4. 持续监控:如果某个 Pod 崩溃,RS Controller 检测到 actual < desired,立即补充新 Pod
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: nginx-deploy
+ namespace: default
+spec:
+ replicas: 3 # 声明:我想要 3 个副本
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.25 # 声明:我想用这个镜像
+ resources:
+ requests:
+ cpu: 100m
+ memory: 128Mi
+ limits:
+ cpu: 500m
+ memory: 256Mi
+```
+
+### 滚动更新(Rolling Update)
+
+滚动更新是 Deployment 最核心的价值。当你修改了 Pod 模板(比如升级镜像版本),Deployment 不会一次性杀掉所有旧 Pod,而是**逐批替换**,在更新过程中始终保持服务可用。
+
+#### 关键参数
+
+| 参数 | 默认值 | 含义 |
+|------|--------|------|
+| `maxSurge` | 25% | 更新过程中允许超出 `replicas` 的最大 Pod 数量 |
+| `maxUnavailable` | 25% | 更新过程中允许不可用的最大 Pod 数量 |
+
+> [!info] 参数计算
+> 对于 `replicas: 4`、`maxSurge: 25%`、`maxUnavailable: 25%` 的配置:
+> - maxSurge = ceil(4 * 0.25) = 1,最多可以有 5 个 Pod 同时存在
+> - maxUnavailable = floor(4 * 0.25) = 1,至少保证 3 个 Pod 可用
+>
+> 这意味着每批替换 1 个 Pod,共需 4 批完成更新。
+
+#### 滚动更新过程
+
+```mermaid
+sequenceDiagram
+ participant U as 用户
+ participant D as Deployment Controller
+ participant RS1 as ReplicaSet v1
+ participant RS2 as ReplicaSet v2
+
+ U->>D: kubectl apply(image: nginx:1.26)
+ D->>RS2: 创建新 RS(image: 1.26, replicas: 0)
+ D->>RS2: scale up +1(新建 1 个新 Pod)
+ Note over RS1,RS2: maxSurge=1, 新 Pod 启动
+ D->>RS1: scale down -1(销毁 1 个旧 Pod)
+ Note over RS1,RS2: maxUnavailable=1, 保证可用
+ D->>RS2: scale up +1
+ D->>RS1: scale down -1
+ Note over RS1,RS2: 重复直到 RS1=0, RS2=4
+ D->>RS1: 保留 RS1(revision=1, 可回滚)
+```
+
+逐批替换的过程(以 `replicas: 4` 为例):
+
+| 阶段 | RS v1 (旧) | RS v2 (新) | 总 Pod 数 | 可用 Pod |
+|------|-----------|-----------|----------|---------|
+| 初始 | 4 | 0 | 4 | 4 |
+| 第 1 批 | 4 → 3 | 0 → 1 | 5 | 4 |
+| 第 2 批 | 3 → 2 | 1 → 2 | 4 | 4 |
+| 第 3 批 | 2 → 1 | 2 → 3 | 4 | 4 |
+| 第 4 批 | 1 → 0 | 3 → 4 | 4 | 4 |
+
+#### 滚动更新配置
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: nginx-deploy
+spec:
+ replicas: 4
+ strategy:
+ type: RollingUpdate # 更新策略
+ rollingUpdate:
+ maxSurge: 1 # 最多多 1 个 Pod
+ maxUnavailable: 1 # 最多少 1 个 Pod
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.25
+ readinessProbe: # 重要:就绪探针决定新 Pod 何时接收流量
+ httpGet:
+ path: /healthz
+ port: 80
+ initialDelaySeconds: 5
+ periodSeconds: 10
+```
+
+> [!warning] readinessProbe 的重要性
+> 滚动更新依赖 `readinessProbe` 判断新 Pod 是否就绪。如果没有配置就绪探针,新 Pod 一创建就被视为 Ready,可能导致流量打到尚未初始化完成的 Pod 上,造成请求失败。
+
+### 回滚策略
+
+Kubernetes 为每次 Deployment 变更自动记录 revision(版本号),支持快速回滚到任意历史版本。
+
+```bash
+# 查看 rollout 历史
+kubectl rollout history deployment nginx-deploy
+
+# 输出示例:
+# REVISION CHANGE-CAUSE
+# 1
+# 2
+# 3
+
+# 查看某个 revision 的详细信息
+kubectl rollout history deployment nginx-deploy --revision=2
+
+# 回滚到上一个版本
+kubectl rollout undo deployment nginx-deploy
+
+# 回滚到指定版本
+kubectl rollout undo deployment nginx-deploy --to-revision=1
+
+# 查看 rollout 状态(是否完成、耗时等)
+kubectl rollout status deployment nginx-deploy
+```
+
+#### revisionHistoryLimit 配置
+
+默认保留 10 个历史 revision。过多的历史 RS 会占用 etcd 资源,过少则可能丢失回滚能力。
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: nginx-deploy
+spec:
+ revisionHistoryLimit: 5 # 只保留最近 5 个 revision
+ # 设为 0 则禁用回滚(不推荐)
+ replicas: 3
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ annotations:
+ kubernetes.io/change-cause: "升级到 nginx 1.26" # 记录变更原因,显示在 rollout history 中
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.26
+```
+
+> [!tip] 记录变更原因
+> 在 Pod template 的 `annotations` 中添加 `kubernetes.io/change-cause`,这样 `kubectl rollout history` 会显示每次变更的原因,方便排查。
+
+### 扩缩容
+
+#### 手动扩缩容
+
+```bash
+# 命令行直接扩缩
+kubectl scale deployment nginx-deploy --replicas=5
+
+# 条件扩缩(只有当前副本数为 3 时才执行)
+kubectl scale deployment nginx-deploy --replicas=5 --current-replicas=3
+```
+
+也可以直接修改 YAML 中的 `replicas` 字段后 `kubectl apply`。
+
+#### HPA 自动扩缩
+
+Horizontal Pod Autoscaler(HPA)根据 CPU、内存或自定义指标自动调整 Deployment 的副本数。
+
+```yaml
+# 前置条件:集群中需要安装 Metrics Server
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: nginx-hpa
+ namespace: default
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: nginx-deploy # 目标 Deployment
+ minReplicas: 2 # 最小副本数
+ maxReplicas: 10 # 最大副本数
+ metrics:
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: 60 # CPU 使用率超过 60% 时扩容
+ - type: Resource
+ resource:
+ name: memory
+ target:
+ type: Utilization
+ averageUtilization: 80 # 内存使用率超过 80% 时扩容
+ behavior:
+ scaleUp:
+ stabilizationWindowSeconds: 60 # 扩容稳定窗口:60s 内取最大值
+ policies:
+ - type: Pods
+ value: 4 # 每次最多扩 4 个 Pod
+ periodSeconds: 60
+ scaleDown:
+ stabilizationWindowSeconds: 300 # 缩容稳定窗口:300s,防止抖动
+ policies:
+ - type: Percent
+ value: 10 # 每次最多缩 10%
+ periodSeconds: 60
+```
+
+```bash
+# 创建 HPA
+kubectl apply -f hpa.yaml
+
+# 或用命令行快速创建
+kubectl autoscale deployment nginx-deploy --cpu-percent=60 --min=2 --max=10
+
+# 查看 HPA 状态
+kubectl get hpa nginx-hpa
+```
+
+> [!warning] HPA 与手动 scale 冲突
+> 如果同时使用 HPA 和手动 `kubectl scale`,HPA 会在下一次指标采集时覆盖手动设置。两者不应混用。
+
+## Deployment 更新策略
+
+Deployment 支持两种更新策略,通过 `spec.strategy.type` 指定:
+
+| 对比维度 | RollingUpdate | Recreate |
+|----------|--------------|----------|
+| 更新方式 | 逐批替换 Pod | 先杀全部旧 Pod,再创建全部新 Pod |
+| 更新期间可用性 | 始终可用(前提:正确配置探针) | 有短暂不可用窗口 |
+| 资源占用 | 可能临时需要更多资源(maxSurge > 0 时) | 只需要新版本的资源 |
+| 适用场景 | 大多数无状态服务 | 不能有两个版本同时运行的场景 |
+| 典型用例 | Web 服务、API 后端 | 涉及全局独占资源(如本地文件锁、全局数据库迁移) |
+
+```yaml
+# Recreate 策略示例
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: legacy-app
+spec:
+ replicas: 3
+ strategy:
+ type: Recreate # 全量替换,更新期间服务不可用
+ selector:
+ matchLabels:
+ app: legacy
+ template:
+ metadata:
+ labels:
+ app: legacy
+ spec:
+ containers:
+ - name: app
+ image: legacy-app:2.0
+```
+
+> [!info] 什么时候用 Recreate?
+> 当新旧版本不能共存时使用 Recreate,比如:应用使用了本地文件锁、需要执行不可逆的数据库 Schema 迁移、或者旧版本会干扰新版本的初始化。大多数情况下 RollingUpdate 是更好的选择。
+
+## 金丝雀发布与蓝绿部署
+
+Deployment 本身不直接支持金丝雀发布和蓝绿部署,但可以通过组合多个 Deployment + Service 来实现。
+
+### 金丝雀发布(Canary Release)
+
+核心思路:同时运行两个版本的 Deployment,通过控制副本比例来控制流量分配。
+
+```yaml
+# 主 Deployment:稳定版本,占 90% 流量
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: app-stable
+spec:
+ replicas: 9 # 9 个副本
+ selector:
+ matchLabels:
+ app: myapp
+ track: stable
+ template:
+ metadata:
+ labels:
+ app: myapp
+ track: stable # 核心标签 app: myapp 相同,Service 可以选中
+ spec:
+ containers:
+ - name: app
+ image: myapp:v1.0
+---
+# 金丝雀 Deployment:新版本,占 10% 流量
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: app-canary
+spec:
+ replicas: 1 # 1 个副本
+ selector:
+ matchLabels:
+ app: myapp
+ track: canary
+ template:
+ metadata:
+ labels:
+ app: myapp
+ track: canary
+ spec:
+ containers:
+ - name: app
+ image: myapp:v2.0
+```
+
+```yaml
+# Service 通过 app: myapp 同时选中两个 Deployment 的 Pod
+apiVersion: v1
+kind: Service
+metadata:
+ name: myapp-svc
+spec:
+ selector:
+ app: myapp # 不指定 track,两个版本都会被选中
+ ports:
+ - port: 80
+ targetPort: 8080
+```
+
+流量分配:9 个旧版本 Pod + 1 个新版本 Pod = 约 10% 流量到达新版本。验证无误后,逐步调整副本比例(如 `canary: 5, stable: 5`),最终全部切换到新版本。
+
+```mermaid
+graph LR
+ Client --> Service
+ Service -->|~90%| Pod1["Pod v1.0 x9"]
+ Service -->|~10%| Pod2["Pod v2.0 x1"]
+```
+
+### 蓝绿部署(Blue-Green Deployment)
+
+核心思路:同时运行蓝(当前版本)和绿(新版本)两套完整的 Deployment,通过切换 Service 的 selector 来瞬间完成流量切换。
+
+```yaml
+# 蓝色 Deployment(当前生产版本)
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: app-blue
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: myapp
+ version: blue
+ template:
+ metadata:
+ labels:
+ app: myapp
+ version: blue
+ spec:
+ containers:
+ - name: app
+ image: myapp:v1.0
+---
+# 绿色 Deployment(新版本,先部署但不接收流量)
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: app-green
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: myapp
+ version: green
+ template:
+ metadata:
+ labels:
+ app: myapp
+ version: green
+ spec:
+ containers:
+ - name: app
+ image: myapp:v2.0
+```
+
+```yaml
+# Service:切换 selector.version 即可瞬间切换流量
+apiVersion: v1
+kind: Service
+metadata:
+ name: myapp-svc
+spec:
+ selector:
+ app: myapp
+ version: blue # 当前指向蓝色,切换为 green 即完成上线
+ ports:
+ - port: 80
+ targetPort: 8080
+```
+
+```bash
+# 切换流量到绿色版本
+kubectl patch service myapp-svc -p '{"spec":{"selector":{"version":"green"}}}'
+
+# 回滚:切换回蓝色版本
+kubectl patch service myapp-svc -p '{"spec":{"selector":{"version":"blue"}}}'
+```
+
+蓝绿部署的优点是切换瞬间完成(无中间状态),缺点是需要双倍资源。适合对上线/回滚速度要求极高的场景。
+
+## Pod 模板字段详解
+
+`spec.template.spec` 是 Pod 模板的核心,以下是常用字段的逐一说明:
+
+```yaml
+spec:
+ template:
+ metadata:
+ labels: # Pod 标签,用于 Service Selector 和 ReplicaSet 匹配
+ app: myapp
+ version: v1
+ annotations: # 非标识性元数据,供工具或控制器使用
+ kubernetes.io/change-cause: "初始部署"
+ spec:
+ # ---- 容器定义 ----
+ containers:
+ - name: app # 容器名称,Pod 内唯一
+ image: myapp:v1.0 # 镜像地址
+ imagePullPolicy: IfNotPresent # Always / IfNotPresent / Never
+ ports:
+ - containerPort: 8080 # 容器监听端口(声明性质,不强制)
+ env: # 环境变量
+ - name: APP_ENV
+ value: "production"
+ - name: DB_PASSWORD
+ valueFrom:
+ secretKeyRef: # 从 Secret 读取敏感信息
+ name: db-secret
+ key: password
+ resources: # 资源请求与限制
+ requests: # 调度依据:节点至少需要这么多资源
+ cpu: 100m
+ memory: 128Mi
+ limits: # 上限:超出会被 OOM Kill 或 CPU 限流
+ cpu: 500m
+ memory: 256Mi
+ readinessProbe: # 就绪探针:决定 Pod 是否接收流量
+ httpGet:
+ path: /healthz
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ livenessProbe: # 存活探针:失败则重启容器
+ httpGet:
+ path: /healthz
+ port: 8080
+ initialDelaySeconds: 15
+ periodSeconds: 20
+ startupProbe: # 启动探针:慢启动应用专用,通过前忽略 liveness
+ httpGet:
+ path: /healthz
+ port: 8080
+ failureThreshold: 30
+ periodSeconds: 10
+
+ # ---- 初始化容器 ----
+ initContainers: # 在主容器启动前顺序执行
+ - name: init-db-migration
+ image: myapp:v1.0
+ command: ["./migrate.sh"]
+
+ # ---- 卷挂载 ----
+ volumes:
+ - name: config-vol
+ configMap:
+ name: app-config # 挂载 ConfigMap 作为文件
+ - name: data-vol
+ emptyDir: {} # 临时卷,Pod 删除后数据丢失
+
+ # ---- 调度相关 ----
+ nodeSelector: # 简单节点选择
+ disk: ssd
+ affinity: # 高级亲和性规则
+ nodeAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ nodeSelectorTerms:
+ - matchExpressions:
+ - key: kubernetes.io/os
+ operator: In
+ values: ["linux"]
+ tolerations: # 容忍污点
+ - key: "dedicated"
+ operator: "Equal"
+ value: "gpu"
+ effect: "NoSchedule"
+
+ # ---- 其他 ----
+ terminationGracePeriodSeconds: 30 # 优雅终止等待时间
+ serviceAccountName: myapp-sa # 使用的 ServiceAccount
+```
+
+> [!tip] imagePullPolicy 最佳实践
+> - 使用 `:latest` 标签时,必须设置 `imagePullPolicy: Always`,否则可能使用本地缓存的旧镜像
+> - 使用固定版本标签(如 `:v1.2.3`)时,`IfNotPresent` 更高效,避免重复拉取
+> - `Never` 仅用于本地开发环境(如 minikube 中直接使用本地构建的镜像)
+
+## 常见陷阱与最佳实践
+
+### 更新卡住排查
+
+滚动更新卡住是常见问题,通常由以下原因导致:
+
+```bash
+# 1. 查看 Deployment 状态
+kubectl rollout status deployment nginx-deploy
+# 可能输出:Waiting for rollout to finish: 2 out of 4 new replicas have been updated...
+
+# 2. 查看 Deployment 的 Conditions
+kubectl get deployment nginx-deploy -o yaml | grep -A 20 conditions:
+# 常见条件:
+# - Progressing: False(更新未推进)
+# - Available: True/False(是否有足够可用 Pod)
+
+# 3. 查看新 ReplicaSet 的事件
+kubectl describe rs nginx-deploy-xxxxxx # 查看新 RS 的 Events
+# 常见原因:
+# - FailedScheduling:资源不足,没有节点能满足 requests
+# - ImagePullBackOff:镜像拉取失败(镜像名错、私有仓库无凭证)
+# - CrashLoopBackOff:新容器启动后立即崩溃
+
+# 4. 查看 Pod 事件
+kubectl get pods -l app=nginx --sort-by=.metadata.creationTimestamp
+kubectl describe pod nginx-deploy-xxxxxx-yyyyy
+```
+
+常见原因和解决方案:
+
+| 症状 | 原因 | 解决方案 |
+|------|------|---------|
+| ImagePullBackOff | 镜像不存在或无权限 | 检查镜像名称、imagePullSecrets |
+| CrashLoopBackOff | 应用启动失败 | 查看 Pod 日志 `kubectl logs` |
+| Pending | 资源不足或节点不满足约束 | 检查 `kubectl describe pod` 中的 Events |
+| readinessProbe 一直失败 | 健康检查路径或端口错误 | 确认探针配置与应用实际端口一致 |
+
+> [!warning] 超时回滚
+> Deployment 有一个 `progressDeadlineSeconds` 参数(默认 600 秒),如果更新在该时间内未完成,Deployment 会标记为 `Progressing=False`。注意:这**不会自动回滚**,只是标记状态。需要手动执行 `kubectl rollout undo` 或编写自动化脚本。
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: nginx-deploy
+spec:
+ progressDeadlineSeconds: 300 # 5 分钟内未完成更新则标记失败
+ minReadySeconds: 10 # Pod Ready 后等待 10 秒才视为 Available
+ replicas: 3
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:1.26
+```
+
+### imagePullPolicy 导致的镜像缓存问题
+
+```bash
+# 场景:修改了 YAML 中的 image 字段但没改 tag,apply 后 Pod 没有更新
+# 原因:imagePullPolicy 默认为 IfNotPresent,本地已有旧镜像就不会重新拉取
+
+# 解决方案 1:始终使用不同的 tag(推荐)
+image: myapp:v1.0.1 # 而不是 myapp:latest
+
+# 解决方案 2:强制设置 imagePullPolicy
+imagePullPolicy: Always
+
+# 解决方案 3:使用 SHA256 digest(最精确)
+image: myapp@sha256:abc123...
+```
+
+### Deployment vs StatefulSet 选择
+
+| 特性 | Deployment | StatefulSet |
+|------|-----------|-------------|
+| Pod 名称 | 随机(`nginx-5d6b8f7c9-abc12`) | 有序(`web-0`, `web-1`, `web-2`) |
+| Pod 启动顺序 | 并行 | 有序(0 → 1 → 2) |
+| Pod 终止顺序 | 并行(默认) | 逆序(2 → 1 → 0) |
+| 网络标识 | 不稳定 | 稳定(`web-0.ng-svc`) |
+| 持久存储 | 共享或无 | 每个 Pod 独立 PVC |
+| 适用场景 | 无状态服务 | 有状态服务(数据库、MQ、ZooKeeper) |
+| 滚动更新 | 替换 Pod | 逐个更新 |
+
+> [!info] 选择原则
+> 如果你的应用满足以下任意条件,应考虑 StatefulSet:
+> - 需要稳定的网络标识(如 ZooKeeper 集群节点互相发现)
+> - 需要持久化的、与 Pod 绑定的存储(如 MySQL 数据目录)
+> - 需要有序的部署/扩缩/更新
+>
+> 其余场景一律使用 Deployment。
+
+### 其他最佳实践
+
+1. **始终配置资源请求和限制**:没有 requests 的 Pod 会被调度器视为零消耗,可能导致多个 Pod 被调度到同一节点,引发资源争抢。
+
+2. **始终配置 readinessProbe**:没有就绪探针,新 Pod 创建后立即被标记为 Ready,流量可能打到未初始化完成的 Pod。
+
+3. **不要直接管理 Deployment 创建的 ReplicaSet**:RS 的生命周期由 Deployment 控制器管理,手动修改 RS(如 scale)会被 Deployment 覆盖。
+
+4. **使用 `kubectl apply` 而非 `kubectl create`**:apply 支持声明式管理,可以幂等执行,适合 GitOps 工作流。
+
+5. **合理设置 minReadySeconds**:该参数让 Pod Ready 后再等待一段时间才被视为 Available,可以用来检测"启动后很快崩溃"的场景。
+
+```yaml
+# 推荐的最小 Deployment 模板
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: myapp
+ labels:
+ app: myapp
+spec:
+ replicas: 3
+ revisionHistoryLimit: 5
+ progressDeadlineSeconds: 300
+ minReadySeconds: 10
+ strategy:
+ type: RollingUpdate
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 0 # 保证更新期间始终有足够的可用 Pod
+ selector:
+ matchLabels:
+ app: myapp
+ template:
+ metadata:
+ labels:
+ app: myapp
+ spec:
+ terminationGracePeriodSeconds: 30
+ containers:
+ - name: myapp
+ image: myapp:v1.0.0
+ imagePullPolicy: IfNotPresent
+ ports:
+ - containerPort: 8080
+ resources:
+ requests:
+ cpu: 100m
+ memory: 128Mi
+ limits:
+ cpu: 500m
+ memory: 256Mi
+ readinessProbe:
+ httpGet:
+ path: /healthz
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8080
+ initialDelaySeconds: 15
+ periodSeconds: 20
+```
+
+## 延伸阅读
+
+- [[k8s-03-pod]] — Pod 的生命周期、调度机制、资源管理
+- [[k8s-05-service-and-networking]] — Service 类型、Ingress、网络策略
+- [Kubernetes 官方文档 - Deployments](https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/)
+- [Kubernetes 官方文档 - ReplicaSet](https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/replicaset/)
+- [Kubernetes 官方文档 - HPA](https://kubernetes.io/zh-cn/docs/tasks/run-application/horizontal-pod-autoscale/)
diff --git a/technical/k8s/k8s-05-service-and-networking.md b/technical/k8s/k8s-05-service-and-networking.md
new file mode 100644
index 0000000..cb933e5
--- /dev/null
+++ b/technical/k8s/k8s-05-service-and-networking.md
@@ -0,0 +1,641 @@
+---
+tags: [k8s, service, networking, devops]
+create time: 2026-07-07 15:48
+---
+
+# Kubernetes Service 与网络
+
+## 概述
+
+Kubernetes 中 Pod 的生命周期是短暂的——它们随时可能被销毁重建,每次重建都会获得新的 IP 地址。如果服务之间直接通过 Pod IP 通信,任何一次 Pod 重启都会导致调用方断连。**Service** 正是为了解决这个问题而生:它为一组动态变化的 Pod 提供一个稳定的访问入口(虚拟 IP),配合 DNS 和负载均衡,让服务发现变得透明且可靠。
+
+## K8s 网络模型
+
+Kubernetes 对集群网络有三个核心要求,任何 CNI(Container Network Interface)插件都必须满足:
+
+| 原则 | 说明 |
+|------|------|
+| **Pod-to-Pod** | 集群内任意两个 Pod 可以直接通过 IP 通信,无需 NAT |
+| **Pod-to-Service** | Pod 通过 Service 的虚拟 IP(ClusterIP)访问后端 Pod,由 kube-proxy 实现负载均衡 |
+| **External-to-Service** | 集群外部流量可以通过 NodePort、LoadBalancer 或 Ingress 进入集群 |
+
+### CNI 插件简介
+
+CNI 插件负责为 Pod 分配 IP 地址并配置网络连通性。常见实现:
+
+- **Calico**:基于 BGP 路由,支持 NetworkPolicy,性能优秀
+- **Flannel**:简单轻量,使用 VXLAN overlay,适合小规模集群
+- **Cilium**:基于 eBPF,高性能,支持 L7 网络策略
+- **Weave Net**:自带加密,配置简单
+
+> [!tip] 如何选择
+> 小规模或学习环境用 Flannel 足够;生产环境推荐 Calico 或 Cilium,它们对 NetworkPolicy 的支持更完善。
+
+## Service 详解
+
+### ClusterIP
+
+**默认类型**,为 Service 分配一个集群内部虚拟 IP,仅集群内可访问。
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: my-app-svc
+ namespace: default
+spec:
+ type: ClusterIP # 默认值,可省略
+ selector:
+ app: my-app
+ ports:
+ - protocol: TCP
+ port: 80 # Service 端口
+ targetPort: 8080 # Pod 端口
+```
+
+流量路径:`Client Pod → ClusterIP(虚拟 IP)→ kube-proxy → 后端 Pod:8080`
+
+ClusterIP 是一个"虚拟 IP"——它不存在于任何网卡上,只存在于 iptables/IPVS 规则中。
+
+### NodePort
+
+在 ClusterIP 的基础上,在**每个节点**上开一个端口(默认范围 30000-32767),外部流量可通过 `:` 访问。
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: my-app-nodeport
+spec:
+ type: NodePort
+ selector:
+ app: my-app
+ ports:
+ - protocol: TCP
+ port: 80 # Service 端口(集群内部访问)
+ targetPort: 8080 # Pod 端口
+ nodePort: 30080 # 节点端口(可选,不指定则自动分配)
+```
+
+> [!info] iptables 规则链路
+> 当外部请求到达 `:30080` 时,kube-proxy 在每个节点上创建的 iptables 规则会将流量 DNAT 到后端 Pod 的 IP:Port。这意味着即使请求落在没有运行目标 Pod 的节点上,流量也会被正确转发。
+
+```mermaid
+flowchart LR
+ A[External Client] -->|NodeIP:30080| B[Node]
+ B -->|iptables DNAT| C[Pod on Node A]
+ B -->|iptables DNAT| D[Pod on Node B]
+ B -->|iptables DNAT| E[Pod on Node C]
+```
+
+### LoadBalancer
+
+在 NodePort 的基础上,向云厂商申请一个外部负载均衡器(如 AWS ELB、GCP CLB),自动将外部流量分发到各节点的 NodePort。
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: my-app-lb
+ annotations:
+ # 云厂商特定注解,例如 AWS:
+ service.beta.kubernetes.io/aws-load-balancer-type: nlb
+spec:
+ type: LoadBalancer
+ selector:
+ app: my-app
+ ports:
+ - protocol: TCP
+ port: 80
+ targetPort: 8080
+```
+
+> [!warning] 继承关系
+> `LoadBalancer` = `ClusterIP` + `NodePort` + 云厂商 LB。如果没有云厂商支持(如裸金属环境),LoadBalancer 类型的 Service 会一直停留在 `` 状态。此时可以考虑 MetalLB 作为替代方案。
+
+### ExternalName
+
+将 Service 映射到一个外部 DNS 名称(CNAME 记录),不涉及任何代理和负载均衡。
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: external-db
+ namespace: default
+spec:
+ type: ExternalName
+ externalName: db.example.com # 映射到外部域名
+```
+
+**适用场景**:
+- 集群内应用需要访问外部数据库或 API
+- 跨命名空间/跨集群服务映射
+- 迁移过程中临时将内部 Service 指向外部服务
+
+当 Pod 查询 `external-db.default.svc.cluster.local` 时,CoreDNS 会返回 `db.example.com` 的 CNAME 记录,Pod 再对 `db.example.com` 发起真正的 DNS 查询。
+
+### Service 类型对比
+
+| 类型 | ClusterIP | NodePort | LoadBalancer | ExternalName |
+|------|-----------|----------|--------------|--------------|
+| **默认** | 是 | 否 | 否 | 否 |
+| **集群内访问** | 支持 | 支持 | 支持 | 通过 CNAME |
+| **集群外访问** | 不支持 | 支持(NodeIP:Port) | 支持(外部 IP) | 不适用 |
+| **端口范围** | 任意 | 30000-32767 | 任意(LB 端口) | 无端口 |
+| **典型场景** | 内部微服务互调 | 开发测试暴露 | 生产环境对外服务 | 访问外部服务 |
+| **云厂商依赖** | 无 | 无 | 需要 | 无 |
+
+## Endpoints 与 EndpointSlices
+
+### Endpoints
+
+每个 Service 背后都有一个同名的 **Endpoints** 对象,记录了当前所有健康 Pod 的 IP:Port 列表。当 Pod 就绪(Readiness Probe 通过)时自动加入,Pod 删除时自动移除。
+
+```bash
+# 查看 Service 对应的 Endpoints
+kubectl get endpoints my-app-svc
+# NAME ENDPOINTS AGE
+# my-app-svc 10.244.1.5:8080,10.244.2.8:8080 5m
+```
+
+### EndpointSlices
+
+Kubernetes 1.21 引入 EndpointSlices 作为 Endpoints 的替代方案。
+
+> [!info] 为什么需要 EndpointSlices?
+> 在大规模集群中,一个 Service 可能关联成百上千个 Pod。Endpoints 对象会将所有 Pod IP 存储在单个对象中,导致:
+> 1. **更新风暴**:任何一个 Pod 变化都要更新整个 Endpoints 对象
+> 2. **内存压力**:kube-proxy 和所有节点的 kubelet 都要 watch 这个大对象
+> 3. **API Server 压力**:每次更新都要全量推送
+>
+> EndpointSlices 将后端列表拆分成多个小切片(默认每个切片最多 100 个端点),增量更新,大幅降低开销。
+
+```bash
+# 查看 EndpointSlices
+kubectl get endpointslices -l kubernetes.io/service-name=my-app-svc
+```
+
+```mermaid
+flowchart TB
+ subgraph Endpoints 旧方式
+ S1[Service] --> E1[Endpoints
全部 Pod IP 在一个对象中]
+ end
+ subgraph EndpointSlices 新方式
+ S2[Service] --> ES1[EndpointSlice-0
Pod 1-100]
+ S2 --> ES2[EndpointSlice-1
Pod 101-200]
+ S2 --> ES3[EndpointSlice-2
Pod 201-300]
+ end
+```
+
+## DNS 解析机制
+
+### CoreDNS
+
+Kubernetes 使用 **CoreDNS** 作为集群内置 DNS 服务(1.12 之前使用 kube-dns)。每个 Service 创建后,CoreDNS 会自动为其注册 DNS 记录。
+
+### Service DNS 格式
+
+| 资源类型 | DNS 格式 | 示例 |
+|----------|----------|------|
+| 普通 Service | `..svc.cluster.local` | `my-app.default.svc.cluster.local` |
+| Headless Service | `..svc.cluster.local` → 解析到所有 Pod IP | 直接返回 Pod IP 列表 |
+| Pod | `..pod.cluster.local` | `10-244-1-5.default.pod.cluster.local` |
+
+> [!tip] 同命名空间内可以省略后缀
+> 在 `default` 命名空间内的 Pod 访问同命名空间的 Service,可以直接用 `my-app-svc`,无需写全限定域名。
+
+### Headless Service
+
+当 Service 的 `clusterIP` 设置为 `None` 时,它成为一个 **Headless Service**。此时 DNS 查询不会返回一个虚拟 IP,而是直接返回所有后端 Pod 的 IP 列表。
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: my-app-headless
+spec:
+ clusterIP: None # Headless
+ selector:
+ app: my-app
+ ports:
+ - port: 80
+ targetPort: 8080
+```
+
+**典型应用场景**:
+- **StatefulSet**:有状态应用(如数据库集群)需要知道每个 Pod 的独立地址
+- **自定义负载均衡**:客户端自己实现负载均衡逻辑
+- **服务发现**:客户端需要获取所有后端地址
+
+```bash
+# 查询 Headless Service 的 DNS
+nslookup my-app-headless.default.svc.cluster.local
+# Name: my-app-headless.default.svc.cluster.local
+# Address: 10.244.1.5
+# Address: 10.244.2.8
+# Address: 10.244.3.12
+```
+
+## kube-proxy 深入
+
+kube-proxy 是运行在每个节点上的网络代理,负责实现 Service 的负载均衡。它有三种工作模式:
+
+### iptables 模式(默认)
+
+kube-proxy 监听 Service 和 Endpoints 的变化,在每个节点上维护 iptables 规则链。
+
+```mermaid
+flowchart TB
+ A[请求到达 ClusterIP] --> B[KUBE-SERVICES 链]
+ B --> C{匹配 Service?}
+ C -->|是| D[KUBE-SVC-XXX 链]
+ D --> E[概率匹配 -m statistic --mode random]
+ E -->|概率 50%| F[KUBE-SEP-AAA 链 → Pod A]
+ E -->|概率 50%| G[KUBE-SEP-BBB 链 → Pod B]
+ C -->|否| H[REJECT]
+```
+
+**规则链路详解**:
+1. `KUBE-SERVICES`:总入口,匹配目标 IP:Port 确定 Service
+2. `KUBE-SVC-XXX`:Service 级别链,使用 `statistic` 模块做随机负载均衡
+3. `KUBE-SEP-XXX`:Endpoint 级别链,执行 DNAT 将目标地址转换为 Pod IP:Port
+
+> [!warning] iptables 模式的局限
+> - 规则数量与 Service × Pod 数量成正比,大规模集群下规则链很长
+> - 线性遍历匹配,时间复杂度 O(n)
+> - 不支持连接追踪以外的负载均衡算法
+
+### IPVS 模式
+
+IPVS(IP Virtual Server)是 Linux 内核中的四层负载均衡器,性能远优于 iptables。
+
+```yaml
+# kube-proxy 配置启用 IPVS
+apiVersion: kubeproxy.config.k8s.io/v1alpha1
+kind: KubeProxyConfiguration
+mode: "ipvs"
+ipvs:
+ scheduler: "rr" # 负载均衡算法:rr / lc / sh / sed / nq
+```
+
+**支持的负载均衡算法**:
+
+| 算法 | 全称 | 说明 |
+|------|------|------|
+| `rr` | Round Robin | 轮询(默认) |
+| `lc` | Least Connections | 最少连接数 |
+| `sh` | Source Hashing | 源地址哈希(会话保持) |
+| `sed` | Shortest Expected Delay | 最短预期延迟 |
+| `nq` | Never Queue | 永不排队 |
+
+### iptables vs IPVS 对比
+
+| 维度 | iptables | IPVS |
+|------|----------|------|
+| **性能** | 规则多时下降明显 | 哈希表查找,O(1) |
+| **最大 Service 数** | 约 5000 开始明显退化 | 可支撑数万 |
+| **负载均衡算法** | 仅随机 | rr / lc / sh / sed / nq |
+| **连接保持** | 有限支持 | 原生支持 |
+| **依赖** | iptables(用户态工具) | ipvs 内核模块 |
+| **调试** | `iptables -L -n -t nat` | `ipvsadm -Ln` |
+
+> [!tip] 生产建议
+> 节点 Service 数超过 1000 时,强烈建议切换到 IPVS 模式。
+
+## Ingress 基础
+
+Service(NodePort/LoadBalancer)工作在四层(TCP/UDP),而 **Ingress** 工作在七层(HTTP/HTTPS),提供基于域名和路径的路由能力。
+
+### Ingress Controller
+
+Ingress 资源本身只是一份声明式配置,真正执行路由逻辑的是 **Ingress Controller**。常见的 Ingress Controller:
+
+- **Nginx Ingress Controller**:最广泛使用,社区版 + F5 版
+- **Traefik**:自动服务发现,配置简单
+- **HAProxy**:高性能,企业级特性丰富
+- **Istio Gateway**:Service Mesh 生态中的网关
+
+### Path-based 路由
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: path-based-ingress
+ annotations:
+ nginx.ingress.kubernetes.io/rewrite-target: /
+spec:
+ ingressClassName: nginx
+ rules:
+ - host: app.example.com
+ http:
+ paths:
+ - path: /api
+ pathType: Prefix
+ backend:
+ service:
+ name: api-svc
+ port:
+ number: 80
+ - path: /web
+ pathType: Prefix
+ backend:
+ service:
+ name: web-svc
+ port:
+ number: 80
+```
+
+### Host-based 路由
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: host-based-ingress
+spec:
+ ingressClassName: nginx
+ rules:
+ - host: api.example.com
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: api-svc
+ port:
+ number: 80
+ - host: web.example.com
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: web-svc
+ port:
+ number: 80
+```
+
+### TLS 终止
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: tls-ingress
+ annotations:
+ nginx.ingress.kubernetes.io/ssl-redirect: "true"
+spec:
+ ingressClassName: nginx
+ tls:
+ - hosts:
+ - app.example.com
+ secretName: app-tls-secret # 存放证书的 Secret
+ rules:
+ - host: app.example.com
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: my-app-svc
+ port:
+ number: 80
+```
+
+证书需要提前创建为 Kubernetes Secret:
+
+```bash
+kubectl create secret tls app-tls-secret \
+ --cert=tls.crt \
+ --key=tls.key
+```
+
+```mermaid
+flowchart LR
+ A[Client] -->|HTTPS| B[Ingress Controller
TLS 终止]
+ B -->|HTTP| C[Service: my-app-svc]
+ C --> D[Pod A]
+ C --> E[Pod B]
+```
+
+## NetworkPolicy
+
+默认情况下,Kubernetes 集群中所有 Pod 之间可以互相通信。**NetworkPolicy** 允许你定义精细的网络访问控制规则。
+
+> [!warning] 前提条件
+> NetworkPolicy 需要 CNI 插件支持。Flannel 不支持 NetworkPolicy,Calico 和 Cilium 支持。
+
+### 默认全通策略
+
+如果不创建任何 NetworkPolicy,所有流量都是放行的:
+
+```mermaid
+flowchart LR
+ A[Pod A] -->|允许| B[Pod B]
+ B -->|允许| A
+ A -->|允许| C[Pod C]
+ C -->|允许| B
+```
+
+### 限制 Pod 间通信
+
+**示例:只允许前端 Pod 访问后端 Pod 的 8080 端口**
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: backend-policy
+ namespace: default
+spec:
+ podSelector:
+ matchLabels:
+ app: backend # 策略作用于 backend Pod
+ policyTypes:
+ - Ingress
+ ingress:
+ - from:
+ - podSelector:
+ matchLabels:
+ app: frontend # 只允许来自 frontend Pod
+ ports:
+ - protocol: TCP
+ port: 8080 # 只允许访问 8080 端口
+```
+
+**示例:限制出站流量,只允许访问指定 DNS**
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: restrict-egress
+ namespace: default
+spec:
+ podSelector:
+ matchLabels:
+ app: my-app
+ policyTypes:
+ - Egress
+ egress:
+ - to:
+ - namespaceSelector:
+ matchLabels:
+ name: kube-system # 允许访问 kube-system 命名空间
+ ports:
+ - protocol: UDP
+ port: 53 # 允许 DNS 查询
+ - to:
+ - ipBlock:
+ cidr: 10.0.0.0/8 # 允许访问内网
+ ports:
+ - protocol: TCP
+ port: 443
+```
+
+> [!info] 策略叠加规则
+> - 一旦某个 Pod 被任何 NetworkPolicy 选中,未被策略明确允许的流量将被**拒绝**
+> - 多个 NetworkPolicy 取**并集**(任一策略允许即放行)
+> - 只定义 `policyTypes: [Ingress]` 不影响出站,反之亦然
+
+```mermaid
+flowchart TB
+ subgraph 策略生效后
+ A[frontend Pod] -->|允许: TCP 8080| B[backend Pod]
+ C[other Pod] -->|拒绝| B
+ B -->|未定义 Egress 策略
默认放行| D[任意目标]
+ end
+```
+
+## 常见陷阱与最佳实践
+
+### Service 无法访问排查清单
+
+当 Service 访问不通时,按以下顺序排查:
+
+1. **确认 Pod 运行正常**
+ ```bash
+ kubectl get pods -l app=my-app -o wide
+ kubectl logs
+ kubectl describe pod
+ ```
+
+2. **确认 Endpoints 已注册**
+ ```bash
+ kubectl get endpoints my-app-svc
+ # 如果 ENDPOINTS 为 ,说明没有健康的 Pod 匹配 selector
+ ```
+
+3. **确认 selector 匹配**
+ ```bash
+ # 对比 Service selector 和 Pod labels
+ kubectl get svc my-app-svc -o jsonpath='{.spec.selector}'
+ kubectl get pods --show-labels
+ ```
+
+4. **确认端口正确**
+ ```bash
+ # 检查 targetPort 是否与 Pod 实际监听端口一致
+ kubectl get svc my-app-svc -o yaml | grep -A5 ports
+ kubectl exec -- ss -tlnp
+ ```
+
+5. **确认 DNS 解析**
+ ```bash
+ kubectl run test --rm -it --image=busybox -- nslookup my-app-svc
+ ```
+
+6. **确认网络策略未阻断**
+ ```bash
+ kubectl get networkpolicy -n
+ ```
+
+7. **确认 kube-proxy 正常**
+ ```bash
+ kubectl get pods -n kube-system -l k8s-app=kube-proxy
+ # 检查 iptables 规则是否存在
+ iptables -t nat -L KUBE-SERVICES | grep my-app-svc
+ ```
+
+### Session Affinity
+
+默认情况下 Service 使用轮询负载均衡。如果需要会话保持(同一客户端始终访问同一 Pod),可以启用 Session Affinity:
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: sticky-svc
+spec:
+ selector:
+ app: my-app
+ sessionAffinity: ClientIP # 基于客户端 IP 保持会话
+ sessionAffinityConfig:
+ clientIP:
+ timeoutSeconds: 1800 # 会话超时时间(默认 10800 秒)
+ ports:
+ - port: 80
+ targetPort: 8080
+```
+
+> [!warning] Session Affinity 注意事项
+> - 在 iptables 模式下,基于源 IP 的会话保持可能在 NAT 环境下失效
+> - IPVS 模式支持更精细的会话保持(`sh` 算法)
+> - 对于 HTTP 应用,更推荐使用 Cookie-based Affinity(在 Ingress 层实现)
+
+### External Traffic Policy
+
+控制外部流量如何路由到 Pod,只对 NodePort 和 LoadBalancer 类型 Service 生效:
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: my-app-svc
+spec:
+ type: LoadBalancer
+ externalTrafficPolicy: Local # 或 Cluster(默认)
+ selector:
+ app: my-app
+ ports:
+ - port: 80
+ targetPort: 8080
+```
+
+| 策略 | 行为 | 优点 | 缺点 |
+|------|------|------|------|
+| **Cluster(默认)** | 流量可能被转发到其他节点上的 Pod | 负载均衡更均匀 | 多一跳转发,丢失源 IP |
+| **Local** | 流量只转发到本节点上的 Pod | 保留源 IP,减少转发延迟 | Pod 分布不均时负载不均衡 |
+
+```mermaid
+flowchart TB
+ subgraph "externalTrafficPolicy: Cluster"
+ A1[Client] -->|请求| B1[Node 1]
+ B1 -->|可能转发| C1[Node 2 的 Pod]
+ B1 -->|本地| D1[Node 1 的 Pod]
+ end
+ subgraph "externalTrafficPolicy: Local"
+ A2[Client] -->|请求| B2[Node 1]
+ B2 -->|仅本地| D2[Node 1 的 Pod]
+ B2 -.->|不转发| C2[Node 2 的 Pod]
+ end
+```
+
+### 其他最佳实践
+
+- **使用命名空间隔离 Service**:避免命名冲突,便于权限管理
+- **合理设置 readinessProbe**:Pod 未就绪时不会被加入 Endpoints,避免流量打到未准备好的实例
+- **避免使用 ExternalName 映射到集群内部 Service**:可能导致 DNS 解析循环
+- **大规模集群启用 EndpointSlices**:减少 API Server 和 kube-proxy 的压力
+- **生产环境使用 IPVS 模式**:在 Service 数量较多时性能显著优于 iptables
+- **为关键 Service 配置 PodDisruptionBudget**:确保滚动更新和节点维护时服务可用性
+
+## 延伸阅读
+
+- [[k8s-01-overview]] — Kubernetes 基础概念总览
+- [[k8s-04-deployment-and-replicaset]] — Deployment 与 ReplicaSet 详解
diff --git a/technical/k8s/k8s-06-configmap-and-secret.md b/technical/k8s/k8s-06-configmap-and-secret.md
new file mode 100644
index 0000000..9eac792
--- /dev/null
+++ b/technical/k8s/k8s-06-configmap-and-secret.md
@@ -0,0 +1,703 @@
+---
+tags: [k8s, configmap, secret, devops]
+create time: 2026-07-07 15:48
+---
+
+# ConfigMap 与 Secret — K8s 配置管理
+
+## 概述
+
+> [!info] 十二要素应用原则(12-Factor App)
+> 配置应当与代码严格分离。镜像是不可变的构建产物,而运行时环境(数据库地址、API Key、特性开关)应当通过外部注入,而非硬编码在镜像中。Kubernetes 的 **ConfigMap** 和 **Secret** 正是实现这一原则的核心机制——它们将配置数据从 Pod 定义中解耦,使得同一镜像可以部署到不同环境而无需重建。
+
+简而言之:
+
+- **ConfigMap**:存放非敏感的配置数据(如 Config 文件、命令行参数、环境变量)。
+- **Secret**:存放敏感数据(如密码、Token、TLS 证书),并提供额外的安全保障。
+
+下图展示了配置数据如何从集群外部流入 Pod 内部:
+
+```mermaid
+flowchart LR
+ subgraph Cluster["K8s Cluster"]
+ CM[ConfigMap]
+ SEC[Secret]
+ ETCD[(etcd)]
+ CM --> ETCD
+ SEC --> ETCD
+ end
+
+ subgraph Pod["Pod"]
+ ENV["环境变量注入"]
+ VOL["Volume 挂载\n(/etc/config, /etc/secret)"]
+ end
+
+ ETCD -->|kubelet sync| VOL
+ ETCD -->|Pod 启动时注入| ENV
+
+ subgraph External["外部管理"]
+ VAULT["HashiCorp Vault"]
+ AWS_SM["AWS Secrets Manager"]
+ end
+
+ External -->|CSI Driver / Sync| SEC
+```
+
+---
+
+## ConfigMap 详解
+
+### 创建方式
+
+#### 1. 命令式创建(kubectl create configmap)
+
+```bash
+# 方式一:从字面值创建
+kubectl create configmap app-config \
+ --from-literal=APP_ENV=production \
+ --from-literal=LOG_LEVEL=info \
+ --from-literal=DB_HOST=mysql.default.svc.cluster.local
+
+# 方式二:从单个文件创建
+kubectl create configmap nginx-conf \
+ --from-file=nginx.conf
+
+# 方式三:从目录创建(目录下所有文件各成为一个 key)
+kubectl create configmap app-config-files \
+ --from-file=config/
+
+# 方式四:指定 key 名称(而非使用文件名作为 key)
+kubectl create configmap app-conf \
+ --from-file=my-config=./app.properties
+
+# 方式五:从 .env 文件创建(key=value 格式)
+kubectl create configmap env-config \
+ --from-env-file=.env
+```
+
+> [!tip] `--from-env-file` vs `--from-file`
+> `--from-env-file` 解析 `KEY=VALUE` 格式,每个键值对成为一个独立的 key;而 `--from-file` 将整个文件内容作为单个 key 的 value。二者用途不同,不要混淆。
+
+#### 2. 声明式创建(YAML)
+
+```yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: app-config
+ namespace: default
+ labels:
+ app: my-app
+data:
+ # 简单键值对
+ APP_ENV: "production"
+ LOG_LEVEL: "info"
+ DB_PORT: "3306"
+
+ # 多行配置文件作为 value
+ application.properties: |
+ server.port=8080
+ spring.datasource.url=jdbc:mysql://mysql:3306/app
+ spring.datasource.username=root
+ logging.level.root=INFO
+
+ # JSON 格式的配置
+ features.json: |
+ {
+ "enableDarkMode": true,
+ "maxRetries": 3,
+ "timeout": 30
+ }
+```
+
+查看创建结果:
+
+```bash
+kubectl describe configmap app-config
+kubectl get configmap app-config -o yaml
+```
+
+### 挂载方式
+
+ConfigMap 中的数据注入到 Pod 有两种主要方式:**环境变量注入** 和 **Volume 挂载**。二者在行为上有显著差异。
+
+| 特性 | 环境变量注入 | Volume 挂载 |
+|------|-------------|------------|
+| 注入时机 | Pod 启动时 | Pod 启动时 + 运行时可更新 |
+| 热更新支持 | 不支持,需重启 Pod | 支持(kubelet 定期同步) |
+| 适用场景 | 简单键值对、启动参数 | 配置文件(nginx.conf 等) |
+| 引用方式 | `configMapKeyRef` / `envFrom` | `volumes.configMap` |
+| 文件权限 | 不涉及 | 可设置 `defaultMode` |
+| 多 key 批量注入 | `envFrom` 批量导入 | 整个目录挂载 |
+
+#### 环境变量注入
+
+**方式一:逐个 key 引用(`configMapKeyRef`)**
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: app-pod
+spec:
+ containers:
+ - name: app
+ image: my-app:1.0
+ env:
+ # 引用 ConfigMap 中的单个 key
+ - name: APP_ENV # 容器内的环境变量名
+ valueFrom:
+ configMapKeyRef:
+ name: app-config # ConfigMap 名称
+ key: APP_ENV # ConfigMap 中的 key 名
+ - name: LOG_LEVEL
+ valueFrom:
+ configMapKeyRef:
+ name: app-config
+ key: LOG_LEVEL
+ optional: true # ConfigMap 或 key 不存在时不报错
+```
+
+**方式二:批量导入(`envFrom`)**
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: app-pod
+spec:
+ containers:
+ - name: app
+ image: my-app:1.0
+ envFrom:
+ - configMapRef:
+ name: app-config
+ prefix: "CFG_" # 可选前缀,避免命名冲突
+```
+
+> [!warning] `envFrom` 会跳过无效 key
+> ConfigMap 中如果存在不符合环境变量命名规范的 key(如包含 `-` 或以数字开头),这些 key 会被静默跳过,不会报错。使用时需注意 key 的命名规范。
+
+#### Volume 挂载
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: app-pod
+spec:
+ containers:
+ - name: app
+ image: nginx:1.25
+ volumeMounts:
+ - name: config-volume
+ mountPath: /etc/nginx/conf.d # 挂载到容器内目录
+ readOnly: true
+ - name: app-properties
+ mountPath: /app/config # 挂载整个目录
+ volumes:
+ # 挂载 ConfigMap 中指定的 key
+ - name: config-volume
+ configMap:
+ name: nginx-conf
+ items:
+ - key: default.conf
+ path: default.conf # 文件名(挂载后的文件名)
+ mode: 0644 # 文件权限
+ # 挂载 ConfigMap 全部 key(每个 key 成为一个文件)
+ - name: app-properties
+ configMap:
+ name: app-config
+ defaultMode: 0644 # 默认文件权限
+```
+
+### 热更新机制
+
+ConfigMap 通过 Volume 挂载后,当 ConfigMap 的内容在集群中被更新时,kubelet 会自动将新内容同步到 Pod 的挂载目录中。这个过程的延迟取决于:
+
+1. **kubelet sync 周期**:默认每 **1 分钟**(`--sync-frequency` 参数控制)。
+2. **传播延迟**:从 API Server 到 kubelet 的 watch 通知延迟。
+
+```mermaid
+sequenceDiagram
+ participant User as 用户/kubectl
+ participant API as API Server
+ participant Kubelet as Kubelet (Node)
+ participant Pod as Pod Container
+
+ User->>API: kubectl apply -f configmap.yaml
+ API->>Kubelet: watch 通知 ConfigMap 变更
+ Note over Kubelet: 等待 sync 周期(最多 1 分钟)
+ Kubelet->>Pod: 更新 Volume 挂载文件(原子替换)
+ Note over Pod: 应用需 reload 配置(如 nginx -s reload)
+```
+
+> [!warning] 热更新 ≠ 自动生效
+> ConfigMap 文件更新后,**应用本身必须能够感知文件变化并重新加载配置**。例如 Nginx 需要 `nginx -s reload`,Spring Boot 需要 `@RefreshScope`。否则虽然文件内容已更新,但应用仍使用旧配置。
+
+**环境变量方式不可热更新**:环境变量在 Pod 启动时由 kubelet 注入到容器的进程环境中,运行期间不会再更新。要更新环境变量,只能删除并重建 Pod。
+
+### 不可变 ConfigMap
+
+从 Kubernetes 1.21 开始,可以将 ConfigMap 标记为不可变(immutable),这是一个重要的性能优化手段。
+
+```yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: app-config-v1
+immutable: true # 标记为不可变
+data:
+ APP_ENV: "production"
+ LOG_LEVEL: "info"
+```
+
+**为什么需要不可变 ConfigMap?**
+
+- **减轻 API Server 压力**:普通 ConfigMap 会被 kubelet watch,任何变更都会触发全集群的通知广播。对于大规模集群(数千节点),大量 watch 事件会显著增加 API Server 的负担。
+- **防止意外修改**:标记为 immutable 后,该 ConfigMap 的内容不可更改(只能删除重建)。
+- **推荐场景**:发布后不再变更的配置(如已上线的版本配置)。
+
+> [!tip] 不可变 ConfigMap 的更新策略
+> 由于 immutable ConfigMap 无法修改,更新配置时需要创建一个新的 ConfigMap(如 `app-config-v2`),然后更新 Deployment 引用新的 ConfigMap 名称。可以结合 Deployment 的滚动更新实现零停机配置切换。
+
+---
+
+## Secret 详解
+
+### 类型
+
+Kubernetes 内置了多种 Secret 类型,用于不同场景。以下是常用类型的对比:
+
+| 类型 | 用途 | data 字段说明 | 典型场景 |
+|------|------|--------------|---------|
+| `Opaque` | 通用型(默认) | 用户自定义的任意 key-value | 数据库密码、API Key 等 |
+| `kubernetes.io/tls` | TLS 证书 | 必须含 `tls.crt` 和 `tls.key` | Ingress TLS 终止 |
+| `kubernetes.io/dockerconfigjson` | 镜像仓库凭证 | 必须含 `.dockerconfigjson` | 私有镜像仓库拉取 |
+| `kubernetes.io/basic-auth` | 基本认证 | `username` + `password` | Git/HTTP Basic Auth |
+| `kubernetes.io/ssh-auth` | SSH 认证 | 必须含 `ssh-privatekey` | Git SSH 拉取 |
+| `kubernetes.io/service-account-token` | SA Token | 自动创建 | Pod 身份认证(已逐步被 Bound SA Token 替代) |
+
+#### Opaque Secret 示例
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ name: db-credentials
+type: Opaque
+data:
+ # 值必须是 Base64 编码
+ username: cm9vdA== # echo -n 'root' | base64
+ password: czNjcjN0UEBzc3cwcmQ= # echo -n 's3cr3tP@ssw0rd' | base64
+# 也可以用 stringData(自动编码,方便调试,生产慎用)
+stringData:
+ connection-string: "mysql://root:s3cr3tP@ssw0rd@mysql:3306/app"
+```
+
+#### TLS Secret 示例
+
+```bash
+# 通过 kubectl 创建 TLS Secret
+kubectl create secret tls my-tls-secret \
+ --cert=tls.crt \
+ --key=tls.key
+```
+
+```yaml
+# 等价的 YAML 声明
+apiVersion: v1
+kind: Secret
+metadata:
+ name: my-tls-secret
+type: kubernetes.io/tls
+data:
+ tls.crt:
+ tls.key:
+```
+
+#### Docker Registry Secret
+
+```bash
+# 创建 Docker Registry 凭证
+kubectl create secret docker-registry regcred \
+ --docker-server=registry.example.com \
+ --docker-username=admin \
+ --docker-password=P@ssw0rd \
+ --docker-email=admin@example.com
+```
+
+```yaml
+# 在 Pod 中使用
+apiVersion: v1
+kind: Pod
+metadata:
+ name: private-app
+spec:
+ imagePullSecrets:
+ - name: regcred # 引用 Docker Registry Secret
+ containers:
+ - name: app
+ image: registry.example.com/my-app:1.0
+```
+
+### 编码 vs 加密
+
+> [!danger] Base64 编码不等于加密
+> Secret 的 `data` 字段使用 Base64 编码,但这只是编码格式,**不是加密**。任何拥有集群读权限的人都可以轻松解码。`kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d` 即可还原明文。
+
+**etcd 加密(EncryptionConfiguration)**
+
+Kubernetes 支持在 etcd 层面对 Secret 进行真正的加密存储:
+
+```yaml
+# /etc/kubernetes/encryption-config.yaml
+apiVersion: apiserver.config.k8s.io/v1
+kind: EncryptionConfiguration
+resources:
+ - resources:
+ - secrets
+ - configmaps # 1.25+ 也支持加密 ConfigMap
+ providers:
+ # 第一个 provider 用于解密(兼容)
+ - aescbc:
+ keys:
+ - name: key1
+ secret: <32-byte-base64-encoded-key> # head -c 32 /dev/urandom | base64
+ # fallback(不加密,仅读取旧数据)
+ - identity: {}
+```
+
+> [!tip] 加密流程
+> 启用 EncryptionConfiguration 后,新写入的 Secret 会使用配置的 provider 加密存储。已有的旧 Secret 在下次写入(update)时才会重新加密。读取时 API Server 自动解密,对客户端透明。
+
+#### 更安全的加密方案对比
+
+| 方案 | 加密层次 | 密钥管理 | 适用场景 |
+|------|---------|---------|---------|
+| Base64(默认) | 无加密 | 无 | 仅编码格式 |
+| aescbc(EncryptionConfiguration) | etcd 存储层 | 静态密钥(文件) | 基础加密需求 |
+| KMS Plugin(AWS KMS、GCP KMS) | etcd 存储层 | 外部 KMS 托管 | 生产环境推荐 |
+| 外部密钥管理(Vault) | 应用层 | 专业密钥管理系统 | 高安全要求 |
+
+### 挂载方式
+
+Secret 的挂载方式与 ConfigMap 基本一致,但有以下关键差异:
+
+| 特性 | ConfigMap | Secret |
+|------|-----------|--------|
+| 存储介质 | 普通目录(tmpfs 或磁盘) | **tmpfs(内存文件系统)**,不落盘 |
+| 默认文件权限 | `0644` | **`0400`**(仅 owner 可读) |
+| 适用内容 | 非敏感配置 | 密码、Token、证书等敏感数据 |
+| etcd 存储 | 明文 | 可加密(EncryptionConfiguration) |
+
+#### 环境变量注入
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: app-with-secret
+spec:
+ containers:
+ - name: app
+ image: my-app:1.0
+ env:
+ - name: DB_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: db-credentials
+ key: password
+ - name: DB_USERNAME
+ valueFrom:
+ secretKeyRef:
+ name: db-credentials
+ key: username
+ # 也可以批量注入所有 Secret key
+ # envFrom:
+ # - secretRef:
+ # name: db-credentials
+ # prefix: "SECRET_"
+```
+
+#### Volume 挂载
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: app-with-tls
+spec:
+ containers:
+ - name: app
+ image: nginx:1.25
+ volumeMounts:
+ - name: tls-certs
+ mountPath: /etc/tls
+ readOnly: true
+ - name: db-secret
+ mountPath: /etc/secrets
+ readOnly: true
+ volumes:
+ - name: tls-certs
+ secret:
+ secretName: my-tls-secret
+ defaultMode: 0400 # 仅 owner 可读
+ items:
+ - key: tls.crt
+ path: server.crt
+ - key: tls.key
+ path: server.key
+ - name: db-secret
+ secret:
+ secretName: db-credentials
+ defaultMode: 0440 # owner + group 可读
+```
+
+> [!info] tmpfs 存储
+> Secret 通过 Volume 挂载时使用 tmpfs(内存文件系统),数据不会写入节点的磁盘。这避免了敏感数据在磁盘上残留的风险,但会占用少量内存。
+
+### 安全最佳实践
+
+#### 1. 使用外部密钥管理系统
+
+在生产环境中,推荐使用专业的密钥管理系统而非原生 Secret:
+
+```mermaid
+flowchart TD
+ subgraph External["外部密钥管理"]
+ VAULT["HashiCorp Vault"]
+ AWS["AWS Secrets Manager"]
+ GCP["GCP Secret Manager"]
+ end
+
+ subgraph K8s["Kubernetes"]
+ CSI["Secrets Store CSI Driver"]
+ SYNC["External Secrets Operator"]
+ POD["Pod"]
+ end
+
+ VAULT --> CSI
+ AWS --> CSI
+ GCP --> CSI
+ CSI -->|挂载为 Volume| POD
+
+ VAULT --> SYNC
+ AWS --> SYNC
+ SYNC -->|同步为 K8s Secret| POD
+```
+
+- **Secrets Store CSI Driver**:将外部密钥直接挂载为 Volume,不在 etcd 中存储。
+- **External Secrets Operator**:从外部系统同步密钥为 K8s Secret,适合需要原生 Secret 兼容的场景。
+
+#### 2. RBAC 权限控制
+
+```yaml
+# 限制特定 ServiceAccount 只能读取特定 Secret
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: secret-reader
+ namespace: production
+rules:
+ - apiGroups: [""]
+ resources: ["secrets"]
+ resourceNames: ["db-credentials", "api-keys"] # 仅限特定 Secret
+ verbs: ["get"] # 仅允许读取
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: app-secret-reader
+ namespace: production
+subjects:
+ - kind: ServiceAccount
+ name: my-app-sa
+ namespace: production
+roleRef:
+ kind: Role
+ name: secret-reader
+ apiGroup: rbac.authorization.k8s.io
+```
+
+#### 3. 审计日志
+
+启用 API Server 的审计日志,追踪 Secret 的读写操作:
+
+```yaml
+# /etc/kubernetes/audit-policy.yaml
+apiVersion: audit.k8s.io/v1
+kind: Policy
+rules:
+ # 记录所有对 Secret 的读写操作
+ - level: Metadata
+ resources:
+ - group: ""
+ resources: ["secrets"]
+ verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
+```
+
+#### 4. 其他安全建议
+
+- **避免在 Pod Spec 中直接写 `stringData`**:生产环境应使用 `data` + Base64,或通过 CI/CD 管道注入。
+- **定期轮换密钥**:结合 External Secrets Operator 实现自动轮换。
+- **避免将 Secret 推送到 Git**:使用 `.gitignore` 排除 Secret YAML,或使用 Sealed Secrets / SOPS 加密后再提交。
+- **最小权限原则**:每个 ServiceAccount 只应拥有它所需的最小 Secret 访问权限。
+
+---
+
+## 配置热更新实践
+
+在生产环境中,单纯的 ConfigMap Volume 挂载虽然支持热更新,但应用本身还需要 reload 机制。以下是几种常见的无重启配置刷新方案。
+
+### 方案一:ConfigMap Reloader(Sidecar)
+
+[stakater/ConfigMapReloader](https://github.com/stakater/Reloader) 是一个 Kubernetes Controller,当它检测到关联的 ConfigMap/Secret 发生变更时,会自动触发 Deployment 的滚动更新(rollout restart),从而实现配置刷新。
+
+```yaml
+# 部署 Reloader 后,在 Deployment 中添加注解
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: my-app
+ annotations:
+ # Reloader 监听这些 ConfigMap/Secret 的变更
+ reloader.stakater.com/reload: "app-config,db-credentials"
+spec:
+ template:
+ spec:
+ containers:
+ - name: app
+ image: my-app:1.0
+```
+
+> [!tip] 原理
+> Reloader 并非真正实现"热更新",而是通过修改 Pod Template 的 annotation 触发 Deployment 的滚动更新。本质上是重启 Pod,但对用户无感知(滚动更新保证零停机)。
+
+### 方案二:应用内 Watch 机制
+
+一些应用框架原生支持配置文件监听:
+
+```go
+// Go 应用使用 fsnotify 监听配置文件变更
+import "github.com/fsnotify/fsnotify"
+
+func watchConfig(path string, reload func()) {
+ watcher, _ := fsnotify.NewWatcher()
+ defer watcher.Close()
+ watcher.Add(path)
+
+ for {
+ select {
+ case event := <-watcher.Events:
+ if event.Op&fsnotify.Write == fsnotify.Write {
+ log.Println("Config file modified, reloading...")
+ reload()
+ }
+ case err := <-watcher.Errors:
+ log.Println("Watch error:", err)
+ }
+ }
+}
+```
+
+### 方案三:SubPath 挂载 + 特殊处理
+
+当使用 `subPath` 挂载时,ConfigMap 的更新不会自动传播到容器。如果必须使用 `subPath`,需要配合其他方案(如 Reloader 触发重启):
+
+```yaml
+volumeMounts:
+ - name: config-volume
+ mountPath: /etc/nginx/nginx.conf
+ subPath: nginx.conf # 只挂载单个文件到指定路径
+```
+
+> [!warning] SubPath 无法热更新
+> 使用 `subPath` 挂载的文件不会随 ConfigMap 更新而更新。这是因为 kubelet 为 subPath 创建的是符号链接或独立 bind mount,不在正常的 sync 范围内。如果需要热更新,请避免使用 `subPath`。
+
+---
+
+## 常见陷阱与最佳实践
+
+### 陷阱一:ConfigMap 大小限制(1MB)
+
+Kubernetes 对 ConfigMap 和 Secret 有 **1MB** 的大小限制(这是 etcd 的 kv 对大小限制决定的)。
+
+```
+# 错误:ConfigMap 超过 1MB 会创建失败
+The ConfigMap "xxx" is invalid: data: Too long: must have at most 1048576 bytes
+```
+
+**解决方案**:
+
+- 拆分为多个 ConfigMap。
+- 使用外部配置存储(如 Consul、Nacos)。
+- 对于大型配置文件,考虑挂载到 PV。
+
+### 陷阱二:环境变量命名冲突
+
+当使用 `envFrom` 批量导入时,多个 ConfigMap/Secret 中可能存在同名 key,后导入的会覆盖先前的。
+
+```yaml
+envFrom:
+ - configMapRef:
+ name: base-config # 含 APP_ENV=staging
+ - configMapRef:
+ name: override-config # 含 APP_ENV=production(覆盖)
+```
+
+> [!tip] 使用 prefix 避免冲突
+> 为每个 `envFrom` 来源设置不同的 `prefix`,如 `base-config` 用 `BASE_`,`override-config` 用 `OVERRIDE_`。
+
+### 陷阱三:Secret 的 watch 机制开销
+
+Pod 通过 Volume 引用 Secret 时,kubelet 会 watch 该 Secret 的变更。在大规模集群中,大量 Secret 的 watch 可能导致 API Server 压力过大。
+
+**缓解措施**:
+
+- 使用 `immutable: true` 标记不会变更的 Secret。
+- 合并小 Secret,减少 watch 对象数量。
+- 使用 KMS Provider 加密时,注意解密操作的性能开销。
+
+### 陷阱四:文件权限与安全上下文
+
+Secret 默认权限为 `0400`,如果容器进程以非 root 用户运行,可能无法读取。
+
+```yaml
+# 解决方案:调整 Secret 文件权限
+volumes:
+ - name: secret-volume
+ secret:
+ secretName: app-secret
+ defaultMode: 0444 # 所有用户可读
+ # 或者精确设置
+ items:
+ - key: config.yaml
+ path: config.yaml
+ mode: 0644
+```
+
+### 最佳实践总结
+
+| 实践 | 说明 |
+|------|------|
+| ConfigMap 与 Config 文件分离 | 不同环境的配置使用不同的 ConfigMap |
+| 使用 `immutable: true` | 已发布配置标记为不可变,减轻 API Server 压力 |
+| Secret 使用外部管理系统 | 生产环境使用 Vault/KMS,而非原生 Secret |
+| 避免 `envFrom` 无差别导入 | 明确列出需要的 key,避免意外覆盖 |
+| 避免 `subPath` 挂载需要热更新的配置 | `subPath` 不支持热更新,优先使用目录挂载 |
+| 设置合理的 RBAC | 每个 ServiceAccount 只授予最小必要权限 |
+| 配置版本管理 | ConfigMap 名称带版本号(如 `app-config-v3`),便于回滚 |
+| CI/CD 中管理 Secret | 使用 Sealed Secrets、SOPS 或 External Secrets Operator |
+
+---
+
+## 延伸阅读
+
+- [[k8s-03-pod]] — Pod 生命周期与资源管理
+- [[k8s-04-deployment-and-replicaset]] — Deployment 滚动更新与回滚
+- [Kubernetes 官方文档 - ConfigMap](https://kubernetes.io/docs/concepts/configuration/configmap/)
+- [Kubernetes 官方文档 - Secret](https://kubernetes.io/docs/concepts/configuration/secret/)
+- [stakater/Reloader - ConfigMap/Secret 自动触发重启](https://github.com/stakater/Reloader)
+- [Secrets Store CSI Driver](https://secrets-store-csi-driver.sigs.k8s.io/)
+- [External Secrets Operator](https://external-secrets.io/)
diff --git a/technical/k8s/k8s-07-storage.md b/technical/k8s/k8s-07-storage.md
new file mode 100644
index 0000000..4002c27
--- /dev/null
+++ b/technical/k8s/k8s-07-storage.md
@@ -0,0 +1,625 @@
+---
+tags: [k8s, storage, pv, pvc, statefulset, devops]
+create time: 2026-07-07 15:48
+---
+
+# Kubernetes 存储 — PV、PVC 与 StatefulSet
+
+## 概述
+
+容器的本质是短暂的(ephemeral):Pod 被删除后,其内部所有文件随之消散。这对无状态服务无关紧要,但对数据库、消息队列等有状态应用来说是致命的。Kubernetes 通过分层存储抽象 —— **Volume → PersistentVolume(PV)→ PersistentVolumeClaim(PVC)→ StorageClass** —— 将"存储供给"与"存储消费"解耦,让开发者无需关心底层基础设施,只需声明"我需要多大、什么访问模式的存储",即可自动获得持久化能力。配合 StatefulSet,K8s 还为有状态应用提供了稳定的网络标识和有序的生命周期管理。
+
+## 卷(Volume)基础
+
+Volume 是 K8s 最早引入的存储概念,与 Pod 同生命周期(除非是持久卷类型)。它定义在 Pod spec 中,可被同一 Pod 内的多个容器共享。
+
+### 常见卷类型
+
+| 卷类型 | 生命周期 | 典型用途 | 是否持久 |
+|--------|----------|----------|----------|
+| `emptyDir` | Pod 存续期间 | 容器间共享临时文件、缓存 | 否 |
+| `hostPath` | 节点存续期间 | 访问宿主机文件(如 Docker socket) | 节点级持久 |
+| `nfs` | 独立于 Pod | 多节点共享存储 | 是 |
+| `configMap` / `secret` | Pod 存续期间 | 注入配置和敏感信息 | 否 |
+| `persistentVolumeClaim` | 独立于 Pod | 绑定 PV,正式的持久化方案 | 是 |
+
+### emptyDir 示例
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: shared-data
+spec:
+ containers:
+ - name: writer
+ image: busybox
+ command: ["sh", "-c", "echo hello > /data/msg && sleep 3600"]
+ volumeMounts:
+ - name: cache
+ mountPath: /data
+ - name: reader
+ image: busybox
+ command: ["sh", "-c", "cat /data/msg && sleep 3600"]
+ volumeMounts:
+ - name: cache
+ mountPath: /data
+ volumes:
+ - name: cache
+ emptyDir: {}
+```
+
+> [!tip] emptyDir 的 `medium: Memory` 配置可将其挂载为 tmpfs(RAM 磁盘),适用于对 I/O 延迟极敏感的场景,但会计入容器内存用量。
+
+### hostPath 注意事项
+
+hostPath 直接暴露宿主机目录,存在安全风险。生产环境应优先使用 PVC。如果必须使用,建议设置 `type` 字段限制行为:
+
+```yaml
+volumes:
+ - name: docker-sock
+ hostPath:
+ path: /var/run/docker.sock
+ type: Socket # 仅允许已存在的 socket 文件
+```
+
+hostPath 的 `type` 可选值:`DirectoryOrCreate`、`Directory`、`FileOrCreate`、`File`、`Socket`、`CharDevice`、`BlockDevice`。
+
+## 持久卷(PersistentVolume)
+
+PersistentVolume(PV)是集群级别的存储资源,由管理员预先创建或由 StorageClass 动态供给。它与 Pod 完全解耦 —— Pod 删除后 PV 仍然存在。
+
+### PV 详解
+
+```yaml
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ name: pv-nfs-data
+spec:
+ capacity:
+ storage: 10Gi # 存储容量
+ accessModes:
+ - ReadWriteMany # 访问模式
+ persistentVolumeReclaimPolicy: Retain # 回收策略
+ storageClassName: slow # 存储类名称
+ nfs:
+ server: 192.168.1.100
+ path: /exports/data
+```
+
+关键字段说明:
+
+| 字段 | 说明 |
+|------|------|
+| `capacity.storage` | PV 的存储容量,只能声明一种资源(目前仅支持 storage) |
+| `accessModes` | 定义卷的挂载方式,可声明多种,但实际只能使用一种 |
+| `persistentVolumeReclaimPolicy` | PVC 释放后 PV 的处理策略:Retain / Delete / Recycle |
+| `storageClassName` | 关联的 StorageClass 名称,为空字符串表示不关联任何类 |
+| `volumeMode` | `Filesystem`(默认)或 `Block`(原始块设备) |
+| `mountOptions` | 挂载选项,如 `["hard", "nfsvers=4.1"]` |
+
+> [!info] PV 的状态包括 `Available`(未绑定)、`Bound`(已绑定到 PVC)、`Released`(PVC 已删除但 PV 未回收)、`Failed`(自动回收失败)。
+
+### 回收策略(Reclaim Policy)
+
+当 PVC 被删除后,PV 的回收策略决定了其后续行为。
+
+| 策略 | 行为 | 适用场景 |
+|------|------|----------|
+| **Retain** | PV 状态变为 `Released`,数据保留,需管理员手动处理 | 有保留价值的数据 |
+| **Delete** | PV 及底层存储资源一并删除 | 动态供给的临时存储 |
+| **Recycle** | 执行 `rm -rf /volume/*`,PV 回到 `Available`(已废弃) | 仅兼容简单存储后端 |
+
+```mermaid
+stateDiagram-v2
+ [*] --> Available: PV 创建
+ Available --> Bound: PVC 绑定
+ Bound --> Released: PVC 删除
+
+ Released --> Available: Recycle (rm -rf)
+ Released --> [*]: Delete (删除底层存储)
+ Released --> Released: Retain (等待手动处理)
+
+ state "管理员手动操作" as manual
+ Released --> manual: 需人工介入
+ manual --> Available: 修复后重新上线
+ manual --> [*]: 彻底删除
+```
+
+> [!warning] Recycle 策略已被废弃,且仅对 NFS 和 hostPath 有效。生产环境应优先使用动态供给 + Delete 策略,或 Retain 策略配合手动运维。
+
+### 访问模式(Access Modes)
+
+| 模式 | 缩写 | 含义 |
+|------|------|------|
+| ReadWriteOnce | RWO | 单节点读写挂载 |
+| ReadOnlyMany | ROX | 多节点只读挂载 |
+| ReadWriteMany | RWX | 多节点读写挂载 |
+| ReadWriteOncePod | RWOP | 单 Pod 读写(K8s 1.22+) |
+
+各存储后端对访问模式的支持情况:
+
+| 存储后端 | RWO | ROX | RWX | RWOP |
+|----------|-----|-----|-----|------|
+| 本地磁盘 (local) | 支持 | - | - | 支持 |
+| AWS EBS | 支持 | - | - | 支持 |
+| GCE PD | 支持 | 支持 | - | 支持 |
+| Azure Disk | 支持 | - | - | 支持 |
+| Azure File | 支持 | 支持 | 支持 | - |
+| Ceph RBD | 支持 | 支持 | - | - |
+| CephFS | 支持 | 支持 | 支持 | - |
+| NFS | 支持 | 支持 | 支持 | - |
+| Longhorn | 支持 | 支持 | 支持 | - |
+
+> [!tip] 为什么不是所有存储都支持 RWX?因为多节点同时写入需要分布式文件系统级别的并发控制(如分布式锁、一致性协议)。块存储(EBS、PD)天然不支持多写,必须通过 NFS/文件系统层封装才能实现 RWX。
+
+## 持久卷声明(PersistentVolumeClaim)
+
+PVC 是用户对存储资源的"请求"。用户不直接创建或管理 PV,而是通过 PVC 声明需求(容量、访问模式、存储类),K8s 负责将合适的 PV 绑定到 PVC。
+
+```yaml
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: mysql-data
+spec:
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 20Gi
+ storageClassName: fast-ssd
+```
+
+### PVC 绑定机制
+
+PVC 与 PV 的绑定有两种模式:
+
+#### 静态供给(Static Provisioning)
+
+管理员手动创建 PV 资源,用户创建 PVC 后,K8s 控制面中的 PV controller 根据容量、访问模式、存储类等条件进行匹配。
+
+#### 动态供给(Dynamic Provisioning)
+
+用户创建 PVC 后,若集群中没有匹配的 PV,且 PVC 指定了 StorageClass,K8s 会调用对应的 provisioner 自动创建 PV 并完成绑定。这是生产环境中最常用的方式。
+
+```mermaid
+flowchart TD
+ A[用户创建 PVC] --> B{是否有匹配的 Available PV?}
+ B -->|是| C[绑定 PV 到 PVC]
+ B -->|否| D{PVC 指定了 StorageClass?}
+ D -->|否| E[PVC 持续 Pending]
+ D -->|是| F[StorageClass Provisioner 创建 PV]
+ F --> C
+ C --> G[Pod 挂载 PVC 使用]
+
+ style E fill:#f96,stroke:#333
+ style G fill:#6f9,stroke:#333
+```
+
+绑定匹配规则:
+1. PV 与 PVC 的 `storageClassName` 必须一致
+2. PV 的 `accessModes` 必须包含 PVC 请求的模式
+3. PV 的 `capacity.storage` 必须 >= PVC 请求的容量
+4. PV 的 `volumeMode` 必须匹配
+5. 若存在多个匹配 PV,优先选择容量最小的满足者(最小匹配原则)
+6. 可通过标签选择器(`selector`)进一步筛选
+
+> [!info] 一对一绑定:一个 PV 只能绑定一个 PVC,一个 PVC 也只能绑定一个 PV。绑定后即使 PV 的标签被修改,绑定关系也不会解除。
+
+### StorageClass
+
+StorageClass 定义了存储的"类别",是动态供给的核心。它告诉 K8s 使用哪个 provisioner、以什么参数创建存储。
+
+```yaml
+apiVersion: storage.k8s.io/v1
+kind: StorageClass
+metadata:
+ name: fast-ssd
+provisioner: kubernetes.io/aws-ebs # 存储驱动
+parameters:
+ type: gp3 # 传递给 provisioner 的参数
+ fsType: ext4
+ iopsPerGB: "50"
+reclaimPolicy: Delete # PV 回收策略
+volumeBindingMode: WaitForFirstConsumer # 绑定时机
+allowVolumeExpansion: true # 允许扩容
+mountOptions:
+ - debug
+```
+
+关键字段详解:
+
+| 字段 | 说明 | 常见值 |
+|------|------|--------|
+| `provisioner` | 存储驱动标识 | `kubernetes.io/aws-ebs`、`rancher.io/local-path`、`csi.hetzner.cloud` 等 |
+| `parameters` | 传递给 provisioner 的键值对,不同驱动参数不同 | EBS: `type`/`fsType`;Ceph: `pool`/`clusterID` |
+| `reclaimPolicy` | 动态创建的 PV 的回收策略,默认 `Delete` | `Retain`、`Delete` |
+| `volumeBindingMode` | PV 创建和绑定的时机 | `Immediate`(默认)、`WaitForFirstConsumer` |
+| `allowVolumeExpansion` | 是否允许 PVC 扩容,默认 `false` | `true`、`false` |
+| `mountOptions` | 动态创建的 PV 的挂载选项 | NFS: `["hard","nfsvers=4.1"]` |
+
+> [!warning] `volumeBindingMode: WaitForFirstConsumer` 的重要性:默认的 `Immediate` 模式会在 PVC 创建时立即绑定/创建 PV,这可能导致 PV 创建在与 Pod 不同的可用区(尤其在多可用区集群中),最终 Pod 调度失败。`WaitForFirstConsumer` 会延迟到 Pod 调度时才创建 PV,确保存储与计算在同一拓扑域。
+
+```mermaid
+sequenceDiagram
+ participant User as 用户
+ participant API as API Server
+ participant SC as StorageClass
+ participant PV Controller
+ participant Provisioner
+
+ Note over User,Provisioner: Immediate 模式
+ User->>API: 创建 PVC
+ API->>PV Controller: 协调 PVC
+ PV Controller->>Provisioner: 创建 PV
+ Provisioner-->>PV Controller: PV Ready
+ PV Controller->>API: 绑定 PV ↔ PVC
+
+ Note over User,Provisioner: WaitForFirstConsumer 模式
+ User->>API: 创建 PVC (Pending)
+ User->>API: 创建 Pod
+ API->>PV Controller: Pod 调度完成,触发绑定
+ PV Controller->>Provisioner: 在 Pod 所在节点/可用区创建 PV
+ Provisioner-->>PV Controller: PV Ready
+ PV Controller->>API: 绑定 PV ↔ PVC
+```
+
+## StatefulSet
+
+StatefulSet 是 K8s 为有状态应用设计的工作负载控制器,解决了 Deployment 无法满足的三个核心需求:
+
+1. **稳定的网络标识**:Pod 名称固定(`pod-0`、`pod-1`),通过 Headless Service 提供 DNS 记录(`pod-0.service-name.namespace.svc.cluster.local`)
+2. **稳定的持久存储**:每个 Pod 拥有独立的 PVC,Pod 重建后 PVC 不变
+3. **有序的部署/扩缩/删除**:Pod 按序号顺序操作,前一个就绪后才启动下一个
+
+### StatefulSet vs Deployment
+
+| 特性 | Deployment | StatefulSet |
+|------|-----------|-------------|
+| Pod 名称 | 随机后缀 | 有序编号(`-0`、`-1`、`-2`) |
+| 部署顺序 | 并行 | 顺序(前一个 Ready 后才启动下一个) |
+| 删除顺序 | 并行 | 逆序 |
+| 网络标识 | 不稳定 | 稳定,配合 Headless Service |
+| 存储 | 共享 PVC 或无存储 | 每个 Pod 独立 PVC(通过 `volumeClaimTemplates`) |
+| 缩容 | 随机删除 | 从最大序号开始删除 |
+
+### volumeClaimTemplates
+
+`volumeClaimTemplates` 是 StatefulSet 的核心特性 —— 它会为每个 Pod 自动创建独立的 PVC。
+
+```yaml
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: mysql
+spec:
+ serviceName: mysql # 必须关联 Headless Service
+ replicas: 3
+ selector:
+ matchLabels:
+ app: mysql
+ template:
+ metadata:
+ labels:
+ app: mysql
+ spec:
+ containers:
+ - name: mysql
+ image: mysql:8.0
+ volumeMounts:
+ - name: data
+ mountPath: /var/lib/mysql
+ volumeClaimTemplates:
+ - metadata:
+ name: data
+ spec:
+ accessModes: ["ReadWriteOnce"]
+ storageClassName: fast-ssd
+ resources:
+ requests:
+ storage: 20Gi
+```
+
+上例中,K8s 会自动创建三个 PVC:`data-mysql-0`、`data-mysql-1`、`data-mysql-2`,每个绑定独立的 PV。
+
+> [!info] StatefulSet 缩容时,PVC 不会被删除。这意味着缩容后再扩容,新 Pod 会重新挂载原来的 PVC,数据不丢失。这是 StatefulSet 最重要的特性之一。
+
+## CSI(Container Storage Interface)
+
+CSI 是 K8s 存储插件的标准化接口规范(自 K8s 1.13 GA),取代了早期的 in-tree volume plugin 方式。
+
+### 为什么需要 CSI
+
+早期 K8s 的存储驱动代码直接存在于 K8s 主仓库(in-tree),导致:
+- 存储供应商必须将代码合入 K8s 仓库,发布周期受限于 K8s
+- K8s 仓库臃肿,维护负担重
+- 驱动 bug 可能影响 K8s 核心组件稳定性
+
+CSI 将存储驱动移出 K8s 核心(out-of-tree),通过 gRPC 接口标准化通信。
+
+### CSI 架构
+
+```mermaid
+graph TD
+ subgraph "Kubernetes 组件"
+ AK[API Server]
+ KC[kube-controller-manager]
+ KM[kubelet]
+ end
+
+ subgraph "CSI 组件(集群级别)"
+ CP[CSI Controller Plugin]
+ CS[CSI Driver 注册]
+ end
+
+ subgraph "CSI 组件(节点级别)"
+ NP[CSI Node Plugin]
+ end
+
+ subgraph "存储后端"
+ S[Cloud Provider / SAN / NFS]
+ end
+
+ AK --> KC
+ KC -->|创建/删除/扩容| CP
+ CP --> S
+ KM -->|挂载/卸载| NP
+ NP --> S
+ CS -.->|注册| AK
+```
+
+CSI 的三个核心组件:
+- **CSI Controller Plugin**:集群级别,负责卷的创建、删除、快照、扩容等控制面操作
+- **CSI Node Plugin**:节点级别,负责卷的挂载(NodePublishVolume)、卸载(NodeUnpublishVolume)等数据面操作
+- **CSIDriver 对象**:在 K8s 中注册驱动,声明能力(如 `attachRequired`、`podInfoOnMount`)
+
+### 常见 CSI 驱动
+
+| 存储后端 | CSI 驱动名称 | 说明 |
+|----------|-------------|------|
+| AWS EBS | `ebs.csi.aws.com` | AWS 官方,替代 in-tree aws-ebs |
+| GCE PD | `pd.csi.storage.gke.io` | GCP 官方 |
+| Azure Disk | `disk.csi.azure.com` | Azure 官方 |
+| Ceph RBD | `rbd.csi.ceph.com` | Ceph 官方 |
+| Longhorn | `driver.longhorn.io` | Rancher 开源分布式存储 |
+| OpenEBS | `openebs.io/local` / `openebs.io/zfs-localpv` | 开源容器原生存储 |
+| Hetzner Cloud | `csi.hetzner.cloud` | Hetzner 云盘 |
+| 本地路径 | `rancher.io/local-path` | Local Path Provisioner,开发测试常用 |
+
+### CSI Driver 部署示例
+
+以 AWS EBS CSI Driver 为例,通常通过 Helm 安装:
+
+```bash
+helm repo add aws-ebs-csi-driver https://kubernetes-sigs.github.io/aws-ebs-csi-driver
+helm install aws-ebs-csi-driver aws-ebs-csi-driver/aws-ebs-csi-driver \
+ --namespace kube-system
+```
+
+安装后会在集群中创建 `ebs.csi.aws.com` CSIDriver 对象和对应的 DaemonSet(Node Plugin)+ Deployment(Controller Plugin)。
+
+## 实战示例
+
+以下完整示例演示如何部署一个带持久化存储的 MySQL StatefulSet。
+
+### 1. StorageClass
+
+```yaml
+apiVersion: storage.k8s.io/v1
+kind: StorageClass
+metadata:
+ name: mysql-ssd
+provisioner: rancher.io/local-path # 本地开发环境使用 local-path
+reclaimPolicy: Retain # 数据库场景用 Retain 更安全
+volumeBindingMode: WaitForFirstConsumer
+allowVolumeExpansion: true
+```
+
+### 2. Headless Service
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: mysql
+ labels:
+ app: mysql
+spec:
+ ports:
+ - port: 3306
+ targetPort: 3306
+ name: mysql
+ clusterIP: None # Headless Service,StatefulSet 必需
+ selector:
+ app: mysql
+```
+
+### 3. StatefulSet
+
+```yaml
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: mysql
+spec:
+ serviceName: mysql
+ replicas: 1 # 单节点 MySQL,生产建议配合 Operator 做主从
+ selector:
+ matchLabels:
+ app: mysql
+ template:
+ metadata:
+ labels:
+ app: mysql
+ spec:
+ containers:
+ - name: mysql
+ image: mysql:8.0
+ ports:
+ - containerPort: 3306
+ name: mysql
+ env:
+ - name: MYSQL_ROOT_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: mysql-secret
+ key: ROOT_PASSWORD
+ volumeMounts:
+ - name: mysql-data
+ mountPath: /var/lib/mysql
+ resources:
+ requests:
+ cpu: 250m
+ memory: 512Mi
+ limits:
+ cpu: "1"
+ memory: 1Gi
+ readinessProbe:
+ exec:
+ command:
+ - mysqladmin
+ - ping
+ - -h
+ - localhost
+ initialDelaySeconds: 10
+ periodSeconds: 5
+ volumeClaimTemplates:
+ - metadata:
+ name: mysql-data
+ spec:
+ accessModes: ["ReadWriteOnce"]
+ storageClassName: mysql-ssd
+ resources:
+ requests:
+ storage: 10Gi
+```
+
+### 4. Secret(MySQL root 密码)
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ name: mysql-secret
+type: Opaque
+stringData:
+ ROOT_PASSWORD: "MyS3cur3P@ssw0rd"
+```
+
+### 5. 客户端 Pod 测试连接
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: mysql-client
+spec:
+ containers:
+ - name: mysql-client
+ image: mysql:8.0
+ command: ["sleep", "3600"]
+```
+
+进入客户端 Pod 后,使用 StatefulSet 的稳定 DNS 名称连接:
+
+```bash
+kubectl exec -it mysql-client -- bash
+mysql -h mysql-0.mysql.default.svc.cluster.local -u root -p
+```
+
+> [!tip] DNS 解析规则:`...svc.cluster.local`。`mysql-0.mysql` 即可解析,完整 FQDN 为 `mysql-0.mysql.default.svc.cluster.local`。
+
+## 常见陷阱与最佳实践
+
+### PVC 持续 Pending
+
+**现象**:PVC 长时间处于 Pending 状态,Pod 无法启动。
+
+**排查步骤**:
+
+```bash
+# 查看 PVC 事件
+kubectl describe pvc
+
+# 查看是否有匹配的 PV
+kubectl get pv
+
+# 查看 StorageClass 是否存在
+kubectl get storageclass
+```
+
+**常见原因**:
+- 没有匹配的 Available PV(静态供给场景下)
+- StorageClass 的 provisioner 未安装或配置错误
+- `volumeBindingMode: WaitForFirstConsumer` 下,没有 Pod 消费该 PVC
+- 底层存储配额不足或权限不足
+
+### 动态供给配置错误
+
+**现象**:PVC Pending,事件中显示 `waiting for first consumer to be created before binding`。
+
+**排查**:确认 `volumeBindingMode` 设置是否符合预期。如果 Pod 已经存在但仍未绑定,检查:
+- Pod 是否成功调度到节点(Pod 事件)
+- CSI Node Plugin 是否在目标节点正常运行(DaemonSet Pod 状态)
+- Provisioner 的参数是否正确(如 `parameters.type` 是否为合法值)
+
+### RWX 不支持的存储类型
+
+**现象**:PVC 声明 `ReadWriteMany` 但一直 Pending。
+
+**原因**:块存储(EBS、GCE PD、Azure Disk)不支持 RWX。解决方案:
+- 使用支持 RWX 的存储后端:NFS、CephFS、Azure File、Longhorn、OpenEBS
+- 使用 ReadWriteOnce(如果应用本身不支持多写,如 MySQL 单实例)
+- 引入分布式锁机制或应用层复制
+
+### PVC 扩容
+
+扩容需要 StorageClass 设置 `allowVolumeExpansion: true`,且 CSI 驱动支持在线扩容:
+
+```bash
+# 编辑 PVC 请求的容量
+kubectl patch pvc mysql-data-mysql-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
+
+# 查看扩容进度
+kubectl describe pvc mysql-data-mysql-0
+```
+
+> [!warning] PVC 扩容只能增大不能缩小。部分存储后端支持在线扩容(无需重启 Pod),部分需要 unmount 后才能扩容,具体取决于 CSI 驱动实现。
+
+### 数据备份策略
+
+PV 中的数据没有内置的备份机制,需要额外配置:
+
+- **VolumeSnapshot**:K8s 1.20+ GA,通过 CSI 驱动创建卷快照
+- **Velero**:开源的 K8s 备份恢复工具,支持卷快照 + 资源对象的完整备份
+- **应用层备份**:对于数据库,使用 `mysqldump`、`pg_dump` 等原生工具定期备份
+- **存储层快照**:利用云平台的 EBS Snapshot、Azure Snapshot 等定期快照
+
+```yaml
+# VolumeSnapshot 示例
+apiVersion: snapshot.storage.k8s.io/v1
+kind: VolumeSnapshot
+metadata:
+ name: mysql-snapshot
+spec:
+ volumeSnapshotClassName: csi-aws-ebs-snapclass
+ source:
+ persistentVolumeClaimName: mysql-data-mysql-0
+```
+
+### 最佳实践总结
+
+1. **始终使用 `volumeBindingMode: WaitForFirstConsumer`**,避免多可用区集群中存储与计算分离
+2. **数据库场景使用 `reclaimPolicy: Retain`**,防止误删 PVC 导致数据丢失
+3. **为 StatefulSet 配置 Readiness Probe**,确保 K8s 在 Pod 真正就绪后才启动下一个
+4. **生产环境使用 CSI 驱动替代 in-tree plugin**,获得更好的维护性和功能支持
+5. **定期备份 PV 数据**,不要依赖 K8s 的存储抽象作为唯一的持久化保障
+6. **监控 PV 使用率**,设置 PVC 扩容告警,避免磁盘满导致服务中断
+7. **避免使用 `hostPath`** 作为生产存储方案,除非有明确的安全和隔离措施
+
+## 延伸阅读
+
+- [[k8s-03-pod]] — Pod 基础与生命周期
+- [[k8s-02-architecture]] — Kubernetes 整体架构
diff --git a/technical/k8s/k8s-08-observability.md b/technical/k8s/k8s-08-observability.md
new file mode 100644
index 0000000..df53385
--- /dev/null
+++ b/technical/k8s/k8s-08-observability.md
@@ -0,0 +1,677 @@
+---
+tags: [k8s, observability, logging, monitoring, devops]
+create time: 2026-07-07 15:48
+---
+
+# Kubernetes 可观测性 — 日志、监控与健康检查
+
+## 概述
+
+可观测性(Observability)是理解系统内部状态的能力,在 Kubernetes 中通常由三大支柱构成:**Logging(日志)**、**Metrics(指标)** 和 **Tracing(链路追踪)**。日志告诉你「发生了什么」,指标告诉你「系统状态如何」,追踪告诉你「请求经过了哪里」。三者相互补充,缺一不可。在 K8s 环境中,由于 Pod 生命周期短暂、服务动态伸缩,传统的 SSH 进机器查日志方式不再适用,必须依赖系统化的可观测性方案。本文将从健康检查探针入手,逐步覆盖日志采集、指标监控和事件管理的完整实践。
+
+---
+
+## 健康检查(Probe)深入
+
+Kubernetes 通过探针(Probe)机制持续监测容器的健康状态,自动完成故障隔离与自愈。这是 K8s 可观测性的第一道防线——不需要外部工具,原生能力就能实现基本的服务可用性保障。
+
+### 三种探针详解
+
+K8s 提供三种探针,各有分工:
+
+| 探针 | 作用 | 失败后果 | 典型场景 |
+|------|------|----------|----------|
+| **livenessProbe** | 检测容器是否存活 | 杀死容器并重启(遵循 restartPolicy) | 应用死锁、无限循环 |
+| **readinessProbe** | 检测容器是否就绪接收流量 | 从 Service 的 Endpoints 中摘除 | 依赖未就绪、预热加载中 |
+| **startupProbe** | 检测容器是否完成启动 | 杀死容器并重启 | 慢启动应用(如 Java、大型模型加载) |
+
+#### 配置参数说明
+
+所有探针共享以下参数:
+
+| 参数 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `initialDelaySeconds` | int | 0 | 容器启动后多久开始第一次探测 |
+| `periodSeconds` | int | 10 | 探测间隔(秒) |
+| `timeoutSeconds` | int | 1 | 探测超时时间(秒) |
+| `failureThreshold` | int | 3 | 连续失败多少次后判定为不健康 |
+| `successThreshold` | int | 1 | 连续成功多少次后恢复(liveness 固定为 1) |
+
+> [!tip] startupProbe 与 livenessProbe 的协作
+> 当 `startupProbe` 配置后,`livenessProbe` 和 `readinessProbe` 会在 startupProbe 成功之后才生效。这意味着你可以用一个宽松的 startupProbe(比如 `failureThreshold: 30`、`periodSeconds: 10`,即允许最多 300 秒启动时间),而 livenessProbe 保持较严格的配置,避免慢启动应用被误杀。
+
+### 探测方式
+
+K8s 支持四种探测机制,选择哪种取决于应用特性和暴露的端口。
+
+#### exec — 执行命令探测
+
+适用于:自定义健康检查逻辑、需要检查内部状态的场景(如数据库连接池)。
+
+```yaml
+livenessProbe:
+ exec:
+ command:
+ - /bin/sh
+ - -c
+ - "pg_isready -U postgres"
+ initialDelaySeconds: 15
+ periodSeconds: 10
+ timeoutSeconds: 5
+ failureThreshold: 3
+```
+
+容器内执行指定命令,退出码为 0 表示健康,非 0 表示不健康。适用于需要检查进程内部状态(如自定义脚本检测连接池)的场景。
+
+#### httpGet — HTTP 请求探测
+
+适用于:Web 服务、API 服务,最常用的探测方式。
+
+```yaml
+livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8080
+ httpHeaders:
+ - name: X-Custom-Header
+ value: "k8s-probe"
+ initialDelaySeconds: 10
+ periodSeconds: 15
+ timeoutSeconds: 3
+ failureThreshold: 3
+```
+
+向容器的指定端口和路径发起 HTTP GET 请求,2xx/3xx 状态码视为健康。建议为健康检查设计轻量级的专用端点,避免在 `/` 上做复杂业务校验。
+
+#### tcpSocket — TCP 端口探测
+
+适用于:非 HTTP 协议的 TCP 服务(如数据库、Redis、gRPC 服务端口)。
+
+```yaml
+readinessProbe:
+ tcpSocket:
+ port: 3306
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ timeoutSeconds: 3
+ failureThreshold: 3
+```
+
+尝试建立 TCP 连接,连接成功即视为健康。适合 MySQL、Redis 等数据库服务,或尚不支持 HTTP 健康检查端点的遗留服务。
+
+#### gRPC — gRPC 健康检查探测
+
+适用于:gRPC 微服务(K8s 1.24+ GA)。
+
+```yaml
+readinessProbe:
+ grpc:
+ port: 50051
+ # 可选:指定服务名
+ # service: "my-grpc-service"
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ timeoutSeconds: 5
+ failureThreshold: 3
+```
+
+利用 gRPC 标准的 [Health Checking Protocol](https://github.com/grpc/grpc/blob/master/doc/health-checking.md) 进行探测,比 httpGet 更自然地适配 gRPC 服务。需要应用实现 `grpc.health.v1.Health` 服务。
+
+### 探针设计原则
+
+> [!tip] 原则一:慢启动应用必须用 startupProbe
+> Java 应用、机器学习模型加载、大型缓存预热等场景,启动时间可能超过 livenessProbe 的 `initialDelaySeconds`。此时应该用 startupProbe 兜底,而不是无限增大 `initialDelaySeconds`——后者会导致所有重启都白白等待固定时长。
+
+```yaml
+# 典型的慢启动应用探针配置
+startupProbe:
+ httpGet:
+ path: /healthz
+ port: 8080
+ failureThreshold: 30 # 允许最多 30 次失败
+ periodSeconds: 10 # 每 10 秒检查一次
+ # 总共允许 30 × 10 = 300 秒启动时间
+
+livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8080
+ periodSeconds: 15
+ timeoutSeconds: 3
+ failureThreshold: 3 # 启动后 3 次失败即重启
+```
+
+> [!tip] 原则二:readiness ≠ liveness,职责不能混
+> - **readinessProbe**:「我准备好接收请求了吗?」——可以是暂时性的(如依赖 Redis 还没连上),摘除流量后恢复即可重新加入。
+> - **livenessProbe**:「我还活着吗?」——失败意味着进程级故障(死锁、崩溃),需要重启。
+> - 常见错误:把依赖检查(如数据库连接)放在 livenessProbe 里。数据库短暂不可达会导致 Pod 被重启,反而加重问题。
+
+> [!tip] 原则三:健康端点要轻量
+> livenessProbe 的 `/healthz` 端点不应该检查下游依赖(数据库、Redis 等),否则下游抖动会导致 Pod 被误杀。readinessProbe 可以做适度的依赖检查,但也要控制超时。
+
+> [!warning] 避免探针与应用抢占资源
+> exec 探针会 fork 子进程,在资源受限的容器中可能加剧 OOM。高并发场景下优先使用 httpGet 或 tcpSocket。
+
+#### 探针决策流程图
+
+```mermaid
+flowchart TD
+ A[应用需要健康检查] --> B{启动时间 > 30 秒?}
+ B -->|是| C[配置 startupProbe]
+ B -->|否| D{需要检查依赖?}
+ C --> D
+ D -->|是, 如 DB/Redis| E[readinessProbe 检查依赖]
+ D -->|否| F[readinessProbe 基础端口/路径]
+ E --> G{livenessProbe 如何配置?}
+ F --> G
+ G --> H[仅检查进程存活
不检查下游依赖]
+ H --> I{协议类型?}
+ I -->|HTTP| J[httpGet /healthz]
+ I -->|gRPC| K[grpc health check]
+ I -->|TCP/数据库| L[tcpSocket port]
+ I -->|自定义逻辑| M[exec command]
+```
+
+---
+
+## 日志管理
+
+### K8s 日志架构
+
+在 Kubernetes 中,日志的生命周期跨越三个层级:容器层、节点层、集群层。理解这个链路是做好日志管理的前提。
+
+```mermaid
+flowchart LR
+ subgraph 容器层
+ A[应用进程] -->|stdout / stderr| B[容器运行时
CRI]
+ end
+
+ subgraph 节点层
+ B -->|写入文件| C["/var/log/pods/"]
+ C --> D[kubelet 日志轮转]
+ end
+
+ subgraph 集群层
+ D --> E[日志采集 Agent
Fluentd / Promtail / Filebeat]
+ E --> F[存储后端
Elasticsearch / Loki / S3]
+ F --> G[查询 UI
Kibana / Grafana]
+ end
+
+ A -.->|Sidecar 模式| H[日志 Sidecar 容器]
+ H --> F
+```
+
+K8s 推荐的最佳实践是:**应用将日志输出到 stdout/stderr**,由 kubelet 负责写入节点磁盘,再由集群级采集工具统一收集。这样做解耦了应用和日志基础设施,应用无需关心日志的存储和传输。
+
+### 节点级日志
+
+#### 目录结构
+
+kubelet 将每个容器的 stdout/stderr 写入节点上的日志文件,路径格式为:
+
+```
+/var/log/pods/__//.log
+```
+
+实际示例:
+
+```
+/var/log/pods/default_nginx-7d8b49557c-abc123_5d1f3a2e-1234-5678-abcd-ef0123456789/
+├── nginx/
+│ ├── 0.log # 第 0 次启动的日志
+│ └── 1.log # 第 1 次重启后的日志
+└── nginx/
+ └── 0.log -> /var/log/containers/nginx-xxx.log # 符号链接
+```
+
+`/var/log/containers/` 目录下的文件是指向 `/var/log/pods/` 的符号链接,文件名包含容器名和容器 ID,方便按容器名查找。
+
+#### 日志轮转(Log Rotation)
+
+kubelet 通过以下参数控制日志轮转(配置在 kubelet 的 `--container-log-*` 参数中):
+
+| 参数 | 说明 | 默认值 |
+|------|------|--------|
+| `--container-log-max-size` | 单个日志文件最大大小 | 10Mi |
+| `--container-log-max-files` | 每个容器保留的日志文件数 | 5 |
+
+在 `KubeletConfiguration` 中配置:
+
+```yaml
+apiVersion: kubelet.config.k8s.io/v1beta1
+kind: KubeletConfiguration
+containerLogMaxSize: "50Mi"
+containerLogMaxFiles: 10
+```
+
+> [!warning] 节点磁盘空间
+> 日志量大的应用(如高并发网关)可能快速占满磁盘。务必设置合理的轮转策略,并配合监控节点磁盘使用率(`node_filesystem_avail_bytes` 指标)。
+
+### 集群级方案
+
+#### 方案对比
+
+| 维度 | EFK(Elasticsearch + Fluentd + Kibana) | Loki + Promtail + Grafana |
+|------|------------------------------------------|---------------------------|
+| **存储模型** | 全文索引,存储成本高 | 标签索引 + 压缩块存储,成本低 |
+| **查询语言** | KQL / Lucene | LogQL(类 PromQL 语法) |
+| **全文搜索** | 原生支持,性能好 | 不支持原生全文搜索,需配合标签 |
+| **资源消耗** | 高(JVM 内存、磁盘 IO) | 低(Go 二进制,无全文索引) |
+| **与 Metrics 集成** | 需额外对接 Prometheus | 天然与 Prometheus/Grafana 生态融合 |
+| **适用场景** | 大规模日志分析、全文检索需求强 | 资源敏感、与 Prometheus 混合部署 |
+| **社区趋势** | 成熟但逐渐被替代 | CNCF 毕业项目,增长迅速 |
+
+#### EFK 架构
+
+```mermaid
+flowchart LR
+ A[Pod stdout/stderr] --> B[节点日志文件]
+ B --> C[Fluentd DaemonSet]
+ C --> D[Elasticsearch]
+ D --> E[Kibana]
+ E --> F[用户查询]
+```
+
+Fluentd 以 DaemonSet 形式部署在每个节点上,tail 节点日志文件并推送到 Elasticsearch。
+
+#### Loki + Promtail 架构
+
+```mermaid
+flowchart LR
+ A[Pod stdout/stderr] --> B[节点日志文件]
+ B --> C[Promtail DaemonSet]
+ C --> D[Loki]
+ D --> E[Grafana]
+ E --> F[用户查询]
+```
+
+Promtail 同样以 DaemonSet 部署,但它只提取标签(namespace、pod、container)和日志行,发送给 Loki 进行存储。Loki 的核心设计理念是「like Prometheus, but for logs」——只对标签建索引,日志内容以压缩块存储。
+
+### 结构化日志
+
+非结构化日志(纯文本)在大规模集群中难以高效查询。结构化日志以 JSON 格式输出,便于日志系统自动解析和过滤。
+
+#### JSON 日志输出示例(Go / zap)
+
+```go
+import "go.uber.org/zap"
+
+logger, _ := zap.NewProduction()
+defer logger.Sync()
+
+logger.Info("request processed",
+ zap.String("method", "GET"),
+ zap.String("path", "/api/v1/users"),
+ zap.Int("status", 200),
+ zap.Duration("latency", 150*time.Millisecond),
+ zap.String("request_id", "abc-123"),
+)
+```
+
+输出:
+
+```json
+{"level":"info","ts":1720340880.123,"msg":"request processed","method":"GET","path":"/api/v1/users","status":200,"latency":0.15,"request_id":"abc-123"}
+```
+
+#### 结构化日志最佳实践
+
+1. **统一字段命名**:团队内约定日志字段名(如 `request_id` 而非 `reqId` / `rid`),便于跨服务聚合。
+2. **必须包含的字段**:时间戳(`ts`)、日志级别(`level`)、消息(`msg`)、请求 ID(`request_id`)、服务名(`service`)。
+3. **避免在日志中输出敏感信息**:密码、Token、身份证号等必须脱敏。
+4. **日志级别控制**:生产环境默认 `info`,可通过环境变量动态调整为 `debug` 以便排查问题。
+
+> [!tip] kubectl 查看结构化日志
+> 使用 `kubectl logs` 结合 `jq` 可以快速过滤 JSON 日志:
+> ```bash
+> kubectl logs my-pod -c my-container | jq 'select(.level == "error")'
+> kubectl logs my-pod -c my-container | jq 'select(.latency > 0.5)'
+> ```
+
+---
+
+## 指标监控
+
+### Metrics Server
+
+Metrics Server 是 Kubernetes 内置的轻量级指标采集组件,提供 CPU 和内存使用数据,是 `kubectl top` 和 HPA(基于资源指标)的基础设施。
+
+#### 工作原理
+
+```mermaid
+flowchart LR
+ A[kubelet] -->|Summary API| B[Metrics Server]
+ B -->|Metrics API| C[kubectl top]
+ B -->|Metrics API| D[HPA Controller]
+```
+
+- 每个 kubelet 暴露 **Summary API**(`/stats/summary`),包含该节点上所有 Pod 的 CPU/内存使用数据。
+- Metrics Server 定期(默认 15 秒)从所有 kubelet 拉取数据,聚合后通过 **Metrics API**(`metrics.k8s.io`)暴露。
+
+#### 安装
+
+```bash
+kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
+```
+
+验证:
+
+```bash
+kubectl top nodes
+kubectl top pods -n default
+```
+
+#### 局限性
+
+- 只提供 CPU 和内存指标,不支持磁盘、网络等。
+- 数据只保留短期窗口(约 1 分钟),不支持历史查询。
+- 不支持自定义指标——需要 Prometheus 等外部系统。
+
+> [!info] Metrics Server vs Prometheus
+> Metrics Server 是 K8s 内置的「最小可用」指标方案,适合 `kubectl top` 和基于资源的 HPA。如果你需要更丰富的指标、历史数据和告警,必须部署 Prometheus。
+
+### Prometheus + Grafana
+
+Prometheus 是 CNCF 毕业项目,是 Kubernetes 生态中最主流的监控方案。Grafana 作为可视化层,提供丰富的仪表盘。
+
+#### 架构概览
+
+```mermaid
+flowchart LR
+ subgraph K8s 集群
+ A[应用 /export_metrics] -->|pull| B[Prometheus Server]
+ C[kube-state-metrics] -->|pull| B
+ D[Node Exporter] -->|pull| B
+ E[ServiceMonitor CRD] -.->|配置发现| B
+ end
+
+ B --> F[Grafana]
+ B --> G[Alertmanager]
+ G --> H[邮件 / Slack / 钉钉]
+ B --> I[长期存储
Thanos / VictoriaMetrics]
+```
+
+核心组件:
+
+- **Prometheus Server**:时序数据库 + 采集引擎,定期从 targets 拉取(pull)指标。
+- **kube-state-metrics**:将 K8s 对象状态(Pod、Deployment、Node 等)转换为 Prometheus 指标。
+- **Node Exporter**:采集节点级系统指标(CPU、内存、磁盘、网络)。
+- **ServiceMonitor CRD**:由 Prometheus Operator 提供,以声明式方式配置采集目标。
+- **Alertmanager**:告警路由、去重、分组、静默。
+
+#### ServiceMonitor 示例
+
+```yaml
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+ name: my-app
+ namespace: monitoring
+ labels:
+ release: prometheus # 匹配 Prometheus 实例的标签选择器
+spec:
+ namespaceSelector:
+ matchNames:
+ - default
+ selector:
+ matchLabels:
+ app: my-app # 匹配目标 Service 的标签
+ endpoints:
+ - port: metrics # Service 中定义的端口名
+ path: /metrics
+ interval: 15s
+```
+
+> [!info] ServiceMonitor 的工作方式
+> Prometheus Operator 会 watch ServiceMonitor 资源的变化,自动更新 Prometheus 的采集配置。运维人员不再需要手写 `prometheus.yml` 的 scrape 配置,实现了采集配置的 GitOps 管理。
+
+#### 常见 K8s 监控指标
+
+| 指标名称 | 含义 | 告警建议 |
+|----------|------|----------|
+| `container_cpu_usage_seconds_total` | 容器累计 CPU 使用时间 | 持续 > 80% requests 告警 |
+| `container_memory_working_set_bytes` | 容器内存工作集 | 接近 limits 时告警 |
+| `kube_pod_restart_total` | Pod 重启次数 | 短时间内重启 > 3 次告警 |
+| `kube_pod_status_phase` | Pod 状态(Pending/Running/Failed) | Pending > 5 分钟告警 |
+| `kube_node_status_condition` | 节点状态(Ready/NotReady) | NotReady 告警 |
+| `kube_deployment_status_replicas_available` | Deployment 可用副本数 | 小于期望副本数告警 |
+| `node_filesystem_avail_bytes` | 节点可用磁盘空间 | < 10% 剩余告警 |
+
+### HPA 自定义指标
+
+HPA 默认支持基于 CPU/Memory 的扩缩容,但实际场景中,基于请求速率(QPS)、队列长度、延迟等自定义指标更有意义。
+
+#### 前置条件
+
+1. 安装 Prometheus Adapter(将 Prometheus 指标转换为 K8s Custom Metrics API):
+ ```bash
+ helm install prometheus-adapter prometheus-community/prometheus-adapter \
+ --namespace monitoring \
+ --set prometheus.url=http://prometheus-server.monitoring.svc
+ ```
+
+2. 确认 Custom Metrics API 可用:
+ ```bash
+ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq .
+ ```
+
+#### HPA 配置示例(基于 HTTP 请求速率)
+
+```yaml
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: my-app-hpa
+ namespace: default
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: my-app
+ minReplicas: 2
+ maxReplicas: 20
+ metrics:
+ # 自定义指标:每 Pod 每秒请求数
+ - type: Pods
+ pods:
+ metric:
+ name: http_requests_per_second
+ target:
+ type: AverageValue
+ averageValue: "100" # 每个 Pod 目标 100 QPS
+ behavior:
+ scaleUp:
+ stabilizationWindowSeconds: 30
+ policies:
+ - type: Percent
+ value: 50
+ periodSeconds: 60
+ scaleDown:
+ stabilizationWindowSeconds: 300 # 缩容冷却 5 分钟,避免抖动
+ policies:
+ - type: Percent
+ value: 25
+ periodSeconds: 60
+```
+
+> [!tip] 扩缩容行为配置
+> `behavior` 字段(K8s 1.18+)允许精细控制扩缩容速度。建议扩容激进(快速应对流量高峰)、缩容保守(`stabilizationWindowSeconds` 设大,避免频繁抖动导致连接中断)。
+
+---
+
+## 事件(Events)
+
+Kubernetes Events 是集群中发生的重大状态变更记录,是排查问题的第一手信息。
+
+### 查看事件
+
+```bash
+# 当前 namespace 的事件,按时间排序
+kubectl get events --sort-by='.lastTimestamp'
+
+# 所有 namespace 的事件
+kubectl get events -A
+
+# 只看 Warning 级别事件
+kubectl get events --field-selector type=Warning
+
+# 查看特定 Pod 的事件
+kubectl describe pod my-pod
+```
+
+### 常见事件类型
+
+| Reason | 类型 | 含义 |
+|--------|------|------|
+| `Scheduled` | Normal | Pod 成功调度到节点 |
+| `Pulling` / `Pulled` | Normal | 正在拉取 / 已拉取镜像 |
+| `Created` / `Started` | Normal | 容器已创建 / 已启动 |
+| `Unhealthy` | Warning | 健康检查失败 |
+| `BackOff` | Warning | 重启退避(CrashLoopBackOff) |
+| `FailedScheduling` | Warning | 调度失败(资源不足、亲和性冲突等) |
+| `OOMKilling` | Warning | 容器因内存不足被杀死 |
+| `Evicted` | Warning | Pod 被驱逐(磁盘压力、内存压力等) |
+
+### 事件 TTL 与持久化
+
+> [!warning] Events 会被自动清理
+> 默认情况下,Events 的 TTL(Time To Live)为 **1 小时**。这意味着如果一个 Pod 在 2 小时前 OOMKilled,你通过 `kubectl describe pod` 看不到相关事件。
+
+解决方法:
+
+1. **调整 TTL**(K8s 1.19+):
+ ```bash
+ # 在 kube-apiserver 中设置
+ --event-ttl=24h
+ ```
+
+2. **持久化到日志系统**:将 Events 采集到 Elasticsearch/Loki 等系统中长期保存。
+ - 使用 kube-state-metrics 的 `kube_event_*` 指标采集到 Prometheus。
+ - 使用专门的 Events 采集工具(如 `event-exporter`)推送到日志系统。
+
+3. **event-exporter 示例**:
+ ```yaml
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: event-exporter
+ namespace: monitoring
+ spec:
+ template:
+ spec:
+ containers:
+ - name: event-exporter
+ image: ghcr.io/resmoio/kubernetes-event-exporter:latest
+ args:
+ - -conf=/data/config.yaml
+ volumeMounts:
+ - name: config
+ mountPath: /data
+ volumes:
+ - name: config
+ configMap:
+ name: event-exporter-config
+ ```
+
+---
+
+## 常见陷阱与最佳实践
+
+### 陷阱一:日志量爆炸
+
+**问题**:高并发服务的 debug 日志未关闭,一天产生几十 GB 日志,磁盘告警或 Elasticsearch 索引膨胀。
+
+**解决方案**:
+- 生产环境默认 `info` 级别,通过环境变量或配置中心动态调整。
+- 使用 Fluentd/Promtail 的 filter 功能丢弃无用日志(如 kube-system 的健康检查日志)。
+- 设置 Elasticsearch 的 ILM(Index Lifecycle Management)策略,自动过期删除旧索引。
+
+```yaml
+# Fluentd filter 示例:丢弃 kube-system 命名空间的日志
+
+ @type grep
+
+ key $.kubernetes.namespace_name
+ pattern /^kube-system$/
+
+
+```
+
+### 陷阱二:探针超时与应用启动时间不匹配
+
+**问题**:Java 应用启动需要 60 秒,但 livenessProbe 的 `initialDelaySeconds` 只设了 30 秒,导致每次重启都进入 CrashLoopBackOff。
+
+**解决方案**:使用 startupProbe 代替盲目增大 `initialDelaySeconds`(详见探针设计原则一)。
+
+### 陷阱三:监控指标标签爆炸
+
+**问题**:在 Prometheus 指标中使用 `user_id`、`request_path` 等高基数(high cardinality)标签,导致时间序列数暴涨,Prometheus 内存 OOM。
+
+**解决方案**:
+- 高基数字段(如 request_id、user_id)放到日志中,不作为指标标签。
+- `request_path` 应归一化(如 `/api/v1/users/123` → `/api/v1/users/:id`)。
+- 使用 Prometheus 的 `metric_relabel_configs` 丢弃不需要的标签。
+
+```yaml
+# Prometheus 配置:限制标签基数
+metric_relabel_configs:
+ - source_labels: [__name__]
+ regex: "container_.*"
+ action: keep
+ - regex: "pod_name"
+ action: labeldrop
+```
+
+### 陷阱四:readinessProbe 失败但 livenessProbe 正常
+
+**问题**:readinessProbe 依赖 Redis,Redis 短暂不可达,Pod 被从 Endpoints 摘除。但 livenessProbe 正常,Pod 不会被重启。流量被摘除后 Pod 中积压的请求超时,用户报错。
+
+**解决方案**:
+- readinessProbe 的依赖检查要设置合理的超时(如 2 秒内无响应即判定失败)。
+- 在应用内部实现熔断机制,不要完全依赖 K8s 探针。
+- 确保 readinessProbe 恢复时,Pod 能快速重新加入 Endpoints(`successThreshold: 1`)。
+
+### 陷阱五:kubectl top 数据不准
+
+**问题**:`kubectl top pods` 显示的 CPU 使用率为 0 或与实际不符。
+
+**原因**:
+- Metrics Server 启动后需要约 1-2 分钟才能采集到数据。
+- 容器的 CPU request 未设置,百分比无法计算。
+- Metrics Server 的 `--kubelet-insecure-tls` 未配置导致与 kubelet 通信失败。
+
+**解决方案**:
+
+```bash
+# 检查 Metrics Server 是否正常运行
+kubectl get deployment metrics-server -n kube-system
+
+# 检查是否有错误日志
+kubectl logs -n kube-system deployment/metrics-server
+
+# 使用 --resource 选项查看原始数值而非百分比
+kubectl top pods --containers
+```
+
+### 最佳实践汇总
+
+| 领域 | 实践 |
+|------|------|
+| 日志 | stdout 输出、JSON 结构化、统一字段命名、合理轮转 |
+| 探针 | startupProbe 兜底慢启动、liveness 不查依赖、readiness 做适度依赖检查 |
+| 监控 | Prometheus + Grafana 标准栈、ServiceMonitor 声明式配置 |
+| 告警 | 分级告警(Warning/Critical)、去重分组、值班轮转 |
+| 指标 | 避免高基数标签、区分 RED(Rate/Error/Duration)指标与 USE(Utilization/Saturation/Errors)指标 |
+| 事件 | 配置持久化、重要事件告警 |
+
+---
+
+## 延伸阅读
+
+- [[k8s-03-pod]] — Pod 生命周期、调度与资源管理
+- [[k8s-02-architecture]] — K8s 控制平面与节点组件架构
+- [Kubernetes 官方文档 - Logging Architecture](https://kubernetes.io/docs/concepts/cluster-administration/logging/)
+- [Kubernetes 官方文档 - Configure Liveness, Readiness and Startup Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)
+- [Prometheus Operator 文档](https://prometheus-operator.dev/)
+- [Grafana Loki 官方文档](https://grafana.com/docs/loki/latest/)
+- [Google SRE Book - Monitoring Distributed Systems](https://sre.google/sre-book/practical-alerting/)
diff --git a/weekly/daily/2026-07-07-daily.md b/weekly/daily/2026-07-07-daily.md
new file mode 100644
index 0000000..b6c743f
--- /dev/null
+++ b/weekly/daily/2026-07-07-daily.md
@@ -0,0 +1,37 @@
+---
+tags: [daily]
+create time: 2026-07-07 15:15
+---
+
+# 2026-07-07 日报
+
+## 今日完成
+- [x] 配置开发环境:brew、Claude Code、Fenno API、Goenv、Go、Nvm、Node.js、pnpm、tsc
+- [x] 安装远程 SSH 连接工具 electerm
+- [x] 配置 Git SSH、GitHub CLI
+- [x] 配置 Obsidian 及相关插件(Better Export PDF、Claudian、Git、Style Settings)
+- [x] 配置 Obsidian 主题:Phycat
+
+## GitHub Issues
+> 当日无关联 Issue
+
+
+
+## 卡点 / 阻塞
+> 当前无卡点
+
+
+
+## 明日计划
+- [ ] 参与部署 K8s
+
+## 备注
diff --git a/weekly/daily/template.md b/weekly/daily/template.md
new file mode 100644
index 0000000..729916f
--- /dev/null
+++ b/weekly/daily/template.md
@@ -0,0 +1,33 @@
+---
+tags: [daily]
+create time:
+---
+
+# YYYY-MM-DD 日报
+
+## 今日完成
+- [ ]
+
+## GitHub Issues
+> 当日无关联 Issue
+
+
+
+## 卡点 / 阻塞
+> 当前无卡点
+
+
+
+## 明日计划
+- [ ]
+
+## 备注