如何在 docker 容器中一个接一个地 运行 多个入口点脚本?

How to run multiple entrypoint scripts one after another inside docker container?

我正在尝试将主机 UID 与容器 UID 进行匹配,如下所示。

Docker 文件

RUN addgroup -g 1000 deploy \
&& adduser -D -u 1000 -G deploy -s /bin/sh deploy

USER deploy

COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
CMD ["php-fpm7","-F"]

entrypoint.sh

whoami # it outputs `deploy`  

# Change UID of 'deploy' as per host user UID
HOST_CURRENT_USER_ID=$(stat -c "%u" /var/www/${PROJECT_NAME})
if [ ${HOST_CURRENT_USER_ID} -ne 0 ]; then
    gosu root usermod -u ${HOST_CURRENT_USER_ID} deploy
    gosu root groupmod -g ${HOST_CURRENT_USER_ID} deploy
fi

whoami  # It outputs as unknown user id 1000. 

请注意上面 whoami 的输出。即使我将部署的 UID 更改为主机 uid,入口点脚本进程也不会更改,因为入口点 shell 已被 UID 1000 调用。

所以我想出了一个解决方案来制作两个入口点脚本,一个是更改 UID,另一个是用于容器的 bootstrap 进程,它将 运行 在一个单独的 shell 在我更改部署的 UID 之后。那么我怎样才能使两个入口点 运行 相继出现。例如

ENTRYPOINT ["/fix-uid.sh && /entrypoint.sh"]

您观察到的行为似乎相当正常:在您的入口点脚本中,您更改了与用户名 deploy 关联的 UID,但是两个 whoami 命令仍然 运行同一用户(首先由 UID 标识,而不是用户名)。

有关 Docker 上下文中 UID 和 GID 的更多信息,请参见例如that reference.

另请注意,使用 gosu 重新成为 root 不是标准做法(请特别参阅上游文档中的 warning)。

对于您的用例,我建议在最后删除 USER deploy 命令并切换用户,方法是按如下方式调整您的入口点脚本:

Dockerfile

(…)
RUN addgroup -g 1000 deploy \
&& adduser -D -u 1000 -G deploy -s /bin/sh deploy

COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
CMD ["php-fpm7","-F"]

entrypoint.sh

#!/bin/sh
whoami # it outputs `root`

# Change UID of 'deploy' as per host user UID
HOST_CURRENT_USER_ID=$(stat -c "%u" /var/www/${PROJECT_NAME})
if [ ${HOST_CURRENT_USER_ID} -ne 0 ]; then
    usermod -u ${HOST_CURRENT_USER_ID} deploy
    groupmod -g ${HOST_CURRENT_USER_ID} deploy
fi

# don't forget the "exec" builtin
exec gosu ${HOST_CURRENT_USER_ID}:${HOST_CURRENT_USER_ID} "$@"

this can be tested using id, for example:

$ docker build -t test-gosu .
$ docker run --rm -it test-gosu /bin/sh
  $ id

看起来您正在设计一个与我创建的解决方案非常相似的解决方案。正如 ErikMD 所提到的,不要使用 gosu 从用户切换到 root,你想要从 root 切换到用户。否则,您的容器内将存在一个开放的安全漏洞,任何用户都无法成为 root,从而违背了 运行将容器设置为不同用户 ID 的目的。


对于我放在一起的解决方案,无论容器是 运行 在生产中作为没有卷安装的用户,还是在开发中通过最初以 root 身份启动容器来安装卷,我都可以工作.您可以拥有一个相同的 Dockerfile,并将入口点更改为具有以下内容:

#!/bin/sh

if [ "$(id -u)" = "0" ]; then
  fix-perms -r -u deploy -g deploy /var/www/${PROJECT_NAME}
  exec gosu deploy "$@"
else
  exec "$@"
fi

上面的 fix-perms 脚本是 from my base image,包括以下代码:

# update the uid
if [ -n "$opt_u" ]; then
  OLD_UID=`getent passwd "${opt_u}" | cut -f3 -d:`
  NEW_UID=`ls -nd "" | awk '{print }'`
  if [ "$OLD_UID" != "$NEW_UID" ]; then
    echo "Changing UID of $opt_u from $OLD_UID to $NEW_UID"
    usermod -u "$NEW_UID" -o "$opt_u"
    if [ -n "$opt_r" ]; then
      find / -xdev -user "$OLD_UID" -exec chown -h "$opt_u" {} \;
    fi
  fi
fi

(请注意,我真的很喜欢您对 stat -c 的使用,并且可能会更新我的 fix-perms 脚本以利用我现在拥有的 ls 命令。)

其中的重要部分是 运行安装容器。当你需要 fix-perms 到 运行 的代码时(对我来说这只是在开发中),我以 root 身份启动容器。这可以是撰写文件中的 docker run -u root:root ...user: "root:root"。最初以 root 身份启动容器,这会触发入口点中 if/else 的前半部分 运行s fix-perms 然后 运行s a gosu deploy 删除在调用“$@”这是您的命令 (CMD) 之前从 root 部署。最终结果是容器中的 pid 1 现在 运行将您的命令作为部署用户执行。


顺便说一句,如果您真的想要一种更简单的方法来 运行 多个入口点片段,并且易于使用子图像进行扩展,我会使用一个 entrypoint.d 文件夹,该文件夹由我的基本图像中的入口点脚本。编写代码来实现该逻辑非常简单:

for ep in /etc/entrypoint.d/*.sh; do
  if [ -x "${ep}" ]; then
    echo "Running: ${ep}"
    "${ep}"
  fi
done

所有这些都可以看到,连同一个使用 nginx 的例子,在:https://github.com/sudo-bmitch/docker-base