Docker 和—— userns-remap,如何管理卷权限以在主机和容器之间共享数据?

在 docker 中,容器内创建的文件在从主机检查时往往具有不可预测的所有权。在默认情况下,卷上的文件的所有者是 root (uid 0) ,但是一旦容器中涉及到非 root 用户帐户并将其写入文件系统,从主机的角度来看,所有者或多或少会变得随机。

It is a problem when you need to access volume data from the host using the same user account which is calling the docker commands.

典型的变通方法是

  • 强制用户在创建 Dockerfiles 时使用 uID (不可移植)
  • 将主机用户的 UID 作为环境变量传递给 docker run命令,然后在入口点脚本中对卷运行一些 chown命令。

这两种解决方案都可以对容器外的实际权限提供一些控制。

我希望用户名称空间是这个问题的最终解决方案。我使用最近发布的版本1.10运行了一些测试,并将用户重新映射设置为我的桌面帐户。然而,我不确定它是否能使挂载卷上的文件所有权更容易处理,我担心它实际上可能正好相反。

假设我启动这个基本容器

docker run -ti -v /data debian:jessie /bin/bash
echo 'hello' > /data/test.txt
exit

然后检查来自主机的内容:

ls -lh /var/lib/docker/100000.100000/volumes/<some-id>/_data/


-rw-r--r-- 1 100000 100000 6 Feb  8 19:43 test.txt

This number '100000' is a sub-UID of my host user, but since it does not correspond to my user's UID, I still can't edit test.txt without privileges. This sub-user does not seem to have any affinity with my actual regular user outside of docker. It's not mapped back.

由于名称空间中出现了 UID->sub-UID映射,本文前面提到的在主机和容器之间对齐 UID 的解决方案不再有效。

那么,是否有一种方法可以在启用了用户名称空间的情况下运行 docker (为了提高安全性) ,同时仍然可以让运行 docker 的主机用户拥有卷上生成的文件?

49010 次浏览

如果您可以提前预先排列用户和组,那么就有可能以这种特定的方式分配 UID 和 GID,以便主机用户与容器中的名称空间用户相对应。

下面是一个例子(Ubuntu 14.04,Docker 1.10) :

  1. 创建一些具有固定数字 ID 的用户:

    useradd -u 5000 ns1
    
    
    groupadd -g 500000 ns1-root
    groupadd -g 501000 ns1-user1
    
    
    useradd -u 500000 -g ns1-root ns1-root
    useradd -u 501000 -g ns1-user1 ns1-user1 -m
    
  2. Manually edit auto-generated subordinate ID ranges in /etc/subuid and /etc/subgid files:

    ns1:500000:65536
    

    (注意,由于 /etc/login.defsMAX_UIDMAX_GID的限制,ns1-rootns1-user1没有记录)

  3. /etc/default/docker中启用用户名称空间:

    DOCKER_OPTS="--userns-remap=ns1"
    

    重新启动后台程序 service docker restart,确保创建了 /var/lib/docker/500000.500000目录。

    现在,在容器内部有 rootuser1,在主机上有 ns1-rootns1-user1,它们具有匹配的 ID

    更新: 保证非 root 用户在容器中有固定的 ID (例如 user11000:1000) ,在构建映像期间显式创建它们。

试驾:

  1. 准备卷目录

    mkdir /vol1
    chown ns1-root:ns1-root /vol1
    
  2. Try it from a container

    docker run --rm -ti -v /vol1:/vol1 busybox sh
    echo "Hello from container" > /vol1/file
    exit
    
  3. Try from the host

    passwd ns1-root
    login ns1-root
    cat /vol1/file
    echo "can write" >> /vol1/file
    

Not portable and looks like a hack, but works.

使用 docker cp指令可以避免权限问题。

所有权设置为目标用户和主要组。例如,用根用户的 UID:GID创建复制到容器的文件。复制到本地计算机的文件使用调用 docker cp命令的用户的 UID:GID创建。

下面的例子切换为使用 docker cp:

$ docker run -ti -v /data debian:jessie /bin/bash
root@e33bb735a70f:/# echo 'hello' > /data/test.txt
root@e33bb735a70f:/# exit
exit
$ docker volume ls
DRIVER              VOLUME NAME
local               f073d0e001fb8a95ad8d919a5680e72b21a457f62a40d671b63c62ae0827bf93
$ sudo ls -l /var/lib/docker/100000.100000/volumes/f073d0e001fb8a95ad8d919a5680e72b21a457f62a40d671b63c62ae0827bf93/_data
total 4
-rw-r--r-- 1 100000 100000 6 Oct  6 10:34 test.txt
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
e33bb735a70f        debian:jessie       "/bin/bash"         About a minute ago   Exited (0) About a minute ago                       determined_hypatia
$ docker cp determined_hypatia:/data/test.txt .
$ ls -l test.txt
-rw-r--r-- 1 don don 6 Oct  6 10:34 test.txt
$ cat test.txt
hello
$

但是,如果只想从容器中读取文件,则不需要命名卷。此示例使用命名容器而不是命名卷:

$ docker run -ti --name sandbox1 debian:jessie /bin/bash
root@93d098233cf3:/# echo 'howdy' > /tmp/test.txt
root@93d098233cf3:/# exit
exit
$ docker cp sandbox1:/tmp/test.txt .
$ ls -l test.txt
-rw-r--r-- 1 don don 6 Oct  6 10:52 test.txt
$ cat test.txt
howdy
$

当我想要将文件复制到容器中时,我发现命名卷非常有用,如 这个问题中所述。

一种解决方案是在构建时动态分配用户的 uid 以匹配主机。

例子 Dockerfile:

FROM ubuntu
# Defines argument which can be passed during build time.
ARG UID=1000
# Create a user with given UID.
RUN useradd -d /home/ubuntu -ms /bin/bash -g root -G sudo -u $UID ubuntu
# Switch to ubuntu user by default.
USER ubuntu
# Check the current uid of the user.
RUN id
# ...

然后按以下方式构建:

docker build --build-arg UID=$UID -t mycontainer .

and run as:

docker run mycontainer

如果您已经有了容器,那么创建一个包装容器,其中包含以下 Dockerfile:

FROM someexistingcontainer
ARG UID=1000
USER root
# This assumes you've the existing user ubuntu.
RUN usermod -u $UID ubuntu
USER ubuntu

这可以用 docker-compose.yml包装,比如:

version: '3.4'
services:
myservice:
command: id
image: myservice
build:
context: .
volumes:
- /data:/data:rw

然后按以下方式构建和运行:

docker-compose build --build-arg UID=$UID myservice; docker-compose run myservice

在某些情况下,Docker 映像被广泛共享,无法修改。我一直在使用“蛮力”的方法,动态地在容器内创建一个匹配的用户每次启动时都会切换到它。不可否认,它很丑陋,但它只需要50行代码,而且一直运行良好:

Https://github.com/thesofproject/sof/blob/0a4b1d62d5b31b41/scripts/sudo-cwd.sh