Incus 非特权容器 UID/GID 映射与共享目录权限问题排障记录
1. 环境
宿主机:
- Arch Linux
- Kernel:
7.1.6-zen1-1-zen - Incus:
7.3 - LXC:
7.0.0
宿主机用户:
uid=1000(shial)
gid=1000(shial)
Incus 容器:
arch
容器内部用户:
uid=1000(arch)
gid=1000(arch)
共享目录:
宿主机:/home/shial/Project
容器:/Project
2. 最初遇到的问题
创建 Incus 容器时出现:
Instance creation failed
Failed creating instance record:
Failed initializing instance:
System doesn't have a functional idmap setup
检查宿主机:
cat /etc/subuid
cat /etc/subgid
结果:
shial:100000:65536
同时确认:
sysctl kernel.unprivileged_userns_clone
结果:
kernel.unprivileged_userns_clone = 1
确认 User Namespace:
zgrep CONFIG_USER_NS /proc/config.gz
结果:
CONFIG_USER_NS=y
CONFIG_USER_NS_UNPRIVILEGED=y
确认:
which newuidmap
which newgidmap
结果:
/usr/bin/newuidmap
/usr/bin/newgidmap
并且:
getcap /usr/bin/newuidmap
getcap /usr/bin/newgidmap
结果:
/usr/bin/newuidmap cap_setuid=ep
/usr/bin/newgidmap cap_setgid=ep
最终通过日志发现真正的问题:
Unable to parse system idmap
err="No map found for user"
Incus 服务使用的是宿主机用户 shial,因此需要确保该用户存在正确的 UID/GID subordinate 映射。
最终确认并修复 UID/GID 映射后,容器可以正常启动。
3. Incus UID/GID 映射
容器启动后检查:
cat /proc/self/uid_map
cat /proc/self/gid_map
结果:
0 165536 65536
含义:
容器 UID 0
↓
宿主机 UID 165536
映射范围:
宿主机 UID 165536-231071
因此:
容器 UID 0 → 宿主机 UID 165536
容器 UID 1000 → 宿主机 UID 166536
计算方式:
165536 + 1000 = 166536
GID 同理。
4. 共享目录出现 nobody
容器中查看 /Project:
ls -ln /Project
大量文件显示:
65534
普通 ls 则显示:
nobody
例如:
drwxr-xr-x nobody anytls-go-script
drwxr-xr-x nobody Aur
drwxr-xr-x nobody conductor
某个文件:
-rw-r--r-- 65534 22 parseUtil.cjs
原因:
宿主机原来的文件 owner UID,例如:
1000
不在容器的 UID 映射范围:
165536-231071
所以这个 UID 无法映射到容器中的正常 UID。
容器最终只能显示:
65534 = nobody
5. chown 出现 Invalid argument
在容器中执行某些开发工具、npm、node_modules 操作时出现:
chown: changing ownership of '...':
Invalid argument
例如:
chown: changing ownership of
'Project/project-init/test/.opencode/node_modules/zod/...':
Invalid argument
根本原因也是 UID/GID 映射。
容器无法把某些宿主机 UID 映射成合法的容器 UID,因此对这些文件执行 chown 时会失败。
6. 采用 UID/GID 对齐方案
参考:
https://blog.kye.dev/proxmox-zfs-mounts/
核心思想:
为共享目录创建一个宿主机 UID/GID,使其能够准确映射到容器中的 UID/GID。
本环境映射起点:
165536
容器中的目标用户:
UID 1000
所以宿主机对应 UID:
165536 + 1000 = 166536
选择容器中的共享组:
GID 10000
因此宿主机对应 GID:
165536 + 10000 = 175536
最终建立:
宿主机 容器
UID 166536 ───────────────→ UID 1000
GID 175536 ───────────────→ GID 10000
7. 宿主机创建映射用户和组
宿主机:
sudo groupadd -g 175536 project_shared
创建用户:
sudo useradd \
-u 166536 \
-g 175536 \
-M \
-s /usr/bin/nologin \
project
检查:
getent passwd project
getent group project_shared
应该类似:
project:x:166536:175536::/home/project:/usr/bin/nologin
project_shared:x:175536:
8. 修改共享目录 owner
共享目录:
/home/shial/Project
宿主机执行:
sudo chown -R 166536:175536 /home/shial/Project
这样宿主机文件:
UID 166536
GID 175536
进入容器后会正确映射为:
UID 1000
GID 10000
而不再显示为:
nobody
9. 容器内创建对应用户
进入容器:
incus exec arch -- bash
创建对应组:
groupadd -g 10000 project_shared
创建用户:
useradd \
-u 1000 \
-g 10000 \
-m \
-s /bin/bash \
project
此时:
容器:
project
UID = 1000
GID = 10000
正好对应宿主机:
project
UID = 166536
GID = 175536
10. 共享目录配置
Incus 将宿主机:
/home/shial/Project
挂载到容器:
/Project
例如:
incus config device add arch project disk \
source=/home/shial/Project \
path=/Project
检查:
incus config show arch
应该存在类似:
devices:
project:
path: /Project
source: /home/shial/Project
type: disk
11. 测试容器写入
进入容器:
incus exec arch -- bash
切换到映射用户:
su - project
检查:
id
应该:
uid=1000(project)
gid=10000(project_shared)
测试写入:
touch /Project/test.txt
echo hello > /Project/test.txt
检查:
ls -ln /Project/test.txt
应该看到:
1000 10000
而不是:
65534
这说明 UID/GID 映射已经正常。
12. 让宿主机 shial 也能写
解决容器写入之后,宿主机 shial 也需要访问共享目录。
宿主机 shial:
UID = 1000
GID = 1000
共享目录使用:
UID = 166536
GID = 175536
因此最简单的方法不是再次修改 owner,而是:
让宿主机用户
shial加入共享组project_shared。
宿主机执行:
sudo usermod -aG project_shared shial
然后退出当前登录会话并重新登录。
检查:
id shial
应该能看到:
project_shared
13. 确保共享组具有写权限
检查:
ls -ld /home/shial/Project
如果 group 没有写权限:
drwxr-xr-x
修改:
sudo chmod -R g+rwX /home/shial/Project
为了让新创建的目录继承 group:
sudo find /home/shial/Project \
-type d \
-exec chmod g+s {} \;
最终理想状态:
owner: 166536
group: 175536 (project_shared)
容器 project
↓
UID 1000 / GID 10000
↓
可以写入 /Project
宿主机 shial
↓
UID 1000
加入 project_shared
↓
可以写入 /home/shial/Project
14. 双向测试
宿主机 → 容器
宿主机:
echo "hello from host" \
> /home/shial/Project/host-test.txt
容器:
cat /Project/host-test.txt
应该输出:
hello from host
容器 → 宿主机
容器:
echo "hello from container" \
> /Project/container-test.txt
宿主机:
cat /home/shial/Project/container-test.txt
应该输出:
hello from container
15. 最终权限模型
最终采用:
宿主机
────────────────────────────────
shial
UID 1000
│
│ 加入 project_shared
↓
project_shared
GID 175536
│
│
↓
/home/shial/Project
UID 166536
GID 175536
│
│ Incus disk mount
↓
────────────────────────────────
容器
/Project
project
UID 1000
GID 10000
容器 UID 1000
↓
宿主机 UID 166536
容器 GID 10000
↓
宿主机 GID 175536
这样可以实现:
宿主机 shial ──┐
│
├── /Project 双向读写
│
容器 project ──┘
16. 关键经验
不要直接把共享目录改回 UID 1000
不要:
sudo chown -R 1000:1000 /home/shial/Project
因为对于当前 Incus UID 映射:
宿主机 UID 1000
并不是容器 UID 1000。
容器看到该 UID 时,文件很可能显示为:
nobody
不要把 nobody 当成真正的 owner
容器看到:
nobody
通常不是文件真的属于 nobody,而是:
宿主机 UID 无法映射到容器 UID。
应该先检查:
cat /proc/self/uid_map
cat /proc/self/gid_map
然后根据映射计算对应的宿主机 UID/GID。
当前环境最重要的计算公式
当前:
Container UID 0
↓
Host UID 165536
所以:
Host UID = 165536 + Container UID
例如:
Container UID 1000
→ Host UID 166536
GID 同理:
Container GID 10000
→ Host GID 175536
17. 最终常用命令速查
查看映射:
cat /proc/self/uid_map
cat /proc/self/gid_map
创建共享组:
sudo groupadd -g 175536 project_shared
创建映射用户:
sudo useradd -u 166536 -g 175536 -M -s /usr/bin/nologin project
共享目录:
sudo chown -R 166536:175536 /home/shial/Project
让宿主机用户加入共享组:
sudo usermod -aG project_shared shial
检查用户组:
id shial
给 group 写权限:
sudo chmod -R g+rwX /home/shial/Project
设置目录 SGID:
sudo find /home/shial/Project \
-type d \
-exec chmod g+s {} \;
Incus 挂载:
incus config device add arch project disk \
source=/home/shial/Project \
path=/Project
测试容器:
incus exec arch -- bash
su - project
touch /Project/test.txt
echo hello > /Project/test.txt
18. 总结
本次问题本质上不是 LXC/Incus 本身无法写文件,而是:
宿主机 UID/GID
↓
没有落在容器 UID/GID mapping 范围
↓
容器显示 nobody
↓
chown 无法完成
↓
开发工具 / npm / node_modules 报 Invalid argument
解决方法是:
- 确认 Incus 的 UID/GID 映射。
- 根据映射计算容器用户对应的宿主机 UID/GID。
- 创建对应的宿主机用户和共享组。
- 让共享目录使用这个 UID/GID。
- 容器内创建对应 UID/GID 的用户。
- 将宿主机
shial加入共享组。 - 确保共享组拥有目录写权限。
- 通过双向读写测试验证。
对于本机当前的映射:
Container UID 1000 → Host UID 166536
Container GID 10000 → Host GID 175536
因此共享目录最终采用:
166536:175536
宿主机 shial 加入:
project_shared (GID 175536)
即可同时满足:
宿主机 shial:可读写
+
Incus 容器:可读写