system / storage / NFS storage / NFS 授權除錯教學 === ###### tags: `OS / Ubuntu / cmd / system / storage` ###### tags: `OS`, `Ubuntu`, `linux`, `command`, `system`, `storage`, `NFS`, `Permission denied` <br> [TOC] <br> :::info ### :bulb: 前言 - **流程**:按「先不影響線上服務」→「必要時才調整/重啟」設計 - 全部指令都以 **Ubuntu/Debian 系**為例(RHEL 類也通用 95%)。 ::: <br> --- ## 零、預備知識 ### 術語 - **伺服器端**:指 NFS Server 主機這邊 - **客戶端**:指掛載 NFS storage 的主機這邊 <br> ### 很重要的事要交待 - NFS server 上要有對應的 uid & gid - server 端可能有 cache - client 端可能有 cache - 備註:最終 GID 以 server 端為準 <br> ### 準備可重製環境 - ### 可在 host / pod 上直接測試 ```bash= # --- Prepare three project directories under /home --- # (or under /tmp, /mnt/data (from NFS), etc.) cd /home # 前提:當前目錄必須在 NFS 上 mkdir project-A mkdir project-B mkdir project-C # Assign ownership chown 1001:1001 project-A chown 1002:1002 project-B chown 1003:1003 project-C # Permissions: rwx for owner & group, no access for others chmod 770 project-A chmod 770 project-B chmod 770 project-C # List with numeric UIDs/GIDs to verify ownership and mode ll -n # --- Create groups and users with matching IDs --- groupadd -g 1001 g1001 groupadd -g 1002 g1002 useradd -u 1001 -g 1001 -m -s /bin/bash id1001 useradd -u 1002 -g 1002 -m -s /bin/bash id1002 # Cross-add each user to the other's group (supplementary group membership) usermod -aG g1002 id1001 usermod -aG g1001 id1002 # Quick sanity check of passwd/group entries getent passwd | tail -3 getent group | tail -3 # --- Access tests as id1001 --- su - id1001 id1001 cd /home/project-A cd /home/project-B # -bash: cd: /home/project-B: Permission denied cd /home/project-C # -bash: cd: /home/project-C: Permission denied exit # --- Access tests as id1002 --- su - id1002 id1002 cd /home/project-A # -bash: cd: /home/project-A: Permission denied cd /home/project-B cd /home/project-C # -bash: cd: /home/project-C: Permission denied exit ``` - ### 建立一個暫時性的 pod ```yaml= # mount-nfs-and-test-permission.yaml apiVersion: v1 kind: Pod metadata: name: mount-nfs-and-test-permission-by-tj spec: # Specify the same machine as the NFS Server? #nodeSelector: # kubernetes.io/hostname: ws-e900-g4-ws980t containers: - name: ubuntu image: ubuntu:24.04 command: ["bash", "-c", "sleep infinity"] volumeMounts: - name: home-storage mountPath: /home volumes: - name: home-storage persistentVolumeClaim: claimName: home-storage-pvc --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: home-storage-pvc spec: storageClassName: default-local accessModes: - ReadWriteMany resources: requests: storage: 100Mi ``` ``` kubectl exec -it mount-nfs-and-test-permission-by-tj -- bash ``` <br> ### 工具安裝 | 工具指令 | 安裝工具 | |---------|--------| | 工具安裝前 | `sudo apt-get update` || | `nfsstat` | `sudo apt-get install -y nfs-common` | <br> --- <br> ## 一、快速判斷:現在是誰卡住了? 當你遭遇 `cd project-A` → `Permission denied` 時,請立刻做這兩件事: 1. **伺服器端**看核心的 GID 快取(`manage-gids` 核心證據) ```bash sudo grep -E '^[0-9]+' /proc/net/rpc/auth.unix.gid/content | tail -n 10 # 看到類似: # 1008 2: 1008 1009 # → 表示 nfsd 以「伺服器查到的完整群組」授權(manage-gids 生效) ``` - **執行範例** ``` $ sudo cat /proc/net/rpc/auth.unix.gid/content #uid cnt: gids... 0 1: 0 401 0: 1001 0: 999 1: 999 65534 1: 65534 1000 9: 4 24 27 30 46 120 133 134 1000 ``` - **格式**:`<uid> <群組數量>: <gid 列表...>` - **[補充] 如何觀測**伺服器端**的 GID 快取變化?** - **[伺服器端]** 先清除 GID 快取 ```bash # 清除 GID 快取 $ date +%s | sudo tee /proc/net/rpc/auth.unix.gid/flush 1761190218 # 重新觀測 $ sudo cat /proc/net/rpc/auth.unix.gid/content #uid cnt: gids... 0 1: 0 999 1: 999 65534 1: 65534 ``` - **[客戶端]** 切到目的地資料,執行 `ls`,即可觸發 GID 快取清單變化 ``` $ su - id1001 # 切換到要測試的帳號,進行測試 $ cd /mnt/work $ /mnt/work$ ls g10000 g10001 g10002 g10003 g1021 g1022 project-A project-B project-C project-ocis ``` 2. **客戶端**看掛載參數與版本(瞭解快取行為) ```bash # $ sudo apt-get update # $ sudo apt-get install -y nfs-common nfsstat -m | sed -n '1,80p' mount | grep ' type nfs' # 例: vers=4.2, sec=sys, rw, relatime ... ``` - **`nfsstat` 參數** - `-m, --mounts`: Show statistics on mounted NFS filesystems - `-c, --client`: Show NFS client statistics - `-s, --server`: Show NFS server statistics - `--version`: 目前 `2.6.4` (2025/10/23) - **執行範例1 (跟 NFS server 是同一台)** ```bash $ nfsstat -m # 沒有任何結果 $ mount | grep nfs # 沒有任何結果 $ mount | grep /path/to/your/mount /dev/md0 on /path/to/your/mount type ext4 (rw,relatime,stripe=256) ``` - 你的 pod 跟 NFS server 是同一台 - 不同台,請看**執行範例2** - **執行範例2 (跟 NFS server 是不同台)** ``` $ nfsstat -m /mnt/work from 10.78.26.222:/data/slurm-work Flags: rw,relatime,vers=4.2,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=10.78.26.220,local_lock=none,addr=10.78.26.222 ``` ``` $ nfsstat -c Client rpc stats: calls retrans authrefrsh 0 0 0 Client nfs v4: null read write commit open 10 0% 217827568 95% 596619 0% 13678 0% 164152 0% open_conf open_noat open_dgrd close setattr 0 0% 1186334 0% 3 0% 1304180 0% 100567 0% fsinfo renew setclntid confirm lock 6 0% 0 0% 0 0% 0 0% 29991 0% lockt locku access getattr lookup 0 0% 27691 0% 171727 0% 4483678 1% 1370590 0% lookup_root remove rename link symlink 2 0% 244375 0% 19166 0% 101642 0% 1102 0% create pathconf statfs readlink readdir 4872 0% 4 0% 315966 0% 1104 0% 114011 0% server_caps delegreturn getacl setacl fs_locations 10 0% 1119426 0% 237 0% 1 0% 4 0% rel_lkowner secinfo fsid_present exchange_id create_session 0 0% 0 0% 0 0% 2 0% 1 0% destroy_session sequence get_lease_time reclaim_comp layoutget 0 0% 22 0% 0 0% 1 0% 0 0% getdevinfo layoutcommit layoutreturn secinfo_no test_stateid 0 0% 0 0% 0 0% 2 0% 0 0% free_stateid getdevicelist bind_conn_to_ses destroy_clientid seek 17900 0% 0 0% 0 0% 0 0% 4 0% allocate deallocate layoutstats clone 6168 0% 0 0% 0 0% 0 0% ``` ``` $ mount | grep nfs 10.78.26.222:/data/slurm-work on /mnt/work type nfs4 (rw,relatime,vers=4.2,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=10.78.26.220,local_lock=none,addr=10.78.26.222) ``` 3. **解讀觀測結果** 1. **[客戶端]** ``` id1001@mount-nfs-and-test-permission-by-tj:/home$ id uid=1001(id1001) gid=1001(g1001) groups=1001(g1001),1002(g1002) id1001@mount-nfs-and-test-permission-by-tj:/home$ ls -n total 20 drwxr-x--- 2 1001 1001 4096 Oct 23 12:16 id1001 drwxr-x--- 2 1002 1002 4096 Oct 23 12:16 id1002 drwxrwx--- 2 1001 1001 4096 Oct 23 12:16 project-A drwxrwx--- 2 1002 1002 4096 Oct 23 12:16 project-B <- 應能進入 group=1002 drwxrwx--- 2 1003 1003 4096 Oct 23 12:16 project-C id1001@mount-nfs-and-test-permission-by-tj:/home$ cd project-B -bash: cd: project-B: Permission denied ``` 2. **[伺服器端]** ``` $ sudo cat /proc/net/rpc/auth.unix.gid/content #uid cnt: gids... 0 1: 0 1001 0: <--- 沒有授權 1002 999 1: 999 65534 1: 65534 ``` 3. **[客戶端] 清除 GID 快取 (測試失敗,沒成功)** ``` id1001@mount-nfs-and-test-permission-by-tj:~$ echo 3 > /proc/sys/vm/drop_caches -bash: /proc/sys/vm/drop_caches: Read-only file system id1001@mount-nfs-and-test-permission-by-tj:~$ exit logout root@mount-nfs-and-test-permission-by-tj:/# echo 3 > /proc/sys/vm/drop_caches bash: /proc/sys/vm/drop_caches: Read-only file system ``` - ### 補充: - `/proc/sys/vm/drop_caches` 檔案權限?只能寫入 ``` $ ll /proc/sys/vm/drop_caches --w------- 1 root root 0 Oct 29 02:30 /proc/sys/vm/drop_caches ``` - `echo 3` 意義 - `1`=page cache - `2`=dentries+inodes - `3`=兩者皆清。 - 寫入前建議先 `sync`,避免還沒 flush 的髒頁被延後處理。 ``` sync; echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null ``` - ### :warning: Read-only file system ? **[解法]** pod yaml 檔中需添加 `securityContext` 設定 ```yaml= spec: containers: - name: ubuntu # add extra permissions securityContext: privileged: true capabilities: add: - SYS_ADMIN ``` - 沒有 `SYS_ADMIN` 能力 → Kubernetes/容器執行時會把 `/proc/sys` 掛成唯讀,避免容器改動**節點**核心行為,所以才會報 `Read-only file system`。 4. **其他留意事項:** - 如果伺服器已出現 `1001 0: `, 但沒有任何 group,表示伺服器這邊沒有對應的 uid & gid;要先補上 uid & gid。 - 如果伺服器已出現 `1001 2: 1001 1002`,但客戶端仍被拒,**九成是客戶端的 ACCESS/屬性/目錄(dentry)快取**還沒失效。 * 如果伺服器沒有出現 `1001 ...` 這條,**回到伺服器進行 NSS/exports 設定檢查**(見下文 §二、§三)。 <br> --- <br> ## 二、伺服器端設定與健康檢查(不重啟優先) > 在 NFS server 上操作 ### 2.1 確認 `manage-gids` 有啟用 - ### 目的 `--manage-gids` 是給 **NFS 伺服器的 rpc.mountd** 用的參數。開啟後,伺服器**不再信任客戶端傳來的群組清單**,而是**在伺服端自行查詢**該使用者所屬的所有群組(透過 `/etc/group` 或 NSS/LDAP),再用這份清單做存取控制。這可以避開 NFS 協定在 **一次最多只傳 16 個群組 ID** 的限制。 - ### 不啟用會怎樣? - 如果管理者想要管理 16 個以上的 project,超過 16 個就會被忽略,而導致 permission denied - 預期客戶端每次 RPC 應會送出 `uid + primary_gid + aux_gids[...]`,但因為 `aux_gids[...]` 最多只允許 16 個**附加群組** (設計上的限制),超過 16 個就會被忽略。 - ### 如何確認 `manage-gids` 有啟用? ```bash # 直接查看設定檔 cat /etc/nfs.conf | grep manage-gids # 設定檔位置(觀看用) sudo sed -n '1,160p' /etc/nfs.conf | sed -n '/^\[mountd\]/,/^\[/p' # 期待看到: # [mountd] # manage-gids = y # 讀設定檔(重點段落) sudo nfsconf --get mountd manage-gids # 期待回 y 或 yes sudo systemctl status nfs-mountd # 確認 mountd 有跑 ps -o pid,cmd -C rpc.mountd # 僅確認進程;參數列看不到 -g 也正常(nfs.conf 內已啟用) ``` > 備註:`nfs.conf` 的 `manage-gids=y` 與帶 `rpc.mountd -g/--manage-gids` 等價。若剛改完 `nfs.conf` 才要生效,**需重啟 `nfs-mountd`**;但你環境上已經是 y,無須重啟。 - **執行範例** ```bash= $ cat /etc/nfs.conf | grep manage-gids manage-gids=y $ sudo sed -n '1,160p' /etc/nfs.conf | sed -n '/^\[mountd\]/,/^\[/p' [mountd] # debug=0 manage-gids=y <------ # descriptors=0 # port=0 # threads=1 # reverse-lookup=n # state-directory-path=/var/lib/nfs # ha-callout= # [nfsdcld] $ sudo nfsconf --get mountd manage-gids y $ sudo systemctl status nfs-mountd ● nfs-mountd.service - NFS Mount Daemon Loaded: loaded (/lib/systemd/system/nfs-mountd.service; static) Active: active (running) since Tue 2025-09-30 11:23:48 CST; 3 weeks 2 days ago Main PID: 2209 (rpc.mountd) Tasks: 1 (limit: 444119) Memory: 2.9M CPU: 10.900s CGroup: /system.slice/nfs-mountd.service └─2209 /usr/sbin/rpc.mountd 九 30 11:23:47 WS-E900-G4-WS980T systemd[1]: Starting NFS Mount Daemon... 九 30 11:23:48 WS-E900-G4-WS980T rpc.mountd[2209]: Version 2.6.1 starting 九 30 11:23:48 WS-E900-G4-WS980T systemd[1]: Started NFS Mount Daemon. $ ps -o pid,cmd -C rpc.mountd PID CMD 2209 /usr/sbin/rpc.mountd ``` <br> ### 2.2 確認 export 選項(避免把身分壓成 nobody) ```bash sudo exportfs -v # 檢查該匯出路徑是否為:sec=sys, no_all_squash # (你目前是 no_all_squash、no_root_squash;no_root_squash 有風險,僅在確定需求時保留) ``` - **執行範例** ``` $ 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) ``` - **留意 squash 選項**:`no_root_squash,no_all_squash` - [[HackMD] NFS storage / User ID Mapping](/UR4NNkkBQACV2Fs-EodiUw#User-ID-Mapping) - **其他除錯情境** - **如果懷疑 permission 是 NFS server 擋住?** - **暫時解法**: - 把所有的 uid 都轉成 nobody(=65534) - 底層 id 都相通,就不會 permission 問題 <br> ### 2.3 確認伺服器 NSS 能查到群組關係 ``` $ tree /proc/net/rpc/auth.unix.gid/ /proc/net/rpc/auth.unix.gid/ ├── channel <- 空的 ├── content <- GUID→GIDs 快取資訊 └── flush <- timestamp ``` ```bash # 前面設定:`usermod -aG g1002 id1001` (把使用者 id1001 加入到群組 g1002 中) getent group 1002 # 期待包含 id1001 # 若使用 LDAP/SSSD/nscd,變更組員後建議同步清快取: # nscd: sudo nscd -i group # SSSD: sudo sss_cache -E ``` <br> ### 2.4 操作 `/proc` 的 GID 快取(**不用重啟**) ```bash # 清空核心的 UID→GIDs 快取(寫任意數字即可,常用時間戳記) date +%s | sudo tee /proc/net/rpc/auth.unix.gid/flush # 讓「客戶端以該使用者」對目標目錄做一次操作(如 `cd project-B`), # 再回到伺服器查看是否出現新的一行: sudo tail -n +1 /proc/net/rpc/auth.unix.gid/content | tail # 期待:1008 <cnt>: ... 1009 ... ``` > 行內格式說明: > `#uid cnt: gids...` > 例如 `1001 2: 1001 1002` 表示 **UID 1001** 的群組清單有 **2** 個 GID:1001、1002。 > 看到這行代表 **manage-gids 正在用伺服端查到的群組覆寫授權**。 <br> ### 2.5 (若需)觀察伺服器的授權決策(除錯用,開了要關) ```bash sudo rpcdebug -m nfsd all # 在客戶端重現一次 cd 失敗/成功 journalctl -k --since "2 min ago" | grep -i nfsd sudo rpcdebug -m nfsd all 0 ``` <br> --- <br> ## 三、**客戶端**:快取與掛載參數檢查 ### 3.1 最小衝擊的「快取刷新」 你已驗證有效的作法: ```bash # 僅刷新本機 dentry/inode/page cache(不會影響服務) echo 3 | sudo tee /proc/sys/vm/drop_caches ``` - 沒效果? <br> ### 3.2 低 TTL 方式(短時間測試用) ```bash # 暫降目錄與檔案屬性快取 TTL sudo mount -o remount,acregmin=0,acregmax=0,acdirmin=0,acdirmax=0 /mnt/nfs/data # 測試完成後,建議改回預設(避免效能下降) ``` > 若除錯期間仍不穩,也可再加:`-o lookupcache=none` 抑制目錄項目快取(效能成本高,僅短期除錯用)。 <br> ### 3.3 確認有效身分(僅確認,不需 `newgrp`) ```bash # 客戶端以該使用者身分 id id -Gn # 應包含 g1009;正常情境不需要 newgrp, # newgrp 只是暫時改「主群組」,會繞過上一個「拒絕快取」的鍵值。 ``` <br> --- <br> ## 四、ACL 與權限鏈(mode 與 ACL 以「更嚴」者為準) 當 mode 看起來正確(例如 `drwxrwx---`,group=1001)仍被拒,請在**伺服器**檢查 ACL: ```bash # POSIX ACL $ getfacl -p /data/slurm-work /data/slurm-work/project-A # file: /data/slurm-work # owner: ocisadmin # group: ocisadmin user::rwx group::rwx other::r-x # file: /data/slurm-work/project-A # owner: 1002 # group: project-A user::rwx group::rwx other::--- # NFSv4 ACL(如果環境有用到) $ nfs4_getfacl /data/slurm-work /data/slurm-work/project-A Operation to request attribute not supported: /data/slurm-work Operation to request attribute not supported: /data/slurm-work/project-A # (沒效果?) ``` * 若看到有 `DENY`、或 `mask` 把 group 的 `x` 壓掉,等於覆蓋了 770。 * 修正範例: ```bash # 讓 g1001 具備 rx(若要 rwx 就用 rwt) sudo setfacl -m g:g1001:rx /data/slurm-work/project-A # NFSv4 風格: sudo nfs4_setfacl -a A:g:g1001:rx /data/slurm-work/project-A ``` > 也別忘了**父層目錄**需要有 `x`(可執行/穿越)權限,否則就算子目錄正確也進不去。 > 你能 `ls` 子目錄列表通常代表父層可穿越 OK,但遇到奇怪現象時仍值得 double-check。 <br> --- <br> ## 五、最小化重現/健檢腳本(含使用者模擬) ### 5.1 建立測試用目錄與權限(伺服器) ```bash sudo mkdir -p /data/tj/tmp/project-X /data/tj/tmp/project-Y sudo chown 1008:1008 /data/tj/tmp/project-X sudo chown 1009:1009 /data/tj/tmp/project-Y sudo chmod 2770 /data/tj/tmp/project-X /data/tj/tmp/project-Y # g+s 維持群組 ``` <br> ### 5.2 直接在伺服器用「數字 UID」模擬授權(不繞 NFS) ```bash sudo -u \#1008 bash -c 'id; cd /data/tj/tmp/project-Y && echo OK || echo FAIL' # OK → 本機檔系統與 ACL 無誤;若 client 還卡,多半就是 client 快取或掛載參數問題。 ``` :+1: :+1: :+1: <br> --- <br> ## 六、遇到狀況時的處理順序(不重啟版本) 1. **伺服器 NSS 有資料嗎?** `getent group 1009` → 應含 `id1008` 2. **清伺服器 GID 快取** `date +%s | sudo tee /proc/net/rpc/auth.unix.gid/flush` 3. **在客戶端重試一次,並清本機快取** `echo 3 | sudo tee /proc/sys/vm/drop_caches` (或暫時 remount 降低 TTL) 4. **檢查 `/proc/net/rpc/auth.unix.gid/content`** 是否有 `1008 ... 1009` 5. **仍卡就看 ACL**(`getfacl`/`nfs4_getfacl`),以及父層可穿越權限。 6. (必要時)開 `rpcdebug -m nfsd` 看伺服器決策,關閉記得 `... all 0`。 <br> --- <br> ## 七、常見迷思與備忘 * **不需要 `newgrp` 才能授權**:`manage-gids` 開啟時,授權以**伺服器查到的完整群組**為準。`newgrp` 只是讓客戶端的憑證變化,偶爾能「躲過」舊的負向快取,並非必要條件。 * **先在伺服器建好人員/群組也仍可能遇到 client 快取**:快取是否存在與建置順序無關,只跟「客戶端是否曾拿過舊的 ACCESS/屬性結果」有關。 * **`exportfs -v` 要是 `no_all_squash`**:避免把所有使用者壓成匿名 `65534`。 * **NFSv4 與名稱映射**:你的環境是 `sec=sys`、雙邊 UID/GID 對齊,OK。若改用名稱映射(idmapd/LDAP 等),仍要確保**伺服端 NSS 能查到正確群組**,`manage-gids` 才能回填正確清單。 * **`no_root_squash` 請謹慎**:它讓 client 的 root 以 root 身分存取(有安全風險)。除非確定需求,建議用預設的 `root_squash`。 <br> --- <br> ## TL;DR(超簡短流程) 1. Server:`nfsconf --get mountd manage-gids` → `y`; `exportfs -v` → `no_all_squash`。 2. Server:`getent group 1009` 有 `id1008`; `echo $(date +%s) > /proc/net/rpc/auth.unix.gid/flush`。 3. Client:`nfsstat -m` 看掛載; `echo 3 > /proc/sys/vm/drop_caches`; 再 `cd project-Y`。 4. Server:`/proc/net/rpc/auth.unix.gid/content` 出現 `1008 ... 1009`。 5. 仍卡 → 檢查 **ACL** 與父層 `x` 權限;必要時 `rpcdebug -m nfsd` 觀察。 這份清單就能涵蓋我們遇到的「server 已認可、client 偶發拒絕」與「UID/GID 對齊、manage-gids 生效」的所有重點。祝除錯順利! <br> {%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}