在controller.go中的New()函数中,会调用c.startExternalKeyListener()启动Unix sock监听。本次分析就介绍controller是如何完成外部net namespace导入到libnetork中的。
(五) 监听unix socket
libnetwork-setkey
我们先来看libnetwork-setkey子命令的执行流程。libnetwork-setkey子命令用于runc向libnetwork报告创建好的net namespace。
在libnetwork包中的sandbox_externalkey.go中,有:
1 2 3 4
| func init() { reexec.Register("libnetwork-setkey", processSetKeyReexec) }
|
该init()函数向docker reexec注册”libnetwork-setkey”子命令,其处理的函数是processSetKeyReexec。
processSetKeyReexec定义在/libnetwork/sandbox_externalkey_unix.go中:
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
| func processSetKeyReexec() { var err error defer func() { if err != nil { logrus.Fatalf("%v", err) } }() if len(os.Args) < 3 { err = fmt.Errorf("Re-exec expects 3 args, received : %d", len(os.Args)) return } containerID := os.Args[1] stateBuf, err := ioutil.ReadAll(os.Stdin) if err != nil { return } var state configs.HookState if err = json.Unmarshal(stateBuf, &state); err != nil { return } controllerID := os.Args[2] err = SetExternalKey(controllerID, containerID, fmt.Sprintf("/proc/%d/ns/net", state.Pid)) return }
|
processSetKeyReexec()最后调用的是SetExternalKey():
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
| func SetExternalKey(controllerID string, containerID string, key string) error { keyData := setKeyData{ ContainerID: containerID, Key: key} c, err := net.Dial("unix", udsBase+controllerID+".sock") if err != nil { return err } defer c.Close() if err = sendKey(c, keyData); err != nil { return fmt.Errorf("sendKey failed with : %v", err) } return processReturn(c) } func sendKey(c net.Conn, data setKeyData) error { var err error defer func() { if err != nil { c.Close() } }() var b []byte if b, err = json.Marshal(data); err != nil { return err } _, err = c.Write(b) return err }
|
SetExternalKey()先生成keyData,然后写入unix sock。
unix sock
先来看startExternalKeyListener(),定义在/libnetwork/sandbox_externalkey_unix.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| func (c *controller) startExternalKeyListener() error { if err := os.MkdirAll(udsBase, 0600); err != nil { return err } uds := udsBase + c.id + ".sock" l, err := net.Listen("unix", uds) if err != nil { return err } if err := os.Chmod(uds, 0600); err != nil { l.Close() return err } c.Lock() c.extKeyListener = l c.Unlock() go c.acceptClientConnections(uds, l) return nil }
|
startExternalKeyListener()会启动一个goroutine执行acceptClientConnections():
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
| func (c *controller) acceptClientConnections(sock string, l net.Listener) { for { conn, err := l.Accept() if err != nil { if _, err1 := os.Stat(sock); os.IsNotExist(err1) { logrus.Debugf("Unix socket %s doesn't exist. cannot accept client connections", sock) return } logrus.Errorf("Error accepting connection %v", err) continue } go func() { defer conn.Close() err := c.processExternalKey(conn) ret := success if err != nil { ret = err.Error() } _, err = conn.Write([]byte(ret)) if err != nil { logrus.Errorf("Error returning to the client %v", err) } }() } }
|
acceptClientConnections()会接收来自unix sock的数据,并启动goroutine处理。处理主要调用的是processExternalKey():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| func (c *controller) processExternalKey(conn net.Conn) error { buf := make([]byte, 1280) nr, err := conn.Read(buf) if err != nil { return err } var s setKeyData if err = json.Unmarshal(buf[0:nr], &s); err != nil { return err } var sandbox Sandbox search := SandboxContainerWalker(&sandbox, s.ContainerID) c.WalkSandboxes(search) if sandbox == nil { return types.BadRequestErrorf("no sandbox present for %s", s.ContainerID) } return sandbox.SetKey(s.Key) }
|
processExternalKey()先从数据中提取ContainerID,然后依据ContainerID找到对应的sandbox,最后调用sandbox的SetKey()方法完成处理。
SetKey()定义在/libnetwork/sandbox.go中,可以认为SetKey()更新了原来sandbox中的net namespace:
sandbox::SetKey()
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 56 57 58 59 60 61
| func (sb *sandbox) SetKey(basePath string) error { start := time.Now() defer func() { log.Debugf("sandbox set key processing took %s for container %s", time.Now().Sub(start), sb.ContainerID()) }() if basePath == "" { return types.BadRequestErrorf("invalid sandbox key") } sb.Lock() oldosSbox := sb.osSbox sb.Unlock() if oldosSbox != nil { sb.releaseOSSbox() } osSbox, err := osl.GetSandboxForExternalKey(basePath, sb.Key()) if err != nil { return err } sb.Lock() sb.osSbox = osSbox sb.Unlock() defer func() { if err != nil { sb.Lock() sb.osSbox = nil sb.Unlock() } }() if oldosSbox != nil && sb.resolver != nil { sb.resolver.Stop() if err := sb.osSbox.InvokeFunc(sb.resolver.SetupFunc()); err == nil { if err := sb.resolver.Start(); err != nil { log.Errorf("Resolver Start failed for container %s, %q", sb.ContainerID(), err) } } else { log.Errorf("Resolver Setup Function failed for container %s, %q", sb.ContainerID(), err) } } for _, ep := range sb.getConnectedEndpoints() { if err = sb.populateNetworkResources(ep); err != nil { return err } } return nil }
|
其中populateNetworkResources()会重新设置更新后sandbox中endpoint的信息。
GetSandboxForExternalKey()定义在/libnetwork/osl/namespace_linux.go中:
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
| func GetSandboxForExternalKey(basePath string, key string) (Sandbox, error) { if err := createNamespaceFile(key); err != nil { return nil, err } if err := mountNetworkNamespace(basePath, key); err != nil { return nil, err } n := &networkNamespace{path: key} sboxNs, err := netns.GetFromPath(n.path) if err != nil { return nil, fmt.Errorf("failed get network namespace %q: %v", n.path, err) } defer sboxNs.Close() n.nlHandle, err = netlink.NewHandleAt(sboxNs, syscall.NETLINK_ROUTE) if err != nil { return nil, fmt.Errorf("failed to create a netlink handle: %v", err) } if err = n.loopbackUp(); err != nil { n.nlHandle.Delete() return nil, err } return n, nil }
|
GetSandboxForExternalKey()会把进程所对应的net namespace,即/proc//ns/net挂载到/var/run/docker/netns/上。从而完成外边的netnamespace的导入。完成更新net namespace后,populateNetworkResources()会重新设置sandbox中endpoint的信息。这些细节还有等深入分析。