开源Linux

一个执着于技术的公众号

Docker的技术依赖于Linux内核的虚拟化技术的发展,Docker使用到的网络技术有Network Namespace、Veth设备对、Iptables/Netfilter、网桥、路由等。接下来,我将以Docker容器网络实现的基础技术来分别阐述,在到真正的容器篇章节之前,能形成一个稳固的基础知识网。

Network Namespace

为了支持网络协议栈的多个实例,Linux在网络栈引入了Network Namespace,这些独立的协议栈被隔离到不同的Namespace中,处于不同Namespace中的网络栈是完全隔离的,彼此无法通信。具体有关Linux Namespace的介绍,可以另行浏览之前写的《Linux Namespace》。

Linux的网络协议栈十分复杂,为了支持独立的协议栈,相关的全局变量都必须修改为协议栈私有。Linux实现Network Namespace的核心就是让这些全局变量称为Network Namespace变量的成员,然后为协议栈的函数调用加入一个Namespace参数。与此同时,为了保证已开发程序及内核代码的兼容性,内核代码隐式地使用了Namespace空间内的变量。应用程序如果没有对Namespace有特殊需求,那么不需要额外的代码,Network Namespace对应用程序而言是透明的。

在建立了新的Network Namespace,并将某个进程关联到这个网络命名空间后,就出现了如下的命名空间下的内核数据结构,所有网络栈变量都放入了Network Namespace的数据结构中,这个Network Namespace是属于它进程组私有的,与其他进程组不冲突。

Docker正是利用了Network Namespace特性,实现了不同容器之间的网络隔离。如果一个容器声明使用宿主机的网络栈(-net = host),即不开启Network Namespace,例如:

docker run –d –net=host --name c_name i_name

这种情况下,这个容器启动之后监听的是宿主机的80端口。像这样直接使用宿主机网络栈的方式,虽然可以为容器提供良好的网络性能,但也不可避免的造成端口冲突等网络资源冲突的问题。

所以在一般情况下,我们都希望程序引入Network Namespace里的网络栈,即这个容器拥有自己的IP和端口。但是,这个时候也会带来一个新的问题,被隔离的容器进程,是怎么与其它被隔离的进程进行网络通信的?

Net Bridge

上文说到,Linux 可以支持不同的网络,他们之间是怎么支持够互相通信的呢?如果是两台主机,那需要的可能只是一根网线,把它们连接在一台交换机上。而在Linux当中,网桥(Bridge)就起到相应的作用。本质上来说,这是一个数据链路层(data link)的设备,根据Mac地址的信息转发到网桥的不同端口上。而Docker就是在宿主机上默认创建一个docker0的网桥,凡是连接docker0的网桥,都可以用它来通信。

细述Bridge

网桥是一个二层的虚拟网络设备,把若干个网络接口“连接”起来,使得网口之间的报文可以转发。网桥能够解析收发的报文,读取目标的Mac地址信息,和自己的Mac地址表结合,来决策报文转发的目标网口。为了实现这些功能,网桥会学习源Mac地址。在转发报文时,网桥只需要向特定的端口转发,从而避免不必要的网络交互。如果它遇到了一个自己从未学过的地址,就无法知道这个报文应该向哪个网口转发,就将报文广播给除了报文来源之外的所有网口。

在实际网络中,网络拓扑不可能永久不变。如果设备移动到另一个端口上,而它没有发送任何数据,那么网桥设备就无法感知到这个变化,结果网桥还是向原来的端口发数据包,在这种情况下数据就会丢失。所以网桥还要对学习到的Mac地址表加上超时时间,默认5min。如果网桥收到了对应端口MAC地址回发的包。则重置超时时间,否则过了超时时间后,就认为哪个设备不在那个端口上了,他就会广播重发。

Linux为了支持越来越多的网卡以及虚拟设备,所以使用网桥去提供这些设备之间转发数据的二层设备。Linux内核支持网口的桥接(以太网接口),这与单纯的交换机还是不太一样,交换机仅仅是一个二层设备,对于接受到的报文,要么转发,要么丢弃。运行着Linux内核的机器本身就是一台主机,有可能是网络报文的目的地,其收到的报文要么转发,要么丢弃,还可能被送到网络协议的网络层,从而被自己主机本身的协议栈消化,所以我们可以把网桥看作一个二层设备,也可以看做是一个三层设备。

Linux中Bridge实现

Linux内核是通过一个虚拟的网桥设备(Net Device)来实现桥接的。这个虚拟设备可以绑定若干个以太网接口,从而将它们连接起来。Net Device网桥和普通的设备不同,最明显的是它还可以有一个ip地址。

如上图所示,网桥设备br0绑定的eth0和eth1。对于网络协议栈的上层来说,只看到br0。因为桥接是在数据链路层实现的,上层不需要关心桥接的细节,于是协议栈上层需要发送的报文被送到br0,网桥设备的处理代码判断报文被转发到eth0还是eth1,或者两者皆转发。反之,从eth0或者从eth1接收到的报文被提交给网桥的处理代码,在这里判断报文应该被转发、丢弃或者提交到协议栈上层。

而有时eth0、eth1也可能会作为报文的源地址或目的地址,直接参与报文的发送和接收,从而绕过网桥。

Bridge常用操作

Docker自动完成了对网桥的创建和维护。如果想要进一步理解网桥,可以看下如下举的一些常用操作命令。

新增一个网桥:

brctl addbr xxxxx

在新增网桥的基础上增加网口,在linux中,一个网口其实就是一个物理网卡。将物理网卡和网桥连接起来:

brctl addif xxxx ethx

网桥的物理网卡作为一个网口,由于在链路层工作,就不再需要IP地址了,这样上面的IP地址自然失效:

ipconfig ethx 0.0.0.0

给网桥配置一个IP地址:

ipconfig brxxx xxx.xxx.xxx.xxx

这样网桥就是一个有了IP地址,而连接在这之上的网卡就是一个纯链路层设备了。

Veth Pair

上文说到,docker在宿主机上创建docker0网桥后,凡是连接到docker0上的网桥,就可以用它来通信。那么这里又有个问题,就是这些容器是如何连接到docker0网桥上的?所以这就是Veth Pair虚拟设备的作用了,Veth Pair就是为了在不同的Network Namespace之间进行通信,利用它,可以将两个Network Namespace连接起来。

Veth Pair设备的特点是:它被创建出来后,总是以两张虚拟网卡(Veth Peer)的形式出现。并且,其中一个网卡发出的数据包,可以直接出现在另一张“网卡”上,哪怕这两张网卡在不同的Network Namespace中。

正是因为这样的特点,Veth Pair成对出现,很像是一对以太网卡,常常被看做是不同Network Namespace直连的“网线”。在Veth一端发送数据时,他会将数据发送到另一端并触发另一端的接收操作。我们可以把Veth Pair其中一端看做另一端的一个Peer。

Veth Pair操作命令

创建Veth Pair:

ip link add veth0 type veth peer name veth1

创建后查看Veth Pair的信息:

ip link show

将其中一个Veth Peer设置到另一个Namespace:

ip link set veth1 netns netns1

在netns1中查看veth1设备:

ip netns exec netns1 ip link show

当然,在docker里面,除了将Veth放入容器,还改名为eth0。想要通信必须先分配IP地址:

ip netns exec netns1 ip addr add 10.1.1.1/24 dev veth1
ip addr add 10.1.1.2/24 dev veth0

启动它们:

ip netns exec netns1 ip link set dev veth1 up
ip link set dev veth0 up

测试通信:

ip netns exec netns1 ping 10.1.1.2

Veth Pair查看端对端

在实际操作Veth Pair时,可以使用ethtool便于操作。

在一个Namespace中查看Veth Pair接口在设备列表中的序列号:

ip netns exec netns1 ethtool -S veth1

如果得知另一端的接口设备序列号,假如序列号为6,则可以继续查看6代表了什么设备:

ip netns exec netns2 ip link | grep 6

Iptables/Netfilter

Linux协议栈非常高效且复杂。如果我们想要在数据处理过程中对关心的数据进行一些操作,则需要Linux提供一套相应的机制帮助用户实现自定义的数据包处理过程。

在Linux网络协议栈有一组网络回调函数挂接点,通过这些挂接点函数挂接的钩子函数可以在Linux网络栈处理数据包的过程中对数据包一些操作,例如过滤、修改、丢弃等。整个挂接点技术叫做Iptables和Netfilter。

Netfilter负责在内核中执行各种各样的挂接规则,运行在内核模式中。而Iptables是在用户模式下运行的进程,负责协助维护内核中Netfilter的各种规则表。通过二者的配合来实现整个Linux网络协议栈中灵活的数据包处理机制。

规则表Table

这些挂载点能挂接的规则也分不同的类型,目前主要支持的Table类型如下:

•RAW•MANGLE•NAT•FILTER

上述4个规则链的优先级是RAW最高,FILTER最低。

在实际应用中,不同挂接点需要的规则类型通常不同。例如,在Input的挂接点上明显不需要FILTER的过滤规则,因为根据目标地址,已经在本机的上层协议栈了,所以无需再挂载FILTER过滤规则。

Route

Linux系统包含了一个完整的路由功能。当IP层在处理数据发送或者转发时,会使用路由表来决定发往哪里。通常情况下,如果主机与目的主机直接相连,那么主机可以直接发送IP报文到目的主机。

路由功能是由IP层维护的一张路由表来实现。当主机收到数据报文时,它用此表来决策接下来应该做什么操作。当从网络侧接收到数据报文时,IP层首先会检查报文的IP地址是否与主机自身的地址相同。如果数据报文中的IP地址是自身主机的地址,那么报文将被发送到传输层相应的协议栈中去。如果报文中的IP地址不是主机自身的地址,并且配置了路由功能,那么报文将被转发,否则报文将被丢弃。

路由表的数据一般以条目形式存在,一个典型的路由表条目通常包含以下主要的条目项:

•目的IP地址•下一个路由器的IP地址•标志•网络接口规范

通过路由表转发时,如果任何条目的第一个字段完全匹配目标条目的IP地址(主机)或部分匹配(网络),那么它将指示下一个路由器的IP地址。这些信息将告诉主机数据包该转发到哪一个“下一个路由器”。而条目中所有其它字段将提供更多的辅助信息来为路由转发做决定。

如果没有一个完全匹配的IP,则继续搜索网络ID。找到则转发数据到指定路由器上。由此可知,网络上所有主机都是通过这个路由表中的单个条目进行管理。

如果上述两个条件都不匹配,则将数据报文转发到一个默认路由器上。

如果上述步骤失败,默认路由器也不存在,那么这个数据报文无法转发。任何无法投递的数据都会产生一个ICMP主机不可达或者ICMP网络不可达的错误,并将该错误返回给生成此数据的应用程序。

Route Table

Linux路由表至少2个,一个是LOCAL,一个是MAIN。

Local表用于供Linux协议栈识别本地地址,以及进行本地各个不同网络之间的数据转发。MAIN表用于各类网络IP的转发。它的建立既可以使用静态配置生成,也可以使用动态路由发现协议生成。动态路由发现协议一般使用组播功能来通过发送路由发现数据,动态获取和交换网络的路由信息,并更新到路由表中。

通过下列命令查看LOCAL表的内容:

ip route show table local type local

路由表的查看:

ip route list

总结

到此为止,介绍了实现docker容器网络最核心的基础部分,包括Network Namespace、Bridge、Veth Pair、Iptables/Netfilter、Route。接下来,将会继续在这基础之上阐述Docker容器网络的具体实现。

链接:https://www.cnblogs.com/sally-zhou/p/13424208.html

作者:Mr_Zack

- End -

关注「开源Linux」加星标,提升IT技能

好文章,分享点赞在看三连哦↓↓↓

Docker容器网络-基础篇的更多相关文章

  1. Docker容器网络-实现篇

    通常,Linux容器的网络是被隔离在它自己的Network Namespace中,其中就包括:网卡(Network Interface).回环设备(Loopback Device).路由表(Routi ...

  2. docker 容器网络基础

    ======================== docker缺省自带的网络 ======================== host 网络, This enables a container to ...

  3. Docker容器网络基础总结

    ifconfig 之 docker0 基于Linux的虚拟网桥(通用网络设备的抽象) 虚拟网桥特点: 1. 可以设置IP地址 2.相当于拥有一个隐藏的虚拟网卡 docker0 的地址划分 IP: 17 ...

  4. docker容器网络bridge

    我们知道docker利用linux内核特性namespace实现了网络的隔离,让每个容器都处于自己的小世界里面,当这个小世界需要与外界(宿主机或其他容器)通信的时候docker的网络就发挥作用了,这篇 ...

  5. Docker容器网络篇

    Docker容器网络篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Docker的网络模型概述 如上图所示,Docker有四种网络模型: 封闭式网络(Closed conta ...

  6. 【转】理解Docker容器网络之Linux Network Namespace

    原文:理解Docker容器网络之Linux Network Namespace 由于2016年年中调换工作的原因,对容器网络的研究中断过一段时间.随着当前项目对Kubernetes应用的深入,我感觉之 ...

  7. Docker镜像管理基础篇

    Docker镜像管理基础篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Docker Images Docker镜像还有启动容器所需要的文件系统及其内容,因此,其用于创建并启 ...

  8. docker容器技术基础之联合文件系统OverlayFS

    我们在上篇介绍了容器技术中资源隔离与限制docker容器技术基础之linux cgroup.namespace 这篇小作文我们要尝试学习容器的另外一个重要技术之联合文件系统之OverlayFS,在介绍 ...

  9. docker容器技术基础入门

    目录 docker容器技术基础入门 容器(Container) 传统虚拟化与容器的区别 Linux容器技术 Linux Namespaces CGroups LXC docker基本概念 docker ...

随机推荐

  1. 我们能自己写一个容器类,然后使用 for-each 循环码?

    可以,你可以写一个自己的容器类.如果你想使用 Java 中增强的循环来遍历, 你只需要实现 Iterable 接口.如果你实现 Collection 接口,默认就具有该属性.

  2. jsp技术之隐藏域

    隐藏域 hidden:隐藏域属性,不显示到页面上,但是会提交的表单项 注意:表单中增加了一个隐藏域,是用户的id.稍后修改联系人信息,提交表单时需要使用到 <!-- hidden:隐藏域,不显示 ...

  3. python模块 | 时间处理模块—datetime模块

    在python中,与时间处理相关的模块有 time,datetime,calendar. 时间的两个概念: UTC(世界协调时): 整个地球分为二十四时区,每个时区都有自己的本地时间.格林威治天文时间 ...

  4. _CrtCheckMemory

    参考: _CrtCheckMemory MSDN 堆异常检查-MS vs stdio 编写程序经常会涉及到堆的申请,但是如果你向所申请堆里写数据,超过了你最开始申请的空间是,运行中就会发生中断. _C ...

  5. 从零开始画自己的DAG作业依赖图(四)--节点连线优化版

    概述 上个版本简单的连线在一些复杂场景,尤其层级比较多,连线跨层级比较多的情况下,会出现线条会穿过矩形的情况,这一讲就是在这个基础上,去优化这个连线. 场景分析 在下面几种情况下,简单版本的画法已经没 ...

  6. 我的python学习记_02

    流程控制 算术运算符: + 加(在字符串中拼接作用) - 减 * 乘 / 除 // 商 % 取余 ** 次幂 比较运算符: > 是否大于 >= 是否大于等于 < 是否小于 != 是否 ...

  7. 【Android开发】简单好用的阴影库 ShadowLayout

    先来看一张使用 ShadowLayout 库实现的各种阴影的效果图,如下图所示: 如上图所示,通过使用 ShadowLayout 可以控制阴影的颜色.范围.显示边界(上下左右四个边界).x 轴和 y ...

  8. vs技巧 - 调试asp.net core源码

    学习asp.net core的方式除了看官方文档,看源码是也是一种很好的方式.本文介绍一种方法,简单配置vs,无需第三方插件就可以将asp.net core的源码链接自己的项目,随时穿梭于core的源 ...

  9. 使用Vue_CLI_3快速创建项目

  10. OllyDbg---寄存器

    寄存器 寄存器的概念和作用 寄存器是CPU内部的高速存储单元,访问速度比常规内存快很多. 处理器在执行程序时,需要一个助手,当执行一条指令时,比如将两个内存单元中存放的内容相加,处理器需要先把其中一个 ...