docker以网络插件方式实现Pipework

  docker目前提供了多种网络模式,但是相信大部分运维人员还是比较相信物理网络,所以就想着将容器的网络配置成物理网络。可以看到开源项目Pipework,当然 Pipework的功能比较强大,这里我只关注容器使用物理网络的实现,如果你手动去配置容器的IP,可以只需要以下几个步骤:

1、创建网桥并配置,将eth0绑定到网桥br0,docker Daemon 监听br0

2、启动容器

1
docker run -d --net=none --name web  10.110.17.138:5000/library/tomcat:7.0.67-jre7

3、创建Container对应的网络命名空间

1
2
mkdir -p /var/run/netns
ln -s /proc/1381/ns/net /var/run/netns/1381

4、容器配置网卡

1
2
3
4
5
6
7
8
9
ip link add veth-a type veth peer name veth-b
brctl addif br0 veth-a
ip link set veth-a up
ip link set veth-b netns 1381
ip netns exec 1381 ip link set dev veth-b name eth0
ip netns exec 1381 ip link set eth0 up
ip netns exec 1381 ip addr add 10.110.17.94/24 dev eth0
ip netns exec 1381 ip route del default
ip netns exec 1381 ip route add default via 10.110.17.254

   不是很复杂,业务系统可以定义一个脚本执行就OK,但是我们再开发使用过程中发现,当我们想着将这种网络模式运用到Compose时,却发现用不了,但是Compose支持docker 的网络插件机制,所以就需要考虑使用docker的网络插件机制实现。参考了Weaveovs-plugin的实现方式,整理出了docker使用物理网络的插件docker-network-plugin-local,当然github上开源的仅仅是网络配置OK,和我们生产环境上的肯定有所区别,不过大家可以参考,在这里说一下几处关键代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (driver \*PipeNetworkDriver) CreateNetwork(createNetworkRequest \*network.CreateNetworkRequest) error {
GlobalEndPointCache.Mutex.Lock()
defer func() {
GlobalEndPointCache.Mutex.Unlock()
}()
gateway, mask, \_ := getGatewayIP(createNetworkRequest)
networkInfo := NetworkInfo{BridgeName: "br0",
Gateway: gateway,
GatewayMask: mask,
ContainerInterface: "eth1",
MTU: 1500,
NetWorkId: createNetworkRequest.NetworkID,
}
GlobalEndPointCache.EndPoints = make(map\[string\]\*EndPoint)
GlobalEndPointCache.Network = &networkInfo
driver.UpdateCacheFile()
return nil
}

 此处是命令行执行docker network create 时调用的API,这个插件是将网络信息缓存到了本地,其他什么也没有做(如果你想在集群环境中使用,可以修改此处,将网络信息保存到Etcd数据库)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (driver \*PipeNetworkDriver) CreateEndpoint(createEndpointRequest \*network.CreateEndpointRequest) (\*network.CreateEndpointResponse, error) {
fmt.Println("create endpoint")
GlobalEndPointCache.Mutex.Lock()
defer func() {
GlobalEndPointCache.Mutex.Unlock()
}()
endPointId := createEndpointRequest.EndpointID
ipaddress := createEndpointRequest.Interface.Address
vethPairTag := truncateID(endPointId)
vethPariA := vethPairTag + "-a"
vethPariB := vethPairTag + "-b"
endPoint := EndPoint{EndpointID: endPointId,
Address: ipaddress,
VethName: vethPariA,
VethPeerName: vethPariB}
GlobalEndPointCache.EndPoints\[endPointId\] = &endPoint
driver.UpdateCacheFile()
return nil, nil
}

此处是在创建容器时,指定driver时调用的API,该插件只是定义了需要创建的link 对(veth pair),然后缓存起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
func (driver \*PipeNetworkDriver) Join(joinRequest \*network.JoinRequest) (\*network.JoinResponse, error) {
fmt.Println("joing....")
fmt.Println("NetworkID is ", joinRequest.NetworkID)
fmt.Println("SandboxKey is ", joinRequest.SandboxKey)

GlobalEndPointCache.Mutex.Lock()
defer func() {
GlobalEndPointCache.Mutex.Unlock()
}()
//query from cache
endPointInfo := GlobalEndPointCache.EndPoints\[joinRequest.EndpointID\]
fmt.Println("vethPairA is ", endPointInfo.VethName)
fmt.Println("vethPairB is ", endPointInfo.VethPeerName)

localVethPair := vethPair(endPointInfo.VethName, endPointInfo.VethPeerName)
if err := netlink.LinkAdd(localVethPair); err != nil {
fmt.Println("failed to create the veth pair named: \[ %v \] error: \[ %s \] ", localVethPair, err)
return nil, err
}
fmt.Println("localVethPair.Name is ", localVethPair.Name)
// 2. add vethPariA to bridge and set up
createdLink, linkErr := netlink.LinkByName(localVethPair.Name)
if linkErr != nil {
fmt.Println("find link failed", linkErr)
return nil, linkErr
}
netinterface := net.Interface{Index: createdLink.Attrs().Index,
Name: createdLink.Attrs().Name,
MTU: createdLink.Attrs().MTU,
Flags: createdLink.Attrs().Flags,
HardwareAddr: createdLink.Attrs().HardwareAddr}

br, err := tenus.BridgeFromName(GlobalEndPointCache.Network.BridgeName)
if err != nil {
fmt.Println("use exist bridge failed", err)
return nil, err
}

if err = br.AddSlaveIfc(&netinterface); err != nil {
fmt.Println("add interface to bridge failed", err)
}
err = netlink.LinkSetUp(createdLink)
if err != nil {
fmt.Println("Error enabling Veth local iface: \[ %v \]", localVethPair)
return nil, err
}
response := network.JoinResponse{InterfaceName: network.InterfaceName{SrcName: endPointInfo.VethPeerName,
DstPrefix: "eth"},
Gateway: GlobalEndPointCache.Network.Gateway,
}
GlobalEndPointCache.EndPoints\[joinRequest.EndpointID\].SandboxKey = joinRequest.SandboxKey
driver.UpdateCacheFile()
return &response, nil
}
`

       
此处代码也是在docker 创建/启动容器时调用的API,该API完成link对的创建,并未改link(在容器内部的部分)配置IP,定义网卡名称,此处最重要的是返回值中将正确的vethpari那么返回,docker daemon会将veth pair加入到容器的网络空间 (PS:开始验证的时候,一直尝试自己将这个veth pair加入到命名空间,总是不成功,后来才发现,docker Daemon 已经把这件事干了)