深入解析docker内核网桥
今天做虚拟桌面,朋友问我,为什么vnc 连接另一个docker 容器一直超时,原因是在docker 启动的时候没有组网,那么接下来我就要解析下docker的内核网络。
我们思考几个问题,带你了解linux 中docker 网络实现的基本原理。
文章分析的基石来源于 Docker 的moby源码:
https://github.com/moby/moby
一、docker 网络组成
docker 默认有主机模式和网桥模式,这里只说网桥模式,我们说一下docker内核的网桥代码
先简单说一下容器,容器底层调用的是linux 的 clone,我们使用clone的时候有一些独特的标志位
CLONE_NEWNES,
CLONE_NEWUTS,
CLONE_NEWIPC,
CLONE_NEWPID,
CLONE_NEWNET
使用linux内核的这些标志位,我们都可以创建出不同NS的进程,其中我们需要注意的是CLONE_NEWNET
二、Docker 桥接模式分析
Docker Bridge 实现方式如下:
1)我们实现要考虑不同的两个进程 netns 是不一样的,那么这两个不同的netns之间如何通信?
我们可以使用linux 的 veth 接口 ,申请两个veth 设备假设是veth0 和 veth1。而veth pair 设备确保无论哪一个收到报文都会发送给另一端
2)我们把veth0 附加到 docker daemon 创建的docker0 网桥上。保证宿主机有能力把报文发送到veth0
3) 然后我们把 veth1 添加到容器所属的命名空间下,veth1 在Docker 容器里看来就是eth0。一方面保证网络报文发往veth0,可以被veth1收到,实现宿主机到docker 容器之间连通性。
1)问题反思?
docker 网桥是如何实现的?
docker 又是怎么通过建立linux 设备veth的?
docker 创建这些基础网络组件的过程中,到底是用了linux 内核的哪些特性?
2)docker 网桥是如何实现的?
go 部分
我们翻阅Docker 源码,发现这么一段
位于源码的
daemon/daemon_unix.go
// --ip processingif cfg.DefaultIP != nil {netOption[bridge.DefaultBindingIP] = cfg.DefaultIP.String()}_, err = controller.NewNetwork("bridge", "bridge", "",libnetwork.NetworkOptionEnableIPv6(cfg.EnableIPv6),libnetwork.NetworkOptionDriverOpts(netOption),libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil),libnetwork.NetworkOptionDeferIPv6Alloc(deferIPv6Alloc))
libnetwork/controller.go
func (c *Controller) addNetwork(n *Network) error {d, err := n.driver(true)if err != nil {return err}// Create the networkif err := d.CreateNetwork(n.id, n.generic, n, n.getIPData(4), n.getIPData(6)); err != nil {return err}n.startResolver()return nil
}
最后我们发现网桥的实现最后到了CreateNetwork中,我们在追踪进入CreateNetwork中
libnetwork/drivers/bridge/bridge_linux.go
CreateNetwork 的实现我们发现具体关键代码位于
// If the bridge interface doesn't exist, we need to start the setup steps// by creating a new device and assigning it an IPv4 address.bridgeAlreadyExists := bridgeIface.exists()if !bridgeAlreadyExists {bridgeSetup.queueStep(setupDevice)bridgeSetup.queueStep(setupDefaultSysctl)}
再继续看 setupDevice,一切水落石处,Docker 网桥创建的关键在于 setupDevice
func setupDevice(config *networkConfiguration, i *bridgeInterface) error {// We only attempt to create the bridge when the requested device name is// the default one. The default bridge name can be overridden with the// DOCKER_TEST_CREATE_DEFAULT_BRIDGE env var. It should be used only for// test purpose.var defaultBridgeName stringif defaultBridgeName = os.Getenv("DOCKER_TEST_CREATE_DEFAULT_BRIDGE"); defaultBridgeName == "" {defaultBridgeName = DefaultBridgeName}if config.BridgeName != defaultBridgeName && config.DefaultBridge {return NonDefaultBridgeExistError(config.BridgeName)}// Set the bridgeInterface netlink.Bridge.i.Link = &netlink.Bridge{LinkAttrs: netlink.LinkAttrs{Name: config.BridgeName,},}// Set the bridge's MAC address. Requires kernel version 3.3 or up.hwAddr := netutils.GenerateRandomMAC()i.Link.Attrs().HardwareAddr = hwAddrlog.G(context.TODO()).Debugf("Setting bridge mac address to %s", hwAddr)if err := i.nlh.LinkAdd(i.Link); err != nil {log.G(context.TODO()).WithError(err).Errorf("Failed to create bridge %s via netlink", config.BridgeName)return err}return nil
}
linux 接口部分:
既然我们找到了库的实现,那么我们继续思考netlink库为什么能调在linux 系统里创建网桥,它到底是使用了kernel 开发的哪些能力?
分析go netlink 库我发现了一个函数叫linkModify,他的一段代码实现如下:
_, err := req.Execute(unix.NETLINK_ROUTE, 0)if err != nil {return err}
底层使用的是
func getNetlinkSocket(protocol int) (*NetlinkSocket, error) {fd, err := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW|unix.SOCK_CLOEXEC, protocol)if err != nil {return nil, err}s := &NetlinkSocket{fd: int32(fd),}s.lsa.Family = unix.AF_NETLINKif err := unix.Bind(fd, &s.lsa); err != nil {unix.Close(fd)return nil, err}return s, nil
}
也就是说Docker 的网络库最终使用的是
netlink socket API:
socket()函数
socket域(地址族)是AF_NETLINK
socket类型是SOCK_RAW或SOCK_DGRAM,因为netlink是一种面向数据的服务
netlink协议类型定义在netlink.h(以下以NETLINK_ROUTE为例),也可以自定义
AF_NETLINK 无疑是linux 中内核态和用户态通信的重要手段,他和ioctl的最大区别是,AF_NETLINK
是一种协议族,提供了一种标准化的机制,用于用户态程序与内核之间进行网络相关的通信。它定义了不同的协议类型,如NETLINK_ROUTE
、NETLINK_SELINUX
等,以支持特定的网络操作和功能。使用AF_NETLINK
,用户态程序可以发送特定类型的消息给内核,以请求或传递网络相关的信息。
docker 是使用AF_NETLINK 做的,而我们使用brctl 却是使用的ioctl
docker veth 设备是如何实现的
case *Veth:data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil)peer := data.AddRtAttr(nl.VETH_INFO_PEER, nil)nl.NewIfInfomsgChild(peer, unix.AF_UNSPEC)peer.AddRtAttr(unix.IFLA_IFNAME, nl.ZeroTerminated(link.PeerName))if base.TxQLen >= 0 {peer.AddRtAttr(unix.IFLA_TXQLEN, nl.Uint32Attr(uint32(base.TxQLen)))}if base.NumTxQueues > 0 {peer.AddRtAttr(unix.IFLA_NUM_TX_QUEUES, nl.Uint32Attr(uint32(base.NumTxQueues)))}if base.NumRxQueues > 0 {peer.AddRtAttr(unix.IFLA_NUM_RX_QUEUES, nl.Uint32Attr(uint32(base.NumRxQueues)))}if base.MTU > 0 {peer.AddRtAttr(unix.IFLA_MTU, nl.Uint32Attr(uint32(base.MTU)))}if link.PeerHardwareAddr != nil {peer.AddRtAttr(unix.IFLA_ADDRESS, []byte(link.PeerHardwareAddr))}if link.PeerNamespace != nil {switch ns := link.PeerNamespace.(type) {case NsPid:val := nl.Uint32Attr(uint32(ns))peer.AddRtAttr(unix.IFLA_NET_NS_PID, val)case NsFd:val := nl.Uint32Attr(uint32(ns))peer.AddRtAttr(unix.IFLA_NET_NS_FD, val)}}
我们发现还是AF_NETLINK 实现的
三、快速组网组成一个测试网络
创建网桥
ip link add br0 type bridge
启动网桥
ip link set br0 up
创建网络命名空间ns1 和ns2
~# ip netns add ns1
~# ip netns add ns2
创建 veth 对
~# ip link add veth0 type veth peer br-veth0
~# ip link add veth1 type veth peer br-veth1
查看创建的 veth 对,通过查看此时 veth对 在 root名称空间下
ip address show
将创建的 veth对 的一端插入到指定的名称空间
~# ip link set veth0 netns ns1
~# ip link set veth1 netns ns2
通过进入不同的名称空间查看网卡的一端
~# ip netns exec ns1 ip a
~# ip netns exec ns2 ip a
将创建的 veth对 的另一端插入到 br0 的桥接网卡
~# ip link set br-veth0 master br0
~# ip link set br-veth1 master br0
启动网卡 veth0 veth1 br-veth0 br-veth1,并配置 ip 地址
// 开启网桥部分
~# ip link set br-veth0 up
~# ip link set br-veth1 up// 开启ns里的veth
~# ip netns exec ns1 ip link set veth0 up
~# ip netns exec ns2 ip link set veth1 up// 设置容器内的veth 设备ip
~# ip netns exec ns1 ifconfig veth0 192.168.100.10/24
~# ip netns exec ns2 ifconfig veth1 192.168.100.20/24
四、总结
Docker 创建网桥和Veth设备都是通过AF_NETLINK 套接字实现的,我需要去读 <精通linux内核网络>这本书去调研一下
相关文章:

深入解析docker内核网桥
今天做虚拟桌面,朋友问我,为什么vnc 连接另一个docker 容器一直超时,原因是在docker 启动的时候没有组网,那么接下来我就要解析下docker的内核网络。 我们思考几个问题,带你了解linux 中docker 网络实现的基本原理。 文…...
ubuntu18.04服务器双网口配置上外网
记录一下配置服务器过程,本以为简单,结果整了一天。 服务器有2个网口,网口2是用来上外网的,原来用的01-netcfg.yaml进行ip地址设置,主要就用2条命令: vi /etc/netplan/01-netcfg.yaml (打开后…...
【安全体系架构】——防御深度架构
防御深度架构: 防御深度架构是一种多层次的安全模型,旨在通过在网络和系统的各个层次上部署多个安全措施,以抵御不同类型的威胁和攻击。这个模型承认单一的安全措施可能无法全面防御所有潜在威胁,因此采用了多层次的安全防御策略…...

Opencv之RANSAC算法用于直线拟合及特征点集匹配详解
Opencv之RANSAC算法用于直线拟合及特征点集匹配详解 讲述Ransac拟合与最小二乘在曲线拟合上的优缺点 讲述在进行特征点匹配时,最近邻匹配与Ransac匹配的不同之处 另外,Ransac也被用于椭圆拟合、变换矩阵求解等 1. 直线拟合 1.1 原理 RANSAC(RANdom …...

Jenkins环境部署与任务构建
一、CI/CD 1、CI/CD 概念: CI/CD 是一种软件开发和交付方法,旨在加速应用程序的开发、测试和部署过程,以提高软件交付的质量和效率。 (1) 持续集成 (CI Continuous Integration): 持续集成是开发团队频繁集成其代码更改的过程。开发者将其…...

ES6 Class和Class继承
1.class的基本语法 class可以理解为是一个语法糖,将js只能通过构造函数创建实例的方法进行了补充 构造函数: function Person ({ name, age18 }) {this.name namethis.age age } new Person({name: 张三}) Class类: class Person {con…...
C++11 packaged_task
std::packaged_task 把一个方法打包成一个task扔到线程中执行,然后通过packaged_task中的furture等待执行结果。 void test_promise() {std::packaged_task <int()> task([]()->int {std::cout << "packaged_task begin \n" << std…...
delete、drop、truncate三兄弟
比较方面/具体命令deletetruncatedrop删除范围逐行删除(记录行)逐页删除(数据页)整张表(数据表结构)所属范畴数据操作语言(DML)数据定义语言(DDL)数据定义语言…...
C/C++运算优先级
文章目录 前言1.运算优先级表2.举例说明:总结 前言 最近复习C基础知识的时候,发现对这部分还是有些模糊。常用的 - ,括号等运算符对于它们的优先级还是比较明确的。但是涉及到移位运算,逻辑运算这种,再结合四则运算…...

apache搭建静态网站,moongoose搭建网站后台,出现的跨域问题解决
文章目录 1,问题描述1.1,当网页和后台是不同服务时会产生跨域问题1.2,跨域问题 2,nginx端口转发解决跨域问题2.1,下载并安装nginx2.1.1,解压后如下所示2.1.2,进入解压目录后,执行配置…...

LiveQing视频点播流媒体RTMP推流服务功能-支持视频点播分屏大屏展示视频轮巡分组播放RMP推流直播大屏展示
LiveQing支持视频点播分屏大屏展示视频轮播分组播放RMP推流直播大屏展示 1、分屏展示2、轮巡播放3、RTMP推流视频直播和点播流媒体服务 1、分屏展示 LiveQing支持将视频点播、鉴权直播,拉转直播视频流,进行分屏播放。 2、轮巡播放 3、RTMP推流视频直播和…...
tf loss构建常用到函数
1、tf.map_fn tf.map_fn是TensorFlow中的一个函数,用于对给定的函数和输入进行逐元素的映射,其定义如下: tf.map_fn(fn,elems,dtypeNone,parallel_iterationsNone,back_propTrue,swap_memoryFalse,infer_shapeTrue,nameNone,fn_output_sign…...
行为型模式-备忘录模式
备忘录模式保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。 主要解决:所谓备忘录模式就是在不破坏…...
Android Studio初学者实例:RecyclerView学习--模仿今日头条--续
新学期开始了,这篇文章收到了很多人的评论有很多地方不懂,所以写下了以下的文章--续篇 首先使用RecyclerView也好还是使用ListView,更或是GridView你都要先构思需要什么 这些东西无一例外通常都是用在列表显示下,那么需要一些&a…...

栈和队列的C++模拟实现
一、栈stack 1.介绍(库里面的文档介绍) 1. stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。 2. stack是作为容器适配器被实现的,容器适配器即是对…...

UE4/5:通过Blender制作BlendShape导入【UE4/5曲线、变形目标,blender形态键】
UE4/5里面,我们经常可以在一些骨骼模型上面看到相关的曲线,如Metahuman里面就是通过这个曲线来改变人物的脸部表情。 而这里笔者将教导如何去制作这种曲线。 这种曲线都是存在于骨骼模型上的,所以我们要么直接制作骨骼模型导入ue࿰…...

微信小程序进阶——后台交互
目录 一、后台准备 1.1 pom.xml 1.2 配置数据源 1.3 整合mybatis 二、前后端交互 2.1 method1 2.2 method2 2.2.1 封装request 2.2.2 头部引用util 2.2.3 编写方法 2.2.4 展示效果 三、WXS的使用 3.1 会议状态 3.1.2 引入wxs 3.1.3 修改代码 3.1.4 展示效果 3…...

二维码智慧门牌管理系统升级解决方案:突破传统,实现质检与抽检的个性化配置
文章目录 前言一、引入“独立质检”二、个性化抽检类别设定三、触发重采要素的功能升级四、升级优势与展望 前言 在数字化时代,智慧门牌管理系统已经成为社会管理的重要工具。为了满足各种复杂需求,系统升级是必然趋势。本次升级主要针对质检和抽检两大…...

《动手学深度学习 Pytorch版》 9.4 双向循环神经网络
之前的序列学习中假设的目标是在给定观测的情况下对下一个输出进行建模,然而也存在需要后文预测前文的情况。 9.4.1 隐马尔可夫模型中的动态规划 数学推导太复杂了,略。 9.4.2 双向模型 双向循环神经网络(bidirectional RNNs)…...

【Axure高保真原型】可视化图表图标
今天和粉丝们免费分享可视化图表图标原型模板,包括柱状图、条形图、环形图、散点图、水波图等常用的可视化图表图标。 【原型效果】 【原型预览】 https://axhub.im/ax9/d402c647c82f9185/#c1 【原型下载】 这个模板可以在 Axure高保真原型哦 小程序里免费下载哦…...

stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...

IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...