前言
我想编写一个通用的 shell 脚本,它将 docker 主机的正确 IP 地址添加到/etc/hosts
容器内部。
你可能会建议我将我想要访问的服务也放在另一个 docker 容器中,但是为了对 CI 进行最快的测试,我想通过容器的 ip 访问 docker 主机,这就是我需要它的原因。
有一个github上的问题有很多评论人们要求制定统一的方式来访问主机IP。没有适用于所有平台 ATM 的通用解决方案。
人们制作这样的脚本:
grep dockerhost /etc/hosts || echo $(ip r | grep ^default | cut -d" " -f3) dockerhost >> /etc/hosts
为了访问 docker 主机,我想使用相同的方法,但它不适用于 Mac atm。返回的结果为:
ip r | grep ^default | cut -d" " -f3
如果 Docker 主机是 Mac ,则会给出错误的答案172.17.0.1
(您实际上无法使用 容器从容器访问您的 Mac 172.17.0.1
)。
这就是为什么 Docker for Mac 中有一个功能 -特殊主机名 docker.for.mac.localhost
您可以使用它从容器访问您的主机 Mac。
问题
我想编写一个通用的sh
ell 脚本,它首先尝试解析docker.for.mac.localhost
并将解析的地址添加到/etc/hosts
.如果没有(这意味着 docker 主机是 windows 或 linux,而不是 Mac),请使用该ip r | grep ^default | cut -d" " -f3
方法并添加到/etc/hosts
。
问题是:我如何尝试docker.for.mac.localhost
在任何容器中解析? (nslookup
例如,某些发行版没有)。或者也许我不应该尝试解决,我可以尝试docker.for.mac.localhost
从某个地方读取值?
我目前的想法是尝试使用getent ahosts docker.for.mac.localhost
,然后如果命令不存在我会尝试使用nslookup
(即Busybox图像没有getent
命令)。也许我尝试解决是错误的docker.for.mac.localhost
,并且可以通过更简单的方式检索它。请分享您的想法。
谢谢。
答案1
您解决 docker.for.mac.localhost 的想法非常正确,您只需要找到能够在几乎任何容器中解决它的东西即可。这应该可以做到(并且根据我的测试):
grep dockerhost /etc/hosts || echo $( (ping -c1 docker.for.mac.localhost || (ip r | grep ^default)) | grep -oE '([0-9]+\.){3}[0-9]+' ) dockerhost >> /etc/hosts
您可以将其作为入口点 ( sh -c
) 的一部分或某种初始化脚本中。
如需更多信息、更完整的评论版本以及注意事项,请继续阅读。
我们所做的就是让 Linux 解析名称。尽管 nc 和traceroute 几乎无处不在,但 Ping 只是最简单的选择。如果所有其他方法都失败了,您实际上可以使用现代(但非 BusyBox)bash 或 syslog 来完成此操作。默认情况下,Docker 设置一个小型 DNS 服务器,用于响应同一网络上任何其他容器(或者在旧版本中,链接容器)的名称。然后docker.for.mac.localhost
添加到其中。
如果没有任何解析,则说明该 dns 服务器未解析它,我们可以正常获取主机。您可能希望改用 /proc/net 方法,但它们在我的测试容器中似乎是等效的。
现在,有一些注意事项。首先,一个大问题:如果您没有网络连接,这将永远超时 - 最多 30 秒。下面的较大脚本中有一些修复。您也不能(默认情况下 - 有方法可以覆盖它)在容器的构建过程中运行它。相反,你可以,它只是不会做任何事情:当你启动容器时,Docker 会挂载一个新的 /etc/hosts。
更好的版本会使用timeout
,但是你遇到了一个问题 - 它(至少)有 2 个不兼容的版本,如果你想假设它可能不存在,还有第三种情况:
grep dockerhost /etc/hosts || echo $( (
(if timeout -t1 env >/dev/null; then \
timeout -t1 ping -c1 docker.for.mac.localhost; \
elif timeout 1s env >/dev/null; then
timeout 1s ping -c1 docker.for.mac.localhost; \
else \
ping -c1 docker.for.mac.localhost; \
fi) \
|| (ip r | grep ^default)) \
| grep -oE '([0-9]+\.){3}[0-9]+' ) dockerhost >> /etc/hosts
我们使用 env 是因为几乎可以肯定有一个版本不是内置的 shell(/usr/bin/env
在大多数情况下),它有一个众所周知的接口(即,它将在没有任何输入的情况下运行),并且不太可能因环境问题而失败原因与ls
依赖于文件系统的讨厌的命令不同。
最后,一个完整的注释版本类似于我正在使用的版本(以便我后面的人了解发生了什么):
add_dockerhost_address() {
# The name we're checking for - in case this technique is useful on Windows
# if we get a Windows version instead of a cross-platform one
local MACHOST="docker.for.mac.localhost"
# The name we want to insert into /etc/hosts
local DOCKERHOSTNAME="dockerhost"
# Don't insert the name twice
if grep "$DOCKERHOSTNAME" /etc/hosts >/dev/null 2>&1; then
return 0
fi
# A command string we'll be building
local COMMAND=""
# Check for the ability to limit the timeout
if command -v timeout >/dev/null 2>&1; then
# We still have a problem - there are two widely used incompatible
# variants of timeout. Let's see which one we have.
# env should not be a builtin, doesn't require a pipe,
# has a consistent command line test, and is unlikely to fail
if timeout -t1 env >/dev/null 2>&1; then
COMMAND="timeout -t1 "
else
COMMAND="timeout 1s "
fi
fi
local IS_NOT_MAC=1
local IP=""
if command -v ping >/dev/null 2>&1; then
COMMAND="$COMMAND ping -c1 $MACHOST"
# Otherwise, BusyBox shows terminated.
IP=$(sh -c "$COMMAND" 2>&1)
# Exit codes: 0 - found the host, 1/2 failed to find, 124/143 terminated
IS_NOT_MAC="$?"
fi
if [ "$IS_NOT_MAC" -eq 0 ]; then
IP=$(echo "$IP" | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
else
IP=$(ip r | grep ^default | cut -d" " -f3)
fi
echo "$IP $DOCKERHOSTNAME" >> /etc/hosts
}
add_dockerhost_address
exec "/usr/sbin/apachectl" "-DFOREGROUND"
答案2
我相信有提到这里可以docker.for.mac.host.internal
使用。没有提到docker.for.mac.localhost
,尽管它也适用于我。
然后,我使用以下行检查我是否在 Docker for Mac 上运行:
$ getent hosts docker.for.mac.host.internal
$?
告诉我是否docker.for.mac.host.internal
找到了。
要获取实际 IP,还可以使用awk
,如下所示:
$ getent hosts docker.for.mac.host.internal | awk '{ print $1 }'