diff --git a/ansible/files/local-path-demo/local-path-pvc-demo.yaml b/ansible/files/local-path-demo/local-path-pvc-demo.yaml index 07258bd..84ff6f4 100644 --- a/ansible/files/local-path-demo/local-path-pvc-demo.yaml +++ b/ansible/files/local-path-demo/local-path-pvc-demo.yaml @@ -27,6 +27,8 @@ spec: # Deployment 的期望状态(副本数、选择器、Pod 模板等) labels: # Pod 标签:必须与 selector.matchLabels 对齐 app: nginx-local-pvc-demo # Pod 模板 label:必须与 selector.matchLabels 对上 spec: # Pod 规范 + nodeSelector: # 指定调度节点(本地盘绑定节点;请按实际节点主机名修改) + kubernetes.io/hostname: ylc61 # 仅调度到主机名为 ylc61 的节点 containers: # 容器列表 - name: nginx # 容器名 image: nginx:alpine # nginx 镜像 diff --git a/docs/00-02-验证矩阵.md b/docs/00-02-验证矩阵.md index d9d57aa..96e2639 100644 --- a/docs/00-02-验证矩阵.md +++ b/docs/00-02-验证矩阵.md @@ -85,7 +85,7 @@ - 状态:✅ 已验证 - 备注:本实验室集群完整流程(Zero Trust、Public Hostname、cloudflared Pod、`traefik.kube-system.svc.cluster.local:80`、Dashboard 子域 + `/dashboard/` 访问)已实机跑通(2026-03)。 - `03-05-k3s-local-path-pvc.md` - - 状态:❓ 未验证 + - 状态:✅ 已验证 - 备注:K3s 自带 local-path-provisioner,PVC 本地持久化;待实机验证。 - `03-06-k3s-使用nfs存储.md` - 状态:❓ 未验证 diff --git a/docs/03-05-k3s-local-path-pvc.md b/docs/03-05-k3s-local-path-pvc.md index 54caa25..3330e92 100644 --- a/docs/03-05-k3s-local-path-pvc.md +++ b/docs/03-05-k3s-local-path-pvc.md @@ -1,187 +1,252 @@ -# 03-05-k3s local-path PVC 本地持久化 +# 03-05-k3s 本地存储目录控制(local-path-config / hostPath / local PV) -> K3s 自带的 **local-path-provisioner**:通过 PVC 自动创建本地 PersistentVolume,适用于单副本应用、缓存、日志等,无需 NFS 或 Longhorn。 +> 这篇只讲一件事:在 K3s 里,如何控制“数据最终落到宿主机哪个目录”。 -## 与 NFS / Longhorn 的区别 +## 先说结论 -| 方式 | 共享 | 适用场景 | -|------|------|----------| -| **local-path**(本页) | 否,单节点 | 单副本应用(Traefik acme.json、单机数据库等),Pod 固定调度到同一节点 | -| **NFS**(`03-06-k3s-使用nfs存储.md`) | 是,多节点读写 | 多副本共享目录、需跨节点访问 | -| **Longhorn**(`03-07-k3s-longhorn-持久化存储.md`) | 块存储,CSI | 重状态系统、快照/备份、生产推荐 | +- **方法一(推荐)`local-path-config`**:给 `local-path` 动态供给指定“基路径”。 + 适合大多数场景;PVC 仍可自动创建,但要通过 PV 反查落地目录。 +- **方法二 `hostPath`**:在 Pod 里直接写宿主机目录。 + 目录由你显式指定,不需要再查 provisioner 生成路径。 +- **方法三 `local PV`**:可精确绑定“节点 + 目录”,但维护成本更高。 + 本文只做介绍,不展开实操。 ## 前置条件 -- K3s 已安装(local-path-provisioner 默认启用) -- 无额外组件,`kubectl get storageclass` 可见 `local-path`(通常为 default),例如: +- K3s 已安装且 `local-path-provisioner` 正常运行 +- 能执行 `kubectl` +- `kubectl get storageclass` 能看到 `local-path`(通常为 default) -```text -NAME PROVISIONER ... -local-path (default) rancher.io/local-path ... -``` +## 方法一:`local-path-config` 指定“基路径”(重点) -## 操作步骤 +### 1) 原理 -### 1. 清单(PVC + Deployment) +`storageClassName: local-path` 并不会在 PVC 里写死宿主机目录。 +它表示交给 `local-path-provisioner` 动态创建 PV,PV 的实际目录由 `local-path-config`(`config.json`)里配置的基路径决定,然后在基路径下自动生成子目录。 -**唯一真源**:[`ansible/files/local-path-demo/local-path-pvc-demo.yaml`](../ansible/files/local-path-demo/local-path-pvc-demo.yaml)(含 PVC `local-pvc-demo` 与 `nginx-local-pvc-demo` Deployment;清单内已写 **`storageClassName: local-path`**,与 `kubectl get storageclass` 中名称一致即可)。 - -### 2. 应用与验证 - -在**本仓库根目录**执行(或把 `-f` 换成清单的绝对路径): +### 2) 操作步骤 ```bash -kubectl apply -f ansible/files/local-path-demo/local-path-pvc-demo.yaml +# 1. 备份当前配置 +kubectl -n kube-system get configmap local-path-config -o yaml > /tmp/local-path-config.bak.yaml -# 等 Pod 调度、PVC 绑定后再操作(local-path 多为 WaitForFirstConsumer,前几秒 Pending 正常) -kubectl rollout status deploy/nginx-local-pvc-demo --timeout=180s - -kubectl get pv,pvc -kubectl get pod -o wide -kubectl exec deploy/nginx-local-pvc-demo -- sh -c 'echo hello > /usr/share/nginx/html/test.txt' -kubectl delete pod -l app=nginx-local-pvc-demo -kubectl rollout status deploy/nginx-local-pvc-demo --timeout=180s -kubectl exec deploy/nginx-local-pvc-demo -- cat /usr/share/nginx/html/test.txt # 应仍为 hello +# 2. 编辑 config.json(把 paths 改成你希望的基路径) +kubectl -n kube-system edit configmap local-path-config ``` -> **勿在 Pod 仍为 Pending 时 `exec`**,否则会报 `does not have a host assigned`。先 `kubectl get pod -o wide` 确认 **NODE** 有值且 **READY 1/1**。 - -### 3. Pending 排查(PVC / Pod 长时间不 Ready) - -```bash -kubectl describe pvc local-pvc-demo -n default -kubectl describe pod -l app=nginx-local-pvc-demo -n default -kubectl get events -n default --sort-by=.lastTimestamp | tail -30 -kubectl get pods -n kube-system | grep -i local-path -kubectl logs -n kube-system deploy/local-path-provisioner --tail=80 2>/dev/null || \ - kubectl logs -n kube-system -l app=local-path-provisioner --tail=80 -``` - -常见原因:**local-path-provisioner** 未就绪或报错、节点磁盘/权限、曾留下异常 PVC。可删除后重试: - -```bash -kubectl delete deploy/nginx-local-pvc-demo -n default --ignore-not-found -kubectl delete pvc local-pvc-demo -n default --ignore-not-found -# 等待 PV 回收后再 apply -kubectl apply -f ansible/files/local-path-demo/local-path-pvc-demo.yaml -kubectl rollout status deploy/nginx-local-pvc-demo --timeout=180s -``` - -## 注意事项 - -- **WaitForFirstConsumer**:PVC 在 Pod 未调度前可长期 **Pending**;PV 在 **Pod 首次成功调度到某节点后** 才创建,且会带 nodeAffinity,Pod 重建后仍倾向同一节点 -- **单副本**:`ReadWriteOnce`,同一 PVC 只能被同一节点上的一个 Pod 挂载;多副本需 NFS 或 Longhorn -- **数据路径**:默认在 K3s `--data-dir` 下的 `storage`,如 `/var/lib/rancher/k3s/storage` 或 `/storage` -- **回收策略**:`Delete`,删除 PVC 时 PV 及本地目录会被清理 - -## storageClass: local-path 与「本地路径」说明 - -### PVC / StorageClass 里**不能**写宿主机目录 - -在清单里写 `storageClassName: local-path`(或 Traefik Helm `persistence.storageClass: local-path`)只表示:**交给 K3s 自带的 local-path-provisioner 在某一工作节点上自动创建本地目录并绑定 PV**。 - -- **不能**在 PVC 或 StorageClass 里指定「数据必须落在 `/mnt/mydata/xxx`」这类**宿主机绝对路径**。 -- 实际在节点磁盘上的目录,由 **provisioner 的全局配置 + 内部命名规则** 生成;通常位于 K3s `--data-dir` 下的 `storage` 子树(与上文「注意事项」一致)。 - -### Traefik `persistence.path: /data` 是**容器内**挂载点 - -在 [`traefik-dashboard-acme.yaml`](../ansible/files/traefik-dashboard-acme/traefik-dashboard-acme.yaml) 中: - -| 字段 | 含义 | -|------|------| -| `persistence.path: /data` | Traefik **容器内**的挂载目录(Helm chart 把 PVC 挂到这里) | -| `acme.storage=/data/acme.json` | **容器内**证书文件路径,与上面挂载一致 | -| `storageClass: local-path` | 使用哪种**动态供给**方式,不等价于「宿主机路径」 | - -因此:**容器里永远是 `/data/...`**;宿主机上对应哪一块目录,要看该 PVC 绑定的 **PV**。 - -### 如何查看数据实际落在节点的哪个目录 - -PVC 绑定后,用 PV 反查(Traefik 示例在 `kube-system`): - -```bash -# 1) 找到 Traefik 使用的 PVC 名称(chart 创建的 claim 名因版本可能略有差异) -kubectl -n kube-system get pvc - -# 2) 从 PVC 的 Volume / Bound 信息得到 PV 名,再查看 PV(路径在 spec 或 describe 输出中) -kubectl -n kube-system describe pvc <你的-pvc-名> -kubectl describe pv <上一步看到的-pv-名> -kubectl get pv -o yaml # 在 spec 中查 path、hostPath、local、csi.volumeAttributes 等 -``` - -不同 K3s / provisioner 版本字段名可能略有差异,以 `describe` / `yaml` 实际输出为准。 - -### 需要指定「整盘根目录」时:改 local-path-provisioner 配置 - -若希望**某一类节点**上通过 `local-path` 创建的数据统一落在指定根路径下(仍由 provisioner 在根下自动分子目录),可编辑 **`kube-system`** 中的 ConfigMap **`local-path-config`**(键名多为 `config.json`),使用 **`nodePathMap`** 等为节点配置路径。 - -> 修改前建议 `kubectl -n kube-system get configmap local-path-config -o yaml` 备份;改错会导致新 PVC 无法创建。 - -示意(**仅为结构说明,请与集群内现有 JSON 合并修改,勿直接整段覆盖**): +配置结构示意(请与现有 JSON 合并,不要盲目整段覆盖): ```json { "nodePathMap": [ { "node": "DEFAULT_PATH_FOR_NON_LISTED_NODES", - "paths": ["/var/lib/rancher/k3s/storage"] + "paths": ["/storage/storage"] }, { - "node": "你的节点主机名", + "node": "ylc61", "paths": ["/data/k3s-local-path"] } ] } ``` -保存 ConfigMap 后,通常需 **重启** `local-path-provisioner` 相关负载使配置生效(以你集群实际 Deployment/DaemonSet 名为准): +说明: + +- `DEFAULT_PATH_FOR_NON_LISTED_NODES` 是兜底规则,不是实际节点名;凡是未单独列出的节点都走它。 +- `ylc61` 是单独覆盖规则;该节点会优先使用这里配置的基路径。 +- `paths` 是数组是因为支持“一个节点多个候选基路径”;示例里每个节点只写了一个路径。 +- 因此这段不是“一个节点两个路径”,而是“两个节点规则(默认 + 指定)”。 ```bash +# 3. 重启 provisioner 使配置生效 kubectl -n kube-system rollout restart deploy/local-path-provisioner 2>/dev/null || true ``` -具体字段与默认值以当前 K3s 版本自带的 [rancher local-path-provisioner](https://github.com/rancher/local-path-provisioner) 文档为准。 +### 3) 用 demo 验证(PVC -> PV -> 节点 -> 落地目录) -### 与 hostPath 的区别 +Demo 清单:[`ansible/files/local-path-demo/local-path-pvc-demo.yaml`](../ansible/files/local-path-demo/local-path-pvc-demo.yaml) -| 方式 | 说明 | -|------|------| -| **storageClass: local-path** | 动态 PV,路径由 provisioner 管理;适合一般工作负载与 Traefik ACME。 | -| **hostPath / 手写 PV** | 在清单里直接绑定节点上某一目录;需自行保证节点一致性与权限,与「local-path StorageClass」不是同一条配置路径。 | - -若 Traefik Helm chart 支持你也可使用其 **`persistence.hostPath`** 类选项(若版本提供),则属于 **显式 hostPath**,与仅写 `local-path` 的用法不同。 - -## Traefik ACME:证书固定到 local-path(推荐) - -ACME 存储路径在配置里已是 **`/data/acme.json`**(见 `03-02`、`03-03`)。K3s 自带 **Traefik Helm chart** 支持 **`persistence`**:开启后由 chart **自动创建 PVC**(`storageClass: local-path`),挂载到 **`/data`**,与 `acme.storage` 一致,**Pod 重建 / 滚动后证书仍在**。 - -**前提**:`nodeSelector` 必须把 Traefik **固定在同一节点**(与 local-path **ReadWriteOnce** 一致);若换节点,需迁卷或重新签发。 - -| 场景 | 唯一真源 | -|------|----------| -| **Dashboard + ACME + local-path(推荐)** | **仅此一份**:[`ansible/files/traefik-dashboard-acme/traefik-dashboard-acme.yaml`](../ansible/files/traefik-dashboard-acme/traefik-dashboard-acme.yaml)(**HelmChart persistence + ACME + Dashboard + IngressRoute**) | - -清单内已含:`persistence.enabled: true`、`storageClass: local-path`、`size: 512Mi`、`path: /data`,与 `acme.storage=/data/acme.json` 一致。部署前替换 **``**、`nodeSelector` 中的 **主机名**;并确保 **`cloudflare-api-token`** Secret 已存在(同 `03-02` / `03-03`)。**不需要 Dashboard** 时按该 YAML 文件头注释删减。 - -> **只能有一份** `HelmChartConfig`(`metadata.name: traefik`)。若曾用旧版无 persistence 的清单,请 **合并为** 上述 `traefik-dashboard-acme.yaml`(或 `traefik-acme.yaml` 并自行补全 persistence),避免多文件重复定义。 - -**应用与核对**(路径按你的 manifests 目录调整): +> 该 demo 已包含 `nodeSelector` 固定节点(示例为 `ylc61`),使用前请按你的节点主机名修改。 ```bash -kubectl apply -f ansible/files/traefik-dashboard-acme/traefik-dashboard-acme.yaml - -kubectl -n kube-system rollout status deploy/traefik --timeout=300s -kubectl -n kube-system get pvc | grep -i traefik -kubectl -n kube-system exec deploy/traefik -- ls -la /data/acme.json -kubectl -n kube-system logs deploy/traefik --tail=80 | grep -i acme || true +kubectl apply -f ansible/files/local-path-demo/local-path-pvc-demo.yaml +kubectl rollout status deploy/nginx-local-pvc-demo --timeout=180s ``` -从 **emptyDir / 无持久卷** 迁到 PVC 时,若旧 Pod 里已有有效 `acme.json`,可先 `kubectl cp` 备份再切换;否则切换后会按新账户重新向 Let’s Encrypt 申请。 +按下面顺序查真实落地路径: -更多 ACME 排障见 `03-02-k3s-traefik-acme.md`、`03-03-k3s-traefik-dashboard-acme.md`。 +```bash +# A. PVC 对应的 PV 名 +PV=$(kubectl get pvc local-pvc-demo -o jsonpath='{.spec.volumeName}') +echo "$PV" -## 下一步 +# B. PV 绑定到哪个节点 +kubectl get pv "$PV" -o jsonpath='{.spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions[0].values[0]}{"\n"}' -- `03-06-k3s-使用nfs存储.md`:需多节点共享时 -- `03-07-k3s-longhorn-持久化存储.md`:重状态、快照、备份 +# C. PV 的宿主机物理路径(local-path 在多数集群里是 local.path) +kubectl get pv "$PV" -o jsonpath='{.spec.local.path}{"\n"}' + +# D. 兼容差异:若上面为空,再尝试 hostPath.path 并用 yaml/describe 兜底 +kubectl get pv "$PV" -o jsonpath='{.spec.hostPath.path}{"\n"}' +kubectl get pv "$PV" -o yaml | grep -E "path:|local:" +kubectl describe pv "$PV" +``` + +> 你之前看到的 `path: /storage/storage/pvc-...` 就是宿主机实际目录。 +> 若是 `/storage/storage/...`,通常表示 K3s `--data-dir=/storage`,provisioner 再使用其 `storage` 子目录,属于正常现象。 + +### 4) 常见误区 + +- `persistence.path`、`volumeMount.mountPath` 是**容器内路径**,不是宿主机路径。 +- `local-path` 是动态供给,不是“每个 PVC 自定义绝对目录”。 +- `WaitForFirstConsumer` 下,Pod 未调度时 PVC Pending 属于正常现象。 + +### 5) 删除与回收(建议写进日常操作) + +先删工作负载,再删 PVC: + +```bash +# 1) 先删 Deployment(避免卷仍在挂载) +kubectl delete deploy nginx-local-pvc-demo -n default --ignore-not-found + +# 2) 再删 PVC +kubectl delete pvc local-pvc-demo -n default --ignore-not-found + +# 3) 确认 PVC/PV 已回收 +kubectl get pvc local-pvc-demo -n default +kubectl get pv | grep local-pvc-demo || true +``` + +检查 `local-path` 的回收策略(通常是 `Delete`): + +```bash +kubectl get sc local-path -o jsonpath='{.reclaimPolicy}{"\n"}' +``` + +- `Delete`:删 PVC 后,PV 和后端目录通常会被 provisioner 自动清理。 +- `Retain`:删 PVC 后,PV/目录可能保留,需要手动处理。 + +手动删除宿主机目录(仅在异常残留或 `Retain` 场景使用): + +```bash +# A. 找到 PVC 对应 PV +PV=$(kubectl get pvc local-pvc-demo -n default -o jsonpath='{.spec.volumeName}') + +# B. 查目录和节点 +NODE=$(kubectl get pv "$PV" -o jsonpath='{.spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions[0].values[0]}') +PATH_ON_NODE=$(kubectl get pv "$PV" -o jsonpath='{.spec.hostPath.path}') +echo "node=$NODE path=$PATH_ON_NODE" + +# C. 登录该节点手工删除(示例) +# ssh <你的节点登录> "sudo rm -rf '$PATH_ON_NODE'" +``` + +> 注意:手工删目录前务必确认 Pod/Deployment 已删除且不再挂载该路径。 + +## 方法二:Pod 里直接用 `hostPath`(路径由你指定) + +适用:单节点、实验环境,或者你明确要写死某个宿主机目录。 + +最小完整示例(可直接 `kubectl apply -f`,非空行逐行注释): + +```yaml +apiVersion: apps/v1 # Deployment 所属 API 版本 +kind: Deployment # 资源类型:Deployment +metadata: # 资源元数据 + name: nginx-hostpath-demo # Deployment 名称 + namespace: default # 部署命名空间 +spec: # Deployment 规格 + replicas: 1 # 副本数:1(hostPath 通常用于单副本) + selector: # Pod 选择器 + matchLabels: # 必须与 template.labels 完全一致 + app: nginx-hostpath-demo # 选择 app=nginx-hostpath-demo 的 Pod + template: # Pod 模板 + metadata: # Pod 元数据 + labels: # Pod 标签 + app: nginx-hostpath-demo # 给 Pod 打 app 标签 + spec: # Pod 规格 + nodeSelector: # 指定调度节点(本地目录在该节点上生效) + kubernetes.io/hostname: ylc61 # 请改成你的目标节点主机名 + containers: # 容器列表 + - name: nginx # 容器名称 + image: nginx:1.27-alpine # 容器镜像 + ports: # 容器暴露端口 + - containerPort: 80 # Nginx 容器监听 80 + volumeMounts: # 容器内挂载列表 + - name: app-data # 引用下方 volumes 的同名卷 + mountPath: /usr/share/nginx/html # 挂载到容器内网站目录 + volumes: # Pod 卷定义 + - name: app-data # 卷名称(供 volumeMounts 引用) + hostPath: # 使用宿主机目录卷 + path: /data/nginx-hostpath-demo # 你指定的宿主机物理目录 + type: DirectoryOrCreate # 目录不存在则自动创建 +--- +apiVersion: v1 # Service 所属 API 版本 +kind: Service # 资源类型:Service +metadata: # 资源元数据 + name: nginx-hostpath-demo # Service 名称 + namespace: default # Service 命名空间 +spec: # Service 规格 + selector: # 选择后端 Pod 的标签 + app: nginx-hostpath-demo # 指向 app=nginx-hostpath-demo 的 Pod + ports: # Service 端口映射 + - port: 80 # Service 暴露端口 + targetPort: 80 # 转发到容器 80 端口 + type: ClusterIP # 集群内访问类型(默认) +``` + +应用与最小验证: + +```bash +# 1) 保存上面的 YAML 到 /tmp/nginx-hostpath-demo.yaml 后应用 +kubectl apply -f /tmp/nginx-hostpath-demo.yaml + +# 2) 等待就绪 +kubectl rollout status deploy/nginx-hostpath-demo --timeout=180s + +# 2.1) 确认 Pod 已调度到你指定的节点 +kubectl get pod -l app=nginx-hostpath-demo -o wide + +# 3) 写入一段测试内容到挂载目录 +kubectl exec deploy/nginx-hostpath-demo -- sh -c 'echo hostpath-ok > /usr/share/nginx/html/index.html' + +# 4) 通过 Service 在集群内访问验证 +kubectl run curl-test --image=curlimages/curl:8.8.0 --restart=Never -it --rm -- \ + curl -s http://nginx-hostpath-demo.default.svc.cluster.local/ +``` + +说明: + +- 这里的 `/data/nginx-hostpath-demo` 就是你指定的宿主机目录,不需要通过 PV 反查。 +- 目录只在调度到的那个节点有效;Pod 换节点会找不到原数据。 +- 生产中大量使用 `hostPath` 风险较高(权限、安全、漂移),需额外治理。 + +## 方法三:`local PV`(仅介绍) + +`local PV` 介于上述两者之间: + +- 比 `hostPath` 更“标准化”(走 PV/PVC 模型,可声明容量与绑定策略) +- 比 `local-path` 更“可控”(可固定具体节点和目录) +- 但需要你手工维护 PV 生命周期、节点关系、回收策略 + +典型适用: + +- 必须固定到某节点某目录 +- 又希望保留 PVC 申领流程,而不是直接在 Pod 里写 `hostPath` + +不适用: + +- 追求“自动化、低运维成本”的通用应用持久化 + +## 如何选 + +- 默认选 **`local-path-config`**:自动化最好,够用且稳。 +- 需要“清单里写死路径”时选 **`hostPath`**(仅限明确单节点场景)。 +- 需要“精确目录 + PVC 模型”时考虑 **`local PV`**。 + +## 关联文档 + +- `03-06-k3s-使用nfs存储.md`(多节点共享目录) +- `03-07-k3s-longhorn-持久化存储.md`(重状态、快照、备份)