# 03-07-k3s Longhorn 持久化存储(4 节点实验环境) > 本实验:**K3s 四节点集群**、**每节点 8 GiB 内存**(与下文「系统资源」建议档一致)、**磁盘基线为约 10G 系统盘 + 32G 数据盘**,**`/storage`** 必须挂在独立数据盘上(与 `/` 不同设备),详见 `00-04-部署环境说明.md`。**没有 NFS**,用 Longhorn 做集群内动态块存储;若后续要部署 GitLab 等重状态系统,可在此基础上接 PVC。副本数可按实验目标在「省空间」与「模拟高可用」之间取舍。 --- ## 磁盘与 `/storage` 前提 Longhorn 与 K3s 的 `containerd`/镜像、local-path 都会大量占用 **`k3s_data_dir`(本仓库默认 `/storage`)**。若 `/storage` 只是根分区上的普通目录,控制节点易出现 **DiskPressure / Evicted**。 - **请先阅读**:[`00-04-部署环境说明.md`](00-04-部署环境说明.md)(四节点统一拓扑、自检命令、推荐 playbook 顺序)。 - **自检**(每台节点):`mountpoint -q /storage && findmnt -n -o SOURCE /` 与 `findmnt -n -o SOURCE /storage` 输出须**不同**。 - **Ansible**:`k3s-init-and-install.yml` 在 `k3s_verify_storage_mount: true`(`group_vars/all.yml` 默认)时会在安装前校验上述条件;可选先跑 `k3s-prepare-storage.yml` 准备第二块盘,见 `01-06-节点初始化-ansible-实践.md`。 - Longhorn 数据目录建议为 **`/storage/longhorn`**(与 Helm `values-lab.yaml` 一致),勿与系统盘混用。 **容量与副本数**:每节点数据盘约 **32G** 时,`defaultReplicaCount` 为 **2 或 3** 会使同一份逻辑卷在集群内占用 **多倍物理空间**(各副本落在不同节点上各占一份),且 Longhorn 元数据与系统组件仍有开销;实验环境可先用副本 **1**,要演练跨节点冗余再调高并预留磁盘。 --- ## 为什么要用 Longhorn(而不是 hostPath / local-path / 容器文件系统) - **容器文件系统**:Pod 重建即丢,基本不可用 - **hostPath 固定目录**:能落盘,但和调度强绑定,迁移/扩缩容/备份都更麻烦 - **local-path PVC**(见 `03-05-k3s-local-path-pvc.md`):K3s 自带,单副本够用;无快照/备份,多副本需 NFS 或 Longhorn - **Longhorn(CSI 块存储)**:对 K8s 来说是标准 PVC;即使你只设 **副本数=1**,也能获得: - 统一的 PVC 管理与回收策略 - 快照(snapshot) - 备份(backup target,可推到对象存储) > 重要:**副本数=1** 时,卷只在集群里有一份数据,**节点故障仍可能丢卷**。四节点集群里若要演练节点级容灾,把卷的副本数调到 2 或 3,并配合备份到外部介质。 --- ## 前置条件(CentOS) 在**每一台**计划参与 Longhorn 的节点上安装依赖(本实验为 **4 节点**,通常四台都要装;若你明确只让部分节点承担存储,也至少要保证这些节点已装齐): ```bash sudo yum install -y iscsi-initiator-utils nfs-utils sudo systemctl enable --now iscsid ``` 准备数据盘目录(你已有专用盘挂载到 `/storage`,建议给 Longhorn 单独目录): ```bash sudo mkdir -p /storage/longhorn sudo chmod 700 /storage/longhorn ``` --- ## 系统资源(CPU / 内存) Longhorn **没有**在文档里给一张适用于所有场景的「每节点必须 ≥X 核、≥Y GB 内存」的固定表。安装前置、磁盘与网络等见官方 [Installation requirements(v1.7.2)](https://longhorn.io/docs/archives/1.7.2/deploy/install/) 与 [Best practices - Minimum recommended hardware](https://longhorn.io/docs/archives/1.7.2/best-practices/#minimum-recommended-hardware)(其中对**磁盘** IOPS/吞吐、SSD 建议、节点间带宽等描述较多)。下面按「能规划容量」和「能对照官方默认值」两部分说明。 ### CPU(与官方默认机制相关) - 使用默认的 **V1 数据引擎**时,全局设置 **[Guaranteed Instance Manager CPU](https://longhorn.io/docs/archives/1.7.2/references/settings/#guaranteed-instance-manager-cpu)** 默认值为 **12**:表示为每个 **Instance Manager** Pod 预留该节点 **可分配 CPU(allocatable)的 12%**,用于保障引擎与副本稳定(详见 [Best practices - V1 Data Engine](https://longhorn.io/docs/archives/1.7.2/best-practices/#v1-data-engine))。 - 节点 **CPU 越小**,在其它负载不变时,Longhorn 与业务争抢余量的压力越大;卷多、重建/备份繁忙时更明显。 **实验环境规划(在 K3s 与系统开销之外,仍能跑少量业务)**: | 档位 | 每节点 CPU(约) | 说明 | | --- | --- | --- | | 建议 | **≥ 4 vCPU** | 与常见实验/小型业务负载较匹配 | | 最低(仅验证组件) | **2 vCPU** | 可能频繁触顶、调度排队;与 Instance Manager 预留叠加后余量偏紧 | ### 内存 - 除 **longhorn-manager、CSI、engine-image** 等常驻组件外,**Instance Manager** 等会随卷、I/O、**副本数**、备份与重建任务变化;官方不以「每节点固定 GB」一条公式概括。 - 若启用 **V2 数据引擎**,节点还需按官方说明预留 **Huge Pages** 等(见 [V2 前置条件](https://longhorn.io/docs/archives/1.7.2/v2-data-engine/prerequisites/)),与 V1 场景不同,勿与下表混用。 **实验环境规划(V1、常规用途)**: | 档位 | 每节点内存(约) | 说明 | | --- | --- | --- | | 建议 | **≥ 8 GiB** | 留给 OS、kubelet、系统 Pod、Longhorn、业务 | | 偏低 | **4 GiB** | 仅适合极简演示;卷与业务稍多时 **OOM 风险**高 | **ARM64、整机只有 4 GiB 内存的设备**:Longhorn **支持** ARM64(见官方 [Best practices - Architecture](https://longhorn.io/docs/archives/1.7.2/best-practices/#architecture)),限制主要来自 **总内存与 CPU 余量**,不是「ARM 能不能装」。在 **4 GiB 整机**上还要跑 K3s 系统组件 + Longhorn DaemonSet/CSI/Instance Manager 时,**不建议**把该节点当作 Longhorn 的**常规存储节点**(更不适合再叠加重状态业务)。更稳妥的做法是:存储用 **`03-05-k3s-local-path-pvc.md`** 的 local-path 或明确路径的 hostPath;或仅在 **≥8 GiB** 的节点上跑 Longhorn,**4 GiB 的 ARM 板**尽量只做**计算节点**并在 Longhorn UI 中**禁用其磁盘/不参与副本**(若仍装 Longhorn 组件,至少减轻数据面压力)。 官方仓库中的「中等规模」**性能测试参考节点**为 **8 vCPU、32 GB RAM**(用于可复现压测,**不是**最低装机要求),见 [medium node spec](https://github.com/longhorn/longhorn/blob/master/scalability/reference-setup-performance-scalability-and-sizing-guidelines/public-cloud/medium-node-spec.md)。 ### 部署后自检(推荐) 已安装 **metrics-server** 时: ```bash kubectl top node kubectl top pod -n longhorn-system ``` 未安装时,至少查看节点 **Allocatable** 与 Longhorn Pod 分布: ```bash kubectl describe node kubectl get pod -n longhorn-system -o wide ``` --- ## SSH 部署试跑顺序(4×8 GiB) 当前规格 **四台、各 8 GiB**,适合按本篇做一次完整试跑:`kubectl apply` 只在**控制节点**执行一次;**四台**都要完成 iSCSI/NFS 依赖与数据目录(见「前置条件」)。 与本仓库 **Ansible 清单**对应时,主机名为 **`ylc61`**(控制 / `k3s_server`)、**`ylc62`–`ylc64`**(工作节点),见 `ansible/inventory.ini`。下列命令里的 `Host` 请换成你 **SSH 已通** 的名字(或 `root@192.168.2.61` 等形式)。 ### SSH 配置说明(本机能否直连「各节点」) - **`ylc61`(控制节点)**:常见做法是在本机 `~/.ssh/config` 里配置 `Host ylc61`,`IdentityFile` 指向**该节点专用私钥**(例如仓库内 `.ssh/id_ed25519_k3s_192.168.2.61`,与 `01-06` / 建链脚本一致)。配好后可 **`ssh ylc61`**,并在其上执行 **`kubectl`**(设好 `KUBECONFIG`),**不必**强求本机安装 kubectl 或直连 API Server。 - **`ylc62`–`ylc64`(工作节点)**:`ansible/inventory.ini` 里为**每台**配置了**不同**的 `ansible_ssh_private_key_file`(如 `~/.ssh/id_ed25519_k3s_192.168.2.62` …)。若本机 `~/.ssh/config` **没有**对应 `Host ylc62` …,则 **`ssh ylc62` 会 `Permission denied`**(用错成控制节点密钥时尤其常见)。需要本机循环 SSH 四台时,请为 **62–64** 各写一段 `Host`,`IdentityFile` 与清单路径一致。 - **只做 Longhorn 安装与排查时**:多数步骤只需 **`ssh ylc61` + `kubectl`**;只有要到**具体工作节点**执行 **`ctr` 预拉镜像**、看 **kubelet/containerd** 时,才必须能登录该节点(直连、串口、或 Ansible `-l ylc63` 等均可)。 `~/.ssh/config` 示例(路径按你机器上实际私钥位置修改): ```sshconfig Host ylc62 HostName 192.168.2.62 User root IdentityFile ~/.ssh/id_ed25519_k3s_192.168.2.62 Host ylc63 HostName 192.168.2.63 User root IdentityFile ~/.ssh/id_ed25519_k3s_192.168.2.63 Host ylc64 HostName 192.168.2.64 User root IdentityFile ~/.ssh/id_ed25519_k3s_192.168.2.64 ``` ### 1. 四台节点:依赖 + 目录(从办公机循环 SSH) ```bash for h in ylc61 ylc62 ylc63 ylc64; do echo "=== $h ===" ssh "$h" 'sudo bash -s' <<'REMOTE' if command -v dnf >/dev/null 2>&1; then dnf install -y iscsi-initiator-utils nfs-utils else yum install -y iscsi-initiator-utils nfs-utils fi systemctl enable --now iscsid mkdir -p /storage/longhorn chmod 700 /storage/longhorn REMOTE done ``` ### 2. 控制节点:安装 Longhorn(只做一次) **首选:Helm + 本仓库 `values-lab.yaml`**(与 K3s 常见实践一致,版本与实验室变量集中在 `ansible/group_vars/all.yml` 的 `longhorn_chart_version`)。 - **Ansible(推荐)**:在控制机执行(与 `01-06` 顺序一致): ```bash cd ansible ansible-playbook -i inventory.ini playbooks/longhorn-install.yml ``` 该 playbook 会在各节点安装 iSCSI/NFS 依赖、在控制节点安装 Helm(若 `dnf/yum` 无 `helm` 包则需按 [Helm 安装文档](https://helm.sh/docs/intro/install/)手工安装后重跑)、再 `helm upgrade --install`。values 真源:[`ansible/files/03-07-longhorn/values-lab.yaml`](../ansible/files/03-07-longhorn/values-lab.yaml)。可选:`longhorn_apply_local_path_lab: true` 时一并应用 `03-05` 中的 local-path 实验室 ConfigMap。 - **手工 Helm**(在 **`ylc61`** 或任意已配置 `KUBECONFIG` 的机器上;kubeconfig 见 `01-01-k3s-控制节点含traefik.md`): ```bash ssh ylc61 export KUBECONFIG=/etc/rancher/k3s/k3s.yaml helm repo add longhorn https://charts.longhorn.io helm repo update # 将仓库内 ansible/files/03-07-longhorn/values-lab.yaml 拷到本机路径后: helm upgrade --install longhorn longhorn/longhorn \ --namespace longhorn-system --create-namespace \ -f ./values-lab.yaml \ --version 1.7.2 \ --wait --timeout 15m ``` Helm 安装时 **`values-lab.yaml` 已包含** `defaultDataPath`、`defaultReplicaCount`、默认 StorageClass 等,**一般无需**再执行下文针对 `kubectl apply` 方式的 **`default-data-path` / StorageClass patch**。 **备选:官方清单 `kubectl apply`**(无 Helm 或需与旧文档对齐时使用): ```bash export KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.7.2/deploy/longhorn.yaml kubectl -n longhorn-system rollout status deploy/longhorn-ui --timeout=300s ``` 然后按下文 **「安装 Longhorn」** 中 **kubectl 路径**补 **`default-data-path` 补丁**、**默认 StorageClass** 等。若经 Windows 再 SSH 导致 JSON patch 失败,用文中 **`python3` 补丁** 段落。 **Longhorn UI 访问**、**手动验证**与下文各节相同。 ### 3. 试跑成功判据(最短) - `kubectl -n longhorn-system get pod` 中关键 Pod 为 **Running**(四节点时 DaemonSet 应对齐节点数;若有 `ImagePullBackOff`,到**对应节点** `ctr` 预拉镜像,见「手动验证」)。 - 能打开 UI(**port-forward** 或临时 **NodePort**),Node 页可见四节点。 - 可选:下文「最小读写验证」PVC + Pod 写文件通过。 --- ## 安装 Longhorn **不是**在每台机器上各执行一遍安装:无论 **Helm** 还是 **`kubectl apply`**,**对整个集群只做一次**(在能访问 API 的机器上执行即可),Longhorn 的控制面、CSI、DaemonSet 等会由 Kubernetes 统一下发。 **每台节点**仍要做的是:前文「磁盘前提」「前置条件」里的 **OS 依赖**与 **`/storage/longhorn` 目录**(Ansible `longhorn-install.yml` 会创建)。若节点在集群内且未被 cordon,Longhorn 的 DaemonSet 往往也会在**各节点**拉起组件 Pod,因此各节点通常都要能 **拉取镜像**;只有「存数据」可以只在部分节点上开启(见下文「只让有大盘的节点承载数据」)。 ### 首选:Helm + `values-lab.yaml` 与上文 **「SSH 试跑顺序 §2」**一致:使用 **`ansible-playbook ... longhorn-install.yml`** 或手工 **`helm upgrade --install ... -f values-lab.yaml`**。Chart 仓库:`https://charts.longhorn.io`;values 字段以 [Longhorn Helm Chart](https://github.com/longhorn/longhorn/tree/master/chart) 当前版本为准。 ### 备选:`kubectl apply` 官方清单 未使用 Helm 时: ```bash kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.7.2/deploy/longhorn.yaml kubectl -n longhorn-system rollout status deploy/longhorn-ui ``` 将 Longhorn 默认数据路径改到 `/storage/longhorn`: ```bash kubectl -n longhorn-system patch settings.longhorn.io default-data-path \ --type=merge -p '{"value":"/storage/longhorn"}' ``` 若出现 `invalid character ':' in string escape code`(多经一层 SSH/脚本时 JSON 被错误转义),在**控制节点**用下面方式补丁(不依赖引号嵌套): ```bash python3 - <<'PY' import subprocess, json patch = json.dumps({"value": "/storage/longhorn"}) subprocess.check_call([ "kubectl", "-n", "longhorn-system", "patch", "settings.longhorn.io", "default-data-path", "--type=merge", "-p", patch, ]) subprocess.run([ "kubectl", "-n", "longhorn-system", "get", "settings.longhorn.io", "default-data-path", "-o", "jsonpath={.status.value}{'\n'}", ]) PY ``` 将 `longhorn` 设为默认 StorageClass(推荐): ```bash kubectl get storageclass kubectl patch storageclass longhorn -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' ``` 若 `patch storageclass` 同样因转义失败,可改用: ```bash python3 - <<'PY' import subprocess, json p = json.dumps({"metadata": {"annotations": {"storageclass.kubernetes.io/is-default-class": "true"}}}) subprocess.check_call(["kubectl", "patch", "storageclass", "longhorn", "-p", p]) PY ``` --- ## Longhorn UI(部署与访问) ### 是否需要单独部署 **不需要。** 官方 `longhorn.yaml` 里已包含 **Deployment `longhorn-ui`** 和 **Service `longhorn-frontend`**(命名空间 `longhorn-system`)。执行上文 `kubectl apply -f .../longhorn.yaml` 后,UI 会与其它组件一起创建;安装阶段里的 `kubectl ... rollout status deploy/longhorn-ui` 就是在等 UI 就绪。 若 UI 长期不 Ready,优先按节点查 **镜像拉取** 与 **Pod 事件**(见下文「手动验证」)。 ### 部署与网络对象核验 ```bash kubectl -n longhorn-system get deploy longhorn-ui kubectl -n longhorn-system get svc longhorn-frontend kubectl -n longhorn-system get pod -l app=longhorn-ui -o wide kubectl -n longhorn-system rollout status deploy/longhorn-ui --timeout=300s ``` `longhorn-frontend` 默认多为 **ClusterIP**,集群外浏览器**不能直接**访问 Service VIP,需用 **port-forward**、**NodePort** 或 **Ingress** 之一。 ### 访问方式一:`kubectl port-forward`(实验环境推荐) 在**已配置 kubeconfig、能访问 API** 的机器上执行(可以是办公机,也可以是某台 SSH 上去的控制节点): ```bash kubectl -n longhorn-system port-forward svc/longhorn-frontend 8080:80 ``` 浏览器打开:**http://127.0.0.1:8080** **常见场景**: - 只在控制节点上有 `kubectl`:先 SSH 到控制节点执行上述命令,再用浏览器访问——若浏览器在本地电脑,需 **SSH 本地转发**,例如在本机执行 `ssh -L 8080:127.0.0.1:8080 user@控制节点`, 然后在控制节点上 `port-forward` 绑定 `127.0.0.1:8080`,本地浏览器仍访问 `http://127.0.0.1:8080`。 - 需要把转发绑到非本机接口时(慎用,内网限定): `kubectl -n longhorn-system port-forward --address 0.0.0.0 svc/longhorn-frontend 8080:80` **无图形界面时的快速验证**(`port-forward` 占用当前终端时,另开一个终端): ```bash curl -sI http://127.0.0.1:8080 | head -n 5 ``` 能返回 `HTTP/1.1 200` 或 `302` 等即说明 UI 服务已响应(具体状态码随版本可能略有不同)。 ### 访问方式二:临时改为 NodePort(仅实验内网) 便于用「任意节点 IP + 端口」访问,**不要对公网暴露**。 ```bash kubectl -n longhorn-system patch svc longhorn-frontend -p '{"spec":{"type":"NodePort"}}' kubectl -n longhorn-system get svc longhorn-frontend ``` 在输出里查看 `PORT(S)` 一列的 `80:3xxxx/TCP`,用浏览器访问:**`http://` + 任意节点内网 IP + `:` + NodePort 端口**。 实验结束后若要改回 **ClusterIP**(与默认清单一致,便于继续只用 port-forward): ```bash kubectl -n longhorn-system patch svc longhorn-frontend -p '{"spec":{"type":"ClusterIP"}}' ``` 一般由 apiserver 重新分配 Cluster IP;若与预期不符,从当前版本的 `longhorn.yaml` 中仅重新 `apply` **Service `longhorn-frontend`** 那一段即可。 ### 访问方式三:Ingress + Traefik(可选,生产需认证与 TLS) K3s 默认带 Traefik 时,可为 `longhorn-frontend` 建 **Ingress**,用域名访问。**生产或跨网段**务必配 **HTTPS** 与 **身份认证**(Longhorn 支持多种认证方式,以[官方文档](https://longhorn.io/docs/)当前版本为准),实验环境若仅用内网域名也至少限制来源网段。 ### 界面内验证要点 打开 UI 后确认: - **Node** 页能看到集群节点,状态与磁盘信息合理(与 `kubectl get nodes` 对照)。 - **Volume** 在创建测试 PVC 后出现对应卷(可与下文「最小读写验证」联动)。 - 需要改**默认副本数**、**节点磁盘是否可调度**时,在 **Setting** / **Node** 中操作(与上文「实验环境建议配置」一致)。 若页面无法加载或接口报错,查看 UI 与后端: ```bash kubectl -n longhorn-system logs deploy/longhorn-ui --tail=80 kubectl -n longhorn-system describe pod -l app=longhorn-ui ``` --- ## 实验环境建议配置(4 节点) ### DaemonSet 与镜像 - `longhorn-manager`、`engine-image` 等会以 DaemonSet 形式跑在**多个节点**上;四节点时**每个节点**都应对应有 Pod 最终就绪(若某台 `ImagePullBackOff`,先在**该节点**用 `ctr` 预拉镜像,见下文「手动验证」)。 - 数据路径与 `default-data-path` 是**按节点**生效的:四台都要能访问你设定的目录(例如每台都有 `/storage/longhorn`,或只在有盘的节点上建目录并在 UI 里禁用无盘节点)。 ### 副本数 - **默认副本数=1**:省磁盘、适合先跑通功能;四节点集群里卷仍可能只落在某一个节点上。在 **每节点约 32G 数据盘** 的实验室基线下,优先用 **1** 避免多副本占满盘。 - **默认副本数=2 或 3**:利用多节点做副本,更接近生产里的冗余;**物理占用约为逻辑卷大小 × 副本数**(再加引擎与文件系统开销),32G 盘上调高前务必在 UI 或 `kubectl` 中确认各节点剩余空间。 - 迁移卷或临时演练容灾时,可对单个卷调高副本数,待同步完成再调回。 ### 只让“有大盘”的节点承载数据 若 4 节点里只有部分机器有 `/storage` 或大盘: - 在 Longhorn UI 里把无盘或不想参与存储的节点的 disk 设为**不可调度**。 - 或给存储节点打标签,配合工作负载的 nodeSelector/affinity(让应用尽量靠近数据)。 > 注意:副本=1 时,卷不会“随使用自动从小盘迁到大盘”,需要你手动迁移或从源头限制调度。 --- ## GitLab 这类重状态系统如何落地 原则:**所有关键组件都用 PVC**(Longhorn)。 - **必须 PVC**:PostgreSQL、Redis、Gitaly(repo)、uploads/artifacts/packages、registry(如启用) - **备份**: - 应用层(GitLab 自带 backup)+ 存储层(Longhorn snapshot/backup)双保险 --- ## Traefik `acme.json` 如何持久化/备份(可选) Traefik 的 ACME 状态很小,但也建议持久化以避免重建后触发频繁签发。 - **推荐**:给 Traefik 的 `/data` 使用 PVC(Longhorn),让 `acme.json` 走标准持久化 - **兜底**:定期导出 `acme.json` 备份(例如 `kubectl cp pod/:/data/acme.json ...`) --- ## 验证 ```bash kubectl -n longhorn-system get pod kubectl get pvc -A kubectl get pv ``` ### 手动验证(按顺序,便于排障) 在 **K3s 节点**上(与 kubelet 使用同一套 containerd),可先手动拉镜像,区分「网络/仓库问题」与「Longhorn 配置问题」: **镜像与节点**:K3s 每台节点自带 containerd,**镜像按节点本地缓存**,不会在节点间自动同步。Pod 第一次调度到某节点时,该节点会自己去仓库拉取(联网正常则不必事先每台手动 `ctr pull`)。Longhorn 的 DaemonSet 在 **4 节点**上各跑一份时,**每台**都可能要拉同一批镜像;某台若 `ImagePullBackOff`,到**该台**上执行下面的 `ctr pull` 做预热或排障。 ```bash # 使用 k3s 的 containerd 命名空间 CTR="sudo ctr --address /run/k3s/containerd/containerd.sock -n k8s.io" $CTR images pull docker.io/longhornio/longhorn-manager:v1.7.2 $CTR images pull docker.io/longhornio/longhorn-ui:v1.7.2 $CTR images pull docker.io/longhornio/longhorn-share-manager:v1.7.2 $CTR images pull docker.io/longhornio/longhorn-engine:v1.7.2 $CTR images pull docker.io/longhornio/longhorn-instance-manager:v1.7.2 $CTR images pull docker.io/longhornio/backing-image-manager:v1.7.2 # 其余 longhornio/* 镜像以你安装的版本清单为准(避免 tag 与文档漂移): # curl -sSL https://raw.githubusercontent.com/longhorn/longhorn/v1.7.2/deploy/longhorn.yaml | grep -E 'image:.*longhornio' | sort -u # 对照:非 Longhorn 镜像(判断是否为 Docker Hub 整体不通) $CTR images pull docker.io/library/nginx:alpine ``` 确认控制面与 DaemonSet(`longhorn-manager` 每节点一份;**4 节点**时应看到与节点数相关的 Pod 分布,`engine-image` 等会陆续就绪): ```bash kubectl -n longhorn-system get pod,deploy,ds -o wide kubectl get nodes -o wide kubectl -n longhorn-system rollout status deploy/longhorn-ui --timeout=300s kubectl -n longhorn-system rollout status deploy/longhorn-driver-deployer --timeout=300s ``` 确认默认数据路径与 StorageClass: ```bash kubectl -n longhorn-system get settings.longhorn.io default-data-path -o yaml kubectl get storageclass ``` 若 `longhorn` 与 `local-path` 同时带有 `storageclass.kubernetes.io/is-default-class: "true"`,未写 `storageClassName` 的 PVC 行为可能不符合预期;建议只保留一个默认类,或业务 PVC 显式写 `storageClassName: longhorn`。 **最小读写验证**(动态卷 + 临时 Pod): ```bash kubectl apply -f - <<'EOF' apiVersion: v1 kind: PersistentVolumeClaim metadata: name: longhorn-pvc-smoke namespace: default spec: accessModes: - ReadWriteOnce storageClassName: longhorn resources: requests: storage: 2Gi --- apiVersion: v1 kind: Pod metadata: name: longhorn-smoke namespace: default spec: containers: - name: app image: busybox:1.36 command: ["sh", "-c", "echo ok-longhorn > /data/test.txt && sleep 3600"] volumeMounts: - name: data mountPath: /data volumes: - name: data persistentVolumeClaim: claimName: longhorn-pvc-smoke EOF kubectl wait --for=condition=Ready pod/longhorn-smoke -n default --timeout=180s kubectl exec -n default longhorn-smoke -- cat /data/test.txt ``` 清理冒烟资源: ```bash kubectl delete pod longhorn-smoke -n default --ignore-not-found kubectl delete pvc longhorn-pvc-smoke -n default --ignore-not-found ``` Longhorn UI 的部署核对、port-forward、NodePort 与界面内验证要点见上文 **「Longhorn UI(部署与访问)」**。 --- ## 下一步 - `03-05-k3s-local-path-pvc.md`:单副本、无快照需求时,用 K3s 自带 local-path 即可 - 返回后续 GitOps / 业务部署(如 GitLab)章节