---
# System prepended metadata

title: system / storage / NFS storage
tags: [system, NFS, linux, OS, storage, command, OS / Ubuntu / cmd / system / storage, Ubuntu]

---

system / storage / NFS storage
===
###### tags: `OS / Ubuntu / cmd / system / storage`
###### tags: `OS`, `Ubuntu`, `linux`, `command`, `system`, `storage`, `NFS`

<br>

[TOC]

<br>

## NFS storage 權限管理
> by gpt-5 (2025/09/15)

### 1) 檔案擁有者與權限（user / group / other）

* 欄位：**擁有者(user)**、**群組(group)**、**其他(other)**；權限 **r**(讀) / **w**(寫) / **x**(執行)
* 目錄的 `x` 代表「可進入/遍歷」，沒有 `x` 就算有 `r` 也不能 `cd` 進去
* 數字表示法：`r=4, w=2, x=1` → 例：`755 = u:7( rwx ) g:5( r-x ) o:5( r-x )`

常用：

```bash
# 看權限/擁有者（不要跟著 -R 一起用，先看目錄本身）

ls -ld /path
# -l: use a long listing format
# -d, --directory: list directories themselves, not their contents
#
# $ ls -ld /mnt
# drwxr-xr-x 4 root root 4096 Jun 18 14:18 /mnt

stat -c '%A %U:%G %a %n' /path
# 
# $ stat -c '%A(%a) %U:%G %n' /mnt
# drwxr-xr-x(755) root:root /mnt

# 改擁有者與群組（-R 會連同子層）
sudo chown -R user:group /path

# 改權限
sudo chmod 755 /path          # u=rwx, g=rx, o=rx
sudo chmod 775 /shared/dir    # 團隊共用常用
sudo chmod 2775 /shared/dir   # 設定 setgid 讓新檔案自動繼承群組（g+s）
```

#### 特殊位元（看 `ls -l` 會出現 s 或 t）

* **setgid (g+s)**：目錄上 `chmod 2775 dir/`；新檔/目錄會**繼承群組**。團隊目錄必備。
* **setuid (u+s)**：多用在可執行檔，讓程式以擁有者的 UID 執行；一般服務目錄 **不要亂用**。
* **sticky bit (t)**：`chmod +t /shared/tmp`；只有**檔案擁有者**或 root 能刪（像 `/tmp`）。

<br>

---

### 2) UID/GID 與 nobody/nogroup

* 每個帳號對應一個 **UID**（群組對應 **GID**）；系統靠 UID/GID 判斷權限
* `nobody:nogroup` 常是 **65534:65534**（匿名身分）

```bash
id                          # 看自己 UID/GID
getent passwd 65534         # 查某個 UID 對應的名稱
```
- **執行範例**
    ```
    $ getent passwd 65534
    nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin

    $ getent group 65534
    nogroup:x:65534:
    ```

<br>

---

### 3) NFS 基礎（Server 與 Client 分清楚）

#### 3.1 伺服器端（`/etc/exports`）

* `root_squash`：**把客戶端的 root 變匿名**（安全預設）
* `no_root_squash`：不壓 root（**高風險**，除非完全可信環境）
* `all_squash`：所有使用者都壓成匿名
* `anonuid=` / `anongid=`：匿名對應到哪個 UID/GID（常設 65534）

範例：

```bash
# 安全常見：壓 root，匿名=65534
/srv/nfs/data 10.0.0.0/24(rw,root_squash,anonuid=65534,anongid=65534)

sudo exportfs -ra          # 重新載入 exports
sudo exportfs -v           # 檢視目前生效的匯出
```

> 關鍵觀念：**在 NFS「伺服器」上改 chown/chmod 才算數**。
> 客戶端若是 root\_squash，很多變更會顯示 `Operation not permitted`。

#### 3.2 客戶端（驗證掛載與寫入）

```bash
# 看 /mnt/nfs/data 是從哪來、用什麼選項
nfsstat -m | sed -n '/\/mnt\/nfs\/data/,+3p'
cat /proc/mounts | grep ' /mnt/nfs/data '

# 用匿名身分測試寫入（模擬 root_squash 後的權限）
sudo -u nobody bash -lc 'touch /mnt/nfs/data/.wtest && rm /mnt/nfs/data/.wtest'
```

#### 3.3 root\_squash 是什麼？

* 客戶端的 `root` 進到 NFS 伺服器的檔案系統時，**被映成匿名（nobody）**
* 目錄若是 `root:root 755` → 匿名沒寫入權 → 你會看到「目錄不存在/不能建立」之類錯誤
* 解法：在 **伺服器**把該共享目錄 `chown` 成匿名可寫，或設定 `anonuid/anongid` 搭配 `chmod 2775`、群組繼承

<br>

---

### 4) 把上述知識套到 K8s 掛載（你這次的重點）

#### 4.1 local-path provisioner 的本質

* `rancher.io/local-path` 是用 **節點本機磁碟（hostPath）** 產生 PV
* 你把它指到 **NFS 掛載點**也能用，但**就會受 NFS 權限/root\_squash 影響**（容易踩雷）
* **RWX**（多 Pod/多節點同掛）請改用 **NFS 動態供應器**（如 nfs-subdir-external-provisioner / NFS CSI）

#### 4.2 常見 Kubernetes 欄位

* **PVC AccessModes**：`RWO`（單節點掛）／`RWX`（多節點同掛）
* **StorageClass.volumeBindingMode**：

  * `Immediate`：PV 先被建立/綁到某節點
  * `WaitForFirstConsumer`：等到排程到目標節點再決定（**建議**，避開跨節點卡死）
* **PV nodeAffinity**：對 local 類型 PV 很重要，確保 Pod 被排到「有實體資料夾」的那台機器
* **hostPath: DirectoryOrCreate**：Kubelet 會試著在 host 建目錄；但在 NFS `root_squash` + 父目錄 755 會**失敗**

#### 4.3 Pod 的檔案權限（securityContext）

```yaml
securityContext:
  runAsUser: 2000
  runAsGroup: 2000
  fsGroup: 2000
```

* 搭配 NFS 伺服器設定 `anonuid=2000,anongid=2000`，再把共享根目錄 `chown 2000:2000`、`chmod 2775`
* 好處：**權限穩定、可預期**，不會忽變 nobody

<br>

---

### 5) 典型修復方案（以 root\_squash 仍開啟為例）

> 在 **NFS 伺服器**：假設實體路徑是 `/srv/nfs/data`、客戶端掛在 `/mnt/nfs/data`

```bash
# /etc/exports（示意）
/srv/nfs/data 10.0.0.0/24(rw,root_squash,anonuid=65534,anongid=65534)
sudo exportfs -ra

# 建立業務目錄與子目錄（例：statesave-slurm-controller-0）
sudo mkdir -p /srv/nfs/data/slurm/statesave-slurm-controller-0
sudo chown -R 65534:65534 /srv/nfs/data/slurm
sudo chmod 2775 /srv/nfs/data/slurm
sudo chmod 2775 /srv/nfs/data/slurm/statesave-slurm-controller-0
```

> 在 **K8s 節點**驗證：

```bash
ls -ld /mnt/nfs/data/slurm /mnt/nfs/data/slurm/statesave-slurm-controller-0
sudo -u nobody bash -lc 'touch /mnt/nfs/data/slurm/statesave-slurm-controller-0/.rw && rm /mnt/nfs/data/slurm/statesave-slurm-controller-0/.rw'

kubectl -n slurm delete pod slurm-controller-0
kubectl -n slurm describe pod slurm-controller-0 | sed -n '/Events:/,$p'
```

<br>

---

### 6) 快速診斷流程（遇到「目錄不存在 / 無法掛載」）

1. **看錯誤訊息**：`path "…" does not exist` / `Operation not permitted`
2. **確認 PVC/PV 狀態**：`kubectl get pvc/pv`，注意 StorageClass/AccessModes
3. **找 PV 實體路徑**：是本機？還是 NFS 挂載點？
4. **如果是 NFS**：

   * 伺服器：`exportfs -v`、檢查 `/etc/exports`（root\_squash、anonuid/anongid）
   * 客戶端：`nfsstat -m`，用 `sudo -u nobody touch` 測試寫入
5. **權限校正**：在**伺服器**對實體目錄 `chown/chmod`，必要時先 `0777` 驗證、通過後再收斂
6. **重啟 Pod**：`kubectl delete pod ...`，再看 `describe pod` 的 `Events` 是否消除錯誤

<br>

---

### 7) 小抄（你會常用的幾行）

```bash
# 顯示權限/擁有者（單一目錄）
stat -c '%A %U:%G %a %n' /path

# 設群組繼承的共享資料夾
sudo chown -R user:team /share
sudo chmod 2775 /share

# 檢查 NFS 匯出（Server）
sudo exportfs -v

# 檢查掛載（Client）
nfsstat -m | sed -n '/\/mnt\/nfs\/data/,+3p'

# 以 nobody 測試寫入（模擬 root_squash）
sudo -u nobody bash -lc 'touch /mnt/nfs/data/.test && rm /mnt/nfs/data/.test'
```

<br>

---

### TL;DR

* **權限=擁有者(user/group/other)+rwx**；團隊資料夾用 **`chmod 2775`**（g+s）。
* **NFS `root_squash`** 會把遠端 root 變 **nobody**，父目錄若 `root:root 755` 就**寫不進去**。
* **在 NFS 伺服器**改 `chown/chmod` 才有效；必要時設定 `anonuid/anongid`。
* **K8s**：local-path 其實是 **本機磁碟**；要 **RWX** 改用 **NFS 動態供應器**，或把權限策略設好（`fsGroup` + exports）。

<br>

---

<br>

## NFS 設定檔

### `/etc/exports` 設定檔預設值
```
rw,root_squash,no_all_squash
```
- ### ✅ 小提醒：
    - 用 `exportfs -v` 可以看到實際生效的選項（包含預設值）。
    - 官方手冊也說明了預設 export 選項含 `root_squash`。 ([manpages.ubuntu.com](https://manpages.ubuntu.com/manpages/jammy/man8/exportfs.8.html?utm_source=chatgpt.com))
    - **`root_squash` + `no_all_squash`** 表示只有 root 會被 squash（對應成 `nobody`），其他使用者則保留原本的 UID/GID。 

---

- ### ✅ `anonuid`（**anonymous user ID**）
    - **用途**：當 NFS 使用者被 "squash"（例如 `root_squash` 或 `all_squash`）為匿名使用者時，該使用者會被對應成 `anonuid` 指定的 UID。
    - **預設值**：65534（通常是 `nobody`）

---

- ### ✅ `anongid`（**anonymous group ID**）
    - **用途**：與 `anonuid` 類似，當使用者被 squash 成匿名用戶時，會被對應成 `anongid` 指定的 GID。
    - **預設值**：65534（通常是 `nogroup`）

<br>

### User ID Mapping
> https://serverfault.com/questions/1089557

正確應為：

| Option        | Client UID | Server UID       |
|---------------|------------|------------------|
| root_squash   | 0          | 65534            |
| root_squash   | 1000       | 1000             |
| no_root_squash| 0          | 0                |
| no_root_squash| 1000       | 1000             |
| all_squash    | 0          | 65534            |
| all_squash    | 1000       | 65534            |

| Option                               | Client UID | Server UID |
| ------------------------------------ | ---------- | ---------- |
| no_all_squash **(+ root_squash)**    | 0          | 65534      |
| no_all_squash **(+ no_root_squash)** | 0          | 0          |
| no_all_squash                        | 1000       | 1000       |
| no_all_squash                        | 10001      | 10001      |

- `root_squash` 只把 **UID 0**（root）壓成匿名 UID（預設 65534，可用 `anonuid=`/`anongid=` 變更），其它使用者維持原樣；
- 若你在 exports 寫了 `anonuid=2000,anongid=2000`，上面所有「65534」就變更為「2000」。

<br>

---

### 解釋：`/etc/exports` (範例設定檔)
```
$ cat /etc/exports 
# /etc/exports: the access control list for filesystems which may be exported
#		to NFS clients.  See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes       hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4        gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes  gss/krb5i(rw,sync,no_subtree_check)
#
/data 10.78.26.99(rw,sync,no_subtree_check,no_root_squash) \
      10.78.26.220(rw,sync,no_subtree_check,no_root_squash) \
      10.78.26.219(rw,sync,no_subtree_check,no_root_squash) \
      10.78.26.76(rw,sync,no_subtree_check,no_root_squash)
```
- ### gpt-5 (2025/10/20)
    上面這段 `/etc/exports` 把 **/data** 這個目錄，僅分享給四台特定主機，且給的是可讀寫且「root 不被壓成匿名」的權限。

    ### 每個選項在做什麼

    * **客戶端限制**：只允許 `10.78.26.99/.220/.219/.76` 這四台掛載。
    * **rw**：讀寫皆可（不是唯讀）。
    * **sync**：**同步寫入**；伺服器在資料落盤前不回應，較安全、較慢。相對於 `async`（快但可能在當機時遺失未落盤資料）。
    * **no_subtree_check**：關閉「子樹檢查」，提升重新命名/移動目錄時的穩定性與效能。通常**輸出整個檔案系統或大目錄**時建議關閉。
    * **no_root_squash**：**不壓縮 root**。也就是說：
      * 用戶端 **UID 0 (root)** 在伺服器端仍是 **UID 0**。
      * 一般使用者維持原本的 UID/GID 映射（1000→1000、10001→10001…）。
          * 預設是 `no_all_squash`（不把所有使用者都壓成匿名）
          * **小提醒**：用 `exportfs -v` 可以看到實際生效的選項（包含預設值）。官方手冊也說明了預設 export 選項含 `root_squash`。 ([manpages.ubuntu.com](https://manpages.ubuntu.com/manpages/jammy/man8/exportfs.8.html?utm_source=chatgpt.com))
            ```
            $ sudo exportfs -v
            /data  10.78.26.99 (sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
            /data  10.78.26.220(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
            /data  10.78.26.219(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
            /data  10.78.26.76 (sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
            ```

    > 在這個設定下的 UID 映射（預設 `anonuid=65534` 未被使用到）
    >
    > * client `0(root)` → server `0(root)`（因為 `no_root_squash`）
    > * client `1000`    → server `1000`
    > * client `10001`   → server `10001`

    ### 影響與風險（重點）

    * `no_root_squash` 讓客戶端的 root 在伺服器端也擁有 **完整 root** 權限。
      * 優點：管理方便（例如安裝軟體、修權限不用卡 anonuid）。
      * 風險：**任何一台客戶端只要 root 被入侵，就等於直接拿到 NFS 伺服器上的資料 root 寫入能力**（可更改 owner、寫入檔案、刪除等）。
    * `sync` 與 `no_subtree_check` 是偏向安全/效能的合理選擇。

    ### 若要加強安全的幾種做法

    依你的場景挑一個或組合使用：

    1. **改回 `root_squash`**（建議預設）：`/data 10.78.26.99(rw,sync,no_subtree_check,root_squash)`
       * client `root(0)` 會被壓成 `anonuid`（預設 65534，可自訂：`anonuid=2000,anongid=2000`）。
    2. **集中所有人為單一帳號**（方便審計）：`all_squash,anonuid=2000,anongid=2000`。
    3. **只對極信任機器給 `no_root_squash`**，其餘維持 `root_squash`。
    4. **NFSv4 + Kerberos**（`sec=krb5`/`krb5i`/`krb5p`）做強式身分驗證與完整性/加密。
    5. **若 /data 底下有另外掛載的子檔案系統**要讓客戶端穿透，請加 `crossmnt`。
    6. 需要當作 NFSv4 伺服器根（pseudo-root）時，通常會把根輸出標成 `fsid=0`。

    ### 套用與檢查

    * 重新載入輸出：`sudo exportfs -ra`
    * 檢視有效輸出：`sudo exportfs -v`
    * NFSv3 檢查（伺服器端）：`showmount -e`
    * 客戶端掛載範例（NFSv4）：
      ```bash
      sudo mount -t nfs -o nfsvers=4 10.78.26.10:/data /mnt/data
      ```
      （若你導入 Kerberos，會加上 `-o sec=krb5i` 或 `sec=krb5p`）


<br>

### 檢視 NFSv4 ACL
- ### 執行範例
    ```
    $ apt-get update && apt-get install -y nfs-common nfs4-acl-tools
    $ nfs4_getfacl /mnt/data/project-B
    
    # file: /mnt/data/project-B
    A::OWNER@:rwaDxtTcCy
    A::GROUP@:rwaDxtcy
    A::EVERYONE@:tcy
    ```

<br>

### 用戶端驗證 root_squash 是否生效

在 **NFS 客戶端**（掛載點假設 `/mnt/data`）：

```bash
sudo -i
id -u                          # 確認現在是 0（root）
touch /mnt/data/_root_test
ls -ln /mnt/data/_root_test    # 看 UID：若顯示 65534 => root_squash 生效；若 0 => no_root_squash
chown 0:0 /mnt/data/_root_test # root_squash 時通常會 Permission denied
```

<br>

{%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}