docker 主机ip Docker 容器如何访问外部网络以及端口映射原理?

写在前面不必太纠结于当下,也不必太忧虑未来,当你经历过一些事情的时候,眼前的风景已经和从前不一样了。——村上春树正常情况下,在 Docker 中启动一个容器

写在前面

不必太纠结于当下,也不必太忧虑未来,当你经历过一些事情的时候,眼前的风景已经和从前不一样了。——村上春树

正常情况下,在 Docker 中启动一个容器,这个容器可以自动的访问外部网络,今天就来看看 docker 中的容器是如何访问外部网络的?

默认情况下,当什么配置都不做,docker 会为每个创建的容器使用 Bridge Network 类型的网络,同时 docker 默认使用过 bridge 的网络驱动

可以通过下面的命令来验证

liruilonger@cloudshell:~$ docker network inspect bridge --format='{{.Driver}}'
bridge
liruilonger@cloudshell:~$ docker network inspect bridge
[
    {
        "Name""bridge",
        "Id""cd77486c39955f3d2369fe32e1f5b9b65d81c1a07bb677b085cec72b8fb52440",
        "Created""2024-03-26T13:03:43.742084591Z",
        "Scope""local",
        "Driver""bridge",
        "EnableIPv6"false,
        "IPAM": {
            "Driver""default",
            "Options": null,
            "Config": [
                {
                    "Subnet""172.17.0.0/16",
                    "Gateway""172.17.0.1"
                }
            ]
        },
        "Internal"false,
        "Attachable"false,
        "Ingress"false,
        "ConfigFrom": {
            "Network"""
        },
        "ConfigOnly"false,
        "Containers": {},
        "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""1460"
        },
        "Labels": {}
    }
]
liruilonger@cloudshell:~$
liruilonger@cloudshell:~$ docker info | grep -i network
  Network: bridge host ipvlan macvlan null overlay

现在我们启动一个 nginx 容器

liruilonger@cloudshell:~$ docker run -d -p 2024:80 --name mynginxs nginx
704b4427a24d56e6a2cc999fcf95125c73e665cb90029b191febc405f90a789a
liruilonger@cloudshell:~$

映射端口访问正常

docker跨主机网络_docker 主机ip_主机游戏

同时在容器内部访问 外部网站正常

liruilonger@cloudshell:~$ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS                  NAMES
704b4427a24d   nginx     "/docker-entrypoint.…"   2 minutes ago   Up 2 minutes   0.0.0.0:2024->80/tcp   mynginxs
liruilonger@cloudshell:~$ docker exec -it 704b4427a24d bash
root@704b4427a24d:/# curl baidu.com

<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">

root@704b4427a24d:/#

现在我么来看看容器访问 baidu.com 是如何发生的?在这之前,我需要看一下当前容器的网络配置

liruilonger@cloudshell:~$ docker inspect 704b4427a24d

之所以能够实现访问外网,下面的配置必不可少

        "NetworkSettings": {
            "Bridge""",
            "SandboxID""29735aa89eefbbbc03beb8f120aab0d0898de7b46959cf560739748458a1f8ca",
            "SandboxKey""/var/run/docker/netns/29735aa89eef",
            "Ports": {
                "80/tcp": [
                    {
                        "HostIp""0.0.0.0",
                        "HostPort""2024"
                    }
                ]
            },
            "HairpinMode"false,
            "LinkLocalIPv6Address""",
            "LinkLocalIPv6PrefixLen"0,
            "SecondaryIPAddresses"null,
            "SecondaryIPv6Addresses"null,
            "EndpointID""c8e13b9e448504121192937ac4e4619c3dbdcc58fd26b89a601f3bba61dd9f21",
            "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,
                    "MacAddress""02:42:ac:11:00:02",
                    "NetworkID""cd77486c39955f3d2369fe32e1f5b9b65d81c1a07bb677b085cec72b8fb52440",
                    "EndpointID""c8e13b9e448504121192937ac4e4619c3dbdcc58fd26b89a601f3bba61dd9f21",
                    "Gateway""172.17.0.1",
                    "IPAddress""172.17.0.2",
                    "IPPrefixLen"16,
                    "IPv6Gateway""",
                    "GlobalIPv6Address""",
                    "GlobalIPv6PrefixLen"0,
                    "DriverOpts"null,
                    "DNSNames"null
                }
            }
        }

通过上面的配置信息,可以找到有用的信息

IP 地址为:”IPAddress”: “172.17.0.2”,网关为:”Gateway”: “172.17.0.1”

简单梳理一下流程:

首先在容器内发起对 baidu.com 的访问请求

请求首先被容器中网络命名空间(/var/run/docker/netns/29735aa89eef)对应的网络栈接收

容器内的网络栈将检查目标地址是否在容器网络的子网范围内。由于 baidu.com 不在容器网络内,网络栈确定需要将请求发送到容器外部网络

所以容器要找网关 172.17.0.1 把请求发出去。这里的网关地址实际上是在安装 docker 是默认创建的桥虚拟接设备 docker0

通过下面的命令我们可以看到

liruilonger@cloudshell:~$ ifconfig  docker0
docker0: flags=4163  mtu 1460
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:87:21:0a:8b  txqueuelen 0  (Ethernet)
        RX packets 23  bytes 2710 (2.6 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 23  bytes 4437 (4.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

liruilonger@cloudshell:~$

实际上在创建 容器之后,docker 会默认帮我们做一些事

可以通过 sudo iptables -t nat -nL 命令查到POSTROUTING 链中配置的 SNAT 规则

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0

它将源地址为 172.17.0.0/16(Docker 桥接网络的子网)的所有数据包的源地址修改为主机的 IP 地址,并将目标地址设置为 0.0.0.0/0,表示任何目标地址。这个规则允许位于 Docker 桥接网络中的容器访问外部网络和互联网资源。

所以在到了网关地址对应的 Linux 网桥设备 docker0 之后,因为默认开启了 ipv4 转发,即可以简单理解为把宿主机当交换机, docker0 的流量会直接转发到外部网络

liruilonger@cloudshell:~$ ip route
default via 10.88.0.1 dev eth0 
10.88.0.0/16 dev eth0 proto kernel scope link src 10.88.0.4 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
liruilonger@cloudshell:~$

Docker 宿主机的网络栈接收到请求后,宿主机的网络配置设置了 SNAT,它将转换容器内部的源 IP 地址为宿主机的 IP 地址,宿主机上的网络栈将根据自己的路由表和网络配置,将请求转发到外部网络,同时以便响应返回时能正确到达容器

之后的请求就是宿主机和公网的通行,这里不多描述

所以一般情况下,容器访问外部网络,需要两个因素:

所以如果发现容器内访问不了外部网络,则需要确认系统的ip_forward是否已打开。或者检查docker daemon启动的时候–ip-forward参数是不是被设置成false了,如果是的话,则需要设置–ip-forward=true重新启动 Docker,Docker 会打开主机的 ip forward。

即从容器网段出来访问外部网络的包,都要做一次MASQUERADE,即出去的包都用主机的IP地址替换源地址。

下面为当前容器宿主机所有链上的 nat 表的防火墙规则

liruilonger@cloudshell:~$ sudo iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DNAT       tcp  --  0.0.0.0/0            169.254.169.254      tcp dpt:80 to:127.0.0.1:900
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
DNAT       tcp  --  0.0.0.0/0            169.254.169.254      tcp dpt:80 to:127.0.0.1:900
DNAT       tcp  --  0.0.0.0/0            169.254.169.254      tcp dpt:8080 to:169.254.169.254:80
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.2           172.17.0.2           tcp dpt:80

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:2024 to:172.17.0.2:80
liruilonger@cloudshell:~$

这里我们顺便看一下,容器端口映射的原理,实际上主要在 DOCKER 这条自定义链上配置了 DNAT

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:2024 to:172.17.0.2:80
liruilonger@cloudshell:~$

第二个规则是针对源地址为0.0.0.0/0,目标地址为0.0.0.0/0,目标端口为2024的TCP数据包。这个规则将数据包的目标地址修改为172.17.0.2:80,即将数据包重定向到172.17.0.2的端口80。

这里实际上进行了端口映射的操作,也就是 DNAT 发生的地方,它有两处引用

分别是PREROUTING链和OUTPUT链,意味着从外面发到本机和本地进程访问本机(由 iptables 匹配规则ADDRTYPE match dst-type LOCAL指定)的 2024 端口的包目的地址都会被修改成 172.17.0.2:80。

关于 docker 的端口映射, 除了使用docker ps命令给出容器的端口映射关系,还可以使用docker port命令查看容器的端口在主机上的映射

这里简单分享一些 DNAT 和 SNAT 的知识

SNAT/DNAT 认知DNAT

DNAT根据指定条件 修改数据包的目标IP地址和目标端口 。DNAT 的原理和我们上文讨论的端口转发原理差不多,差别是端口转发不修改IP地址。使用iptables做目的地址转换的一个典型例子如下:

iptables -t nat -A PREROUTING -d 1.2.3.4  -p tcp -dport 80 -j DNAT  --to-destination 10.20.30.40:8080 

同样,DNAT不修改协议。如果要匹配网卡,可以用 -i eth0 指定收到包的网卡(i 是 input 的缩写)。需要注意的是,DNAT 只发生在 nat表的 PREROUTING 链和 OUTPUT,这也是我们要指定收到包的网卡而不是发出包的网卡的原因

当涉及转发的目的IP地址是外机时,需要确保启用 ip forward 功能,即把 Linux :

echo 1 > /proc/sys/net/ipv4/ip_forward

SNAT/ 网络地址欺骗

神秘的网络地址欺骗其实是SNAT的一种。SNAT 根据指定条件修改数据包的源IP地址,即 DNAT 的逆操作。与 DNAT 的限制类似,SNAT 策略只能发生在 nat 表的 POSTROUTING 链 和 INPUT 链。

ipttables -t nat -A POSTROUTING -s 192.168.26.12 -o eth0 -j SNAT -to-source 10.127.16.1 

至于网络地址伪装,与SNAT类似,其实就是一种特殊的源地址转换,报文从哪个网卡出就用该网卡上的IP地址替换该报文的源地址,具体用哪个IP地址由内核决定。下面这条规则的意思是:源地址是 10.8.0.0/16 的报文都做一次 Masq 。

iptable -t nat -A POSTROUTING -s 10.8.0.0/16 -j MASQUERADE

docker 主机ip_主机游戏_docker跨主机网络

博文部分内容参考

原创文章,作者:筱凯,如若转载,请注明出处:https://www.jingyueyun.com/ask/431.html

(0)
筱凯筱凯
上一篇 2024 年 7 月 11 日
下一篇 2024 年 7 月 11 日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

云产品限时秒杀。精选云产品高防服务器,500M大带宽限量抢购  >>点击进入