1. 前言
在 [mydocker]---网络net/netlink api 使用解析 文中已经基本涵盖了大部分的内容. 本文将整个流程梳理起来.
2. 网络
在本机上查看默认的网络.
docker network ls
NETWORK ID NAME DRIVER SCOPE
3547024b25e7 bridge bridge local
974d4b4a2465 host host local
88561b2dac7f none null local
可以看到有三个网络, 每个网络都属于哪种驱动
DRIVER
, 其实说白了就是属于哪种类型. 比如最常见的网桥bridge
类型. 所以接下来会看一下bridge
类型如何实现.
2.1 数据结构
可以看到网络
Network
的结构, 有一个Name
为网络名字,IpRange
为该网络的网段,Driver
就是该网络是用什么驱动.
type Network struct {
Name string
IpRange *net.IPNet
Driver string
}
网络端点
Endpoint
是用于连接容器与网络的,保证容器内部与网络的通信.
type Endpoint struct {
ID string `json:"id"`
Device netlink.Veth `json:"dev"`
IPAddress net.IP `json:"ip"`
MacAddress net.HardwareAddr `json:"mac"`
Network *Network
PortMapping []string
}
网络驱动, 是一个接口, 包括了5个方法, 所有的驱动类型都需要实现这些方法.
type NetworkDriver interface {
Name() string // 名字
Create(subnet string, name string) (*Network, error) // 创建网络
Delete(network Network) error // 删除网络
Connect(network *Network, endpoint *Endpoint) error
Disconnect(network Network, endpoint *Endpoint) error
}
3. 驱动bridge
的实现
先看看驱动的实现, 这些方法[mydocker]---网络net/netlink api 使用解析 基本上全部已经用过了.
3.1 结构
type BridgeNetworkDriver struct {
}
func (d *BridgeNetworkDriver) Name() string {
return "bridge"
}
3.2 公共方法
initBridge
方法就相当于
ip link add bridgeName type bridge
ip addr add ip dev bridgeName
ip link set bridgeName up
func (d *BridgeNetworkDriver) initBridge(n *Network) error {
// try to get bridge by name, if it already exists then just exit
bridgeName := n.Name
if err := createBridgeInterface(bridgeName); err != nil {
return fmt.Errorf("Error add bridge: %s, Error: %v", bridgeName, err)
}
// Set bridge IP
gatewayIP := *n.IpRange
gatewayIP.IP = n.IpRange.IP
if err := setInterfaceIP(bridgeName, gatewayIP.String()); err != nil {
return fmt.Errorf("Error assigning address: %s on bridge: %s with an error of: %v", gatewayIP, bridgeName, err)
}
if err := setInterfaceUP(bridgeName); err != nil {
return fmt.Errorf("Error set bridge up: %s, Error: %v", bridgeName, err)
}
// Setup iptables
if err := setupIPTables(bridgeName, n.IpRange); err != nil {
return fmt.Errorf("Error setting iptables for %s: %v", bridgeName, err)
}
return nil
}
// deleteBridge deletes the bridge
func (d *BridgeNetworkDriver) deleteBridge(n *Network) error {
bridgeName := n.Name
// get the link
l, err := netlink.LinkByName(bridgeName)
if err != nil {
return fmt.Errorf("Getting link with name %s failed: %v", bridgeName, err)
}
// delete the link
if err := netlink.LinkDel(l); err != nil {
return fmt.Errorf("Failed to remove bridge interface %s delete: %v", bridgeName, err)
}
return nil
}
func createBridgeInterface(bridgeName string) error {
_, err := net.InterfaceByName(bridgeName)
if err == nil || !strings.Contains(err.Error(), "no such network interface") {
return err
}
// create *netlink.Bridge object
la := netlink.NewLinkAttrs()
la.Name = bridgeName
br := &netlink.Bridge{la}
if err := netlink.LinkAdd(br); err != nil {
return fmt.Errorf("Bridge creation failed for bridge %s: %v", bridgeName, err)
}
return nil
}
func setInterfaceUP(interfaceName string) error {
iface, err := netlink.LinkByName(interfaceName)
if err != nil {
return fmt.Errorf("Error retrieving a link named [ %s ]: %v", iface.Attrs().Name, err)
}
if err := netlink.LinkSetUp(iface); err != nil {
return fmt.Errorf("Error enabling interface for %s: %v", interfaceName, err)
}
return nil
}
// Set the IP addr of a netlink interface
func setInterfaceIP(name string, rawIP string) error {
retries := 2
var iface netlink.Link
var err error
for i := 0; i < retries; i++ {
iface, err = netlink.LinkByName(name)
if err == nil {
break
}
log.Debugf("error retrieving new bridge netlink link [ %s ]... retrying", name)
time.Sleep(2 * time.Second)
}
if err != nil {
return fmt.Errorf("Abandoning retrieving the new bridge link from netlink, Run [ ip link ] to troubleshoot the error: %v", err)
}
ipNet, err := netlink.ParseIPNet(rawIP)
if err != nil {
return err
}
addr := &netlink.Addr{ipNet, "", 0, 0, nil}
return netlink.AddrAdd(iface, addr)
}
func setupIPTables(bridgeName string, subnet *net.IPNet) error {
iptablesCmd := fmt.Sprintf("-t nat -A POSTROUTING -s %s ! -o %s -j MASQUERADE", subnet.String(), bridgeName)
cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...)
//err := cmd.Run()
output, err := cmd.Output()
if err != nil {
log.Errorf("iptables Output, %v", output)
}
return err
}
3.3 Creat方法
返回一个
Network
, 该Network
会有一个网桥, 名字为传入的name
, 网段为传入的网段subnet
, 并且该网络已经up
并且已经设置好iptables
的MASQUERADE
规则.
func (d *BridgeNetworkDriver) Create(subnet string, name string) (*Network, error) {
ip, ipRange, _ := net.ParseCIDR(subnet)
ipRange.IP = ip
n := &Network {
Name: name,
IpRange: ipRange,
Driver: d.Name(),
}
err := d.initBridge(n)
if err != nil {
log.Errorf("error init bridge: %v", err)
}
return n, err
}
3.4 删除方法
删除该网络的网桥设备相当于
ip link delete bridgeName type bridge
.
func (d *BridgeNetworkDriver) Delete(network Network) error {
bridgeName := network.Name
br, err := netlink.LinkByName(bridgeName)
if err != nil {
return err
}
return netlink.LinkDel(br)
}
3.5 关联设备
Connect
方法生成一个设备Veth
并且将其中一端attach
到该网络的网桥上. 具体测试参考[mydocker]---网络net/netlink api 使用解析中的TestNet003
.
func (d *BridgeNetworkDriver) Connect(network *Network, endpoint *Endpoint) error {
bridgeName := network.Name
br, err := netlink.LinkByName(bridgeName)
if err != nil {
return err
}
la := netlink.NewLinkAttrs()
la.Name = endpoint.ID[:5]
la.MasterIndex = br.Attrs().Index
endpoint.Device = netlink.Veth{
LinkAttrs: la,
PeerName: "cif-" + endpoint.ID[:5],
}
if err = netlink.LinkAdd(&endpoint.Device); err != nil {
return fmt.Errorf("Error Add Endpoint Device: %v", err)
}
if err = netlink.LinkSetUp(&endpoint.Device); err != nil {
return fmt.Errorf("Error Add Endpoint Device: %v", err)
}
return nil
}
func (d *BridgeNetworkDriver) Disconnect(network Network, endpoint *Endpoint) error {
return nil
}
4. 网络Network
的实现
有了驱动,
Network
基本上都是调用某个驱动的方法.
4.1 基本方法
不多说了,
dump
就是把当前网络nw
保存到dumpPath
的路径上,load
就是把dumpPath
路径上的信息加载到nw
中.
var (
defaultNetworkPath = "/var/run/mydocker/network/network/"
drivers = map[string]NetworkDriver{}
networks = map[string]*Network{}
)
func (nw *Network) dump(dumpPath string) error {
if _, err := os.Stat(dumpPath); err != nil {
if os.IsNotExist(err) {
os.MkdirAll(dumpPath, 0644)
} else {
return err
}
}
nwPath := path.Join(dumpPath, nw.Name)
nwFile, err := os.OpenFile(nwPath, os.O_TRUNC | os.O_WRONLY | os.O_CREATE, 0644)
if err != nil {
logrus.Errorf("error:", err)
return err
}
defer nwFile.Close()
nwJson, err := json.Marshal(nw)
if err != nil {
logrus.Errorf("error:", err)
return err
}
_, err = nwFile.Write(nwJson)
if err != nil {
logrus.Errorf("error:", err)
return err
}
return nil
}
func (nw *Network) load(dumpPath string) error {
nwConfigFile, err := os.Open(dumpPath)
defer nwConfigFile.Close()
if err != nil {
return err
}
nwJson := make([]byte, 2000)
n, err := nwConfigFile.Read(nwJson)
if err != nil {
return err
}
err = json.Unmarshal(nwJson[:n], nw)
if err != nil {
logrus.Errorf("Error load nw info", err)
return err
}
return nil
}
4.2 初始化方法
可以看到初始化方法做了三件事
1. 初始化一个网桥驱动
BridgeNetworkDriver
并放到drviers
中.
2. 如果默认路径不存在则创建.
3. 加载所有现存网络并放到networks
中.
func Init() error {
var bridgeDriver = BridgeNetworkDriver{}
drivers[bridgeDriver.Name()] = &bridgeDriver
if _, err := os.Stat(defaultNetworkPath); err != nil {
if os.IsNotExist(err) {
os.MkdirAll(defaultNetworkPath, 0644)
} else {
return err
}
}
filepath.Walk(defaultNetworkPath, func(nwPath string, info os.FileInfo, err error) error {
if strings.HasSuffix(nwPath, "/") {
return nil
}
_, nwName := path.Split(nwPath)
nw := &Network{
Name: nwName,
}
if err := nw.load(nwPath); err != nil {
logrus.Errorf("error load network: %s", err)
}
networks[nwName] = nw
return nil
})
//logrus.Infof("networks: %v", networks)
return nil
}
4.3 增加删除查找网络
可以看到增加一个网络的时候需要传入
driver
(比如bridge
就会对应到上面说的BridgeNetworkDriver
), 网段subnet
, 网络的名字name
.
如果
driver
是网桥bridge
则drivers[driver]=BridgeNetworkDriver
,drivers[driver].Create(cidr.String(), name)
会调用BridgeNetworkDriver
的Create
方法创建一个网桥并设置好iptables
规则.
ListNetwork
就是把Init
方法中的networks
输出.
DeleteNetwork
首先归还ip
并调用驱动的删除方法, (如果是bridge
驱动则会删除宿主机上该网络对应的网桥),最后移除本地保存的文件.
func CreateNetwork(driver, subnet, name string) error {
_, cidr, _ := net.ParseCIDR(subnet)
ip, err := ipAllocator.Allocate(cidr)
if err != nil {
return err
}
cidr.IP = ip
nw, err := drivers[driver].Create(cidr.String(), name)
if err != nil {
return err
}
return nw.dump(defaultNetworkPath)
}
func ListNetwork() {
w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0)
fmt.Fprint(w, "NAME\tIpRange\tDriver\n")
for _, nw := range networks {
fmt.Fprintf(w, "%s\t%s\t%s\n",
nw.Name,
nw.IpRange.String(),
nw.Driver,
)
}
if err := w.Flush(); err != nil {
logrus.Errorf("Flush error %v", err)
return
}
}
func DeleteNetwork(networkName string) error {
nw, ok := networks[networkName]
if !ok {
return fmt.Errorf("No Such Network: %s", networkName)
}
if err := ipAllocator.Release(nw.IpRange, &nw.IpRange.IP); err != nil {
return fmt.Errorf("Error Remove Network gateway ip: %s", err)
}
if err := drivers[nw.Driver].Delete(*nw); err != nil {
return fmt.Errorf("Error Remove Network DriverError: %s", err)
}
return nw.remove(defaultNetworkPath)
}
func (nw *Network) remove(dumpPath string) error {
if _, err := os.Stat(path.Join(dumpPath, nw.Name)); err != nil {
if os.IsNotExist(err) {
return nil
} else {
return err
}
} else {
return os.Remove(path.Join(dumpPath, nw.Name))
}
}
4.4 关联
enterContainerNetns
和configEndpointIpAddressAndRoute
在[mydocker]---网络net/netlink api 使用解析 已经测试使用过, 其实该部分对应着TestNet006
,Connect
其实就是首先获得容器的Ip
, 以网桥驱动为例drivers[network.Driver].Connect(network, ep)
会创建一对Veth
设备, 并将某一端放到与网桥相联,configEndpointIpAddressAndRoute
把Veth
其中一端放到容器中并且配置容器ip, 路由等等.
func Connect(networkName string, cinfo *container.ContainerInfo) error {
network, ok := networks[networkName]
if !ok {
return fmt.Errorf("No Such Network: %s", networkName)
}
// 分配容器IP地址
ip, err := ipAllocator.Allocate(network.IpRange)
if err != nil {
return err
}
// 创建网络端点
ep := &Endpoint{
ID: fmt.Sprintf("%s-%s", cinfo.Id, networkName),
IPAddress: ip,
Network: network,
PortMapping: cinfo.PortMapping,
}
// 调用网络驱动挂载和配置网络端点
if err = drivers[network.Driver].Connect(network, ep); err != nil {
return err
}
// 到容器的namespace配置容器网络设备IP地址
if err = configEndpointIpAddressAndRoute(ep, cinfo); err != nil {
return err
}
return configPortMapping(ep, cinfo)
}
func Disconnect(networkName string, cinfo *container.ContainerInfo) error {
return nil
}
func enterContainerNetns(enLink *netlink.Link, cinfo *container.ContainerInfo) func() {
f, err := os.OpenFile(fmt.Sprintf("/proc/%s/ns/net", cinfo.Pid), os.O_RDONLY, 0)
if err != nil {
logrus.Errorf("error get container net namespace, %v", err)
}
nsFD := f.Fd()
runtime.LockOSThread()
// 修改veth peer 另外一端移到容器的namespace中
if err = netlink.LinkSetNsFd(*enLink, int(nsFD)); err != nil {
logrus.Errorf("error set link netns , %v", err)
}
// 获取当前的网络namespace
origns, err := netns.Get()
if err != nil {
logrus.Errorf("error get current netns, %v", err)
}
// 设置当前进程到新的网络namespace,并在函数执行完成之后再恢复到之前的namespace
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
logrus.Errorf("error set netns, %v", err)
}
return func () {
netns.Set(origns)
origns.Close()
runtime.UnlockOSThread()
f.Close()
}
}
func configEndpointIpAddressAndRoute(ep *Endpoint, cinfo *container.ContainerInfo) error {
peerLink, err := netlink.LinkByName(ep.Device.PeerName)
if err != nil {
return fmt.Errorf("fail config endpoint: %v", err)
}
defer enterContainerNetns(&peerLink, cinfo)()
interfaceIP := *ep.Network.IpRange
interfaceIP.IP = ep.IPAddress
if err = setInterfaceIP(ep.Device.PeerName, interfaceIP.String()); err != nil {
return fmt.Errorf("%v,%s", ep.Network, err)
}
if err = setInterfaceUP(ep.Device.PeerName); err != nil {
return err
}
if err = setInterfaceUP("lo"); err != nil {
return err
}
_, cidr, _ := net.ParseCIDR("0.0.0.0/0")
defaultRoute := &netlink.Route{
LinkIndex: peerLink.Attrs().Index,
Gw: ep.Network.IpRange.IP,
Dst: cidr,
}
if err = netlink.RouteAdd(defaultRoute); err != nil {
return err
}
return nil
}
另外
configPortMapping
就是实现iptables DNAT
.
func configPortMapping(ep *Endpoint, cinfo *container.ContainerInfo) error {
for _, pm := range ep.PortMapping {
portMapping :=strings.Split(pm, ":")
if len(portMapping) != 2 {
logrus.Errorf("port mapping format error, %v", pm)
continue
}
iptablesCmd := fmt.Sprintf("-t nat -A PREROUTING -p tcp -m tcp --dport %s -j DNAT --to-destination %s:%s",
portMapping[0], ep.IPAddress.String(), portMapping[1])
cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...)
//err := cmd.Run()
output, err := cmd.Output()
if err != nil {
logrus.Errorf("iptables Output, %v", output)
continue
}
}
return nil
}
5. 参考
1. 自己动手写docker.(基本参考此书,加入一些自己的理解,加深对
docker
的理解)
6. 全部内容
1. [mydocker]---环境说明
2. [mydocker]---urfave cli 理解
3. [mydocker]---Linux Namespace
4. [mydocker]---Linux Cgroup
5. [mydocker]---构造容器01-实现run命令
6. [mydocker]---构造容器02-实现资源限制01
7. [mydocker]---构造容器02-实现资源限制02
8. [mydocker]---构造容器03-实现增加管道
9. [mydocker]---通过例子理解存储驱动AUFS
10. [mydocker]---通过例子理解chroot 和 pivot_root
11. [mydocker]---一步步实现使用busybox创建容器
12. [mydocker]---一步步实现使用AUFS包装busybox
13. [mydocker]---一步步实现volume操作
14. [mydocker]---实现保存镜像
15. [mydocker]---实现容器的后台运行
16. [mydocker]---实现查看运行中容器
17. [mydocker]---实现查看容器日志
18. [mydocker]---实现进入容器Namespace
19. [mydocker]---实现停止容器
20. [mydocker]---实现删除容器
21. [mydocker]---实现容器层隔离
22. [mydocker]---实现通过容器制作镜像
23. [mydocker]---实现cp操作
24. [mydocker]---实现容器指定环境变量
25. [mydocker]---网际协议IP
26. [mydocker]---网络虚拟设备veth bridge iptables
27. [mydocker]---docker的四种网络模型与原理实现(1)
28. [mydocker]---docker的四种网络模型与原理实现(2)
29. [mydocker]---容器地址分配
30. [mydocker]---网络net/netlink api 使用解析
31. [mydocker]---网络实现
32. [mydocker]---网络实现测试