如何在 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
我正在尝试将主机 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