深入解析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高保真原型哦 小程序里免费下载哦…...
【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)
引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...
k8s从入门到放弃之HPA控制器
k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...
