☰
深入解析 moby/ipvs:docker-ce 中基于 netlink 的 Linux 内核 IPVS 负载均衡 Go 库
2026/10/13 21:01:48 网站建设 项目流程
  • 容器运行时
  • 云原生

【免费下载链接】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 内核的IPVS(IP Virtual Server)模块通信,实现对内核级负载均衡规则(Service 虚拟服务、Destination 真实服务器、超时配置)的完整管理。在 docker-ce 中,它支撑着 Swarm 模式的服务发现与负载均衡(load balancer)数据面。读完本文,你将掌握该库的完整 API 用法、底层 netlink 消息格式,以及它在 docker-ce libnetwork 中的真实调用方式,并能在自己的 Go 项目中直接引入它来操作 IPVS 规则。

一、moby/ipvs 是什么

官方 README(components/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.mod,module 为github.com/moby/ipvs,Go 版本要求 1.13 及以上;
  • 依赖:vishvananda/netlink(netlink 基础库)、vishvananda/netns(网络命名空间切换)、golang.org/x/sys/unix、logrus;
  • 版本锁定:docker-ce 通过 vendor.conf 将github.com/moby/ipvs固定为v1.0.1(commit4566ccea0e08d68e9614c3e7a64a23b850c4bb35),随 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) } }

这段代码的关键点:

  1. ipvs.New("")创建句柄。第一个参数是网络命名空间的路径(如/var/run/netns/xxx);传空字符串时使用当前进程所在的命名空间;
  2. handle.GetServices()返回[]*Service,即内核中当前配置的全部虚拟服务;
  3. 用完后应当调用handle.Close()关闭 netlink socket(示例中因进程立即退出而省略)。

三、核心数据结构与句柄 API

moby/ipvs 定义在 ipvs.go 中,全部 API 都挂在*Handle上。下面逐一展开。

3.1 Service —— 虚拟服务

Service完整描述一个 IPVS 虚拟服务(对应ipvsadm -A创建的服务):

字段类型说明
Addressnet.IP虚拟服务地址(VIP)
Protocoluint16传输层协议(TCP/UDP 等)
Portuint16虚拟服务端口
FWMarkuint32服务的防火墙标记(firewall mark),非零时按 mark 匹配而非按地址/端口
SchedNamestring调度算法名称,如rr、wlc等(见下文常量)
Flagsuint32服务标志
Timeoutuint32服务超时
Netmaskuint32虚拟服务地址掩码(用于 LVS 集群模式)
AddressFamilyuint16地址族,AF_INET或AF_INET6
PENamestring持久性引擎(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:80,TCP,轮询调度 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.ConnectionFlags)

IPVS 支持三种内核态转发模式,加上一个本地处理模式:

常量值语义
ConnectionFlagFwdMask0x0007转发方式位的掩码
ConnectionFlagMasq0x0000Masquerade(NAT):修改报文目标地址后送回真实服务器,回程经负载均衡器转发
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):

    1. 通过sb.Key()(沙箱对应的网络命名空间路径)调用ipvs.New(sb.Key()),在 LB sandbox 所在的命名空间内创建句柄,defer i.Close()保证用完即关;
    2. 构造ipvs.Service,关键特征是用FWMark(防火墙标记)而不是地址+端口来标识虚拟服务:SchedName: ipvs.RoundRobin,即使用rr轮询算法;
    3. i.IsServicePresent(s)判重,若不存在则先调用invokeFWMarker在内核 mangle 表中给发往 VIP 的报文打上fwMark标记,再i.NewService(s)(容忍EEXIST);
    4. 构造ipvs.Destination{Weight: 1}调用i.NewDestination(s, d)加入真实服务器;当网络配置为 DSR 模式时,将d.ConnectionFlags置为ipvs.ConnFwdDirectRoute。
  • rmLBBackend(删除后端)(service_linux.go):

    • fullRemove=false时不真正删除,而是把d.Weight置 0 后调用UpdateDestination,实现"软摘除"(deweight);
    • fullRemove=true时调用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.go);
  • DSR 模式:转发方式改为ConnFwdDirectRoute,直接路由模式下回程不经过负载均衡器,需要下游真实服务器配置好本地回环上的 VIP。

5.3 fwmark 与 IPVS 的配合

addLBBackend中虚拟服务只用FWMark而不用Address/Port来标识,是因为 Swarm 的 ingress 流量会先进入DOCKER-INGRESS链做端口 DNAT,再由 mangle 表的 PREROUTING/INPUT 规则按目标端口打上不同的fwMark,IPVS 再根据 mark 把报文分发给对应服务后端的容器。这种"iptables 打标 + IPVS 按 mark 分发"的组合是 docker-ce 默认路由网状网络(Routing Mesh)的数据面核心。

六、底层实现:netlink 消息的封装与解析

moby/ipvs的价值在于无需ipvsadm即可用纯 Go 完成上述全部操作,其底层实现在 netlink.go 中。

6.1 Generic Netlink 与内核家族协商

IPVS 通过 Linux 的generic netlink(genl)子系统暴露接口。库在首次使用时通过sync.Once执行一次性初始化(setup()函数,netlink.go):

  1. 调用exec.Command("modprobe", "-va", "ip_vs")尝试加载ip_vs内核模块;失败时仅记录logrus.Warnf,不会中断后续流程(因此内核模块必须预先启用,否则后续操作会失败);
  2. 发送genlCtrlCmdGetFamily请求查询名为"IPVS"的 genl family,从返回的genlCtrlAttrFamilyID属性中解析出 family id(getIPVSFamily(),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);
  • 响应侧通过deserializeGenlMsg+nl.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.18),destination 响应中可能缺少地址族属性,assembleDestination会通过getIPFamily根据原始字节推断AF_INET还是AF_INET6(netlink.go),并注释解释了 netlink 返回的 IPv4 地址是 16 字节数组中的前 4 字节这一特殊性;
  • netlink.go 末尾附有完整的 netlink/IPVS 消息格式示意图(genlMsgHdr→ 属性列表 → IPVS 属性),可作为阅读该文件时的参考。

七、使用前置条件与注意事项

  1. 仅限 Linux:ipvs.go、netlink.go、constants.go均带// +build linux构建标签,其他平台无法编译;
  2. 内核必须启用 IPVS:库会尽力modprobe ip_vs,但若内核未编译 IPVS 支持(如某些容器环境、受限内核),setup()会告警且后续 API 将返回错误;
  3. 命名空间隔离:ipvs.New(path)通过netns.GetFromPath在指定网络命名空间内创建 netlink socket,因此不同网络命名空间的 IPVS 规则相互隔离——这正是 docker-ce 把每个 service 的 LB 规则放在专属 LB sandbox 命名空间的原因;
  4. 错误处理惯例:docker-ce 在调用NewService/NewDestination时对syscall.EEXIST(已存在)、DelService/DelDestination对syscall.ENOENT(不存在)做了容忍,业务代码可参考这一模式;
  5. 权限:操作 IPVS 属于内核配置类操作,进程通常需要 root 或CAP_NET_ADMIN能力,docker 的 libnetwork 正是通过特权组件执行这些调用。

八、延伸阅读

  • 官方 README:components/engine/vendor/github.com/moby/ipvs/README.md
  • 核心 API 定义:ipvs.go
  • netlink 底层实现与消息格式:netlink.go
  • 调度算法 / 转发方式常量:constants.go
  • 依赖与版本锁定:go.mod、vendor.conf
  • docker-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
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询