Docker之网络

目录

网络

  1. Docker安装时会自动在host上创建三个网络

    $ docker network ls
    NETWORK ID          NAME                       DRIVER              SCOPE
    5376af5a55ad        bridge                     bridge              local
    049e90fb8744        host                       host                local
    1c17c651c262        none                       null                local
    
  2. Docker服务启动时会首先在主机上自动创建一个docker0虚拟网桥,实际上是一个Linux网桥。网桥可以理解为一个软件交换机,负责挂载其上的接口之间进行包转发。Docker随机分配一个本地未占用的私有网段中的一个地址给docker0接口

  3. 当创建一个Docker容器的时候,同时会创建了一对veth pair互联接口。当向任一个接口发送包时,另外一个接口自动收到相同的包。互联接口的一端位于容器内,即eth0;另一端在本地并被挂载到docker0网桥,名称以veth开头(例如vethAQI2QT)。通过这种方式,主机可以与容器通信,容器之间也可以相互通信。

none

  1. none网络就是什么都没有的网络。挂在这个网络下的容器除了lo,没有其他任何网卡。容器创建时,可以通过--network=none指定使用none网络

    $ docker run -it --network=none busybox
    
  2. 封闭意味着隔离,一些对安全性要求高并且不需要联网的应用可以使用none网络。比如某个容器的唯一用途是生成随机密码,就可以放到none网络中避免密码被窃取。

host

  1. 连接到host网络的容器共享Docker host的网络栈,容器的网络配置与host完全一样。可以通过 --network=host指定使用host网络

    $ docker run -it --network=host busybox
    
    进入docker容器中执行
    $ ip l
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000
        link/ether 00:0c:29:bb:6b:c7 brd ff:ff:ff:ff:ff:ff
    3: virbr0: <BROADCAST,MULTICAST> mtu 1500 qdisc noqueue qlen 1000
        link/ether 52:54:00:bb:93:fc brd ff:ff:ff:ff:ff:ff
    ...省略...
    $ hostname
    jannal.docker.com
    
    在容器中可以看到host的所有网卡,并且连hostname也是host的。
    
  2. 直接使用Docker host的网络最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择host网络。当然不便之处就是牺牲一些灵活性,比如要考虑端口冲突问题,Docker host上已经使用的端口就不能再用了。

  3. Docker host的另一个用途是让容器可以直接配置host网路,比如某些跨host的网络解决方案,其本身也是以容器方式运行的,这些方案需要对网络进行配置,比如管理iptables

brige

  1. Docker安装时会创建一个命名为docker0的Linux bridge。如果不指定--network,创建的容器默认都会挂到docker0上

    一个新的网络接口veth80c2b3d被挂到了docker0上,veth80c2b3d就是新创建容器的虚拟网卡
    $ brctl show
    bridge name     bridge id               STP enabled     interfaces
    ...省略...
    br-fe270d626e9c         8000.02420cbe41a7       no
    docker0         8000.02423a05b84c       no              veth80c2b3d
    virbr0          8000.525400bb93fc       yes             virbr0-nic
    
    $ docker ps
    CONTAINER ID        IMAGE    ...
    8c7f87e79c4f        busybox  ...
    
    $ docker exec -it 8c7f87e79c4f /bin/bash
    容器内执行
    $ ip a
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
    57: eth0@if58: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
        link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
        inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
           valid_lft forever preferred_lft forever
    
    
  2. 容器内有一个网卡eth0@if58veth80c2b3deth0@if58实际上是一对veth pair。veth pair是一种成对出现的特殊网络设备,可以把它们想象成由一根虚拟网线连接起来的一对网卡,网卡的一头(eth0@if58)在容器中,另一头(veth80c2b3d)挂在网桥docker0上,其效果就是将eth0@if58也挂在了docker0上。

  3. eth0@if58的Ip网段为什么是172.17.0.3/16?

    bridge网络配置的subnet就是172.17.0.0/16,并且网关是172.17.0.1
    网关就是docker0
    容器创建时,docker会自动从172.17.0.0/16中分配一个IP,这里16位的掩码保证有足够多的IP可以供容器使用
    $ docker network inspect bridge
    [
        {
            "Name": "bridge",
            "Id": "5376af5a55ada8e8c62464f537c6c01c520346c579834db2f19c723114195ccc",
            "Created": "2021-08-16T10:40:49.736330436+08:00",
            "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": {
                "8c7f87e79c4f7e376d8641615ad3d2f3071ceb2e78a21df9a2929978b1662f55": {
                    "Name": "busy",
                    "EndpointID": "f5aa27be680b1066d00bef2b4379a079f7af45905346192aa1eec868db242345",
                    "MacAddress": "02:42:ac:11:00:03",
                    "IPv4Address": "172.17.0.3/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": {}
        }
    ]
    
    可以看到docker0的 Ip是 172.17.0.1 
    $  ifconfig docker0
    docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
            inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
            inet6 fe80::42:3aff:fe05:b84c  prefixlen 64  scopeid 0x20<link>
            ether 02:42:3a:05:b8:4c  txqueuelen 0  (Ethernet)
            RX packets 28  bytes 2469 (2.4 KiB)
            RX errors 0  dropped 0  overruns 0  frame 0
            TX packets 97  bytes 22049 (21.5 KiB)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    
  4. 网络拓扑图

    Docker之网络

容器与host veth pair

  1. 容器中的eth0实际上和外面host上的某个veth是成对的(pair)关系,那么,有没有办法知道host上的vethxxx和哪个container eth0是成对的关系呢?

  2. 方法一:

    1. 在目标容器里查看:
    $ cat /sys/class/net/eth0/iflink
    5
    
    2. 在主机上遍历/sys/claas/net下面的全部目录,
    查看子目录ifindex的值和容器里查出来的iflink值相当的veth名字,
    这样就找到了容器和主机的veth pair关系
    $ cat /sys/class/net/veth80c2b3d/ifindex
    5
    
  3. 方法二

    1. 在目标容器中执行
    $ ip link show eth0
    116:eth0@if117,其中116是eth0接口的index,117是和它成对的veth的index。
    
    2. 在host中执行。可以看到对应117的veth网卡是哪一个
    $ ip link show |grep 117
    
  4. 方法三

    1. 可以通过ethtool-S命令列出veth pair对端的网卡index
    $ ethtool -S eth0
    
    2. 查看主机上index为6的网卡
    $ ip addr
    

自定义

  1. Docker提供三种user-defined网络驱动:bridge、overlay和macvlan。overlay和macvlan用于创建跨主机的网络

  2. 可通过bridge驱动创建类似前面默认的bridge网络

    $ docker network create --driver bridge --subnet 172.50.16.0/24 net0
    391304d84cec40ccef6aa9f46580b4f2c6f42e0026c294a680ece26524f35f77
    $ docker network inspect net0
    [
        {
            "Name": "net0",
            "Id": "391304d84cec40ccef6aa9f46580b4f2c6f42e0026c294a680ece26524f35f77",
            "Created": "2021-09-16T21:43:52.880434594+08:00",
            "Scope": "local",
            "Driver": "bridge",
            "EnableIPv6": false,
            "IPAM": {
                "Driver": "default",
                "Options": {},
                "Config": [
                    {
                        "Subnet": "172.50.16.0/24"
                    }
                ]
            },
            "Internal": false,
            "Attachable": false,
            "Ingress": false,
            "ConfigFrom": {
                "Network": ""
            },
            "ConfigOnly": false,
            "Containers": {},
            "Options": {},
            "Labels": {}
        }
    ]
    
    $ docker network ls
    NETWORK ID          NAME                DRIVER              SCOPE
    5376af5a55ad        bridge              bridge              local
    049e90fb8744        host                host                local
    391304d84cec        net0                bridge              local
    1c17c651c262        none                null                local
    
    --ip指定静态IP。只有使用 --subnet创建的网络才能指定静态IP。
    $ docker run -it --network=net0 --ip 172.50.16.2 busybox 
    在docker容器中执行,可以看到eth0@if65网卡对应的IP是172.50.16.2
    $ ip a
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
    64: eth0@if65: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
        link/ether 02:42:ac:32:10:02 brd ff:ff:ff:ff:ff:ff
        inet 172.50.16.2/24 brd 172.50.16.255 scope global eth0
           valid_lft forever preferred_lft forever
    
  3. 使用默认docker0创建一个容器

    $ docker run -it --name docker0-busybox busybox 
    在容器中执行
    $ ip a
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
    68: eth0@if69: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
        link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
        inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
           valid_lft forever preferred_lft forever
    
  4. 使用docker0的容器IP是172.17.0.3,使用net0的容器IP是172.50.16.2。在两个容器中ping各自的IP,默认情况下肯定是无法ping通的。因为两个网络属于不同的网桥

    $ ping 172.17.0.3
    PING 172.17.0.3 (172.17.0.3): 56 data bytes
    $ ping 172.50.16.2
    PING 172.50.16.2 (172.50.16.2): 56 data bytes
    
  5. 查看

    $ brctl show 
    bridge name     bridge id               STP enabled     interfaces
    br-391304d84cec         8000.02423c2a9a63       no              veth7870726
    docker0         8000.02423a05b84c       no              veth4beae2e
                                                            veth8cf70c5
    virbr0          8000.525400bb93fc       yes             virbr0-nic
    

    Docker之网络

  6. 如果host上对每个网络都有一条路由,同时操作系统上打开了ip forwarding, host就成了一个路由器,挂接在不同网桥上的网络就能够相互通信。通过命令可以看出host确实已经成为一个路由器了

    1. 在Docker Host(宿主机)上查看路由表
    $ ip r
    default via 192.168.100.2 dev ens33 proto static metric 100 
    172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
    172.50.16.0/24 dev br-391304d84cec proto kernel scope link src 172.50.16.1 
    192.168.100.0/24 dev ens33 proto kernel scope link src 192.168.100.128 metric 100
    
    2. 查看ip forward是否开启
    $ sysctl net.ipv4.ip_forward
    net.ipv4.ip_forward = 1
    
    3. 查看iptables,iptables DROP掉了网桥br-391304d84cec与docker0之间双向的流量(这才是不能通信的原因)
    $ iptable-save
    ...省略...
    -A DOCKER-ISOLATION-STAGE-1 -i br-391304d84cec ! -o br-391304d84cec -j DOCKER-ISOLATION-STAGE-2
    -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
    ...省略...
    
  7. 既然iptables已经断掉了两个网络之间的流量,那么怎样才能想让两个网络能互通?

    $ docker ps
    CONTAINER ID        IMAGE       				 NAMES
    694b8f056a5d        busybox   ...省略...  docker0-busybox
    a32815a8712b        busybox  
    
    通过docker network connect 实现为694b8f056a5d添加一个网卡
    $ docker network connect net0 694b8f056a5d
    
    在docker0-busybox 容器中查看。发现多了一个网卡eth1@if71,分配的地址为172.50.16.3
    $ ip a
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
    68: eth0@if69: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
        link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
        inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
           valid_lft forever preferred_lft forever
    70: eth1@if71: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
        link/ether 02:42:ac:32:10:03 brd ff:ff:ff:ff:ff:ff
        inet 172.50.16.3/24 brd 172.50.16.255 scope global eth1
           valid_lft forever preferred_lft forever
    
    再次测试,发现可以ping通
    $ ping 172.50.16.2
    PING 172.50.16.2 (172.50.16.2): 56 data bytes
    64 bytes from 172.50.16.2: seq=0 ttl=64 time=0.283 ms
    64 bytes from 172.50.16.2: seq=1 ttl=64 time=0.093 ms       
    

网络配置

  1. 在启动容器的时候,如果不指定对应参数,在容器外部是无法通过网络访问容器内部服务
  2. 可以通过-P或者-p指定端口映射,使用-P时,docker会随机映射一个49000-49900的端口至容器内部开放的网络端口-p可以指定要映射的端口,并且在一个指定端口上只可以绑定一个容器,多次使用-p可以绑定多个端口。
  3. 映射所有网卡接口的地址
    • hostPort:containerPort
    • docker run -d -p 3000:3000 jannal/centos6.6-sshd:latest /bin/bash 此时会默认绑定本地所有网卡接口的所有地址
  4. 映射到指定地址的指定端口
    • ip:hostPort:containerPort
    • docker run -d -p 192.168.6.107:3000:3000 jannal/centos6.6-sshd:latest /bin/bash
  5. 映射到指定地址的任意端口,本地宿主机会随机分配一个地址
    • docker run -d -p 192.168.6.107::3000 jannal/centos6.6-sshd:latest /bin/bash 本地会自动分配一个端口
  6. 可以使用udp标记来指定udp端口
    • docker run -d -p 192.168.6.107:3000:3000/udp jannal/centos6.6-sshd:latest /bin/bash
  7. 查看映射端口配置
    • docker port

容器间通信

  1. 容器的连接系统是除了端口映射外另一种可以与容器中应用进行交互的方式。它会在源与接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息
  2. 通过--name 给容器起一个容易识别的名字,如果不指定,会随机起一个名字。容器的名称是唯一的,如果已经命名了一个叫web的容器,当你再次使用web这个名称的时候,需要先用docker rm来删除之前创建的同名容器。
  3. 容器间可通过IP、Docker DNS Server或join容器三种方式通信
    • IP方式:在容器创建时通过--network指定相应的网络,或者通过docker network connect将现有容器加入到指定网络
    • Docker DNS Server:从Docker 1.10版本开始,docker daemon实现了一个内嵌的DNS server,使容器可以直接通过容器名通信。使用docker DNS有个限制:只能在自定义网络中使用。即默认的bridge网络是无法使用DNS的。
    • joined容器:可以使两个或多个容器共享一个网络栈,共享网卡和配置信息,joined容器之间可以通过127.0.0.1直接通信
      • 不同容器中的程序希望通过loopback高效快速地通信,
      • 希望监控其他容器的网络流量,比如运行在独立容器中的网络监控程序。
  4. 容器互联:Docker相当于在两个互联的容器之间创建了一个虚机通道,而且不用映射它们的端口到宿主主机上。在启动db容器的时候并没有使用-p-P标记,从而避免了暴露数据库服务端口到外部网络上
    • 使用--link可以让容器之间安全的进行交互,接收容器可以通过容器名快速访问到源容器,而不用指定具体的IP地址
    • docker run -d --name mysql jannal/centos6.6-mysql
    • docker run -d --name web --link db:db webapp
    • --link name:alias: name是要链接的容器的名称,alias是这个连接的别名
    • docker ps来查看容器的连接
  5. Docker通过两种方式为容器公开互联信息:
    • 更新环境变量;
    • 更新/etc/hosts文件。

容器DNS和主机名

  1. Docker服务启动后会默认启用一个内嵌的DNS服务,来自动解析同一个网络中的容器主机名和地址,如果无法解析,则通过容器内的DNS相关配置进行解析

  2. 容器中主机名和DNS配置信息可以通过三个系统配置文件来管理:/etc/resolv.conf、/etc/hostname和/etc/hosts。在容器中使用mount命令可以看到这三个文件挂载信息

    $  docker exec -it b804919a247b /bin/bash
    $ mroot@b804919a247b:/# mount
    ...省略...
    /dev/sda2 on /etc/resolv.conf type xfs (rw,relatime,attr2,inode64,noquota)
    /dev/sda2 on /etc/hostname type xfs (rw,relatime,attr2,inode64,noquota)
    /dev/sda2 on /etc/hosts type xfs (rw,relatime,attr2,inode64,noquota)
    ...省略...
    
    
  3. Docker启动容器时,会从宿主机上复制 /etc/resolv.conf文件,并删除掉其中无法连接到的DNS服务器

    root@b804919a247b:/# cat /etc/resolv.conf
    search docker.com
    nameserver 127.0.0.11
    options ndots:0
    
  4. /etc/hosts文件中默认只记录了容器自身的地址和名称

    root@b804919a247b:/# cat /etc/hosts
    127.0.0.1	localhost
    ::1	localhost ip6-localhost ip6-loopback
    fe00::0	ip6-localnet
    ff00::0	ip6-mcastprefix
    ff02::1	ip6-allnodes
    ff02::2	ip6-allrouters
    172.18.0.2	b804919a247b
    
  5. /etc/hostname文件则记录了容器的主机名:

    root@b804919a247b:/#  cat /etc/hostname
    b804919a247b
    
  6. 容器运行时,可以在运行中的容器里直接编辑/etc/hosts、/etc/hostname和/etc/resolve. conf文件。但是这些修改是临时的,只在运行的容器中保留,容器终止或重启后并不会被保存下来,也不会被docker commit提交。

上一篇:ubuntu20安装kvm


下一篇:设计模式第七讲--Bridge 桥模式