容器端口映射到主机端口探究

2016/9/19 posted in  Docker

容器的网络

在说端口之前,先明确下docker 容器的网络,可以用过docker network命令常看docker的网络:

# docker network ls
NETWORK ID          NAME                DRIVER
33b01b58a9a2        bridge              bridge
ffe4299ed1a9        host                host
fc93a0e85b75        none                null

可以看到,docker中包含bridge,host,none三种网络,其实除了这三种,还有container第四种网络。

其中,bridge是默认的网络,也就是在不指定网络模式的时候docker run使用的的网络模式,而none,正如其名,是不分配任何网络。host模式和container模式不是那么常用,前者是使用宿主机的网络,而后者是和指定的容器共享网络。

而这所讨论的容器端口与主机端口映射的原理,就是docker默认的bridge网络模式。

bridge网络

docker run一个容器,可以使用-p参数指定一个开放端口,甚至可以把这个端口映射的主机的某一端口上。比如,可以通过以下命令将nginx容器的80端口映射到主机的8080端口上:

# docker run -d -p 8080:80 --name nginx daocloud.io/library/nginx
81ea762a6bb99c1ee8c72d0fcc91f5151f8f65cf7c26747dd1f64d260845e21c
# docker ps
CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS              PORTS                           NAMES
81ea762a6bb9        daocloud.io/library/nginx   "nginx -g 'daemon off"   13 seconds ago      Up 12 seconds       443/tcp, 0.0.0.0:8080->80/tcp   nginx

可以尝试访问宿主机的8080端口,可以看到是一个运行的ngxin。

使用docker inspect命令可以看到nginx容器网络相关的配置:

"NetworkSettings": {
    "Bridge": "",
    "SandboxID": "0af9e9bf575206c247c0c0e1915dfec533dc173ae55917662e8c08df19441fe3",
    "HairpinMode": false,
    "LinkLocalIPv6Address": "",
    "LinkLocalIPv6PrefixLen": 0,
    "Ports": {
        "443/tcp": null,
        "80/tcp": [
            {
                "HostIp": "0.0.0.0",
                "HostPort": "8080"
            }
        ]
    },
    "SandboxKey": "/var/run/docker/netns/0af9e9bf5752",
    "SecondaryIPAddresses": null,
    "SecondaryIPv6Addresses": null,
    "EndpointID": "16ab10c9a32ea277f3f78b589ceee0f8698feafdfe6061c9ca3ac9f9633d1af8",
    "Gateway": "172.17.0.1",
    "GlobalIPv6Address": "",
    "GlobalIPv6PrefixLen": 0,
    "IPAddress": "172.17.0.2",
    "IPPrefixLen": 16,
    "IPv6Gateway": "",
    "MacAddress": "02:42:ac:11:00:02",
    "Networks": {
        "bridge": {
            "IPAMConfig": null,
            "Links": null,
            "Aliases": null,
            "NetworkID": "33b01b58a9a2ac92011051f1f75aa94ec5eb29bdbac06580aef462d700e527ef",
            "EndpointID": "16ab10c9a32ea277f3f78b589ceee0f8698feafdfe6061c9ca3ac9f9633d1af8",
            "Gateway": "172.17.0.1",
            "IPAddress": "172.17.0.2",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "MacAddress": "02:42:ac:11:00:02"
        }
    }
}

docker也提供了直接对网络进行管理的docker network命令,可以通过docker network inspect命令查看docker网络的详细信息:

# docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "33b01b58a9a2ac92011051f1f75aa94ec5eb29bdbac06580aef462d700e527ef",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16"
                }
            ]
        },
        "Internal": false,
        "Containers": {
            "81ea762a6bb99c1ee8c72d0fcc91f5151f8f65cf7c26747dd1f64d260845e21c": {
                "Name": "nginx",
                "EndpointID": "16ab10c9a32ea277f3f78b589ceee0f8698feafdfe6061c9ca3ac9f9633d1af8",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

端口映射

通过上面可以发现,bridge只不过是ip域为172.17.0.0/16子网,通过ifconfig命令也可以获得一些信息:

# ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:72:b4:25:d9
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:72ff:feb4:25d9/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:37 errors:0 dropped:0 overruns:0 frame:0
          TX packets:39 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:3684 (3.6 KB)  TX bytes:3399 (3.3 KB)

docker是通过iptables将流量打到docker0的子网上的,因此,可以尝试找下iptables的配置:

# iptables-save |grep 172.17.0.*
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 80 -j MASQUERADE
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT

在上面已经得知nginx容器的ip正是172.17.0.2,iptables会将来自8080端口的流量打在80端口上。
其实在宿主机直接访问子网172.17.0.2的80端口就可以获得nginx正确的返回:

# curl 172.17.0.2:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>