在Docker容器中以主机用户身份运行

42

在我的团队中,我们使用Docker容器在本地运行网站应用程序,同时进行开发。

假设我正在开发一个 Flask 应用程序,并在 app.py 中使用了 requirements.txt 中的依赖项,则工作流大致如下:

# I am "robin" and I am in the docker group
$ whoami
robin
$ groups
robin docker

# Install dependencies into a docker volume
$ docker run -ti -v `pwd`:`pwd` -w `pwd` -v pydeps:/usr/local python:3-slim pip install -r requirements.txt
Collecting Flask==0.12.2 (from -r requirements.txt (line 1))
# ... etc.

# Run the app using the same docker volume
$ docker run -ti -v `pwd`:`pwd` -w `pwd` -v pydeps:/usr/local -e FLASK_APP=app.py -e FLASK_DEBUG=true -p 5000:5000 python:3-slim flask run -h 0.0.0.0
 * Serving Flask app "app"
 * Forcing debug mode on
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 251-131-649

现在我们有一个本地服务器运行我们的应用程序,我们可以对本地文件进行更改,当需要时服务器将刷新。

在上面的示例中,应用程序最终以root用户身份运行。这不是问题,除非应用程序将文件写回工作目录。如果确实如此,我们可能会在工作目录中拥有由root拥有的文件(例如cache.sqlitedebug.log)。这给我们团队的用户造成了许多问题。

对于我们的其他应用程序,我们通过使用主机用户的UID和GID来运行应用程序来解决这个问题 - 例如,对于Django应用程序:

$ docker run -ti -u `id -u`:`id -g` -v `pwd`:`pwd` -w `pwd` -v pydeps:/usr/local -p 8000:8000 python:3-slim ./manage.py runserver
在这种情况下,应用程序将作为容器内ID为1000、不存在的用户运行,但任何写入主机目录的文件都将正确归属于robin用户。在Django中可以正常使用。
然而,在Flask中,以不存在的用户身份运行会被拒绝(在调试模式下)。
$ docker run -ti -u `id -u`:`id -g` -v `pwd`:`pwd` -w `pwd` -v pydeps:/usr/local -e FLASK_APP=app.py -e FLASK_DEBUG=true -p 5000:5000 python:3-slim flask run -h 0.0.0.0
 * Serving Flask app "app"
 * Forcing debug mode on
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
Traceback (most recent call last):
...
  File "/usr/local/lib/python3.6/getpass.py", line 169, in getuser
    return pwd.getpwuid(os.getuid())[0]
KeyError: 'getpwuid(): uid not found: 1000'

请问有没有方法可以让Flask不担心未分配的用户ID,或者在运行时将用户ID动态分配给用户名,又或者允许Docker应用作为主机用户创建文件?

  • 我目前只想到了一个超级hacky的解决方案:更改docker镜像中/etc/passwd文件的权限为全局可写,然后在运行时向该文件添加一行以将新的UID/GID对分配给用户名。
3个回答

50

你可以共享主机的密码文件:

docker run -ti -v /etc/passwd:/etc/passwd -u `id -u`:`id -g` -v `pwd`:`pwd` -w `pwd` -v pydeps:/usr/local -p 8000:8000 python:3-slim ./manage.py runserver

或者,使用useradd将用户添加到镜像中,在同样的方式下使用/etc作为卷:

docker run -v etcvol:/etc python..... useradd -u `id -u` $USER

(在 Docker 收到命令之前,宿主机 shell 中已经解析了 id -u 和 $USER)


3
当系统允许我的时候,我会在13个小时后授予你赏金。 - Robin Winslow
很高兴知道它有用! - Robert
建议的技巧假定数字用户ID(图像使用的ID和运行图像时使用的ID)与主机/etc/passwd中的ID相符。如果它们不匹配(通常我会这样期望),那么解决方法也不会是安全的。我们应该了解以相同用户身份运行的吸引力,以及如何通过抽象化用户名称来内化这种冲动。 - eel ghEEz
忽略用户名称是抽象化的第一步,docker run -v "${HOME}:/root" -w "/root" SERVER/IMAGE:VERSION env HOME="/root" COMMAND。但是,运行后需要进行清理,因为 docker 守护进程将作为 root 写入 ${HOME} - eel ghEEz
我需要 passwdshadow,就像这样:-v /etc/passwd:/etc/passwd -v /etc/shadow:/etc/shadow - pestophagous
显示剩余3条评论

6

我刚遇到这个问题,找到了一个不同的解决方法。

来自getpass.py

def getuser():
    """Get the username from the environment or password database.

    First try various environment variables, then the password
    database.  This works on Windows as long as USERNAME is set.

    """


    for name in ('LOGNAME', 'USER', 'LNAME', 'USERNAME'):
        user = os.environ.get(name)
        if user:
            return user


    # If this fails, the exception will "explain" why
    import pwd
    return pwd.getpwuid(os.getuid())[0]

如果没有设置以下任何环境变量: LOGNAMEUSERLNAMEUSERNAME,才会调用 getpwuid()

设置其中任何一个将允许容器启动。

$ docker run -ti -e USER=someuser ...

在我的情况下,调用 getuser() 的请求似乎来自于 Werkzeug 库试图生成调试器 pin 码。

4
谢谢。这几乎可以肯定是最“正确”的回答。尤其当你看看Werkzeug实际上使用用户名做什么,它是非常琐碎的,并且完全不依赖于值的“正确性”。读者应避免将 /etc/passwd 进行绑定挂载或在Docker容器内创建用户。这些都是非常糟糕的解决方案。 - Kye

0

如果您可以使用另一个Python包来启动容器,也许您想使用我的Python包https://github.com/boon-code/docker-inside,它会覆盖入口点并即时在容器中创建您的用户...

docker-inside -v `pwd`:`pwd` -w `pwd` -v pydeps:/usr/local -e FLASK_APP=app.py -e FLASK_DEBUG=true -p 5000:5000 python:3-slim -- flask run -h 0.0.0.0

如果您想坚持使用Docker CLI,那么在命令行上覆盖入口点并传递一个创建用户的脚本也可能适合您。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接