5、Docker容器网络

使用Linux进行IP层网络管理的指     http://linux-ip.net/html/

# yum install iproute   

http://linux-ip.net/html/tools-ip-route.html

https://segmentfault.com/a/1190000000638244

参考:https://cizixs.com/2017/02/10/network-virtualization-network-namespace/

network namespace 是实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,它们有独自的网络栈信息。不管是虚拟机还是容器,运行的时候仿佛自己就在独立的网络中。

介绍 network namespace 的基本概念和用法,network namespace 是 linux 内核提供的功能,借助 ip 命令来完成各种操作。ip 命令来自于 iproute2 安装包,一般系统会默认安装,如果没有的话,请读者自行安装。

NOTE:ip 命令因为需要修改系统的网络配置,默认需要 sudo 权限。这篇文章使用 root 用户执行,请不要在生产环境或者重要的系统中用 root 直接执行,以防产生错误。

ip 命令管理的功能很多,和 network namespace 有关的操作都是在子命令 ip netns 下进行的,可以通过 ip netns help 查看所有操作的帮助信息。

默认情况下,使用 ip netns 是没有网络 namespace 的,所以 ip netns ls 命令看不到任何输出。

# ip
Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }
       ip [ -force ] -batch filename
where  OBJECT := { link | address | addrlabel | route | rule | neigh | ntable |
                   tunnel | tuntap | maddress | mroute | mrule | monitor | xfrm |
                   netns | l2tp | fou | macsec | tcp_metrics | token | netconf | ila |
                   vrf }
       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |
                    -h[uman-readable] | -iec |
                    -f[amily] { inet | inet6 | ipx | dnet | mpls | bridge | link } |
                    -4 | -6 | -I | -D | -B | -0 |
                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] |
                    -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |
                    -rc[vbuf] [size] | -n[etns] name | -a[ll] |?-c[olor]}
# ip netns help
Usage: ip netns list
       ip netns add NAME
       ip netns set NAME NETNSID
       ip [-all] netns delete [NAME]
       ip netns identify [PID]
       ip netns pids NAME
       ip [-all] netns exec [NAME] cmd ...
       ip netns monitor
       ip netns list-id

创建 network namespace 也非常简单,直接使用 ip netns add 后面跟着要创建的 namespace 名称。
如果相同名字的 namespace 已经存在,命令会报 Cannot create namespace 的错误。

# ip netns add r1
# ip netns list
    r1

ip netns 命令创建的 network namespace 会出现在 /var/run/netns/ 目录下,如果需要管理其他不是 ip netns 创建的 network namespace,只要在这个目录下创建一个指向对应 network namespace 文件的链接就行。

有了自己创建的 network namespace,我们还需要看看它里面有哪些东西。对于每个 network namespace 来说,它会有自己独立的网卡、路由表、ARP 表、iptables 等和网络相关的资源。ip命令提供了 ip netns exec 子命令可以在对应的 network namespace 中执行命令,比如我们要看一下这个 network namespace 中有哪些网卡。更棒的是,要执行的可以是任何命令,不只是和网络相关的(当然,和网络无关命令执行的结果和在外部执行没有区别)。比如下面例子中,执行 bash 命令了之后,后面所有的命令都是在这个 network namespace 中执行的,好处是不用每次执行命令都要把 ip netns exec NAME 补全,缺点是你无法清楚知道自己当前所在的 shell,容易混淆。

[@node3 ~]# ip netns exec r1 ifconfig
[@node3 ~]# ip netns exec r1 ip addr
     1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

[@node3 ~]# ip netns exec r1 bash
[@node3 ~]# ip addr
    1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

每个 namespace在创建的时候会自动创建一个 lo 的 interface,它的作用和 linux 系统中默认看到的 lo 一样,都是为了实现 loopback 通信。如果希望 lo 能工作,不要忘记启用它:

[root@node3 ~]# ip netns exec r1 ip link set lo up

默认情况下,network namespace 是不能和主机网络,或者其他 network namespace 通信的。

# ip link help
Usage: ip link add [link DEV] [ name ] NAME
                   [ txqueuelen PACKETS ]
                   [ address LLADDR ]
                   [ broadcast LLADDR ]
                   [ mtu MTU ] [index IDX ]
                   [ numtxqueues QUEUE_COUNT ]
                   [ numrxqueues QUEUE_COUNT ]
                   type TYPE [ ARGS ]

       ip link delete { DEVICE | dev DEVICE | group DEVGROUP } type TYPE [ ARGS ]

       ip link set { DEVICE | dev DEVICE | group DEVGROUP }
                      [ { up | down } ]
                      [ type TYPE ARGS ]
                      [ arp { on | off } ]
                      [ dynamic { on | off } ]
                      [ multicast { on | off } ]
                      [ allmulticast { on | off } ]
                      [ promisc { on | off } ]
                      [ trailers { on | off } ]
                      [ carrier { on | off } ]
                      [ txqueuelen PACKETS ]
                      [ name NEWNAME ]
                      [ address LLADDR ]
                      [ broadcast LLADDR ]
                      [ mtu MTU ]
                      [ netns { PID | NAME } ]
                      [ link-netnsid ID ]
              [ alias NAME ]
                      [ vf NUM [ mac LLADDR ]
                   [ vlan VLANID [ qos VLAN-QOS ] [ proto VLAN-PROTO ] ]
                   [ rate TXRATE ]
                   [ max_tx_rate TXRATE ]
                   [ min_tx_rate TXRATE ]
                   [ spoofchk { on | off} ]
                   [ query_rss { on | off} ]
                   [ state { auto | enable | disable} ] ]
                   [ trust { on | off} ] ]
                   [ node_guid { eui64 } ]
                   [ port_guid { eui64 } ]
              [ xdp { off |
                  object FILE [ section NAME ] [ verbose ] |
                  pinned FILE } ]
              [ master DEVICE ][ vrf NAME ]
              [ nomaster ]
              [ addrgenmode { eui64 | none | stable_secret | random } ]
                      [ protodown { on | off } ]

       ip link show [ DEVICE | group GROUP ] [up] [master DEV] [vrf NAME] [type TYPE]

       ip link xstats type TYPE [ ARGS ]

       ip link afstats [ dev DEVICE ]

       ip link help [ TYPE ]

TYPE := { vlan | veth | vcan | dummy | ifb | macvlan | macvtap |
          bridge | bond | team | ipoib | ip6tnl | ipip | sit | vxlan |
          gre | gretap | ip6gre | ip6gretap | vti | nlmon | team_slave |
          bond_slave | ipvlan | geneve | bridge_slave | vrf | macsec }

network namespace 之间通信

有了不同 network namespace 之后,也就有了网络的隔离,但是如果它们之间没有办法通信,也没有实际用处。要把两个网络连接起来,linux 提供了 veth pair 。可以把 veth pair 当做是双向的 pipe(管道),从一个方向发送的网络数据,可以直接被另外一端接收到;或者也可以想象成两个 namespace 直接通过一个特殊的虚拟网卡连接起来,可以直接通信。

使用 ip link add type veth 来创建一对 veth pair 出来,需要记住的是 veth pair 无法单独存在,删除其中一个,另一个也会自动消失。

# ip link add name veth1.1 type veth peer name veth1.2

# ip link sh

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    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 state UP mode DEFAULT group default qlen 1000
    link/ether 00:0c:29:82:17:a2 brd ff:ff:ff:ff:ff:ff
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default 
    link/ether 02:42:1a:5e:9d:4e brd ff:ff:ff:ff:ff:ff
4: veth1.2@veth1.1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000    //此时veth1.1和veth1.2都是在宿主机上
    link/ether 36:59:f5:02:39:1d brd ff:ff:ff:ff:ff:ff
5: veth1.1@veth1.2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 0e:b8:58:dd:5d:5a brd ff:ff:ff:ff:ff:ff
# ip addr list
2: veth1.2@veth1.1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000 //此时都是没有启动的 link/ether d2:e2:d8:fd:50:60 brd ff:ff:ff:ff:ff:ff 3: veth1.1@veth1.2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 2a:b1:4b:19:fa:b8 brd ff:ff:ff:ff:ff:ff

创建 veth pair 的时候可以自己指定它们的名字,比如 ip link add veth1.1 type veth peer name veth1.2 创建出来的两个名字就是 veth1.1 和 veth1.2 。

如果 pair 的一端接口处于 DOWN 状态,另一端能自动检测到这个信息,并把自己的状态设置为 NO-CARRIER

创建结束之后,能看到名字为 veth1.1 和 veth1.2 两个网络接口。

# ip link help

Usage: ip link add [link DEV] [ name ] NAME      
       ip link set { DEVICE | dev DEVICE | group DEVGROUP }
                      [ { up | down } ]
                      [ type TYPE ARGS ]
                      [ arp { on | off } ]
                      [ dynamic { on | off } ]
                      [ multicast { on | off } ]
                      [ allmulticast { on | off } ]
                      [ promisc { on | off } ]
                      [ trailers { on | off } ]
                      [ carrier { on | off } ]
                      [ txqueuelen PACKETS ]
                      [ name NEWNAME ]
                      [ address LLADDR ]
                      [ broadcast LLADDR ]
                      [ mtu MTU ]
                      [ netns { PID | NAME } ]
                      [ link-netnsid ID ]
              [ alias NAME ]
                      [ vf NUM [ mac LLADDR ]      
TYPE := { vlan | veth | vcan | dummy | ifb | macvlan | macvtap |
          bridge | bond | team | ipoib | ip6tnl | ipip | sit | vxlan |
          gre | gretap | ip6gre | ip6gretap | vti | nlmon | team_slave |
          bond_slave | ipvlan | geneve | bridge_slave | vrf | macsec }

接下来,要做的是把veth1.2放到namespace --> r1,把veth1.1留在宿主机上,这个可以使用 ip link set DEV netns NAME 来实现

# ip link set dev veth1.2 netns r1   //利用内核级的系统调用将网卡设备放到名称空间

# ip link sh

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    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 state UP mode DEFAULT group default qlen 1000
    link/ether 00:0c:29:82:17:a2 brd ff:ff:ff:ff:ff:ff
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default 
    link/ether 02:42:1a:5e:9d:4e brd ff:ff:ff:ff:ff:ff
5: veth1.1@if4: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000    //此时宿主机空间只有veth1.1了
    link/ether 0e:b8:58:dd:5d:5a brd ff:ff:ff:ff:ff:ff link-netnsid 0

# ip netns exec r1 ifconfig -a    //一个设备只能属于一个名称空间,查看r1名称空间信息

lo: flags=8<LOOPBACK>  mtu 65536
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth1.2: flags=4098<BROADCAST,MULTICAST>  mtu 1500
        ether 36:59:f5:02:39:1d  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

# ip netns exec r1 ip link set dev veth1.2 name eth0   //更改网卡名称

# ifconfig veth1.1 10.1.0.1/24 up    //激活网卡
# ifconfig

veth1.1: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 10.1.0.1  netmask 255.255.255.0  broadcast 10.1.0.255
        ether 0e:b8:58:dd:5d:5a  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

# ip netns exec r1 ifconfig eth0 10.1.0.2/24 up

# ip netns exec r1 ifconfig -a

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.1.0.2  netmask 255.255.255.0  broadcast 10.1.0.255
        inet6 fe80::3459:f5ff:fe02:391d  prefixlen 64  scopeid 0x20<link>
        ether 36:59:f5:02:39:1d  txqueuelen 1000  (Ethernet)
        RX packets 8  bytes 656 (656.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 8  bytes 656 (656.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
# ping 10.1.0.2    //此时用宿主机是可以ping通的
PING 10.1.0.2 (10.1.0.2) 56(84) bytes of data.
64 bytes from 10.1.0.2: icmp_seq=1 ttl=64 time=0.153 ms
64 bytes from 10.1.0.2: icmp_seq=2 ttl=64 time=0.041 ms
# ping 10.1.0.1
PING 10.1.0.1 (10.1.0.1) 56(84) bytes of data.
64 bytes from 10.1.0.1: icmp_seq=1 ttl=64 time=0.054 ms
64 bytes from 10.1.0.1: icmp_seq=2 ttl=64 time=0.036 ms

# ip netns add r2   //添加名称空间r2

# ip link set dev veth1.1 netns r2   //把veth1.1放到新建的名称空间r2上

# ip netns exec r2 ifconfig -a

# ip netns exec r2 ifconfig veth1.1 10.1.0.3/24 up    //配置IP并激活

# ip netns exec r2 ping 10.1.02   //ping r1 的网卡IP是通的

以上的网络拓扑结构

在宿主机中创建两个namespace --> r1和r2,并利用ip netns创建一对虚拟网卡设备veth1.1和veth1.2,在网卡设备没有放进namespace的时候,这对设备是在宿主机上的,但是并没有启动,此时是可以对这对网卡配置IP并启动的。

注意:当veth1.1放进namespace r1,而veth1.2仍然在宿主机上时,如果r1上的veth1.1和veth1.2通信时,相当于r1和宿主机通信。

当把这对网卡设备放进namespace中时,在宿主上是无法查看到的,只能在namespace中可以查看,因为一个设备只支持一个名称空间

四种网络模型

 

第二种网络模型的配置即bridge网络模型

# docker run --name t1 -ti --rm busybox:latest    //第二种网络模型,拥有自己的网络接口
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
697743189b6d: Pull complete 
Digest: sha256:061ca9704a714ee3e8b80523ec720c64f6209ad3f97c0ff7cb9ec7d19f15149f
Status: Downloaded newer image for busybox:latest
/ # ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02  
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:14 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1172 (1.1 KiB)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
 --rm    Automatically remove the container when it exits

下面对第二种网络模型进行显式配置

# docker run --name t1 -it --network bridge --rm busybox:late   //使用network指定bridge网络模式与不指定网络模式是一样的,因为docker的默认网络模式就是bridge

# docker ps -a   //查看是否有运行的容器

# docker run --name t1 -it --network none --rm busybox:latest    //无网络模式

# docker run --name t1 -it --network bridge --rm busybox:late    //bridge模式是用来对外通信的,即使不对宿主机之外的主机进行通信,也可以对统一宿主机内的其他容器进行通信,

主机间通信很多会使用主机名

/ # hostname
d291645c7bac    //docker容器的ID号就是docker的主机名,可以使用#docker ps查看,也可以手动指定主机名

# docker run --name t1 -it --network bridge -h t1.beisen --rm busybox:latest   //直接指定主机名

/ # hostname   //注意:这个容器想要通过主机名的方式访问其他主机/容器,有种方式:一:通过DNS解析主机名,二:通过本地的hosts文件
t1.beisen

/ # 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.17.0.2 t1.beisen t1   //对自己解析没有问题

/ # cat /etc/resolv.conf
# Generated by NetworkManager
nameserver 8.8.8.8   //这里所用的DNS解析服务器是宿主机的DNS服务器,如果能与8.8.8.8通信,那么此处的DNS服务器就可以解析此容器的主机名

/ # nslookup -type=A www.baidu.com
Server:        8.8.8.8
Address:    8.8.8.8:53

Non-authoritative answer:
www.baidu.com    canonical name = www.a.shifen.com
Name:    www.a.shifen.com
Address: 61.135.169.121
Name:    www.a.shifen.com
Address: 61.135.169.125

想要对某一主机的访问不是通过DNS解析服务器,而是通过/etc/hosts文件来访问

# docker run --help   //非常有帮助

Usage:    docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

Run a command in a new container

Options:
      --add-host list                  Add a custom host-to-IP mapping (host:ip)  
  -a, --attach list                    Attach to STDIN, STDOUT or STDERR
      --blkio-weight uint16            Block IO (relative weight), between 10 and 1000, or 0 to
                                       disable (default 0)
.........
      --dns list                       Set custom DNS servers   
      --dns-option list                Set DNS options
      --dns-search list                Set custom DNS search domains   //指明搜索域
      --entrypoint string              Overwrite the default ENTRYPOINT of the image........
  -v, --volume list                    Bind mount a volume
      --volume-driver string           Optional volume driver for the container
      --volumes-from list              Mount volumes from the specified container(s)
  -w, --workdir string                 Working directory inside the container

# docker run --name t1 -it --network bridge -h t1.beisen --dns 114.114.114.114 --rm busybox:latest   //指定域名服务器

/ # cat /etc/resolv.conf
nameserver 114.114.114.114

# docker run --name t1 -it --network bridge -h t1.beisen --dns 114.114.114.114 --dns-search ilinux.io --rm busybox:latest    //指定搜索域

/ # cat /etc/resolv.conf
search ilinux.io
nameserver 114.114.114.114

如果期望在/etc/hosts文件中自动解析www.baidu.com这样的主机名该如何操作?

Usage:    docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Run a command in a new container
Options:
      --add-host list                  Add a custom host-to-IP mapping (host:ip)    //生成hosts文件的解析记录

# docker run --name t1 -it --network bridge -h t1.beisen --dns 114.114.114.114 --dns-search ilinux.io --add-host www.baidu.com:1.1.1.1 --rm busybox:latest  

//这里直接在容器外通过它的文件系统向内注入信息,而不用启动容器后再进行编辑

/ # 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
1.1.1.1    www.baidu.com
172.17.0.2    t1.beisen t1

 

开放内网通信

假如容器在bridge网络模式下,docker容器提供的nginx服务,那么nginx是向外提供服务的。但是默认情况下这个容器是隐藏在docker bridge之后的,容器的IP地址172.17网络是个私有地址。

比如在node2上启动一个容器,IP为172.17.0.2,在node3上是无法ping通此IP的。因此需要把想运行在容器中的nginx这样的服务暴露到对外通信的网络中去。

暴露的方式有四种:

                    Opening inbound communication

# docker images

REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
studycolumn/httpd        v0.2                24577eceda35        5 days ago          1.2MB   //将此版本启动一个容器,将此容器的80端口映射至宿主机的一个随机端口
beisen/httpd             v0.1-1              439595aa4b0f        6 days ago          1.2MB
redis                    4-alpine            23d561d12e92        2 weeks ago         35.5MB
nginx                    1.14-alpine         66952fd0a8ef        2 weeks ago         16MB
busybox                  latest              3a093384ac30        7 weeks ago         1.2MB
quay.io/coreos/flannel   v0.10.0-amd64       f0fad859c909        13 months ago       44.6MB

1、-p <containerPort>

将指定的容器端口映射至主机所有地址的一个动态端口

注意:"主机所有端口"表示一个宿主机上可能会有多个IP,那么 -p 80会把此宿主机的所有IP的 32768 端口都映射为 80 端口。

2、-p <hostPort>::<containerPort>   

将容器端口<containerPort>映射至指定的主机端口<hostPort>

3、-p <ip>::<containerPort>   //中间两个冒号,两个冒号中间表示宿主机使用什么端口,如果为空就表示随机端口

将指定的容器端口<containerPort>映射至主机指定<ip>的动态端口

4、-p <ip>:<hostPort>:<containerPort>

将指定的容器端口<containerPort>映射至主机指定<ip>的端口<hostPort>

#  docker run --name myweb --rm -p 80 studycolumn/httpd:v0.2

# docker inspect myweb

。。。。。。

"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.7",

。。。。。。

# curl 172.17.0.7
   <h1>Busybox server<h1>   //在本机内访问容器的nginx服务是可以的,但这只是主机内部的访问,我们想要的效果是外部的物理主机访问此宿主机的IP时,容器内运行的nginx程序提供的web服务器应有相应

但是外部的网络访问容器所在的宿主机IP时,应该访问哪个端口?因为80端口是容器内的程序提供的,而不是宿主机直接提供的。 

# iptables -t nat -vnL   //可以使用此命令在宿主机上进行查看

Chain PREROUTING (policy ACCEPT 1 packets, 52 bytes)
 pkts bytes target     prot opt in     out     source               destination         
   11   572 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 1 packets, 52 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 3 packets, 208 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 3 packets, 208 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0           
    0     0 MASQUERADE  tcp  --  *      *       172.17.0.7           172.17.0.7           tcp dpt:80

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0           
    0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:32768 to:172.17.0.7:80    //此规则是在上面创建容器是 -p 80 自动生成的,当容器消失时,这条规则会自动删除

在浏览器中输入宿主机的IP+映射端口:192.168.128.131:32768 即可访问容器中nginx程序提供的web服务

查看映射端口的另一种方法

# docker port myweb      //查看映射端口的另一种方法
  80/tcp -> 0.0.0.0:32769   //0.0.0.0表示宿主机的所有端口,注意在容器被删除后,再创建同样的容器,映射的随机端口是不断增长的,比如上面映射的端口是32768,推出后再次创建容器映射的随机端口就是32769了。

# docker kill myweb   //此时容器myweb被删除,iptables规则就不存在了

如果希望指定映射端口或者IP,而不是自动生成iptables规则,不是映射为一个动态端口,而是映射为固定的地址。

3、-p <ip>::<containerPort>   //中间两个冒号,两个冒号中间表示宿主机使用什么端口,如果为空就表示随机端口

将指定的容器端口<containerPort>映射至主机指定<ip>的动态端口

#  docker run --name myweb --rm -p 192.168.128.131::80 studycolumn/httpd:v0.2

# docker port myweb
  80/tcp -> 192.168.128.131:32768 

如果宿主机的80端口没有被使用,也可以指定宿主机的80端口

#  docker run --name myweb --rm -p 80:80 studycolumn/httpd:v0.2

 

# docker port myweb
  80/tcp -> 0.0.0.0:80

4、-p <ip>:<hostPort>:<containerPort>

将指定的容器端口<containerPort>映射至主机指定<ip>的端口<hostPort>

#  docker run --name myweb --rm -p 192.168.128.131:80:80 studycolumn/httpd:v0.2

# docker port myweb
  80/tcp -> 192.168.128.131:80

第三种网络模型:联盟式容器 Joined containers

# docker images

REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
studycolumn/httpd        v0.2                24577eceda35        6 days ago          1.2MB
beisen/httpd             v0.1-1              439595aa4b0f        6 days ago          1.2MB
redis                    4-alpine            23d561d12e92        2 weeks ago         35.5MB
nginx                    1.14-alpine         66952fd0a8ef        2 weeks ago         16MB
busybox                  latest              3a093384ac30        7 weeks ago         1.2MB
quay.io/coreos/flannel   v0.10.0-amd64       f0fad859c909        13 months ago       44.6MB

# docker run --name b2 -it --rm busybox    

/ # ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:07  
          inet addr:172.17.0.7  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:6 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:516 (516.0 B)  TX bytes:0 (0.0 B)

# docker run --name b2 --network container:b1 -it --rm busybox   //上面启动的容器地址与本次启动的容器地址是不同的,因为本次创建的容器是共享了b1的IP,但是b1和b2之间的文件系统等还是隔离的。

/ # ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:04  
          inet addr:172.17.0.4  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:18 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1252 (1.2 KiB)  TX bytes:0 (0.0 B)

/ # echo "hello world" > /tmp/index.html   //注意这里是容器:b2
/ # httpd -h /tmp/   //启动httpd,并把根目录放到/tmp

在b1上访问容器的回环地址lo,可以看到容器b2提供的web服务,所以b1和b2两者是共享网络空间

/ # wget -O - -q 127.0.0.1
hello world

 

第四种网络模型:开放式网络,直接共享宿主机的网络

下面把两个容器全部退出,然后重新创建容器b2

# docker run --name b2 --network host -it --rm busybox   //此次创建的容器b2共享的是宿主机的网络

/ # ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:8F:0E:6A:12  
          inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0
          inet6 addr: fe80::42:8fff:fe0e:6a12/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:111 errors:0 dropped:0 overruns:0 frame:0
          TX packets:122 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:9305 (9.0 KiB)  TX bytes:12522 (12.2 KiB)

ens33     Link encap:Ethernet  HWaddr 00:0C:29:EE:53:1E  
          inet addr:192.168.128.131  Bcast:192.168.128.255  Mask:255.255.255.0
          inet6 addr: fe80::f528:9792:1744:13a7/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:209474 errors:0 dropped:0 overruns:0 frame:0
          TX packets:186992 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:107144705 (102.1 MiB)  TX bytes:340231253 (324.4 MiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
/ # echo "hello world" > /tmp/index.html
/ # httpd -h /tmp/
/ # netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      
tcp        0      0 :::80                   :::*                    LISTEN    //可以看出这里监听的是本主机的80端口,所以外网主机可以直接访问容器提供的web服务
tcp        0      0 :::22                   :::*                    LISTEN      
tcp        0      0 ::1:25                  :::*                    LISTEN    

基于 --network host 这种方式的好处在于:不用安装httpd程序,当宿主机出问题的时候,可以直接把容器迁移到其他主机上,直接共享宿主机的网络

自定义docker桥,分别使用不同网段的地址

如何更改默认docker0上的网络地址

自定义docker0桥的网络属性信息:/etc/docker/daemon.json文件
{
"bip": "192.168.1.5/24",         //只要修改bip,其他选项即可通过计算得知,除了dns
"fixed-cidr": "10.20.0.0/16",    //注意:只要不是最后一行,行尾一定要有 ","
"fixed-cidr-v6": "2001:db8::/64",
"mtu": 1500,
"default-gateway": "10.20.1.1",
"default-gateway-v6": "2001:db8:abcd::89",
"dns": ["10.20.1.2","10.20.1.3"]
}

核心选项为bip,即bridge ip之意,用于指定docker0桥自身的IP地址,系统会自动推算出这个bridge ip所属网络,
并把这个网络当作随后加入这个bridge的所有容器的默认网络。

演示:修改默认docker0桥的网络IP

# docker kill NAME   //杀死所有运行的容器

# systemctl stop docker

# vim /etc/docker/daemon.json  

{
  "registry-mirrors": ["https://registry.docker-cn.com","https://7g3yx938.mirror.aliyuncs.com"],  //注意一定要有逗号
  "bip": "10.0.0.1/16"    //添加此行
}

# systemctl start docker
# ifconfig

docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 10.0.0.1  netmask 255.255.0.0  broadcast 10.0.255.255      //docker0的默认IP地址已经更换,此时docker0的DNS服务器的IP是宿主机的,因为没有指明
        inet6 fe80::42:1aff:fe5e:9d4e  prefixlen 64  scopeid 0x20<link>
        ether 02:42:1a:5e:9d:4e  txqueuelen 0  (Ethernet)
        RX packets 10  bytes 535 (535.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 18  bytes 1390 (1.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

dockerd守护进程的C/S,其默认仅监听Unix SOcket格式的地址,所以docker仅支持本地通信,路径是:/var/run/docker.sock;

所以不同宿主机上的容器是不能互相管理的

 

如果想要docker服务器可以被外网访问,需要使用TCP套接字,/etc/docker/daemon.json:
"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]   //本来只有此路径:unix:///var/run/docker.sock,这里再添加tcp://0.0.0.0:2375表示监听本机tcp协议的所有IP地址的2375端口
也可向docker直接传递"-H|--host"选项;  

# systemctl stop docker

# vim /etc/docker/daemon.json 

{
  "registry-mirrors": ["https://registry.docker-cn.com","https://7g3yx938.mirror.aliyuncs.com"],
  "bip": "10.0.0.1/16",
  "host": ["tcp://0.0.0.0:2375","unix:///var/run/docker.sock"]   //添加此行
  "storage-driver": "devicemapper"   //此行可以先不添加,如果有此报错信息:start request repeated too quickly for docker.service 再添加
}

# systemctl start docker 

 

自定义网络类型 --> bridge

# docker info

...

Network: bridge host macvlan null overlay    //docker有多种类型(overlay是叠加网络)

...

# docker network --help

Usage:    docker network COMMAND

Manage networks

Commands:
  connect     Connect a container to a network
  create      Create a network
  disconnect  Disconnect a container from a network
  inspect     Display detailed information on one or more networks
  ls          List networks
  prune       Remove all unused networks
  rm          Remove one or more networks

Run 'docker network COMMAND --help' for more information on a command.

# docker network create --help

Usage:    docker network create [OPTIONS] NETWORK

Create a network

Options:
      --attachable           Enable manual container attachment
      --aux-address map      Auxiliary IPv4 or IPv6 addresses used by Network driver (default map[])
      --config-from string   The network from which copying the configuration
      --config-only          Create a configuration only network
  -d, --driver string        Driver to manage the Network (default "bridge")
      --gateway strings      IPv4 or IPv6 Gateway for the master subnet
      --ingress              Create swarm routing-mesh network
      --internal             Restrict external access to the network
      --ip-range strings     Allocate container ip from a sub-range
      --ipam-driver string   IP Address Management Driver (default "default")
      --ipam-opt map         Set IPAM driver specific options (default map[])
      --ipv6                 Enable IPv6 networking
      --label list           Set metadata on a network
  -o, --opt map              Set driver specific options (default map[])
      --scope string         Control the network's scope
      --subnet strings       Subnet in CIDR format that represents a network segment

# docker network create -d bridge --subnet "172.26.0.0/16" --gateway "172.26.0.1" mybr0    //注意:mybr0是网络,而不是网络接口

# docker network ls

NETWORK ID          NAME                DRIVER              SCOPE
f1f7dae9d2d8        bridge              bridge              local
c7a430036ed4        host                host                local
c1eea5d428d2        mybr0               bridge              local
e1bfb53c06c1        none                null                local

# ifconfig    //br-c1eea5d428d2是网络接口

br-c1eea5d428d2: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.26.0.1  netmask 255.255.0.0  broadcast 172.26.255.255
        ether 02:42:54:30:6a:12  txqueuelen 0  (Ethernet)
        RX packets 64  bytes 5568 (5.4 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 64  bytes 5568 (5.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

# ifconfig br-c1eea5d428d2 down   //重命名网络接口:br-c1eea5d428d2,修改之前必须使此网络接口下线

# ip link set dev br-c1eea5d428d2 name docker1   //重命名

# ifconfig docker1 up  //启动网络接口

docker1: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.26.0.1  netmask 255.255.0.0  broadcast 172.26.255.255
        ether 02:42:54:30:6a:12  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

创建容器是直接把这个网络加入就可以了,但是由于更改了网络接口名称,在把网络mybr0加入到容器时出了一些问题,不确定是否是修改网络接口的问题,所以再次创建了网络mybr1

# docker run --name t1 -it --net mybr1 busybox:latest  //--net是--network的缩写

/ # ifconfig   //此时就有两个桥接时网络
eth0      Link encap:Ethernet  HWaddr 02:42:AC:1B:00:02  
          inet addr:172.27.0.2  Bcast:172.27.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:12 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1032 (1.0 KiB)  TX bytes:0 (0.0 B)

下面再创建一个容器,使用docker0网络,即原有的bridge网络

# docker run --name t2 -it --net bridge busybox:latest    //使用docker默认的桥接网路

/ # ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:0A:00:00:02  
          inet addr:10.0.0.2  Bcast:10.0.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:16 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1312 (1.2 KiB)  TX bytes:0 (0.0 B)

此时容器t1和t2是否能通信?是可以通信的,因为网卡1 mybr0和网卡2 docker0同时都有一个接口与宿主机相连接,只需要在宿主机上打开核心转发功能即可

# cat /proc/sys/net/ipv4/ip_forward   //核心转发功能已经打开
1

/ # ping 172.27.0.2   //在容器t2上ping容器t1的IP,此时无法ping通,因为宿主机上存在很多iptables规则

# iptables -vnL --line-number   //分析iptables规则后发现有一条规则是组织容器间通信

Chain DOCKER-ISOLATION-STAGE-2 (3 references)
num   pkts bytes target     prot opt in     out     source               destination         
1        4   336 DROP       all  --  *      br-fa8eef203943  0.0.0.0/0            0.0.0.0/0         //修改此条iptables规则的target为ACCEPT    
2        0     0 DROP       all  --  *      docker0  0.0.0.0/0            0.0.0.0/0           
3        0     0 DROP       all  --  *      br-c1eea5d428d2  0.0.0.0/0            0.0.0.0/0           
4        0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0     

# iptables -t filter -R DOCKER-ISOLATION-STAGE-2 1 -j ACCEPT    //此规则可参考:http://www.zsythink.net/archives/1517

/ # ping 172.27.0.2    //此时在容器t2(10.0.0.2)上即可ping通容器t1
PING 172.27.0.2 (172.27.0.2): 56 data bytes
64 bytes from 172.27.0.2: seq=0 ttl=63 time=1.385 ms
64 bytes from 172.27.0.2: seq=1 ttl=63 time=0.264 ms

可以想象为什么有iptables规则阻止两个容器通信,是因为既然是两个网段就是不让通信的。

 

posted @ 2019-02-21 17:38  Study~Column  阅读(704)  评论(0编辑  收藏  举报