映射容器端口到宿主主机的实现
默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。
容器访问外部实现
容器所有到外部网络的连接,源地址都会被NAT成本地系统的IP地址。这是使用 iptables 的源地址伪装操作实现的。
查看主机的 NAT 规则。
$ sudo iptables -t nat -nL
...
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16
...
其中,上述规则将所有源地址在 172.17.0.0/16 网段,目标地址为其他网段(外部网络)的流量动态伪装为从系统网卡发出。MASQUERADE 跟传统 SNAT 的好处是它能动态从网卡获取地址。
外部访问容器实现
容器允许外部访问,可以在 docker run 时候通过 -p 或 -P 参数来启用。
不管用那种办法,其实也是在本地的 iptable 的 nat 表中添加相应的规则。
使用 -P 时:
$ iptables -t nat -nL
...
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80
使用 -p 80:80 时:
$ iptables -t nat -nL
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
注意:
这里的规则映射了 0.0.0.0,意味着将接受主机来自所有接口的流量。用户可以通过 -p IP:host_port:container_port 或 -p IP::port 来指定允许访问容器的主机上的 IP、接口等,以制定更严格的规则。
如果希望永久绑定到某个固定的 IP 地址,可以在 Docker 配置文件 /etc/default/docker 中指定 DOCKER_OPTS="--ip=IP_ADDRESS",之后重启 Docker 服务即可生效。
使用iptables管理docker容器做端口映射
昨天写了篇文章是关于docker如何绑定静态的ip,使容器里面的ip是固定的ip地址…. 另外关于绑定ip地址,我们也是可以在docker run的时候用 docker run -p ip:port:port的方式…. 他其实就是调用的iptables的方法…
原文链接是, http://xiaorui.cc/?p=1502 http://xiaorui.cc
docker run -d -p 9000:9000 redis_cluster 9000 ,生成一个外部9000对应容器端口9000的容器….
Python
root@ubuntu:~# iptables -t nat -L -n
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
MASQUERADE tcp -- 172.17.0.1 172.17.0.1 tcp dpt:9000
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:9000 to:172.17.0.1:9000
通过上面的信息我们可以确定,他的端口转换不是在docker的服务端实现的,还是借助于linux的iptables策略实现的…. 那么我们就可以自己写DNAT的命令,让外部的端口进行转换… docker创建了一个名为DOKCER的自定义的链条Chain … … iptables自定义链条的好处就是可以让防火墙的策略更加的层次化… … 不至于因为构建一大堆的命令后,而看的有些迷糊…. 如果是自己手动创建的端口映射,在我们删除docker 容器的时候,他不会把这条规则删除…. 因为我们在docker rm 容器id 进行删除的时候,他也会把这容器相关联的映射关系给删掉。
iptables -t nat -A PREROUTING -p tcp –dport 80 -j DNAT –to 172.17.0.1:80
Python
root@ubuntu:~# iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N DOCKER
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER -d 172.17.0.1/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 9000 -j ACCEPT
root@ubuntu:~# iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A PREROUTING -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.1:80
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.1/32 -d 172.17.0.1/32 -p tcp -m tcp --dport 9000 -j MASQUERADE
-A DOCKER -p tcp -m tcp --dport 9000 -j DNAT --to-destination 172.17.0.1:9000
我们才别的节点进行测试… …. 结果是OK的 !
Python
[ruifengyun@devops ~ ]$ curl -Iv 192.168.1.110
* Rebuilt URL to: 192.168.1.110/
* Hostname was NOT found in DNS cache
* Trying 192.168.1.110...
* Connected to 192.168.1.110 (192.168.1.110) port 80 (#0)
> HEAD / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: 192.168.1.110
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
* Server nginx/1.0.15 is not blacklisted
< Server: nginx/1.0.15
Server: nginx/1.0.15
< Date: Tue, 19 May 2015 16:16:34 GMT
Date: Tue, 19 May 2015 16:16:34 GMT
< Content-Type: text/html
Content-Type: text/html
< Content-Length: 3698
Content-Length: 3698
< Last-Modified: Tue, 11 Nov 2014 16:27:04 GMT
Last-Modified: Tue, 11 Nov 2014 16:27:04 GMT
< Connection: keep-alive
Connection: keep-alive
< Accept-Ranges: bytes
Accept-Ranges: bytes
<
* Connection #0 to host 192.168.1.110 left intact
Dcoker的网络不怎么好理解,咱们一说桥接可能更多的是和源服务器本身一个同等的网络…. 但是docker的桥接bridge和我们以前的vmware virtualbox的nat是差不多的概念…. 这大家要注意下。 在复杂的环境下,总是来来回回的用iptables脚本有些杂乱,如果有闲心,推荐开发一个公司内部自己的docker管理平台,里面可以组织性的管理iptables防火墙…
Docker网络原则入门:EXPOSE,-p,-P,-link
如果你已经构建了一些多容器的应用程序,那么肯定需要定义一些网络规则来设置容器间的通信。有多种方式可以实现:可以通过--expose参数在运行时暴露端口,或者在Dockerfile里使用EXPOSE指令。还可以在Docker run的时候通过-p或者-P参数来发布端口。或者通过--link链接容器。虽然这些方式几乎都能达到一样的结果,但是它们还是有细微区别。那么到底应该使用哪一种呢?
TL;DR
使用-p或者-P来创建特定端口绑定规则最为可靠,EXPOSE可以看做是容器文档化的方式,谨慎使用--link的方式。
在比较这些不同方式之前,我们先分别了解细节。
通过EXPOSE或者-expose暴露端口
有两种方式可以用来暴露端口:要么用EXPOSE指令在Dockerfile里定义,要么在docker run时指定--expose=1234。这两种方式作用相同,但是,--expose可以接受端口范围作为参数,比如 --expose=2000-3000。但是,EXPOSE和--expose都不依赖于宿主机器。默认状态下,这些规则并不会使这些端口可以通过宿主机来访问。
基于EXPOSE指令的上述限制,Dockerfile的作者一般在包含EXPOSE规则时都只将其作为哪个端口提供哪个服务的提示。使用时,还要依赖于容器的操作人员进一步指定网络规则。和-P参数联合使用的情况,下文会进一步阐述。不过通过EXPOSE命令文档化端口的方式十分有用。
本质上说,EXPOSE或者--expose只是为其他命令提供所需信息的元数据,或者只是告诉容器操作人员有哪些已知选择。
实际上,在运行时暴露端口和通过Dockerfile的指令暴露端口,这两者没什么区别。在这两种方式启动的容器里,通过docker inspect $container_id | $container_name查看到的网络配置是一样的:
"NetworkSettings": {
"PortMapping": null,
"Ports": {
"1234/tcp": null
}
},
"Config": {
"ExposedPorts": {
"1234/tcp": {}
}
}
可以看到端口被标示成已暴露,但是没有定义任何映射。注意这一点,因为我们查看的是发布端口。
ProTip:使用运行时标志--expose是附加的,因此会在Dockerfile的EXPOSE指令定义的端口之外暴露添加的端口。
使用-p发布特定端口
可以使用-p参数显式将一个或者一组端口从容器里绑定到宿主机上,而不仅仅是提供一个端口。注意这里是小写的p,不是大写。因为该配置依赖于宿主机器,所以Dockerfile里没有对应的指令,这是运行时才可用的配置。-p参数有几种不同的格式:
ip:hostPort:containerPort| ip::containerPort | hostPort:containerPort | containerPort
实际中,可以忽略ip或者hostPort,但是必须要指定需要暴露的containerPort。另外,所有这些发布的规则都默认为tcp。如果需要udp,需要在最后加以指定,比如-p 1234:1234/udp。如果只是用命令docker run -p 8080:3000 my-image运行一个简单的应用程序,那么容器里运行在3000端口的服务在宿主机的8080端口也就可用了。端口不需要一样,但是在多个容器都暴露端口时,必须注意避免端口冲突。
避免冲突的最佳方法是让Docker自己分配hostPort。在上述例子里,可以选择docker run -p 3000 my_image来运行容器,而不是显式指定宿主机端口。这时,Docker会帮助选择一个宿主机端口。运行命令docker port $container_id | $container_name可以查看Docker选出的端口号。除了端口号,该命令只能显示容器运行时端口绑定信息。还可以通过在容器上运行docker inspect查看详细的网络信息,在定义了端口映射时,这样的信息就很有用。该信息在Config、HostConfig和NetworkSettings部分。我们查看这些信息来对比不同方式搭建的容器间的网络区别。
ProTip:可以用-p参数定义任意数量的端口映射。
-expose/EXPOSE和-p对比
为了更好得理解两者之间的区别,我们使用不同的端口设置来运行容器。
运行一个很简单的应用程序,会在curl它的时候打印‘hello world‘。称这个镜像为no-exposed-ports:
FROM ubuntu:trusty
MAINTAINER Laura Frank <laura.frank@centurylink.com>
CMD while true; do echo ‘hello world‘ | nc -l -p 8888; done
实验时注意使用的是Docker主机,而不是boot2docker。如果使用的是boot2docker,运行本文示例命令前先运行boot2docker ssh。
注意,我们使用-d参数运行该容器,因此容器一直在后台运行。(端口映射规则只适用于运行着的容器):
$ docker run -d --name no-exposed-ports no-exposed-ports
e18a76da06b3af7708792765745466ed485a69afaedfd7e561cf3645d1aa7149
这儿没有太多的信息,只是回显了容器的ID,提示服务已经成功启动。和预期结果一样,运行docker port no-exposed-ports和docker inspect no-exposed-ports时没显示什么信息,因为我们既没有定义端口映射规则也没有发布任何端口。
因此,如果我们发布一个端口会发生什么呢,-p参数和EXPOSE到底有什么区别呢?
还是使用上文的no-exposed-ports镜像,在运行时添加-p参数,但是不添加任何expose规则。在config.ExposedPorts里重新查看--expose参数或者EXPOSE指令的结果。
$ docker run -d --name no-exposed-ports-with-p-flag -p 8888:8888 no-exposed-ports
c876e590cfafa734f42a42872881e68479387dc2039b55bceba3a11afd8f17ca
$ docker port no-exposed-ports-with-p-flag
8888/tcp -> 0.0.0.0:8888
太棒了!我们可以看到可用端口。注意默认这是tcp。我们到网络设置里看看还有什么信息:
"Config": {
[...]
"ExposedPorts": {
"8888/tcp": {}
}
},
"HostConfig": {
[...]
"PortBindings": {
"8888/tcp": [
{
"HostIp": "",
"HostPort": "8888"
}
]
}
},
"NetworkSettings": {
[...]
"Ports": {
"8888/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "8888"
}
]
}
}
注意”Config“部分的暴露端口字段。这和我们使用EXPOSE或者--expose暴露的端口是一致的。Docker会隐式暴露已经发布的端口。已暴露端口和已发布端口的区别在于已发布端口在宿主机上可用,而且我们可以在“HostConfig”和“NetworkSettings”两个部分都能看到已发布端口的信息。
所有发布(-p或者-P)的端口都暴露了,但是并不是所有暴露(EXPOSE或--expose)的端口都会发布。
使用-P和EXPOSE发布端口
因为EXPOSE通常只是作为记录机制,也就是告诉用户哪些端口会提供服务,Docker可以很容易地把Dockerfile里的EXPOSE指令转换成特定的端口绑定规则。只需要在运行时加上-P参数,Docker会自动为用户创建端口映射规则,并且帮助避免端口映射的冲突。
添加如下行到上文使用的Web应用Dockerfile里:
EXPOSE 1000
EXPOSE 2000
EXPOSE 3000
构建镜像,命名为exposed-ports。
docker build -t exposed-ports .
再次用-P参数运行,但是不传入任何特定的-p规则。可以看到Docker会将EXPOSE指令相关的每个端口映射到宿主机的端口上:
$ docker run -d -P --name exposed-ports-in-dockerfile exposed-ports
63264dae9db85c5d667a37dac77e0da7c8d2d699f49b69ba992485242160ad3a
$ docker port exposed-ports-in-dockerfile
1000/tcp -> 0.0.0.0:49156
2000/tcp -> 0.0.0.0:49157
3000/tcp -> 0.0.0.0:49158
很方便,不是么?
--link怎么样呢?
你可能在多容器应用程序里使用过运行时参数 --link name:alias来设定容器间关系。虽然--link非常易于使用,几乎能提供和端口映射规则和环境变量相同的功能。但是最好将--link当做服务发现的机制,而不是网络流量的门户。
--link参数唯一多做的事情是会使用源容器的主机名和容器ID来更新新建目标容器(使用--link参数创建的容器)的/etc/hosts文件。
当使用--link参数时,Docker提供了一系列标准的环境变量,如果想知道细节的话可以查看相应文档。
虽然--link对于需要隔离域的小型项目非常有用,它的功能更像服务发现的工具。如果项目中使用了编排服务,比如Kubernetes或者Fleet,很可能就会使用别的服务发现工具来管理关系。这些编排服务可能会不管理Docker的链接,而是管理服务发现工具里包含的所有服务,在Panamax项目里使用的很多远程部署适配器正是做这个的。
找到平衡
哪一种网络选择更为适合,这取决于谁(或者哪个容器)使用Docker运行的服务。需要注意的是一旦镜像发布到Docker Hub之后,你无法知道其他人如何使用该镜像,因此要尽可能让镜像更加灵活。如果你只是从Docker Hub里取得镜像,使用-P参数运行容器是最方便迅速的方式,来基于作者的建议创建端口映射规则。记住每一个发布的端口都是暴露端口,但是反过来是不对的。
默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。
容器访问外部实现
容器所有到外部网络的连接,源地址都会被NAT成本地系统的IP地址。这是使用 iptables 的源地址伪装操作实现的。
查看主机的 NAT 规则。
$ sudo iptables -t nat -nL
...
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16
...
其中,上述规则将所有源地址在 172.17.0.0/16 网段,目标地址为其他网段(外部网络)的流量动态伪装为从系统网卡发出。MASQUERADE 跟传统 SNAT 的好处是它能动态从网卡获取地址。
外部访问容器实现
容器允许外部访问,可以在 docker run 时候通过 -p 或 -P 参数来启用。
不管用那种办法,其实也是在本地的 iptable 的 nat 表中添加相应的规则。
使用 -P 时:
$ iptables -t nat -nL
...
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80
使用 -p 80:80 时:
$ iptables -t nat -nL
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
注意:
这里的规则映射了 0.0.0.0,意味着将接受主机来自所有接口的流量。用户可以通过 -p IP:host_port:container_port 或 -p IP::port 来指定允许访问容器的主机上的 IP、接口等,以制定更严格的规则。
如果希望永久绑定到某个固定的 IP 地址,可以在 Docker 配置文件 /etc/default/docker 中指定 DOCKER_OPTS="--ip=IP_ADDRESS",之后重启 Docker 服务即可生效。
使用iptables管理docker容器做端口映射
昨天写了篇文章是关于docker如何绑定静态的ip,使容器里面的ip是固定的ip地址…. 另外关于绑定ip地址,我们也是可以在docker run的时候用 docker run -p ip:port:port的方式…. 他其实就是调用的iptables的方法…
原文链接是, http://xiaorui.cc/?p=1502 http://xiaorui.cc
docker run -d -p 9000:9000 redis_cluster 9000 ,生成一个外部9000对应容器端口9000的容器….
Python
root@ubuntu:~# iptables -t nat -L -n
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
MASQUERADE tcp -- 172.17.0.1 172.17.0.1 tcp dpt:9000
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:9000 to:172.17.0.1:9000
通过上面的信息我们可以确定,他的端口转换不是在docker的服务端实现的,还是借助于linux的iptables策略实现的…. 那么我们就可以自己写DNAT的命令,让外部的端口进行转换… docker创建了一个名为DOKCER的自定义的链条Chain … … iptables自定义链条的好处就是可以让防火墙的策略更加的层次化… … 不至于因为构建一大堆的命令后,而看的有些迷糊…. 如果是自己手动创建的端口映射,在我们删除docker 容器的时候,他不会把这条规则删除…. 因为我们在docker rm 容器id 进行删除的时候,他也会把这容器相关联的映射关系给删掉。
iptables -t nat -A PREROUTING -p tcp –dport 80 -j DNAT –to 172.17.0.1:80
Python
root@ubuntu:~# iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N DOCKER
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER -d 172.17.0.1/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 9000 -j ACCEPT
root@ubuntu:~# iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A PREROUTING -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.1:80
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.1/32 -d 172.17.0.1/32 -p tcp -m tcp --dport 9000 -j MASQUERADE
-A DOCKER -p tcp -m tcp --dport 9000 -j DNAT --to-destination 172.17.0.1:9000
我们才别的节点进行测试… …. 结果是OK的 !
Python
[ruifengyun@devops ~ ]$ curl -Iv 192.168.1.110
* Rebuilt URL to: 192.168.1.110/
* Hostname was NOT found in DNS cache
* Trying 192.168.1.110...
* Connected to 192.168.1.110 (192.168.1.110) port 80 (#0)
> HEAD / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: 192.168.1.110
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
* Server nginx/1.0.15 is not blacklisted
< Server: nginx/1.0.15
Server: nginx/1.0.15
< Date: Tue, 19 May 2015 16:16:34 GMT
Date: Tue, 19 May 2015 16:16:34 GMT
< Content-Type: text/html
Content-Type: text/html
< Content-Length: 3698
Content-Length: 3698
< Last-Modified: Tue, 11 Nov 2014 16:27:04 GMT
Last-Modified: Tue, 11 Nov 2014 16:27:04 GMT
< Connection: keep-alive
Connection: keep-alive
< Accept-Ranges: bytes
Accept-Ranges: bytes
<
* Connection #0 to host 192.168.1.110 left intact
Dcoker的网络不怎么好理解,咱们一说桥接可能更多的是和源服务器本身一个同等的网络…. 但是docker的桥接bridge和我们以前的vmware virtualbox的nat是差不多的概念…. 这大家要注意下。 在复杂的环境下,总是来来回回的用iptables脚本有些杂乱,如果有闲心,推荐开发一个公司内部自己的docker管理平台,里面可以组织性的管理iptables防火墙…
Docker网络原则入门:EXPOSE,-p,-P,-link
如果你已经构建了一些多容器的应用程序,那么肯定需要定义一些网络规则来设置容器间的通信。有多种方式可以实现:可以通过--expose参数在运行时暴露端口,或者在Dockerfile里使用EXPOSE指令。还可以在Docker run的时候通过-p或者-P参数来发布端口。或者通过--link链接容器。虽然这些方式几乎都能达到一样的结果,但是它们还是有细微区别。那么到底应该使用哪一种呢?
TL;DR
使用-p或者-P来创建特定端口绑定规则最为可靠,EXPOSE可以看做是容器文档化的方式,谨慎使用--link的方式。
在比较这些不同方式之前,我们先分别了解细节。
通过EXPOSE或者-expose暴露端口
有两种方式可以用来暴露端口:要么用EXPOSE指令在Dockerfile里定义,要么在docker run时指定--expose=1234。这两种方式作用相同,但是,--expose可以接受端口范围作为参数,比如 --expose=2000-3000。但是,EXPOSE和--expose都不依赖于宿主机器。默认状态下,这些规则并不会使这些端口可以通过宿主机来访问。
基于EXPOSE指令的上述限制,Dockerfile的作者一般在包含EXPOSE规则时都只将其作为哪个端口提供哪个服务的提示。使用时,还要依赖于容器的操作人员进一步指定网络规则。和-P参数联合使用的情况,下文会进一步阐述。不过通过EXPOSE命令文档化端口的方式十分有用。
本质上说,EXPOSE或者--expose只是为其他命令提供所需信息的元数据,或者只是告诉容器操作人员有哪些已知选择。
实际上,在运行时暴露端口和通过Dockerfile的指令暴露端口,这两者没什么区别。在这两种方式启动的容器里,通过docker inspect $container_id | $container_name查看到的网络配置是一样的:
"NetworkSettings": {
"PortMapping": null,
"Ports": {
"1234/tcp": null
}
},
"Config": {
"ExposedPorts": {
"1234/tcp": {}
}
}
可以看到端口被标示成已暴露,但是没有定义任何映射。注意这一点,因为我们查看的是发布端口。
ProTip:使用运行时标志--expose是附加的,因此会在Dockerfile的EXPOSE指令定义的端口之外暴露添加的端口。
使用-p发布特定端口
可以使用-p参数显式将一个或者一组端口从容器里绑定到宿主机上,而不仅仅是提供一个端口。注意这里是小写的p,不是大写。因为该配置依赖于宿主机器,所以Dockerfile里没有对应的指令,这是运行时才可用的配置。-p参数有几种不同的格式:
ip:hostPort:containerPort| ip::containerPort | hostPort:containerPort | containerPort
实际中,可以忽略ip或者hostPort,但是必须要指定需要暴露的containerPort。另外,所有这些发布的规则都默认为tcp。如果需要udp,需要在最后加以指定,比如-p 1234:1234/udp。如果只是用命令docker run -p 8080:3000 my-image运行一个简单的应用程序,那么容器里运行在3000端口的服务在宿主机的8080端口也就可用了。端口不需要一样,但是在多个容器都暴露端口时,必须注意避免端口冲突。
避免冲突的最佳方法是让Docker自己分配hostPort。在上述例子里,可以选择docker run -p 3000 my_image来运行容器,而不是显式指定宿主机端口。这时,Docker会帮助选择一个宿主机端口。运行命令docker port $container_id | $container_name可以查看Docker选出的端口号。除了端口号,该命令只能显示容器运行时端口绑定信息。还可以通过在容器上运行docker inspect查看详细的网络信息,在定义了端口映射时,这样的信息就很有用。该信息在Config、HostConfig和NetworkSettings部分。我们查看这些信息来对比不同方式搭建的容器间的网络区别。
ProTip:可以用-p参数定义任意数量的端口映射。
-expose/EXPOSE和-p对比
为了更好得理解两者之间的区别,我们使用不同的端口设置来运行容器。
运行一个很简单的应用程序,会在curl它的时候打印‘hello world‘。称这个镜像为no-exposed-ports:
FROM ubuntu:trusty
MAINTAINER Laura Frank <laura.frank@centurylink.com>
CMD while true; do echo ‘hello world‘ | nc -l -p 8888; done
实验时注意使用的是Docker主机,而不是boot2docker。如果使用的是boot2docker,运行本文示例命令前先运行boot2docker ssh。
注意,我们使用-d参数运行该容器,因此容器一直在后台运行。(端口映射规则只适用于运行着的容器):
$ docker run -d --name no-exposed-ports no-exposed-ports
e18a76da06b3af7708792765745466ed485a69afaedfd7e561cf3645d1aa7149
这儿没有太多的信息,只是回显了容器的ID,提示服务已经成功启动。和预期结果一样,运行docker port no-exposed-ports和docker inspect no-exposed-ports时没显示什么信息,因为我们既没有定义端口映射规则也没有发布任何端口。
因此,如果我们发布一个端口会发生什么呢,-p参数和EXPOSE到底有什么区别呢?
还是使用上文的no-exposed-ports镜像,在运行时添加-p参数,但是不添加任何expose规则。在config.ExposedPorts里重新查看--expose参数或者EXPOSE指令的结果。
$ docker run -d --name no-exposed-ports-with-p-flag -p 8888:8888 no-exposed-ports
c876e590cfafa734f42a42872881e68479387dc2039b55bceba3a11afd8f17ca
$ docker port no-exposed-ports-with-p-flag
8888/tcp -> 0.0.0.0:8888
太棒了!我们可以看到可用端口。注意默认这是tcp。我们到网络设置里看看还有什么信息:
"Config": {
[...]
"ExposedPorts": {
"8888/tcp": {}
}
},
"HostConfig": {
[...]
"PortBindings": {
"8888/tcp": [
{
"HostIp": "",
"HostPort": "8888"
}
]
}
},
"NetworkSettings": {
[...]
"Ports": {
"8888/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "8888"
}
]
}
}
注意”Config“部分的暴露端口字段。这和我们使用EXPOSE或者--expose暴露的端口是一致的。Docker会隐式暴露已经发布的端口。已暴露端口和已发布端口的区别在于已发布端口在宿主机上可用,而且我们可以在“HostConfig”和“NetworkSettings”两个部分都能看到已发布端口的信息。
所有发布(-p或者-P)的端口都暴露了,但是并不是所有暴露(EXPOSE或--expose)的端口都会发布。
使用-P和EXPOSE发布端口
因为EXPOSE通常只是作为记录机制,也就是告诉用户哪些端口会提供服务,Docker可以很容易地把Dockerfile里的EXPOSE指令转换成特定的端口绑定规则。只需要在运行时加上-P参数,Docker会自动为用户创建端口映射规则,并且帮助避免端口映射的冲突。
添加如下行到上文使用的Web应用Dockerfile里:
EXPOSE 1000
EXPOSE 2000
EXPOSE 3000
构建镜像,命名为exposed-ports。
docker build -t exposed-ports .
再次用-P参数运行,但是不传入任何特定的-p规则。可以看到Docker会将EXPOSE指令相关的每个端口映射到宿主机的端口上:
$ docker run -d -P --name exposed-ports-in-dockerfile exposed-ports
63264dae9db85c5d667a37dac77e0da7c8d2d699f49b69ba992485242160ad3a
$ docker port exposed-ports-in-dockerfile
1000/tcp -> 0.0.0.0:49156
2000/tcp -> 0.0.0.0:49157
3000/tcp -> 0.0.0.0:49158
很方便,不是么?
--link怎么样呢?
你可能在多容器应用程序里使用过运行时参数 --link name:alias来设定容器间关系。虽然--link非常易于使用,几乎能提供和端口映射规则和环境变量相同的功能。但是最好将--link当做服务发现的机制,而不是网络流量的门户。
--link参数唯一多做的事情是会使用源容器的主机名和容器ID来更新新建目标容器(使用--link参数创建的容器)的/etc/hosts文件。
当使用--link参数时,Docker提供了一系列标准的环境变量,如果想知道细节的话可以查看相应文档。
虽然--link对于需要隔离域的小型项目非常有用,它的功能更像服务发现的工具。如果项目中使用了编排服务,比如Kubernetes或者Fleet,很可能就会使用别的服务发现工具来管理关系。这些编排服务可能会不管理Docker的链接,而是管理服务发现工具里包含的所有服务,在Panamax项目里使用的很多远程部署适配器正是做这个的。
找到平衡
哪一种网络选择更为适合,这取决于谁(或者哪个容器)使用Docker运行的服务。需要注意的是一旦镜像发布到Docker Hub之后,你无法知道其他人如何使用该镜像,因此要尽可能让镜像更加灵活。如果你只是从Docker Hub里取得镜像,使用-P参数运行容器是最方便迅速的方式,来基于作者的建议创建端口映射规则。记住每一个发布的端口都是暴露端口,但是反过来是不对的。
来源:CSDN
作者:zhoyfirst1
链接:https://blog.csdn.net/zhoyfirst1/article/details/52201329