如何让主机和容器使用 Docker 读/写相同的文件?

San*_*ing 2 javascript python linux docker

我想将一个目录从 Docker 容器批量挂载到我的工作站,所以当我从我的工作站编辑卷挂载中的内容时,它也会在容器中更新。一般而言,它对于测试和开发 Web 应用程序非常有用。

但是我在容器中获得了拒绝的权限,因为容器和主机中的 UID 不同。Docker 的初衷不就是让开发更快更容易吗?

这个答案解决了我在将 Docker 容器卷安装到我的工作站时面临的问题。但是通过这样做,我对生产中不需要的容器进行了更改,这违背了在开发期间使用 Docker 的目的。

容器是Alpine Linux、工作站Fedora 29 和编辑器Atom

还有另一种方法,这样我的工作站和容器都可以读/写相同的文件吗?

BMi*_*tch 9

有多种方法可以做到这一点,但核心问题是绑定挂载不包括任何 UID 映射功能,主机上的 UID 是出现在容器内的内容,反之亦然。如果这两个 UID 不匹配,您将使用不同的 UID 读取/写入文件,并且可能会遇到权限问题。


选项 1:获取 Mac 或在 VirtualBox 内部署 docker。这两种环境都有一个文件系统集成,可以动态更新 UID。对于 Mac,这是使用OSXFS实现的。请注意,这种便利会带来性能损失。


选项 2:更改您的主机。如果主机上的 UID 与容器内的 UID 匹配,您将不会遇到任何问题。您只需在主机上的用户上运行 usermod 以更改您的 UID,事情就会发生,至少在您在容器内运行具有不同 UID 的不同图像之前。


选项 3:更改您的图像。有些人会将图像修改为与其环境匹配的静态 UID,通常是为了匹配生产中的 UID。其他人将传递一个 build arg 与类似--build-arg UID=$(id -u)作为构建命令的一部分,然后 Dockerfile 与类似的东西:

FROM alpine
ARG UID=1000
RUN adduser -u ${UID} app
Run Code Online (Sandbox Code Playgroud)

这样做的缺点是每个开发人员可能需要不同的图像,因此他们要么在每个工作站上本地构建,要么您集中构建多个图像,一个用于开发人员之间存在的每个 UID。这两者都不是理想的。


选项 4:更改容器 UID。这可以在撰写文件中完成,也可以在一次性容器中完成,例如docker run -u $(id -u) your_image. 容器现在将使用新的 UID 运行,并且可以访问卷中的文件。但是,容器内的用户名不一定会映射到您的 UID,这对于您在容器内运行的任何命令来说可能看起来很奇怪。更重要的是,容器内用户拥有的任何文件,您没有隐藏在您的卷中,都将具有原始 UID,并且可能无法访问。


选项 5:放弃,以 root 身份运行所有内容,或将权限更改为 777,允许所有人无限制地访问该目录。这不会映射到您应该如何在生产中运行事物,并且容器可能仍会写入具有有限权限的新文件,使您无法在容器外部访问它们。这也会造成以 root 身份运行代码或让文件系统对主机上的任何用户进行读写的安全风险。


选项 6:设置一个动态更新容器的入口点。尽管不想更改您的图像,但这是我首选的完整性解决方案。您的容器确实需要以 root 身份启动,但仅限于开发阶段,并且应用程序仍将以用户身份运行,与生产环境相匹配。但是,该入口点的第一步是更改容器内用户的 UID/GID 以匹配您卷的 UID/GID。这类似于选项 4,但现在图像中未被卷替换的文件具有正确的 UID,容器内的用户现在将显示更改的 UID,因此命令如下ls显示容器内的用户名,而不是 UID 可能映射到另一个用户或根本没有用户。虽然这是对您的映像的更改,但代码仅在开发中运行,并且仅作为为该开发人员设置容器的简短入口点,之后容器内的过程看起来与生产环境中的过程相同。

为了实现这一点,我进行了以下更改。首先,Dockerfile 现在包含一个 fix-perms 脚本和来自我推送到集线器的基本映像的 gosu(这是一个 Java 示例,但更改可移植到其他环境):

FROM openjdk:jdk as build
# add this copy to include fix-perms and gosu or install them directly
COPY --from=sudobmitch/base:scratch / /
RUN  apt-get update \
 &&  apt-get install -y maven \
 &&  useradd -m app
COPY code /code
RUN  mvn build
# add an entrypoint to call fix-perms
COPY entrypoint.sh /usr/bin/
ENTRYPOINT ["/usr/bin/entrypoint.sh"]
CMD ["java", "-jar", "/code/app.jar"]
USER app
Run Code Online (Sandbox Code Playgroud)

entrypoint.sh 脚本调用 fix-perms 然后 exec 和 gosu 从 root 删除到应用程序用户:

#!/bin/sh
if [ "$(id -u)" = "0" ]; then
  # running on a developer laptop as root
  fix-perms -r -u app -g app /code
  exec gosu app "$@"
else
  # running in production as a user
  exec "$@"
fi
Run Code Online (Sandbox Code Playgroud)

开发者撰写文件挂载卷并以 root 身份启动:

version: '3.7'
volumes:
  m2:
services:
  app:
    build:
      context: .
      target: build
    image: registry:5000/app/app:dev
    command: "/bin/sh -c 'mvn build && java -jar /code/app.jar'"
    user: "0:0"
    volumes:
    - m2:/home/app/.m2
    - ./code:/code
Run Code Online (Sandbox Code Playgroud)

这个例子取自我在此处提供的演示文稿:https : //sudo-bmitch.github.io/presentations/dc2019/tips-and-tricks-of-the-captains.html#fix-perms

fix-perms 和其他示例的代码可在我的基本映像存储库中找到:https : //github.com/sudo-bmitch/docker-base