# 03-05-k3s 本地存储目录控制(local-path-config / hostPath / local PV) > 这篇只讲一件事:在 K3s 里,如何控制“数据最终落到宿主机哪个目录”。 ## 先说结论 - **方法一(推荐)`local-path-config`**:给 `local-path` 动态供给指定“基路径”。 适合大多数场景;PVC 仍可自动创建,但要通过 PV 反查落地目录。 - **方法二 `hostPath`**:在 Pod 里直接写宿主机目录。 目录由你显式指定,不需要再查 provisioner 生成路径。 - **方法三 `local PV`**:可精确绑定“节点 + 目录”,但维护成本更高。 本文只做介绍,不展开实操。 ## 前置条件 - K3s 已安装且 `local-path-provisioner` 正常运行 - 能执行 `kubectl` - `kubectl get storageclass` 能看到 `local-path`(通常为 default) ## 方法一:`local-path-config` 指定“基路径”(重点) ### 1) 原理 `storageClassName: local-path` 并不会在 PVC 里写死宿主机目录。 它表示交给 `local-path-provisioner` 动态创建 PV,PV 的实际目录由 `local-path-config`(`config.json`)里配置的基路径决定,然后在基路径下自动生成子目录。 ### 2) 操作步骤 ```bash # 1. 备份当前配置 kubectl -n kube-system get configmap local-path-config -o yaml > /tmp/local-path-config.bak.yaml # 2. 编辑 config.json(把 paths 改成你希望的基路径) kubectl -n kube-system edit configmap local-path-config ``` 配置结构示意(请与现有 JSON 合并,不要盲目整段覆盖): ```json { "nodePathMap": [ { "node": "DEFAULT_PATH_FOR_NON_LISTED_NODES", "paths": ["/storage/storage"] }, { "node": "ylc61", "paths": ["/data/k3s-local-path"] } ] } ``` 说明: - `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 ``` ### 3) 用 demo 验证(PVC -> PV -> 节点 -> 落地目录) Demo 清单:[`ansible/files/local-path-demo/local-path-pvc-demo.yaml`](../ansible/files/local-path-demo/local-path-pvc-demo.yaml) > 该 demo 已包含 `nodeSelector` 固定节点(示例为 `ylc61`),使用前请按你的节点主机名修改。 ```bash kubectl apply -f ansible/files/local-path-demo/local-path-pvc-demo.yaml kubectl rollout status deploy/nginx-local-pvc-demo --timeout=180s ``` 按下面顺序查真实落地路径: ```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"}' # 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`(重状态、快照、备份)