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 %}