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

解决方法是:

  1. 确认 Incus 的 UID/GID 映射。
  2. 根据映射计算容器用户对应的宿主机 UID/GID。
  3. 创建对应的宿主机用户和共享组。
  4. 让共享目录使用这个 UID/GID。
  5. 容器内创建对应 UID/GID 的用户。
  6. 将宿主机 shial 加入共享组。
  7. 确保共享组拥有目录写权限。
  8. 通过双向读写测试验证。

对于本机当前的映射:

Container UID 1000 → Host UID 166536
Container GID 10000 → Host GID 175536

因此共享目录最终采用:

166536:175536

宿主机 shial 加入:

project_shared (GID 175536)

即可同时满足:

宿主机 shial:可读写
        +
Incus 容器:可读写