资讯详情 深入解析 moby/ipvs:docker-ce 中基于 netlink 的 Linux 内核 IPVS 负载均衡 Go 库
📅 2026/10/13 21:02:07
容器运行时云原生【免费下载链接】docker-ce:warning: This repository is deprecated and will be archived (Docker CE itself is NOT deprecated) see the https://github.com/docker/docker-ce/blob/master/README.md :warning:项目地址https://gitcode.com/gh_mirrors/do/docker-ce点击查看免费下载导读moby/ipvs是随 docker-ce 一并维护、并在 Docker Engine 内部实际使用的一个纯 Go 库它通过netlink socket直接与 Linux 内核的IPVSIP Virtual Server模块通信实现对内核级负载均衡规则Service 虚拟服务、Destination 真实服务器、超时配置的完整管理。在 docker-ce 中它支撑着 Swarm 模式的服务发现与负载均衡load balancer数据面。读完本文你将掌握该库的完整 API 用法、底层 netlink 消息格式以及它在 docker-ce libnetwork 中的真实调用方式并能在自己的 Go 项目中直接引入它来操作 IPVS 规则。一、moby/ipvs 是什么官方 READMEcomponents/engine/vendor/github.com/moby/ipvs/README.md对该库的定义非常精炼ipvs provides a native Go implementation for communicating with IPVS kernel module using a netlink socket.即这是一个通过 netlink socket 与 IPVS 内核模块通信的原生 Go 实现。所谓原生意味着它不依赖ipvsadm等外部命令行工具而是直接构造内核 netlink 请求并解析内核返回的数据。库中的ipvs.go与netlink.go两个文件在文件头部都带有// build linux构建标签因此该库仅适用于 Linux这与其依赖内核模块的定位一致。模块信息见 go.modmodule 为github.com/moby/ipvsGo 版本要求 1.13 及以上依赖vishvananda/netlinknetlink 基础库、vishvananda/netns网络命名空间切换、golang.org/x/sys/unix、logrus版本锁定docker-ce 通过 vendor.conf 将github.com/moby/ipvs固定为v1.0.1commit4566ccea0e08d68e9614c3e7a64a23b850c4bb35随 engine 一并 vendored。二、快速上手官方最小示例README 给出了一个最小使用示例——创建一个句柄并列出当前命名空间中的所有 IPVS 服务import ( log github.com/moby/ipvs ) func main() { handle, err : ipvs.New() if err ! nil { log.Fatalf(ipvs.New: %s, err) } svcs, err : handle.GetServices() if err ! nil { log.Fatalf(handle.GetServices: %s, err) } }这段代码的关键点ipvs.New()创建句柄。第一个参数是网络命名空间的路径如/var/run/netns/xxx传空字符串时使用当前进程所在的命名空间handle.GetServices()返回[]*Service即内核中当前配置的全部虚拟服务用完后应当调用handle.Close()关闭 netlink socket示例中因进程立即退出而省略。三、核心数据结构与句柄 APImoby/ipvs 定义在 ipvs.go 中全部 API 都挂在*Handle上。下面逐一展开。3.1 Service —— 虚拟服务Service完整描述一个 IPVS 虚拟服务对应ipvsadm -A创建的服务字段类型说明Addressnet.IP虚拟服务地址VIPProtocoluint16传输层协议TCP/UDP 等Portuint16虚拟服务端口FWMarkuint32服务的防火墙标记firewall mark非零时按 mark 匹配而非按地址/端口SchedNamestring调度算法名称如rr、wlc等见下文常量Flagsuint32服务标志Timeoutuint32服务超时Netmaskuint32虚拟服务地址掩码用于 LVS 集群模式AddressFamilyuint16地址族AF_INET或AF_INET6PENamestring持久性引擎persistence engine名称StatsSvcStats服务统计信息查询时回填3.2 Destination —— 真实服务器Destination描述虚拟服务下的一个真实服务器backend对应ipvsadm -a添加的后端字段类型说明Addressnet.IP真实服务器地址Portuint16真实服务器端口Weightint权重在加权算法中生效0 表示临时摘除ConnectionFlagsuint32转发方式标志见下文ConnectionFlag*常量AddressFamilyuint16地址族UpperThreshold/LowerThresholduint32连接数上/下限阈值用于动态负载均衡算法ActiveConnections/InactiveConnectionsint活跃/非活跃连接数查询时回填StatsDstStats真实服务器统计DstStats是SvcStats的类型别名SvcStats与DstStats承载内核返回的统计信息包括Connections连接数、PacketsIn/PacketsOut进出包数、BytesIn/BytesOut进出字节数以及CPS、PPSIn/PPSOut、BPSIn/BPSOut等速率指标。3.3 Config —— 全局超时配置Config用于读写 IPVS 的全局超时配置字段为time.Duration类型对应内核的三类连接超时type Config struct { TimeoutTCP time.Duration // TCP 会话超时 TimeoutTCPFin time.Duration // TCP 状态为 FIN 时的超时 TimeoutUDP time.Duration // UDP 会话超时 }3.4 Handle 方法清单从 ipvs.go 中可以看到完整的方法集覆盖了 IPVS 规则的增删改查方法作用New(path string) (*Handle, error)在指定命名空间创建句柄空字符串 当前命名空间Close()关闭句柄关闭后句柄不可再使用NewService(s *Service) error创建虚拟服务UpdateService(s *Service) error更新已有虚拟服务DelService(s *Service) error删除虚拟服务IsServicePresent(s *Service) bool查询虚拟服务是否已存在GetService(s *Service) (*Service, error)获取单个虚拟服务详情含统计要求恰好匹配一个GetServices() ([]*Service, error)列出全部虚拟服务Flush() error清空当前命名空间内全部 IPVS 服务NewDestination(s, d) error为已有服务添加真实服务器UpdateDestination(s, d) error更新真实服务器如改权重、改阈值DelDestination(s, d) error删除真实服务器GetDestinations(s) ([]*Destination, error)列出某服务下的全部真实服务器GetConfig() (*Config, error)读取当前超时配置SetConfig(c *Config) error设置超时配置字段为 0 表示不改动3.5 一个完整可运行示例结合上述 API下面是一个创建服务 → 添加后端 → 查询 → 更新权重 → 删除的完整流程示例package main import ( fmt log net github.com/moby/ipvs ) func main() { h, err : ipvs.New() // 当前网络命名空间 if err ! nil { log.Fatalf(ipvs.New: %s, err) } defer h.Close() // 1. 定义并创建虚拟服务VIP 10.0.0.10:80TCP轮询调度 svc : ipvs.Service{ Address: net.ParseIP(10.0.0.10), Protocol: 6, // IPPROTO_TCP Port: 80, SchedName: ipvs.RoundRobin, // rr AddressFamily: net.IPv4len, // 配合 vishvananda/netlink 的 nl.FAMILY_V4 更佳 } if err : h.NewService(svc); err ! nil err.Error() ! file exists { log.Fatalf(NewService: %s, err) } fmt.Println(service created, present:, h.IsServicePresent(svc)) // 2. 添加两个真实服务器 for _, backend : range []string{192.168.1.11, 192.168.1.12} { dst : ipvs.Destination{ Address: net.ParseIP(backend), Port: 8080, Weight: 1, ConnectionFlags: ipvs.ConnFwdMasq, // NAT/Masquerade 转发 AddressFamily: net.IPv4len, } if err : h.NewDestination(svc, dst); err ! nil { log.Printf(NewDestination %s: %s, backend, err) } } // 3. 查询该服务的全部后端 dsts, err : h.GetDestinations(svc) if err ! nil { log.Fatalf(GetDestinations: %s, err) } for _, d : range dsts { fmt.Printf(backend %s:%d weight%d active%d\n, d.Address, d.Port, d.Weight, d.ActiveConnections) } // 4. 临时摘除一个后端权重置 0 offline : ipvs.Destination{Address: net.ParseIP(192.168.1.11), Port: 8080, Weight: 0} if err : h.UpdateDestination(svc, offline); err ! nil { log.Printf(UpdateDestination: %s, err) } // 5. 清理删除后端再删除服务 for _, backend : range []string{192.168.1.11, 192.168.1.12} { dst : ipvs.Destination{Address: net.ParseIP(backend), Port: 8080} _ h.DelDestination(svc, dst) } _ h.DelService(svc) }四、调度算法与转发方式常量constants.go 以 Go 常量形式把 IPVS 的调度算法与转发方式暴露给调用方避免手写易错的魔法字符串/数字。4.1 调度算法SchedName常量值语义RoundRobinrr轮询将连接平均分发给各真实服务器WeightedRoundRobinwrr加权轮询按权重比例分配权重高者优先获得更多新连接LeastConnectionlc最少连接分配给活跃连接最少的服务器WeightedLeastConnectionwlc加权最少连接结合权重与当前连接数DestinationHashingdh目标地址哈希按目标 IP 查静态哈希表SourceHashingsh源地址哈希按源 IP 查静态哈希表4.2 转发方式Destination.ConnectionFlagsIPVS 支持三种内核态转发模式加上一个本地处理模式常量值语义ConnectionFlagFwdMask0x0007转发方式位的掩码ConnectionFlagMasq0x0000MasqueradeNAT修改报文目标地址后送回真实服务器回程经负载均衡器转发ConnectionFlagLocalNode0x0001本地节点处理本机回环ConnectionFlagTunnel0x0002IPIP 隧道将报文封装进隧道转发ConnectionFlagDirectRoute0x0003直接路由DSR不修改报文仅改写 MAC 地址转发此外文件中还有一组语义相同的连接级常量ConnFwdMask / ConnFwdMasq / ConnFwdLocalNode / ConnFwdTunnel / ConnFwdDirectRoute并额外定义了ConnFwdBypass 0x0004绕过缓存直通。docker-ce 的 libnetwork 使用的是ConnFwd*这组常量见下文第五节。五、docker-ce 中的实战Swarm 服务负载均衡moby/ipvs 不是孤立存在的库它在 docker-ce 的libnetwork中承担着 Swarm 模式下服务负载均衡数据面的核心职责。这正是该库被 vendored 进 docker-ce 的根本原因。5.1 调用链概览在 libnetwork/service_linux.go 中addLBBackend与rmLBBackend两个函数完整展示了 IPVS 规则的生命周期管理addLBBackend添加后端service_linux.go通过sb.Key()沙箱对应的网络命名空间路径调用ipvs.New(sb.Key())在 LB sandbox 所在的命名空间内创建句柄defer i.Close()保证用完即关构造ipvs.Service关键特征是用FWMark防火墙标记而不是地址端口来标识虚拟服务SchedName: ipvs.RoundRobin即使用rr轮询算法i.IsServicePresent(s)判重若不存在则先调用invokeFWMarker在内核 mangle 表中给发往 VIP 的报文打上fwMark标记再i.NewService(s)容忍EEXIST构造ipvs.Destination{Weight: 1}调用i.NewDestination(s, d)加入真实服务器当网络配置为 DSR 模式时将d.ConnectionFlags置为ipvs.ConnFwdDirectRoute。rmLBBackend删除后端service_linux.gofullRemovefalse时不真正删除而是把d.Weight置 0 后调用UpdateDestination实现软摘除deweightfullRemovetrue时调用DelDestination、DelService彻底删除并同步清理 mangle 表的防火墙标记规则和 VIP 的 IP alias。5.2 NAT 与 DSR 两种负载均衡模式network.go 定义了两种模式常量const ( loadBalancerModeNAT NAT loadBalancerModeDSR DSR loadBalancerModeDefault loadBalancerModeNAT // 默认 NAT 模式 )NAT 模式默认libnetwork 在fwMarkerreexec 例程中额外下发一条-m ipvs --ipvs -d 子网 -j SNAT --to-source 端点IP的 POSTROUTING 规则并写入/proc/sys/net/ipv4/vs/conntrack启用连接跟踪以保证回程流量能正确回到客户端见 service_linux.goDSR 模式转发方式改为ConnFwdDirectRoute直接路由模式下回程不经过负载均衡器需要下游真实服务器配置好本地回环上的 VIP。5.3 fwmark 与 IPVS 的配合addLBBackend中虚拟服务只用FWMark而不用Address/Port来标识是因为 Swarm 的 ingress 流量会先进入DOCKER-INGRESS链做端口 DNAT再由 mangle 表的 PREROUTING/INPUT 规则按目标端口打上不同的fwMarkIPVS 再根据 mark 把报文分发给对应服务后端的容器。这种iptables 打标 IPVS 按 mark 分发的组合是 docker-ce 默认路由网状网络Routing Mesh的数据面核心。六、底层实现netlink 消息的封装与解析moby/ipvs的价值在于无需ipvsadm即可用纯 Go 完成上述全部操作其底层实现在 netlink.go 中。6.1 Generic Netlink 与内核家族协商IPVS 通过 Linux 的generic netlinkgenl子系统暴露接口。库在首次使用时通过sync.Once执行一次性初始化setup()函数netlink.go调用exec.Command(modprobe, -va, ip_vs)尝试加载ip_vs内核模块失败时仅记录logrus.Warnf不会中断后续流程因此内核模块必须预先启用否则后续操作会失败发送genlCtrlCmdGetFamily请求查询名为IPVS的 genl family从返回的genlCtrlAttrFamilyID属性中解析出 family idgetIPVSFamily()netlink.go。若解析失败会记录一条关键告警Could not get ipvs family information from the kernel. It is possible that ipvs is not enabled in your kernel. Native loadbalancing will not work until this is fixed.6.2 请求构造与响应解析每个 netlink 请求由newIPVSRequest(cmd)构造携带genlMsgHdr{cmd, version: 1}头与递增的Seq序列号atomic.AddUint32(i.seq, 1)服务/目的地字段通过fillService/fillDestination编码为嵌套的 netlink 属性attribute例如端口必须按**网络字节序BigEndian**写入netlink.go响应侧通过deserializeGenlMsgnl.ParseRouteAttr逐层剥壳再由assembleService/assembleDestination/assembleStats把属性组装回 Go 结构体execute()netlink.go统一处理NLMSG_DONE、NLMSG_ERROR、NLM_F_MULTI多消息与超时EAGAIN并且校验响应的Seq与Pid以防止串包。6.3 超时防护为避免与内核通信时发生死锁ipvs.go 定义了两组超时const ( netlinkRecvSocketsTimeout 3 * time.Second // 接收超时 netlinkSendSocketTimeout 30 * time.Second // 发送超时 )New()创建 socket 后立即通过SetSendTimeout/SetReceiveTimeout应用这两个值。6.4 内核兼容性细节对老内核 3.18destination 响应中可能缺少地址族属性assembleDestination会通过getIPFamily根据原始字节推断AF_INET还是AF_INET6netlink.go并注释解释了 netlink 返回的 IPv4 地址是 16 字节数组中的前 4 字节这一特殊性netlink.go 末尾附有完整的 netlink/IPVS 消息格式示意图genlMsgHdr→ 属性列表 → IPVS 属性可作为阅读该文件时的参考。七、使用前置条件与注意事项仅限 Linuxipvs.go、netlink.go、constants.go均带// build linux构建标签其他平台无法编译内核必须启用 IPVS库会尽力modprobe ip_vs但若内核未编译 IPVS 支持如某些容器环境、受限内核setup()会告警且后续 API 将返回错误命名空间隔离ipvs.New(path)通过netns.GetFromPath在指定网络命名空间内创建 netlink socket因此不同网络命名空间的 IPVS 规则相互隔离——这正是 docker-ce 把每个 service 的 LB 规则放在专属 LB sandbox 命名空间的原因错误处理惯例docker-ce 在调用NewService/NewDestination时对syscall.EEXIST已存在、DelService/DelDestination对syscall.ENOENT不存在做了容忍业务代码可参考这一模式权限操作 IPVS 属于内核配置类操作进程通常需要 root 或CAP_NET_ADMIN能力docker 的 libnetwork 正是通过特权组件执行这些调用。八、延伸阅读官方 READMEcomponents/engine/vendor/github.com/moby/ipvs/README.md核心 API 定义ipvs.gonetlink 底层实现与消息格式netlink.go调度算法 / 转发方式常量constants.go依赖与版本锁定go.mod、vendor.confdocker-ce 中的实际调用Swarm LB 规则增删与 NAT/DSR 模式service_linux.go负载均衡模式常量定义network.go赞分享容器运行时云原生【免费下载链接】docker-ce:warning: This repository is deprecated and will be archived (Docker CE itself is NOT deprecated) see the https://github.com/docker/docker-ce/blob/master/README.md :warning:项目地址https://gitcode.com/gh_mirrors/do/docker-ce点击查看免费下载相关推荐Moby ipvs用纯 Go 与 IPVS 内核模块通信的 netlink 编程实战Moby ipvs用纯 Go 与 IPVS 内核模块通信的 netlink 编程实战 导读 本文围绕 Moby 仓库中内嵌的 moby/ipvs 组件 REA云原生容器运行时虚拟化容器编排scope 项目中的 Go netlink 库基于 vishvananda/netlink 的 Linux 内核网络编程指南scope 项目中的 Go netlink 库基于 vishvananda/netlink 的 Linux 内核网络编程指南 导读 本文以仓库中 vendor云原生可观测性容器编排运维Wereader测试与部署指南确保浏览器扩展稳定运行的完整流程Wereader测试与部署指南确保浏览器扩展稳定运行的完整流程 Wereader是一款专为微信读书用户设计的浏览器扩展特别适合习惯使用Markdown做笔记前端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考