解密Docker容器网络

news2024/10/5 12:35:35

一个Linux容器能看见的“网络栈”,被隔离在它自己的Network Namespace中。

1 “网络栈”的内容

  • 网卡(Network Interface)
  • 回环设备(Loopback Device)
  • 路由表(Routing Table)
  • iptables规则

对于一个进程,这些构成它发起、响应网络请求的基本环境。

作为一个容器,它可声明直接使用宿主机的网络栈(–net=host),即不开启Network Namespace,如:

$ docker run –d –net=host --name nginx-host nginx

这容器启动后,直接监听的就是宿主机80端口。

这种直接使用宿主机网络栈,虽可为容器提供良好网络性能,但也引入共享网络资源问题,如端口冲突。所以,大多情况下,都希望容器进程能使用自己Network Namespace里的网络栈,即拥有属于自己的IP地址和端口。

但这被隔离的容器进程,如何和其他Network Namespace里的容器进程交互?

可将每个容器看做一台主机,它们都有一套独立“网络栈”:

  • 若想要实现两台主机之间的通信,最直接的就是把它们用一根网线连接
  • 若你想实现多台主机之间的通信,就需要用网线,把它们连接在一台交换机

Linux中起到虚拟交换机作用的网络设备,是网桥(Bridge),工作在数据链路层(Data Link)的设备,根据MAC地址学习来将数据包转发到网桥的不同端口(Port)。

为何这些主机间需MAC地址才能进行通信,这就是网络分层模型的基础知识。这篇文章。

为实现上述目的,Docker会默认在宿主机创建一个docker0网桥,凡是连接在docker0网桥上的容器,就可通过它进行通信。

如何把这些容器“连接”到docker0网桥?

需要使用一种名叫Veth Pair的虚拟设备。

Veth Pair设备特点

它被创建出来后,总以两张虚拟网卡(Veth Peer)形式成对出现。且从其中一个“网卡”发出的数据包,可直接出现在与它对应的另一张“网卡”,哪怕这两个“网卡”在不同Network Namespace。

这使Veth Pair常被用作连接不同Network Namespace 的“网线”。

启动nginx-1容器:

$ docker run –d --name nginx-1 nginx

进入容器,查看其网络设备:

# 在宿主机上
$ docker exec -it nginx-1 /bin/bash
# 在容器里
root@2b3c181aecf1:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 0.0.0.0
        inet6 fe80::42:acff:fe11:2  prefixlen 64  scopeid 0x20<link>
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 364  bytes 8137175 (7.7 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 281  bytes 21161 (20.6 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        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
        
$ route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         172.17.0.1      0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

可这容器里有张eth0网卡,正是一个Veth Pair设备在容器里的这端。

通过route命令查看nginx-1容器的路由表,可见,这eth0网卡是这容器里的默认路由设备;所有对172.17.0.0/16网段的请求,也会被交给eth0处理(第二条172.17.0.0路由规则)。

而这Veth Pair设备的另一端,则在宿主机。可通过查看宿主机的网络设备看到它:

# 在宿主机上
$ ifconfig
...
docker0   Link encap:Ethernet  HWaddr 02:42:d8:e4:df:c1  
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:d8ff:fee4:dfc1/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:309 errors:0 dropped:0 overruns:0 frame:0
          TX packets:372 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0 
          RX bytes:18944 (18.9 KB)  TX bytes:8137789 (8.1 MB)
veth9c02e56 Link encap:Ethernet  HWaddr 52:81:0b:24:3d:da  
          inet6 addr: fe80::5081:bff:fe24:3dda/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:288 errors:0 dropped:0 overruns:0 frame:0
          TX packets:371 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0 
          RX bytes:21608 (21.6 KB)  TX bytes:8137719 (8.1 MB)
          
$ brctl show
bridge name bridge id  STP enabled interfaces
docker0  8000.0242d8e4dfc1 no  veth9c02e56

nginx-1容器对应的Veth Pair设备,在宿主机上是张虚拟网卡-veth9c02e56。通过brctl show的输出,可见这张网卡被“插”在docker0。

这时候,如再在这台宿主机上启动另一个Docker容器,如nginx-2:

$ docker run –d --name nginx-2 nginx
$ brctl show
bridge name bridge id  STP enabled interfaces
docker0  8000.0242d8e4dfc1 no  veth9c02e56
       vethb4963f3

就会发现新的、名叫vethb4963f3的虚拟网卡,也被“插”在docker0网桥。

这时候,若你在nginx-1容器ping一下nginx-2容器的IP地址(172.17.0.3),就会发现同一宿主机上的两个容器默认相互连通。

同一宿主机的容器默认相互连通的原理

在nginx-1容器访问nginx-2容器IP地址(如ping 172.17.0.3)时,这目的IP地址会匹配到nginx-1容器里的第二条路由规则。可见,这条路由规则的网关(Gateway)是0.0.0.0,即这是一条直连规则,凡是匹配到这条规则的IP包,应经过本机的eth0网卡,通过二层网络直接发往目的主机。

要通过二层网络到达nginx-2容器,就要有172.17.0.3 IP地址对应的MAC地址。所以nginx-1容器的网络协议栈,就需要通过eth0网卡发送一个ARP广播,来通过IP地址查找对应的MAC地址。

ARP(Address Resolution Protocol),通过三层的IP地址找到对应的二层MAC地址的协议。

我们前面提到过,这个eth0网卡,是一个Veth Pair,它的一端在这个nginx-1容器的Network Namespace里,而另一端则位于宿主机上(Host Namespace),并且被“插”在了宿主机的docker0网桥上。

一旦一张虚拟网卡被“插”在网桥,它就会变成该网桥的“从设备”。从设备会被“剥夺”调用网络协议栈处理数据包的资格,“降级”成网桥上的一个端口。这端口唯一作用:接收流入的数据包,然后把这些数据包的“生杀大权”(如转发或丢弃),全部交给对应网桥。

所以,收到这些ARP请求后,docker0网桥就会扮演二层交换机,把ARP广播转发到其他被“插”在docker0上的虚拟网卡上。这样,同样连接在docker0上的nginx-2容器的网络协议栈就会收到这个ARP请求,从而将172.17.0.3所对应的MAC地址回复给nginx-1容器。

有这目的MAC地址,nginx-1容器的eth0网卡就可将数据包发出去。

根据Veth Pair设备原理,这数据包会立刻出现在宿主机上的veth9c02e56虚拟网卡。不过,此时这veth9c02e56网卡的网络协议栈资格已被“剥夺”,所以这数据包就直接流入到docker0网桥。

docker0处理转发的过程,则继续扮演二层交换机的角色。此时,docker0网桥根据数据包的目的MAC地址(即nginx-2容器的MAC地址),在其CAM表(即交换机通过MAC地址学习维护的端口和MAC地址的对应表)里查到对应端口为:vethb4963f3,然后把数据包发往这端口。

这端口正是nginx-2容器“插”在docker0网桥的另一块虚拟网卡,也是个Veth Pair设备。这样,数据包就进入了nginx-2容器的Network Namespace。

所以,nginx-2容器看到它自己的eth0网卡出现了流入的数据包。这样,nginx-2的网络协议栈就会对请求进行处理,最后将响应(Pong)返回到nginx-1。

这就是同一宿主机的不同容器通过docker0网桥进行通信的流程:

实际数据传递时,上述数据的传递过程在网络协议栈的不同层次,都有Linux内核Netfilter参与。可通过打开iptables的TRACE功能查看到数据包传输过程:

# 在宿主机上执行
$ iptables -t raw -A OUTPUT -p icmp -j TRACE
$ iptables -t raw -A PREROUTING -p icmp -j TRACE

就可在/var/log/syslog里看到数据包传输的日志。iptables的相关知识进行实践,验证数据包传递流程。

默认情况下,被限制在Network Namespace里的容器进程,就是通过Veth Pair设备+宿主机网桥,实现同其他容器的数据交换。

类似地,当你在一台宿主机,访问该宿主机上的容器的IP地址时,这请求的数据包,也是先根据路由规则到达docker0网桥,然后被转发到对应Veth Pair设备,最后出现在容器:

接下来,这数据包就会经宿主机的eth0网卡转发到宿主机网络,最终到达10.168.0.3对应宿主机。这过程要求这两台宿主机本身连通:

在最后一个“Docker容器连接其他宿主机”案例,你可能联想到:若在另一宿主机(如:10.168.0.3)也有一个Docker容器。我们的nginx-1容器又该如何访问它?

容器的“跨主通信”问题

Docker默认配置下,一台宿主机上的docker0网桥,和其他宿主机上的docker0网桥无任何关联,互相无法连通。所以,连接在这些网桥的容器,自然也无法通信,咋办呢?

若通过软件创建一个整个集群“公用”的网桥,再把集群里的所有容器都连接到这网桥,不就能相互通信?

这样,整个集群里的容器网络就类似:

构建这种容器网络的核心:要在已有的宿主机网络,再通过软件构建一个覆盖在已有宿主机网络之上的、可将所有容器连通在一起的虚拟网络。所以,这技术称为:Overlay Network(覆盖网络)。

而这Overlay Network本身,可由每台宿主机上的一个“特殊网桥”共同组成。如当Node 1上的Container 1要访问Node 2上的Container 3时,Node 1上的“特殊网桥”在收到数据包后,能通过某种方式,把数据包发到正确宿主机,如Node 2。

而Node 2上的“特殊网桥”收到数据包后,也能通过某种方式,把数据包转发给正确容器,如Container 3。

甚至,每台宿主机上,都无需这种特殊网桥,而仅通过某种方式配置宿主机的路由表,就能把数据包转发到正确宿主机。

总结

本地环境下,单机容器网络的实现原理和docker0网桥的作用。

容器想跟外界通信,它发出的IP包须从它的Network Namespace里出来,来到宿主机。

解决这问题的方法:为容器创建一个一端在容器里充当默认网卡、另一端在宿主机上的Veth Pair设备。

FAQ

尽管容器的Host Network模式有缺点,但性能好、配置简单,并且易于调试,所以很多团队会直接使用Host Network。要在生产环境中使用容器的Host Network模式,做哪些额外准备工作?

  • 限制容器使用的端口范围,避免与主机上的其他服务冲突
  • 部署网络安全措施,如防火墙和访问控制列表,以保护主机和容器之间的通信
  • 配置适当的监控和日志记录,以便及时发现和解决问题
  • 对主机进行安全加固,以避免容器之间或容器与主机之间的攻击
  • 对主机进行性能测试,以确保它能够承受额外的负载,并且不会影响其他服务的性能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/699202.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【PCB专题】Allegro如何设置电源电压属性,将飞线隐藏?

在PCB设计过程中,布局完成之后的布线的顺序一般是先走信号线,然后进行电源的处理、分割。因为电源往往在整个板子上是都有的,所以电源的飞线是非常多,在布线时特别影响其他信号线的布线,界面看着比较杂乱。 如下所示GND和1.8V都存在各种飞线,比较杂乱。 Allegro中有设置…

随想012:断言

C 标准库提供了名为 assert 的断言宏&#xff1b; C# 语言提供了名为 Debug.Assert 的断言方法&#xff1b; Java 语言提供名为 assert 的断言关键字。 主流编程语言不约而同的在语言层面上提供了 断言 机制。 David R. Jamson&#xff0c;编译器 Icc 的开发者之一&#xf…

掌握IO流这一篇就够了

IO流&#xff08;几种常见的流&#xff09; IO流概述IO的分类顶级父类 字节流、字符流字节输出流OutputStream字节输入流InputStreamFileOutputStream类FileOutputStream**写出字节数据** FileInputStream类FileInputStream读取字节数据复制图片 字符流字符输入流Reader字符输出…

主动式和被动式电容笔哪个好用?苹果平板平替笔排行

被动式电容笔与主动式电容笔最大的不同在于主动式电容笔具有更广泛的应用领域&#xff0c;可以与不同种类的电容屏幕进行匹配。随着人们对其了解的不断深入&#xff0c;其应用也日益广泛。除此之外&#xff0c;平替电容笔的技术&#xff0c;也在不断的改进和提高&#xff0c;逐…

ResizeKit.NET 自动更改所有控件和字体大小 -Crack Version

ResizeKit2.NET ---Added support for Microsoft .NET 7.0. 使您的应用程序大小和分辨率独立。 ResizeKit.NET 自动更改所有控件和字体的大小&#xff0c;以便它们可以显示在任何大小的表单上。提供完全控制来自定义调整大小过程。即使用户在运行应用程序时切换表单的大小&…

ReentrantLock源码

介绍 ReentrantLock是Java中的一种可重入锁&#xff0c;也称为重入锁。是一种递归无阻塞的同步机制。它可以等同于 synchronized 的使用&#xff0c;但是 ReentrantLock 提供了比 synchronized 更强大、灵活的锁机制&#xff0c;可以减少死锁发生的概率。 ReentrantLock实现了…

Spring学习笔记---下篇

文章目录 Spring下篇1、代理模式1.1、静态代理1.2、加深理解1.3、动态代理 2、AOP2.1、什么是AOP2.2 、Aop在Spring中的作用2.3、在Spring中使用Aop 3、整合MyBatis3.1、[mybatis-spring介绍](https://mybatis.org/spring/zh/getting-started.html)3.2、整合步骤 4、事务4.1、事…

Linux基础工具|文本编辑器Vim的使用

0.前言 您好这里是limou3434的个人博客&#xff0c;感兴趣可以看看我的其他内容。 本次我给您带来的是Linux下Vim文本编辑器的使用&#xff0c;关于vim&#xff0c;您只需要知道一些常用的指令和操作即可&#xff0c;快速上手的秘诀是实践&#xff0c;并且是多次实践。 1.Vi…

12 MFC常用控件(一)

文章目录 button 按钮设置默认按钮按下回车后会响应禁用开启禁用设置隐藏设置显示设置图片设置Icon设置光标 Cbutton 类创建按钮创建消息单选按钮多选按钮 编辑框组合框下拉框操作 CListBox插入数据获取当前选中 CListCtrl插入数据设置表头修改删除 button 按钮 设置默认按钮按…

将 YAPF 设置为默认的 Python 代码格式化工具 (VS Code, PyCharm)

yapf 是一个 python 代码格式化工具, 和 black, autopep8, pycharm 自带的格式化功能相同用途. 使用 yapf 作为我的默认格式化工具, 出于以下考虑: 我和团队使用多种 ide, 而 pycharm 自带的格式化功能在其他 ide 上没法用. 所以我需要一个通用的格式化方案来保持代码风格的一…

Unity | HDRP高清渲染管线学习笔记:Rendering Debugger窗口

HDRP给我们提供了一套完整的可视化Debug工具&#xff0c;集成在Rendering Debugger窗口。通过顶部菜单Window→Analysis→Rendering Debugger可以打开窗口。Rendering Debugger窗口不仅仅可以在编辑模式下使用&#xff0c;也可以在真机上运行时使用。&#xff08;要在真机上运行…

数据结构--栈(Stack)的基本概念

数据结构–栈(Stack)的基本概念 线性表是具有相同数据类型的n ( n ≥ 0 n\ge0 n≥0&#xff09;个数据元素的有限序列&#xff0c;其中n为表长&#xff0c;当n 0时线性表是一个空表。若用L命名线性表&#xff0c;则其一般表示为: L ( a 1 , a 2 . . . , a i , a i 1 , . . …

JavaScript 手写代码 第七期(重写数组方法三) 用于遍历的方法

文章目录 1. 为什么要手写代码&#xff1f;2. 手写代码2.1 forEach2.1.1 基本使用2.1.2 手写实现 2.2 map2.2.1 基本使用2.2.2 手写实现 2.3 filter2.3.1 基本使用2.3.2 手写实现 2.4 every2.4.1 基本使用2.4.2 手写实现 2.5 some2.5.1 基本使用2.5.2 手写实现 2.6 reduce2.6.1…

大学实训报告范文6篇

大学实训报告范文篇一&#xff1a;js实训报告 一、简介&#xff1a; Web标准并不是一个单一的标准&#xff0c;而是一个系列的标准的集合。Web标准中具有代表性的几种语言有&#xff1a;_ML可扩展标记语言、_HTML可扩展超文本标记语言、CSS层叠样式表、DOM文档对象模型、Java…

助你丝滑过度到 Vue3 常用的组合式API ②④

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; VUE3~TS &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f449;…

HOT25-环形链表

leetcode原题链接&#xff1a;环形链表 题目描述 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数…

数据库分库分表(上)

数据库分库分表 1&#xff0c;概念 分库分表是一种数据库架构设计的方法&#xff0c;用于解决大规模数据存储和查询的性能问题。它将一个大型数据库拆分成多个小型数据库&#xff0c;每个数据库负责存储一部分数据&#xff0c;从而提高数据的读写效率和并发处理能力。 分库分…

MySQL数据库基础 17

第十七章 触发器 1. 触发器概述2. 触发器的创建2.1 创建触发器语法2.2 代码举例 3. 查看、删除触发器3.1 查看触发器3.2 删除触发器 4. 触发器的优缺点4.1 优点4.2 缺点4.3 注意点 在实际开发中&#xff0c;我们经常会遇到这样的情况&#xff1a;有 2 个或者多个相互关联的表&a…

虚幻引擎(UE5)-大世界分区WorldPartition教程(三)

文章目录 前言LevelInstance的使用1.ALevelInstance2.选择Actor创建关卡3.运行时加载LevelInstance 总结 上一篇&#xff1a;虚幻引擎(UE5)-大世界分区WorldPartition教程(二) 前言 在制作大关卡时&#xff0c;可能会遇到这样一种情况&#xff0c;就是关卡中的某些Actor会重复…

【每日一题】——Majority

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;每日一题 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日反刍 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓称…