一、Docker网络-简介

Docker网络架构源自一种叫做容器网络模型(CNM)的方案,该方案是开源的并且支持插接式连接。Libnetwork是Docker对CNM的一种实现,提供了Docker核心网络架构的全部功能。不同的驱动可以通过插拔的方式接入Libnetwork来提供定制化的网络拓扑。

为了实现开箱即用的效果,Docker封装了一系列本地驱动,覆盖了大部分常见的网络需求。其中包括单机桥接网络(Single-Host Bridge Network)、多机覆盖网络(Multi-Host Overlay),并且支持接入现有VLAN。

二、Docker网络-详解

2.1 基础理论

在顶层设计中,Docker网络架构由3个主要部分构成:CNM、Libnetwork和驱动。

CNM是设计标准。在CNM中,规定了Docker网络架构的基础组成要素。 Libnetwork是CNM的实现,并且被Docker采用。Libnetwork通过Go语言编写,并实现了CNM中列举的核心组件。 驱动通过实现特定网络拓扑的方式来拓展该模型的能力。

2.1.1 CNM

在CNM中,规定了Docker网络架构的基础组成要素。详细请查看 CNM完整介绍

CNM定义了三个基本要素:沙盒、终端和网络。 沙盒是一个独立的网络栈。其中包括以太网接口、端口、路由表以及DNS配置。 终端就是虚拟网络接口。跟普通网络接口一样,终端主要职责是负责创建连接。在CNM中,终端负责将沙盒连接到网络。一个终端只能接入一个网络中。 网络是802.1d网桥的软件实现。因此,网络就是需要交互的终端的集合,并且终端之间相互独立。

Docker环境中最小的调度单位就是容器,而CNM负责为容器提供网络功能。

2.1.2 Libnetwork

CNM是设计规范文档,Libnetwork是标准的实现。Libnetwork是开源的,采用Go语言编写,它跨平台且被Docker所使用。 Libnetwork实现了CNM中定义的全部3个组件。此外它还实现了本地服务发现、基于Ingress的容器负载均衡,以及网络控制层和管理层功能。

2.1.3 驱动

Libnetwork实现了控制层和管理层功能,而驱动负责实现数据层。Libnetwork支持同时激活多个网络驱动。

Docker封装了若干内置驱动,通常被称作原生驱动或者本地驱动。在Linux上包括Bridge、Overlay以及Macvlan,在Windows包括NAT、Overlay、Transport以及L2 Bridge。

其中控制层、管理层与数据层关系如下:

控制层、管理层与数据层关系

第三方也可以编写Docker驱动,这些驱动叫做远程驱动,如Calico、Contiv、Kuryr以及Weave。每个驱动都负责其上所有网络资源的创建和管理。

2.2 单机桥接网络

单机桥接网络是最简单的Docker网络。

Linux docker创建单机桥接网络采用内置的桥接驱动,而Windows docker创建时使用内置的NAT驱动。

每个Docker主机都有一个默认的单机桥接网络。在Linux上网络名称为bridge,在Windows上叫做nat。这里需要注意,默认情况下,新创建的容器都会连接到该网络。除非通过命令行创建容器时指定参数--network。

在Linux主机上,Docker网络由Bridge驱动创建,而Bridge底层是基于Linux内核中的Linux Bridge技术。在Linux Docker主机之上,默认的"bridge"网络被映射到内核中为"docker0"的Linux网桥,该网桥可以通过主机以太网接口的端口映射进行反向关联。

通过使用Linux brctl工具来查看系统的Linux网桥。若没有,则可使用apt-get install bridge-utils命令安装broctl安装包。

如果在相同网络中继续接入新的容器,那么在新接入容器中是可以通过之前加入的容器进行Ping通的。这里需要注意,Linux上默认的Bridge网络是不支持通过Docker DNS服务进行域名解析的。自定义网络可以。

下面进行测试: 1、创建名为"c2"的容器,并接入"c1"所在的localnet网络。

root@zq-virtual-machine:~# docker container run it --name c2 --network localnet alpine sh 

2、在"c2"容器中,通过"c1"容器名称执行ping命令。观察到,命令生效。这是因为c2容器运行了一个本地DNS解析器,该解析器将请求转发到了Docker内容DNS服务器当中。DNS服务器中记录了容器启动时通过--name或--net-alias参数指定的名称与容器之间的映射关系。

/ # ping c1
PING c1 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=1.701 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.119 ms
^C
--- c1 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.119/0.910/1.701 ms

端口映射允许将某个容器端口映射到Docker主机端口上。对于配置中指定的Docker主机端口,任何发送到该端口的流量,都会转发到容器。下面通过示例了解容器上运行着WEB服务的端口80,映射到Docker主机端口的5000的进程。 1、运行一个新的Web服务容器,并将容器80端口映射到Docker主机的5000端口。

root@zq-virtual-machine:~# docker container run -d --name web --network localnet --publish 5000:80 nginx

2、确认端口映射

root@zq-virtual-machine:~# docker port web
80/tcp -> 0.0.0.0:5000
80/tcp -> :::5000

3、通过WEB服务器访问Docker主机的5000端口,验证配置是否生效。

访问Docker主机的5000端口

2.3 多机覆盖网络

覆盖网络适用于多机环境。它允许单个网络包含多个主机,这样不同主机上的容器间就可以在链路层实现通信。覆盖网络是理想的容器间通信方式,支持完全容器化的应用,并且具备良好的伸缩性。

Docker为覆盖网络提供了本地驱动。在docker network create命令中添加-d overlay参数。

2.4 接入现有网络

Docker内置的Macvlan驱动实现将容器化应用连接到外部系统以及物理网络。

Macvlan的优缺点: 优点 性能优异,无须端口映射或者额外桥接,可以直接通过主机接口(或者子接口)访问容器接口。

缺点 需要将主机网卡(NIC)设置为混杂模式(Promiscuous Mode),公有云平台不允许。

2.4.1 使用场景

一个物理网络上,配置了两个VLAN—VLAN100:10.0.0.0/24和VLAN 200:192.168.3.0/24。

2.4.1.1 需求一:接入容器到VLAN100 1、首先使用Macvlan驱动创建新的Docker网络,Macvlan驱动在连接到目标网络前,需要设置参数 * 子网信息 * 网关 * 可分配给容器的IP范围

添加docker主机并连接到该网络

2、创建macvlan100网络以及eth0.100子接口。

$ docker network create -d macvlan \
  --subnet=10.0.0.0/24 \
  --ip-range=10.0.0.0/25 \
  --gateway=10.0.0.1 \
  -o parent=eth0.100 \
  macvlan100

--ip-range:告知Macvlan网络在子网中有哪些IP地址可以分配给容器。这些地址必须保留,不能用于其他节点或者DHCP服务器,因为没有任何管理层功能来检查IP区域重合的问题

添加docker主机并连接到该网络-创建macvlan100网络以及eth0.100子接口

3、将容器部署到该网络中,至此容器通过Macvlan接入了现有的VLAN当中。

$ docker container run -d --name mactainer1  \
  --network macvlan100 \
  alpine sleep 1d

2.4.2 用于故障排除的容器和服务日志

如果遇到容器间网络连接问题,检查daemon日志以及容器日志。在windows上,daemon日志存储在AppData\Local\Docker,可以通过Windows事件查看器来浏览。在Linux系统上,daemon日志的存储位置取决于当前系统正在使用的初始化方式。

容器日志生效的前提是应用进程在容器内部PID为1,并且将正常日志输出到STDOUT,将异常日志输出到STDERR。日志驱动就会将这些"日志"转发到日志驱动配置到指定的位置。 2.4.2.1 设置daemon日志详细程度 编辑daemon配置文件(/etc/docker/daemon.json),将debug设置为true,同时设置log-level为下面值: 1、debug:最详细的日志级别

{
  <Snip>
  "debug":true,
  "log-level":"debug",
  <Snip>
}

2、info:默认值,次详细日志级别

{
  <Snip>
  "debug":true,
  "log-level":"info",
  <Snip>
}

3、warn:第三详细日志级别

{
  <Snip>
  "debug":true,
  "log-level":"warn",
  <Snip>
}

4、error:第四详细日志级别

{
  <Snip>
  "debug":true,
  "log-level":"error",
  <Snip>
}

5、fatal:最粗略的日志级别

{
  <Snip>
  "debug":true,
  "log-level":"fatal",
  <Snip>
}

注意:修改完配置文件后,需要重启!!!

2.4.2.2 常用日志驱动以及如何配置 1、json-file,默认日志驱动 2、journald,只在运行systemd的Linux主机中生效

{
  "log-driver":"journald"
}

3、syslog (1)在/etc/docker/daemon.json文件中添加以下内容

{
  "log-driver":"syslog"
}

4、splunk

{
  "log-driver":"splunk"
}

5、gelf

{
  "log-driver":"gelf"
}

2.4.2.3 设置日志策略 在启动的时候通过--log-driver和--log-opts指定特定的日志驱动即可,这样会覆盖掉daemon.json中的配置。

2.5 服务发现

服务发现允许容器和Swarm服务通过名称互相定位。唯一的要求就是需要处于同一个网络当中。

底层实现是利用了Docker内置的DNS服务器,为每个容器提供DNS解析功能。

下面展示容器"c1"通过名称ping容器"c2"的过程。 1、ping c2命令调用本地DNS解析器,尝试将"c2"解析为具体IP地址。 2、如果本地解析器在本地缓存中没有找到"c2"对应的IP地址,本地解析器会向Docker DNS服务器发起一个递归查询。本地服务器解析器是预先配置好并知道Docker DNS服务器细节的。 3、Docker DNS服务器记录了全部容器名称和IP地址的映射关系,其中容器名称是容器在创建时通过--name或者--net-alias参数设置的。 4、DNS服务器返回"c2"对应的IP地址到"c1"本地DNS解析器。 5、ping命令发往"c2"对应的IP地址。

2.5.1 参数说明

1、--name 用于指定容器名称

$ docker container run -it --name c1 alpine sh

2、--dns 用于指定自定义的DNS服务列表,以防出现内置的Docker DNS服务器解析失败的情况。

$ docker container run -it --name c1 --dns=8.8.8.8 alpine sh

3、--dns-search 用于指定自定义查询时所使用的域名

$ docker container run -it --name c1 --dns-search=dockercerts.com alpine sh

2.6 Ingress网络

Swarm支持两种服务发布模式,两种模式均保证服务从集群外可访问。一种是Ingress(默认),另一种是Host模式。

2.6.1 Host模式

以Host模式发布的服务只能通过运行服务副本的节点来访问。

下面以Host模式举例:

$ docker service create -d --name svc1 --publish published=5000,target=80,mode=host nginx

针对以上参数进行说明: 1、published=5000表示服务通过端口5000提供外部服务 2、target=80表示发送到published端口5000的请求,映射到服务器副本的80端口 3、mode=host表示只有外部请求发送到运行了服务副本的节点才可以访问该服务

2.6.2 Ingress模式

通过Ingress模式发布服务,可以保证从Swarm集群中任一节点(即使没有运行服务的副本)都能访问该服务.在底层,Ingress模式采用名为Service Mesh或者Swarm Mode Service Mesh的四层路由网络来实现。

下面以Ingress模式举例:

$ docker service create -d --name svc1 --network overnet --publish published=5000,target=80,mode=host nginx

针对以上参数进行说明: 1、published=5000表示服务通过端口5000提供外部服务 2、target=80表示发送到published端口5000的请求,映射到服务器副本的80端口 3、mode=host表示只有外部请求发送到运行了服务副本的节点才可以访问该服务 4、network overnet表示指定网络类型为overnet

总结 1、入站流量无论访问哪个swarm节点中的任一一个,结果都是一样。 2、如果存在多个运行中的副本,流量会平均在每个副本之上。

三、Docker网络相关操作

3.1 创建网络

1、执行docker network create -d bridge localnet命令创建新的单机桥接网络,名为"localnet"。其中-d参数用于指定驱动(网络类型),-o encrypted对数据层进行加密

[root@localhost ~]# docker network create -d bridge localnet
1c731406ff136013a0aef3010053936511a9b6bc8f78b5ccdf329b76d1451503

2、执行docker network ls命令查看网络是否创建成功,这里观察到已成功创建成功

[root@localhost ~]# docker network ls
NETWORK ID     NAME              DRIVER    SCOPE
1c731406ff13   localnet          bridge    local

3.2 创建新容器并接入新建网络

1、执行docker container run -d --name c1 --network localnet alpine sleep 1d命令创建新容器c1并接入新建网络localnet

[root@localhost ~]# docker container run -d --name c1 --network localnet alpine sleep 1d

3.3 删除网络

1、执行docker network rm localnet命令删除名为localnet的网络

[root@localhost ~]# docker network rm localnet 
localnet

2、执行docker network ls命令观察网络已删除

[root@localhost ~]# docker network ls
NETWORK ID     NAME              DRIVER    SCOPE

3.4 删除Docker主机未使用的网络

[root@localhost ~]# docker network prune

3.5 列出本地Docker主机网络

1、执行docker network ls命令列出运行在本地Docker主机上的全部网络

[root@localhost ~]# docker network ls
NETWORK ID     NAME              DRIVER    SCOPE
f72d8bd25765   bridge            bridge    local
8e3f74e2f68e   docker_gwbridge   bridge    local
81ab398335fd   host              host      local
2bc306370a20   none              null      local

3.6 查看Docker网络详细配置

1、执行docker network inspect bridge命令提供Docker网络详细配置信息

[root@localhost ~]# docker network inspect bridge 
[
    {
        "Name": "bridge",
        "Id": "f72d8bd257657c7a9ef682638a9f79c39dbf1b9fc947553f940b3634fc7eeb18",
        "Created": "2022-11-05T08:53:34.516807186+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": {},
        "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": {}
    }
]

3.7 查看docker0网桥

1、执行ip link show docker0命令查看docker0网络

[root@localhost ~]# ip link show docker0
6: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default 
    link/ether 02:42:8b:0f:97:d0 brd ff:ff:ff:ff:ff:ff

3.8 查看Linux网桥

1、执行brctl show命令使用brctl工具查看网桥,其中br-a2cf1b22d749与上述新建的网络localnet对应。

[root@localhost ~]# brctl show
bridge name bridge id       STP enabled interfaces
br-a2cf1b22d749     8000.0242bd03bc11   no      
docker0     8000.02428b0f97d0   no      
docker_gwbridge     8000.0242db9678df   no      
virbr0      8000.5254002fa5d0   yes     virbr0-nic