루트리스 모드 트러블슈팅

루트리스 모드 트러블슈팅

Docker 루트리스(Rootless) 모드를 사용하다 보면 배포판별 설정, 커널 파라미터, 네트워크 드라이버 등 다양한 환경 요인 때문에 예상치 못한 오류를 만나게 됩니다. 이 문서에서는 배포판별로 필요한 추가 설정과 자주 발생하는 오류들의 원인 및 해결 방법을 하나씩 짚어드릴게요. 코드 블록과 명령어는 그대로 따라 하시면 됩니다.

출처: 공식문서

본문

배포판별 추가 설정 힌트

Ubuntu

Ubuntu 24.04 이상 버전에서는 기본적으로 제한된 비특권 사용자 네임스페이스(restricted unprivileged user namespaces)가 활성화되어 있습니다. 이 때문에 AppArmor 프로필이 비특권 프로세스의 사용자 네임스페이스 생성을 허용하도록 설정되어 있지 않으면, 일반 사용자 권한으로는 사용자 네임스페이스를 만들 수 없습니다.

deb 패키지로 docker-ce-rootless-extras를 설치했다면(apt-get install docker-ce-rootless-extras), rootlesskit용 AppArmor 프로필이 이미 apparmor deb 패키지에 포함되어 있어서 별도로 설정할 필요가 없습니다. 하지만 설치 스크립트로 루트리스 확장 기능을 설치한 경우에는 rootlesskit용 AppArmor 프로필을 직접 추가해야 합니다.

  1. 현재 로그인한 사용자의 AppArmor 프로필을 생성하고 설치합니다.
$ filename=$(echo $HOME/bin/rootlesskit | sed -e 's@^/@@' -e 's@/@.@g')
$ [ ! -z "${filename}" ] && sudo cat <<EOF > /etc/apparmor.d/${filename}
abi <abi/4.0>,
include <tunables/global>

"$HOME/bin/rootlesskit" flags=(unconfined) {
 userns,

 include if exists <local/${filename}>
}
EOF
  1. AppArmor를 재시작합니다.
$ systemctl restart apparmor.service

Arch Linux

/etc/sysctl.conf(또는 /etc/sysctl.d)에 kernel.unprivileged_userns_clone=1을 추가하고 sudo sysctl --system을 실행하세요.

openSUSE 및 SLES

sudo modprobe ip_tables iptable_mangle iptable_nat iptable_filter 명령이 필요합니다. 다른 배포판에서도 설정에 따라 이 명령이 필요할 수 있습니다.

openSUSE 15와 SLES 15에서 동작이 확인되었습니다.

CentOS, RHEL 및 Fedora

RHEL 8 및 유사한 배포판에서는 fuse-overlayfs 설치를 권장합니다. sudo dnf install -y fuse-overlayfs를 실행하세요. RHEL 9 및 유사한 배포판에서는 이 단계가 필요하지 않습니다.

sudo dnf install -y iptables가 필요할 수도 있습니다.

알려진 제한 사항

  • 지원되는 스토리지 드라이버는 다음뿐입니다:
    • overlay2 (커널 5.11 이상에서만)
    • fuse-overlayfs (커널 4.18 이상이고 fuse-overlayfs가 설치된 경우에만)
    • btrfs (커널 4.18 이상이거나, ~/.local/share/dockeruser_subvol_rm_allowed 마운트 옵션으로 마운트된 경우에만)
    • vfs
  • cgroup은 cgroup v2와 systemd를 함께 사용할 때만 지원됩니다. 리소스 제한 문서를 참고하세요.
  • 다음 기능은 지원되지 않습니다:
    • AppArmor
    • Checkpoint
    • Overlay network
    • SCTP 포트 노출
  • ping 명령을 사용하려면 ping 패킷 라우팅 문서를 참고하세요.
  • 특권 TCP/UDP 포트(1024 미만)를 노출하려면 특권 포트 노출 문서를 참고하세요.
  • docker inspect에 표시되는 IPAddress는 RootlessKit의 네트워크 네임스페이스 안에 한정되어 있습니다. 즉, 호스트에서 nsenter로 해당 네트워크 네임스페이스에 들어가지 않는 한 이 IP 주소로는 접근할 수 없습니다.
  • docker run -p를 사용한 포트 포워딩은 기본적으로 소스 IP 주소를 전달하지 않습니다. 소스 IP 전파를 활성화하려면 docker run -p does not propagate source IP addresses를 참고하세요.
  • NFS 마운트를 Docker의 "data-root"로 사용하는 것은 지원되지 않습니다. 이 제한은 루트리스 모드에만 해당하는 것은 아닙니다.
  • --cap-add로 추가한 capabilities는 컨테이너의 사용자 네임스페이스가 관리하는 리소스에만 적용됩니다. 호스트나 다른 전역 리소스에 대한 권한을 부여하지는 않습니다. 따라서 초기 사용자 네임스페이스에서 capabilities가 필요한 작업은 여전히 실패할 수 있습니다.

과거 버전의 제한 사항

Docker Engine v29.5 이전

  • 호스트 네트워크(docker run --net=host)가 RootlessKit 내부 네임스페이스에 한정되어 있었습니다. 즉, --net=host로 컨테이너가 리슨하는 포트는 실제 호스트 네트워크 네임스페이스에서 접근할 수 없었습니다.

트러블슈팅

systemd가 있는 시스템에서 systemd로 설치하지 못하는 경우

$ dockerd-rootless-setuptool.sh install
[INFO] systemd not detected, dockerd-rootless.sh needs to be started manually:
...

sudo su로 사용자를 전환하면 rootlesskit이 systemd를 제대로 감지하지 못합니다. 로그인할 수 없는 사용자의 경우 systemd-container 패키지에 포함된 machinectl 명령을 사용해야 합니다. systemd-container를 설치한 후 다음 명령으로 myuser로 전환하세요:

$ sudo machinectl shell myuser@

여기서 myuser@는 원하는 사용자 이름이고, @는 이 머신을 의미합니다.

Docker 데몬 시작 시 발생하는 오류

[rootlesskit:parent] error: failed to start the child: fork/exec /proc/self/exe: operation not permitted

이 오류는 대부분 /proc/sys/kernel/unprivileged_userns_clone 값이 0으로 설정되어 있을 때 발생합니다:

$ cat /proc/sys/kernel/unprivileged_userns_clone
0

이 문제를 해결하려면 /etc/sysctl.conf(또는 /etc/sysctl.d)에 kernel.unprivileged_userns_clone=1을 추가하고 sudo sysctl --system을 실행하세요.

[rootlesskit:parent] error: failed to start the child: fork/exec /proc/self/exe: no space left on device

이 오류는 대부분 /proc/sys/user/max_user_namespaces 값이 너무 작을 때 발생합니다:

$ cat /proc/sys/user/max_user_namespaces
0

이 문제를 해결하려면 /etc/sysctl.conf(또는 /etc/sysctl.d)에 user.max_user_namespaces=28633을 추가하고 sudo sysctl --system을 실행하세요.

[rootlesskit:parent] error: failed to setup UID/GID map: failed to compute uid/gid map: No subuid ranges found for user 1001 ("testuser")

이 오류는 /etc/subuid/etc/subgid가 설정되지 않았을 때 발생합니다. 사전 요구 사항 문서를 참고하세요.

could not get XDG_RUNTIME_DIR

이 오류는 $XDG_RUNTIME_DIR이 설정되지 않았을 때 발생합니다.

systemd가 없는 호스트에서는 디렉터리를 만든 다음 경로를 설정해야 합니다:

$ export XDG_RUNTIME_DIR=$HOME/.docker/xrd
$ rm -rf $XDG_RUNTIME_DIR
$ mkdir -p $XDG_RUNTIME_DIR
$ dockerd-rootless.sh

참고

로그아웃할 때마다 이 디렉터리를 반드시 제거해야 합니다.

systemd 호스트에서는 pam_systemd를 통해 호스트에 로그인하세요(아래 참고). 값은 자동으로 /run/user/$UID로 설정되며 로그아웃할 때마다 정리됩니다.

systemctl --user 실행 시 "Failed to connect to bus: No such file or directory" 오류

이 오류는 대부분 sudo로 root 사용자에서 일반 사용자로 전환했을 때 발생합니다:

# sudo -iu testuser
$ systemctl --user start docker
Failed to connect to bus: No such file or directory

sudo -iu <USERNAME> 대신 pam_systemd를 통해 로그인해야 합니다. 예를 들어:

  • 그래픽 콘솔로 로그인
  • ssh <USERNAME>@localhost
  • machinectl shell <USERNAME>@

데몬이 자동으로 시작되지 않는 경우

데몬이 자동으로 시작되게 하려면 sudo loginctl enable-linger $(whoami) 명령이 필요합니다. 고급 사용법 문서를 참고하세요.

docker pull 오류

docker: failed to register layer: Error processing tar file(exit status 1): lchown : invalid argument

이 오류는 /etc/subuid 또는 /etc/subgid의 사용 가능한 항목 수가 충분하지 않을 때 발생합니다. 필요한 항목 수는 이미지마다 다르지만, 대부분의 이미지에는 65,536개 항목이면 충분합니다. 사전 요구 사항 문서를 참고하세요.

docker: failed to register layer: ApplyLayer exit status 1 stdout: stderr: lchown : operation not permitted

이 오류는 대부분 ~/.local/share/docker가 NFS에 있을 때 발생합니다.

해결 방법으로 ~/.config/docker/daemon.json에서 NFS가 아닌 data-root 디렉터리를 다음과 같이 지정할 수 있습니다:

{"data-root":"/somewhere-out-of-nfs"}

docker run 오류

docker: Error response from daemon: OCI runtime create failed: ...: read unix @->/run/systemd/private: read: connection reset by peer: unknown.

이 오류는 cgroup v2 호스트에서 사용자용 dbus 데몬이 실행 중이지 않을 때 대부분 발생합니다.

$ systemctl --user is-active dbus
inactive

$ docker run hello-world
docker: Error response from daemon: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:385: applying cgroup configuration for process caused: error while starting unit "docker
-931c15729b5a968ce803784d04c7421f791d87e5ca1891f34387bb9f694c488e.scope" with properties [{Name:Description Value:"libcontainer container 931c15729b5a968ce803784d04c7421f791d87e5ca1891f34387bb9f694c488e"} {Name:Slice Value:"use
r.slice"} {Name:PIDs Value:@au [4529]} {Name:Delegate Value:true} {Name:MemoryAccounting Value:true} {Name:CPUAccounting Value:true} {Name:IOAccounting Value:true} {Name:TasksAccounting Value:true} {Name:DefaultDependencies Val
ue:false}]: read unix @->/run/systemd/private: read: connection reset by peer: unknown.

이 문제를 해결하려면 sudo apt-get install -y dbus-user-session 또는 sudo dnf install -y dbus-daemon을 실행한 후 다시 로그인하세요.

여전히 오류가 발생한다면 systemctl --user enable --now dbus(sudo 없이)를 실행해 보세요.

--cpus, --memory, --pids-limit 플래그가 무시되는 경우

이것은 cgroup v1 모드에서 예상된 동작입니다. 이 플래그들을 사용하려면 호스트가 cgroup v2를 활성화하도록 구성되어야 합니다. 자세한 내용은 리소스 제한 문서를 참고하세요.

네트워킹 오류

이 섹션에서는 루트리스 모드의 네트워킹 트러블슈팅 팁을 제공합니다.

루트리스 모드의 네트워킹은 RootlessKit의 네트워크 드라이버와 포트 드라이버를 통해 지원됩니다. 네트워크 성능과 특성은 사용하는 네트워크 드라이버와 포트 드라이버의 조합에 따라 달라집니다. 네트워킹 관련 예기치 않은 동작이나 성능 문제가 발생한다면 RootlessKit이 지원하는 구성과 비교한 다음 표를 확인해 보세요:

네트워크 드라이버 포트 드라이버 네트워크 처리량 포트 처리량 소스 IP 전파 SUID 불필요 비고
gvisor-tap-vsock builtin 느림 빠름 ✅ ✅ (*) slirp4netns가 설치되지 않은 경우의 기본값
slirp4netns builtin 느림 빠름 ✅ ✅ (*) slirp4netns가 설치된 경우의 기본값
vpnkit builtin 느림 빠름 ✅ ✅ (*) 레거시
gvisor-tap-vsock gvisor-tap-vsock 느림 느림 비권장. builtin 포트 드라이버를 사용하세요.
slirp4netns slirp4netns 느림 느림
pasta implicit 느림 빠름 ✅ 실험적; pasta 버전 2023_12_04 이상 필요
pasta pesto IPv4 전용; Docker Engine 29.8+ 필요
lxc-user-nic builtin 빠름 ✅ 빠름 ✅ ✅ (*) 실험적
bypass4netns bypass4netns 빠름 ✅ 빠름 ✅ 참고: 커스텀 seccomp 프로필이 필요하므로 RootlessKit에 통합되지 않음

(*) RootlessKit v3.0부터 적용됩니다. 또한 userland-proxy를 비활성화해야 합니다.

특정 네트워킹 문제에 대한 트러블슈팅 정보는 다음을 참고하세요:

docker run -p 실행 시 cannot expose privileged port 오류

호스트 포트로 특권 포트(1024 미만)를 지정하면 docker run -p가 다음 오류와 함께 실패합니다.

$ docker run -p 80:80 nginx:alpine
docker: Error response from daemon: driver failed programming external connectivity on endpoint focused_swanson (9e2e139a9d8fc92b37c36edfa6214a6e986fa2028c0cc359812f685173fa6df7): Error starting userland proxy: error while calling PortManager.AddPort(): cannot expose privileged port 80, you might need to add "net.ipv4.ip_unprivileged_port_start=0" (currently 1024) to /etc/sysctl.conf, or set CAP_NET_BIND_SERVICE on rootlesskit binary, or choose a larger port number (>= 1024): listen tcp 0.0.0.0:80: bind: permission denied.

이 오류가 발생하면 특권이 없는 포트를 사용하는 것을 고려해 보세요. 예를 들어 80 대신 8080을 사용하는 것입니다.

$ docker run -p 8080:80 nginx:alpine

특권 포트 노출을 허용하려면 특권 포트 노출 문서를 참고하세요.

Ping이 동작하지 않는 경우

/proc/sys/net/ipv4/ping_group_range1 0으로 설정되어 있으면 ping이 동작하지 않습니다:

$ cat /proc/sys/net/ipv4/ping_group_range
1 0

자세한 내용은 ping 패킷 라우팅 문서를 참고하세요.

docker inspect에 표시된 IPAddress에 접근할 수 없는 경우

데몬이 RootlessKit의 네트워크 네임스페이스 안에 있기 때문에 이는 예상된 동작입니다. 대신 docker run -p를 사용하세요.

--net=host가 호스트 네트워크 네임스페이스에서 포트를 리슨하지 않는 경우

Docker Engine v29.5까지는 데몬이 RootlessKit의 네트워크 네임스페이스 안에 있었기 때문에 예상된 동작이었습니다. 대신 docker run -p를 사용하거나 Docker Engine v29.5 이상으로 업그레이드하세요.

네트워크가 느린 경우

루트리스 모드의 Docker는 사용자 모드에서 실행되는 TCP/IP 스택을 사용합니다. 예를 들면:

사용자 모드의 TCP/IP 스택은 일반적으로 커널 모드의 스택보다 느리며, 사용하는 네트워크 드라이버에 따라 성능이 달라질 수 있습니다.

자세한 내용은 RootlessKit 문서를 참고하세요.

해결 방법 1: 사용자 모드 TCP/IP 스택 우회

docker run --net=host를 사용하면 사용자 모드 TCP/IP 스택을 우회할 수 있습니다. Docker Engine v29.5부터 적용 가능합니다. 다만 컨테이너가 호스트 네트워크 네임스페이스를 공유해야 하므로 보안상 바람직하지 않을 수 있습니다.

해결 방법 2: 사용자 모드 TCP/IP 스택 비활성화

또는 lxc-user-nic 네트워크 드라이버(실험적)를 사용하여 사용자 모드 TCP/IP 스택을 완전히 비활성화할 수 있습니다. 단, 특권 헬퍼를 활성화하려면 /etc/lxc/lxc-usernet을 구성해야 합니다.

sudo apt-get install -y lxc
sudo mkdir -p /etc/lxc
cat <<EOF | sudo tee /etc/lxc/lxc-usernet
# USERNAME TYPE BRIDGE COUNT
$USER veth lxcbr0 10
EOF

또한 루트풀 데몬이 실행 중이지 않은지 확인하세요. 루트풀 데몬의 iptables 규칙이 lxc-user-nic 드라이버와 충돌할 수 있습니다.

$ systemctl is-active docker.service
inactive

$ systemctl is-active docker.socket
inactive

네트워크 드라이버는 ~/.config/systemd/user/docker.service.d/override.conf 파일을 만들고 다음 내용을 넣어 지정할 수 있습니다:

[Service]
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET=lxc-user-nic"
# Optional: specify MTU (may affect throughput)
# Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=<INTEGER>"

그런 다음 데몬을 재시작합니다:

systemctl --user daemon-reload
systemctl --user restart docker

docker run -p가 소스 IP 주소를 전달하지 않는 경우

RootlessKit v3.0 이상

Docker Engine의 userland-proxy가 RootlessKit의 소스 IP 전파와 호환되지 않기 때문입니다.

userland-proxy를 비활성화하려면 ~/.config/docker/daemon.json에 다음 구성을 추가하세요:

{"userland-proxy": false}

그런 다음 데몬을 재시작합니다:

systemctl --user restart docker

br_netfilter 커널 모듈을 로드해야 할 수도 있습니다:

sudo tee /etc/modules-load.d/docker.conf <<EOF >/dev/null
br_netfilter
EOF

sudo systemctl restart systemd-modules-load.service

이전 버전

RootlessKit의 builtin 포트가 v3.0까지는 소스 IP 전파를 지원하지 않았기 때문입니다. 소스 IP 전파를 활성화하려면 다음 방법을 사용할 수 있습니다:

  • slirp4netns RootlessKit 포트 드라이버 사용
  • pasta RootlessKit 네트워크 드라이버를 implicit 포트 드라이버와 함께 사용

pasta 네트워크 드라이버는 실험적이지만 slirp4netns 포트 드라이버보다 향상된 처리량 성능을 제공합니다. pasta 드라이버는 Docker Engine 25.0 이상이 필요합니다.

RootlessKit 네트워킹 구성을 변경하려면:

  1. ~/.config/systemd/user/docker.service.d/override.conf 파일을 생성합니다.

  2. 사용하려는 구성에 따라 다음 내용을 추가합니다:

    • slirp4netns
    [Service]
    Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET=slirp4netns"
    Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns"
    
    • pasta 네트워크 드라이버 + implicit 포트 드라이버
    [Service]
    Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET=pasta"
    Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=implicit"
    
  3. 데몬을 재시작합니다:

$ systemctl --user daemon-reload
$ systemctl --user restart docker

RootlessKit의 네트워킹 옵션에 대한 자세한 내용은 다음을 참고하세요:

디버깅 팁

dockerd 네임스페이스 안으로 들어가기

dockerd-rootless.sh 스크립트는 자체 사용자, 마운트, 네트워크 네임스페이스 안에서 dockerd를 실행합니다.

디버깅을 위해 nsenter -U --preserve-credentials -n -m -t $(cat $XDG_RUNTIME_DIR/docker.pid) 명령으로 네임스페이스 안에 들어갈 수 있습니다.

설치 제거

Docker 데몬의 systemd 서비스를 제거하려면 dockerd-rootless-setuptool.sh uninstall을 실행하세요:

$ dockerd-rootless-setuptool.sh uninstall
+ systemctl --user stop docker.service
+ systemctl --user disable docker.service
Removed /home/testuser/.config/systemd/user/default.target.wants/docker.service.
[INFO] Uninstalled docker.service
[INFO] This uninstallation tool does NOT remove Docker binaries and data.
[INFO] To remove data, run: `/usr/bin/rootlesskit rm -rf /home/testuser/.local/share/docker`

~/.bashrc에 PATH와 DOCKER_HOST 환경 변수를 추가했다면 해당 변수를 해제하세요.

데이터 디렉터리를 제거하려면 rootlesskit rm -rf ~/.local/share/docker를 실행하세요.

바이너리를 제거하려면 패키지 관리자로 Docker를 설치한 경우 docker-ce-rootless-extras 패키지를 제거하세요. https://get.docker.com/rootless로 Docker를 설치한 경우(패키지 없이 설치), ~/bin 아래의 바이너리 파일을 제거하세요:

$ cd ~/bin
$ rm -f containerd containerd-shim containerd-shim-runc-v2 ctr docker docker-init docker-proxy dockerd dockerd-rootless-setuptool.sh dockerd-rootless.sh rootlesskit rootlesskit-docker-proxy runc vpnkit

더 알아보기