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