Docker网络

发布时间:2024年01月12日

Docker网络

Docker 网络中的相关命令非常少,但需要掌握的底层原理相对较多。

Network Namespace

Docker 网络的底层原理是 Linux 的 Network Namespace,所以对于 Linux Network Namespace 的理解对 Docker 网络底层原理的理解非常重要。

Network Namespace 是 Linux 内核提供的用于实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,每个独立的网络空间内的防火墙、网卡、路由表、邻居表、协议栈都是独立的。不管是虚拟机还是容器,当运行在独立的命名空间时,就像是一台单独的主机一样。

Network Namespace 是 Linux 内核提供的用于实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,每个独立的网络空间内的防火墙、网卡、路由表、邻居表、协议栈都是独立的。不管是虚拟机还是容器,当运行在独立的命名空间时,就像是一台单独的主机一样。

上图的意思是创建两个namespace,然后创建虚拟网卡,分配ip,并且启动网卡。

下面要通过手工方式创建两个 Network Namespace,并最终让它们相互连通,即可以通过 ping 命令测试成功。

创建命名空间

ip netns add ns1   //创建一个叫ns1的命名空间
ip netns add ns2   //创建一个叫ns2的命名空间
ip netns list   //查看命名空间
ip netns delete ns1   //删除ns1命名空间

执行命名空间

ip netns exec ns1 ip a

创建网络接口

如果要让两个命名空间连通,则需要用到虚拟设备接口技术 veth pair。该技术需要一对网络接口分别置于两个命名空间中。

ip link addiproute2 工具集中用于创建新网络接口的命令。它允许在Linux内核中动态添加新的网络设备,包括虚拟设备(如虚拟以太网对、VLAN接口、Tunnel接口等)。

基本语法如下:

ip link add [NAME] type TYPE [OPTIONS]
  • [NAME]:要创建的新网络接口的名称。
  • type TYPE:指定要创建的接口类型,比如 vlanvethtunnelbridge 等。
  • [OPTIONS]:根据不同的接口类型,可能需要提供额外的选项来定义接口的具体属性,例如对于虚拟以太网对 (veth) 可能需要指定另一端的名字,对于 VLAN 接口则需要指定 VLAN ID 等。

示例:

创建一对虚拟以太网设备(veth pair):

ip link add veth0 type veth peer name veth1

此命令会在系统中创建两个相互连接的虚拟网络接口 veth0 和 veth1。

创建一个802.1Q VLAN接口:

ip link add link eth0 name eth0.10 type vlan id 10

此命令会在已存在的 eth0 接口上创建一个 VLAN ID 为 10 的 VLAN 子接口,命名为 eth0.10。

以下命令用于创建一对网络接口 veth-ns1 与 veth-ns2。

ip link add veth-ns1 type veth peer name veth-ns2

分配网卡

ip link set veth-ns1 netns ns1
ip link set veth-ns2 netns ns2

分配IP

ip netns exec ns1 ip addr add 192.168.1.1/24 dev veth-ns1
ip netns exec ns2 ip addr add 192.168.1.2/24 dev veth-ns2

该命令由几个部分组成:

  1. ip netns exec ns1:这部分用于在名为 “ns1” 的网络命名空间中执行后面的命令。网络命名空间是一种Linux内核功能,用于隔离网络资源,每个命名空间有自己的网络接口、路由表等,就像运行在独立主机上的网络栈一样。

  2. ip addr add 192.168.1.1/24:这部分是对网络接口执行的操作,具体来说,是向网络接口添加一个IPv4地址。地址是 192.168.1.1,子网掩码是 /24,这代表了24位网络前缀,相当于 255.255.255.0

  3. dev veth-ns1:这部分指定了要添加IP地址的网络接口设备,这里是名为 “veth-ns1” 的虚拟以太网设备。通常,这样的虚拟接口会被用作连接不同网络命名空间的桥梁。

所以,整个命令的作用就是在名为 “ns1” 的网络命名空间内的 “veth-ns1” 虚拟网络接口上,添加一个IPv4地址 192.168.1.1 并且设置其子网掩码为 /24,从而使得该命名空间内的网络服务可以通过这个IP地址与其他网络进行通信。

绑定成功。

开启网卡

ip netns exec ns1 ip link set dev veth-ns1 up
ip netns exec ns2 ip link set dev veth-ns2 up

这个命令同样由几个部分组成:

  1. ip netns exec ns1:这部分指示在名为 “ns1” 的网络命名空间中执行接下来的命令。网络命名空间是Linux内核提供的一种网络资源隔离机制,每个命名空间都有自己的网络接口、路由表等。

  2. ip link set dev veth-ns1:这部分是用来修改网络接口设备配置的命令。dev 后面跟着的是要操作的网络接口设备名,这里是指名为 “veth-ns1” 的虚拟以太网设备。

  3. up:这是对上述网络接口设备进行的具体操作,即启用(启动)该网络接口。当网络接口处于 “up” 状态时,意味着它可以发送和接收数据包,参与到网络通信中。

综上所述,整个命令的作用是在名为 “ns1” 的网络命名空间内,启动名为 “veth-ns1” 的虚拟网络接口设备,使其能够正常进行网络通信。在创建虚拟网络对或者在容器中构建网络时,这种操作是必要的一步,确保网络接口可用并能够与外部或者其他命名空间中的网络接口通信。

开始ping

CNM

CNM定义了3个基本要素:沙盒(Sandbox)、终端(Endpoint)和网络(Network)。

沙盒 是一个独立的网络栈。其中包括以太网接口、端口、路由表以及DNS配置。

终端 就是虚拟网络接口。就像普通网络接口一样,终端主要职责是负责创建连接。在CNM中,终端负责将沙盒连接到网络。

网络 是802.1d网桥(类似大家熟知的交换机)的软件实现。因此,网络就是需要交互的终端的集合,并且终端之间相互独立。

容器A只有一个接口(终端)并连接到了网络A。容器B有两个接口(终端)并且分别接入了网络A和网络B。容器A与B之间是可以相互通信的,因为都接入了网络A。但是,如果没有三层路由器的支持,容器B的两个终端之间是不能进行通信的。

在容器环境下,当提到“三层路由”时,通常指的是IP路由,也就是在网络协议栈的第三层(网络层)进行数据包路由的过程。在这个上下文中,假设容器A和容器B都在同一台宿主机上运行,且都连接到一个或多个网络(此处分别为网络A和网络B)。

  • 容器A只有一个接口,连接到了网络A。
  • 容器B有两个接口,分别连接到网络A和网络B。

由于容器A和容器B都接入了网络A,它们可以通过网络A进行通信,因为同一网络内的容器可以通过二层(数据链路层)的MAC地址发现彼此并交换数据包。

然而,容器B的两个接口分别属于不同的网络(网络A和网络B),如果这两个网络没有三层路由信息,即在容器B的网络栈中没有适当的路由规则表明如何去往另一个网络B,那么容器B内的进程就无法通过网络B的接口直接与网络A的接口进行通信。

在没有三层路由支持的情况下,即使同属一个宿主机的容器,也无法跨越不同的网络进行通信。要实现容器B的两个接口之间的通信,通常需要:

  1. 在容器B内配置路由规则:通过在容器B内部添加路由条目,明确指出从一个接口发出的数据包应该通过哪个接口去往另一个网络。

  2. 宿主机路由配置:宿主机操作系统也可以配置相应的路由规则,以便正确地转发进出容器B的不同接口的数据包。

  3. 使用覆盖网络(Overlay Network)或网络策略:在更复杂的容器编排场景(如Kubernetes或Docker Swarm)中,会利用覆盖网络技术或网络策略自动配置路由规则,使不同网络间的容器可以相互通信。

需要重点理解的是,终端与常见的网络适配器类似,这意味着终端只能接入某一个网络。因此,如果容器需要接入到多个网络,就需要多个终端。

虽然容器A和容器B运行在同一个主机上,但其网络堆栈在操作系统层面是互相独立的,这一点由沙盒机制保证。

Libnetwork

CNM 是设计规范,而 Libnetwork 是开源的、由 Go 语言编写的、跨平台的 CNM 的标准实现。

Libnetwork 除了实现了 CNM 的三个组件,还实现了本地服务发现、容器负载均衡,以及网络控制层与管理层功能。

驱动

如果说Libnetwork实现了控制层和管理层功能,那么驱动就负责实现数据层。比如,网络连通性和隔离性是由驱动来处理的,驱动层实际创建网络对象也是如此。

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

每个驱动都负责其上所有网络资源的创建和管理。举例说明,一个叫作“prod-fe-cuda”的覆盖网络由Overlay 驱动所有并管理。这意味着Overlay 驱动会在创建、管理和删除其上网络资源的时候被调用。

为了满足复杂且不固定的环境需求,Libnetwork支持同时激活多个网络驱动。这意味着Docker环境可以支持一个庞大的异构网络。

单机桥接网络

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

从名称中可以看到两点。

  • 单机 意味着该网络只能在单个Docker主机上运行,并且只能与所在Docker主机上的容器进行连接。
  • 桥接 意味着这是802.1.d桥接的一种实现(二层交换机)。

Linux Docker创建单机桥接网络采用内置的桥接驱动,而Windows Docker创建时使用内置的NAT驱动。实际上,这两种驱动工作起来毫无差异。

图11.6展示了两个均包含相同本地桥接网络mynet的Docker主机。虽然网络是相同的,但却是两个独立的网络。这意味着图11.6中容器无法直接进行通信,因为并不在一个网络当中。

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

可以通过docker netnwork ls来查看网络

docker network ls

网络的名称和创建时使用的驱动名称是一致的——这只是个巧合。

docker network inspect   //可以查看到详细信息
eg:
docker network inspect bridge   //查看网桥的详细信息
[
    {
        "Name": "bridge",
        "Id": "7b6cfd6e08db301a71ee1e30be8ab43211a528d8fb98839f263d2b2107792c14",
        "Created": "2023-12-29T23:18:40.305436637+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": {
            "467abb2bc4836f63c76b1054056fee3c2b90c9bd7a639e3ad606aa1c6f3a6a8e": {
                "Name": "mycent",
                "EndpointID": "95d436c0dc389f5df88183555a82a8a2fb24dc50b59b49c5ef6accce51cf57f8",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            },
            "4d141acf74fbff1dded24d5fc5bdade6291935f5e96314fec4f24752da9d6ede": {
                "Name": "mytom10",
                "EndpointID": "3e349c0408bd7347778b81f6dbdf67754377432874367b084a9177eac5f58dd2",
                "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": {}
    }
]

在Linux Docker主机之上,默认的“bridge”网络被映射到内核中为“docker0 ”的Linux网桥。

ip link show docker0

Docker默认“bridge”网络和Linux内核中的“docker0”网桥之间的关系。

在顶部补充了接入“bridge”网络的容器。“bridge”网络在主机内核中映射到名为“docker0”的Linux网桥,该网桥可以通过主机以太网接口的端口映射进行反向关联。

docker network create

docker network create -d bridge localnet   //创建新的单机桥接网络,名为localnet

输出内容中包含了两个网桥。第一行是前文提过的docker0 网桥,该网桥对应Docker中的默认网络bridge ;第二个网桥(br-5b1e651bfe7c)与新建的“localnet”Docker桥接网络相对应。两个网桥目前都没有开启STP,并且也都没有任何设备接入(对应的interfaces 列为空)。

docker container run -d --name c1 --network localnet centos:7 sleep 1d

docker network inspect localnet --format='{{range $key, $value := .Containers}}{{$value.Name}}{{"\n"}}{{end}}'

输出内容表明“c1 ”容器已经位于桥接(Bridge/NAT)网络localnet 之上。

如果再次运行brctl show 命令,就能看到c1 的网络接口连接到了br-5b1e651bfe7c 网桥。

如果在相同网络中继续接入新的容器,那么在新接入容器中是可以通过“c1”的容器名称来ping通的。这是因为新容器都注册到了指定的Docker DNS服务,所以相同网络中的容器可以解析其他容器的名称。

Linux上默认的Bridge网络是不支持通过Docker DNS服务进行域名解析的。自定义桥接网络可以!

(1)创建名为“c2”的容器,并接入“c1”所在的localnet 网络。

docker container run --name c2 -it --network localnet centos:7 /bin/bash
ping c1

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

桥接网络中的容器只能与位于相同网络中的容器进行通信。但是,可以使用端口映射(Port Mapping)来绕开这个限制。

端口映射允许将某个容器端口映射到Docker主机端口上。对于配置中指定的Docker主机端口,任何发送到该端口的流量,都会被转发到容器。

容器内部应用开放端口为80。该端口被映射到了Docker主机的10.0.0.15 接口的5000端口之上。最终结果就是访问10.0.0.15:5000 的所有流量都被转发到了容器的80端口。

docker container run -d --name web --network localnet --publish 5000:8081 nginx

确认端口映射

docekr port web

外部系统现在可以通过Docker主机的TCP端口5000,来访问运行在桥接网络上的Nginx容器了。

端口映射工作原理大致如此,但这种方式比较笨重并且不能扩展。举个例子,在只有单一容器的情况下,它可以绑定到主机的任意端口。这意味着其他容器就不能再使用已经被Nginx容器占用的5000端口了。这也是单机桥接网络只适用于本地开发环境以及非常小的应用的原因。

多机覆盖网络

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

Docker为覆盖网络提供了本地驱动。这使得创建覆盖网络非常简单,只需要在docker network create 命令中添加--d overlay 参数。伸缩性。

服务发现

作为核心网络架构,Libnetwork还提供了一些重要的网络服务。

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

其底层实现是利用了Docker内置的DNS服务器,为每个容器提供DNS解析功能。图11.19展示了容器“c1”通过名称ping容器“c2”的过程。Swarm服务原理相同。

(1)ping c2 命令调用本地DNS解析器,尝试将“c2”解析为具体IP地址。每个Docker容器都有本地DNS解析器。

(2)如果本地解析器在本地缓存中没有找到“c2”对应的IP地址,本地解析器会向Docker DNS服务器发起一个递归查询。本地服务解析器是预先配置好并知道Docker DNS服务器细节的。

(3)Docker DNS服务器记录了全部容器名称和IP地址的映射关系,其中容器名称是容器在创建时通过--name 或者--net-alias 参数设置的。这意味着Docker DNS服务器知道容器“c2”的IP地址。

(4)DNS服务返回“c2”对应的IP地址到“c1”本地DNS解析器。之所以会这样是因为两个容器位于相同的网络当中,如果所处网络不同则该命令不可行。

(5)ping命令被发往“c2”对应的IP地址。

文章来源:https://blog.csdn.net/2301_79516932/article/details/135554015
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。