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