460

我正在尝试创建一个充当完整虚拟机的 Docker 容器。我知道我可以使用 Dockerfile 中的 EXPOSE 指令来公开端口,并且可以使用-p标志docker run来分配端口,但是一旦容器实际运行,是否有命令可以实时打开/映射其他端口?

例如,假设我有一个运行 sshd 的 Docker 容器。其他人使用容器 ssh 并安装 httpd。有没有办法把容器上的80端口暴露出来,映射到宿主机上的8080端口,这样人们就可以访问容器中运行的web服务器,而不需要重启呢?

4

15 回答 15

377

您无法通过 Docker 执行此操作,但您可以从主机访问容器的未公开端口。

如果您有一个容器在其端口 8000 上运行某些东西,您可以运行

wget http://container_ip:8000

要获取容器的 IP 地址,请运行 2 个命令:

docker ps
docker inspect container_name | grep IPAddress

在内部,当您运行映像时,Docker 会调用 iptables,因此可能对此进行一些变体。

要在本地主机的端口 8001 上公开容器的端口 8000:

iptables -t nat -A  DOCKER -p tcp --dport 8001 -j DNAT --to-destination 172.17.0.19:8000

解决此问题的一种方法是使用所需的端口映射设置另一个容器,并比较iptables-save命令的输出(不过,我必须删除一些其他强制流量通过 docker 代理的选项)。

注意:这是颠覆 docker,所以应该意识到它可能会产生蓝烟。

或者

另一种选择是查看(新?发布 0.6.6?) -P 选项 - 它将使用随机主机端口,然后将它们连接起来。

或者

在 0.6.5 中,您可以使用 LINKs 功能来调出一个与现有容器对话的新容器,并通过一些额外的中继到该容器的 -p 标志?(我还没有使用链接。)

或者

使用 docker 0.11?您可以使用docker run --net host ..将容器直接附加到主机的网络接口(即 net 没有命名空间),因此您在容器中打开的所有端口都被公开。

于 2013-11-11T11:47:07.667 回答
150

这是我要做的:

  • 提交实时容器。
  • 使用新映像再次运行容器,并打开端口(我建议安装共享卷并打开 ssh 端口)
sudo docker ps 
sudo docker commit <containerid> <foo/live>
sudo docker run -i -p 22 -p 8000:80 -m /data:/data -t <foo/live> /bin/bash
于 2014-01-27T07:15:51.920 回答
69

虽然您不能公开现有容器的新端口,但您可以在同一个 Docker 网络中启动一个新容器并让它将流量转发到原始容器。

# docker run \
  --rm \
  -p $PORT:1234 \
  verb/socat \
    TCP-LISTEN:1234,fork \
    TCP-CONNECT:$TARGET_CONTAINER_IP:$TARGET_CONTAINER_PORT

工作示例

启动一个侦听端口 80 的 Web 服务,但不要暴露其内部端口 80(哎呀!):

# docker run -ti mkodockx/docker-pastebin   # Forgot to expose PORT 80!

找到它的 Docker 网络 IP:

# docker inspect 63256f72142a | grep IPAddress
                    "IPAddress": "172.17.0.2",

使用暴露的 8080 端口启动verb/socat,并让它将 TCP 流量转发到该 IP 的 80 端口:

# docker run --rm -p 8080:1234 verb/socat TCP-LISTEN:1234,fork TCP-CONNECT:172.17.0.2:80

您现在可以在http://localhost:8080/上访问 pastebin ,您的请求将socat:1234转发到pastebin:80,并且响应反向传播相同的路径。

于 2017-02-06T15:37:02.057 回答
38

IPtables hack 不起作用,至少在 Docker 1.4.1 上是这样。

最好的方法是使用暴露的端口运行另一个容器并使用 socat 中继。这是我为(临时)使用 SQLPlus 连接到数据库所做的:

docker run -d --name sqlplus --link db:db -p 1521:1521 sqlplus

Dockerfile:

FROM debian:7

RUN apt-get update && \
    apt-get -y install socat && \
    apt-get clean

USER nobody

CMD socat -dddd TCP-LISTEN:1521,reuseaddr,fork TCP:db:1521
于 2015-01-26T23:02:41.180 回答
37

这是另一个想法。使用 SSH 进行端口转发;当您的 Docker 主机是 VM 时,这也有利于在 OS X(可能还有 Windows)中工作。

docker exec -it <containterid> ssh -R5432:localhost:5432 <user>@<hostip>
于 2015-03-26T14:32:11.207 回答
9

要添加到已接受的答案 iptables解决方案,我必须在主机上再运行两个命令才能将其向外界打开。

HOST> iptables -t nat -A DOCKER -p tcp --dport 443 -j DNAT --to-destination 172.17.0.2:443
HOST> iptables -t nat -A POSTROUTING -j MASQUERADE -p tcp --source 172.17.0.2 --destination 172.17.0.2 --dport https
HOST> iptables -A DOCKER -j ACCEPT -p tcp --destination 172.17.0.2 --dport https

注意:我打开端口 https (443),我的 docker 内部 IP 是172.17.0.2

注2:这些规则是临时的,只会持续到容器重新启动

于 2017-07-27T14:38:33.550 回答
8

我不得不处理同样的问题,并且能够在不停止任何正在运行的容器的情况下解决它。这是截至 2016 年 2 月的最新解决方案,使用 Docker 1.9.1。无论如何,这个答案是@ricardo-branco 答案的详细版本,但对新用户来说更深入。

在我的场景中,我想临时连接到在容器中运行的 MySQL,并且由于其他应用程序容器链接到它,因此停止、重新配置和重新运行数据库容器是不可能的。

由于我想从外部访问 MySQL 数据库(从 Sequel Pro 通过 SSH 隧道),我将使用33306主机上的端口。(不是3306,以防万一有外部 MySQL 实例在运行。)

大约一个小时的调整iptables被证明是徒劳的,尽管:

一步一步,这就是我所做的:

mkdir db-expose-33306
cd db-expose-33306
vim Dockerfile

编辑dockerfile,把它放在里面:

# Exposes port 3306 on linked "db" container, to be accessible at host:33306
FROM ubuntu:latest # (Recommended to use the same base as the DB container)

RUN apt-get update && \
    apt-get -y install socat && \
    apt-get clean

USER nobody
EXPOSE 33306

CMD socat -dddd TCP-LISTEN:33306,reuseaddr,fork TCP:db:3306

然后构建镜像:

docker build -t your-namespace/db-expose-33306 .

然后运行它,链接到您正在运行的容器。(使用-d而不是将-rm其保留在后台,直到明确停止和删除。在这种情况下我只希望它暂时运行。)

docker run -it --rm --name=db-33306 --link the_live_db_container:db -p 33306:33306  your-namespace/db-expose-33306
于 2016-02-14T00:19:35.650 回答
5

您可以使用 SSH 创建隧道并在主机中公开您的容器。

您可以通过两种方式进行操作,从容器到主机以及从主机到容器。但是你需要一个像 OpenSSH 这样的 SSH 工具(一个是客户端,另一个是服务器)。

例如,在容器中,您可以这样做

$ yum install -y openssh openssh-server.x86_64
service sshd restart
Stopping sshd:                                             [FAILED]
Generating SSH2 RSA host key:                              [  OK  ]
Generating SSH1 RSA host key:                              [  OK  ]
Generating SSH2 DSA host key:                              [  OK  ]
Starting sshd:                                             [  OK  ]
$ passwd # You need to set a root password..

您可以从此行(在容器中)找到容器 IP 地址:

$ ifconfig eth0 | grep "inet addr" | sed 's/^[^:]*:\([^ ]*\).*/\1/g'
172.17.0.2

然后在主机中,你可以这样做:

sudo ssh -NfL 80:0.0.0.0:80 root@172.17.0.2
于 2015-05-07T21:43:26.433 回答
3

有一个方便的 HAProxy 包装器。

docker run -it -p LOCALPORT:PROXYPORT --rm --link TARGET_CONTAINER:EZNAME -e "BACKEND_HOST=EZNAME" -e "BACKEND_PORT=PROXYPORT" demandbase/docker-tcp-proxy

这将为目标容器创建一个 HAProxy。十分简单。

于 2017-08-08T23:37:38.027 回答
3

如果没有答案对某人有用 - 检查您的目标容器是否已经在 docker 网络中运行:

CONTAINER=my-target-container
docker inspect $CONTAINER | grep NetworkMode
        "NetworkMode": "my-network-name",

将其保存在变量中以备后用$NET_NAME

NET_NAME=$(docker inspect --format '{{.HostConfig.NetworkMode}}' $CONTAINER)

如果是,您应该在同一网络中运行代理容器。

接下来查找容器的别名:

docker inspect $CONTAINER | grep -A2 Aliases
                "Aliases": [
                    "my-alias",
                    "23ea4ea42e34a"

将其保存在变量中以备后用$ALIAS

ALIAS=$(docker inspect --format '{{index .NetworkSettings.Networks "'$NET_NAME'" "Aliases" 0}}' $CONTAINER)

现在socat在网络中的容器中运行$NET_NAME以桥接到$ALIASed 容器的暴露(但未发布)端口:

docker run \
    --detach --name my-new-proxy \
    --net $NET_NAME \
    --publish 8080:1234 \
    alpine/socat TCP-LISTEN:1234,fork TCP-CONNECT:$ALIAS:80
于 2018-04-18T15:17:35.270 回答
3

以下是一些解决方案:

https://forums.docker.com/t/how-to-expose-port-on-running-container/3252/12

容器运行时映射端口的解决方案。

docker run -d --net=host myvnc

这将自动公开端口并将其映射到您的主机

于 2018-12-02T10:14:12.463 回答
3

根据Robm 的回答,我创建了一个 Docker 映像和一个名为portcat.

使用portcat,您可以轻松地将多个端口映射到现有的 Docker 容器。使用(可选)Bash 脚本的示例:

curl -sL https://raw.githubusercontent.com/archan937/portcat/master/script/install | sudo bash
portcat my-awesome-container 3456 4444:8080

你去吧!Portcat 正在映射:

  • 端口3456my-awesome-container:3456
  • 端口4444my-awesome-container:8080

请注意,Bash 脚本是可选的,以下命令:

ipAddress=$(docker inspect my-awesome-container | grep IPAddress | grep -o '[0-9]\{1,3\}\(\.[0-9]\{1,3\}\)\{3\}' | head -n 1)
docker run -p 3456:3456 -p 4444:4444 --name=alpine-portcat -it pmelegend/portcat:latest $ipAddress 3456 4444:8080

我希望portcat对你们有用。干杯!

于 2021-01-31T12:46:26.423 回答
2

您可以使用像Weave Net这样的覆盖网络,它将为每个容器分配一个唯一的 IP 地址,并将所有端口隐式地暴露给网络的每个容器部分。

Weave 还提供主机网络集成。默认情况下它是禁用的,但是,如果您还想从主机访问容器 IP 地址(及其所有端口),您可以简单地运行 run weave expose

全面披露:我在 Weaveworks 工作。

于 2016-01-09T19:17:28.453 回答
1

请先阅读Ricardo的回复。这对我有用。

但是,如果使用 docker-compose 启动正在运行的容器,则存在这样的情况。这是因为 docker-compose(我正在运行 docker 1.17)创建了一个新网络。解决这种情况的方法是

docker network ls

然后附加以下内容 docker run -d --name sqlplus --link db:db -p 1521:1521 sqlplus --net network_name

于 2017-08-18T15:06:12.587 回答
0

进行实时端口映射是不可能的,但有多种方法可以让 Docker 容器拥有类似于虚拟机的真实接口。

Macvlan 接口

Docker 现在包括一个Macvlan 网络驱动程序。这会将 Docker 网络连接到“真实世界”接口,并允许您将该网络地址直接分配给容器(如虚拟机桥接模式)。

docker network create \
    -d macvlan \
    --subnet=172.16.86.0/24 \
    --gateway=172.16.86.1  \
    -o parent=eth0 pub_net

pipework还可以将真实接口映射到容器中,或者在旧版本的 Docker 中设置子接口。

路由IP

如果您可以控制网络,则可以将其他网络路由到 Docker 主机以在容器中使用。

然后将该网络分配给容器并设置 Docker 主机以通过 docker 网络路由数据包。

共享主机接口

--net host选项允许将主机接口共享到容器中,但由于共享性质,这对于在一个主机上运行多个容器可能不是一个好的设置。

于 2017-05-03T22:41:27.180 回答