什么是reexec机制
通常,我们在定义命令行对应的函数时,通常使用第三方包进行。所以这就要求预先规划好该可执行文件支持多少子命令。但在Docker中,有一种需要动态注册子命令机制供第三方包或docker本身使用,这些子命令通常不是面向用户。
Docker提供的注册机制就是reexec机制。
reexec
reexec机制提供了Register()函数注册子命令及对应函数,定义在/pkg/reexec/reexec.go中:
1 2 3 4 5 6 7 8 9 10
| var registeredInitializers = make(map[string]func()) // Register adds an initialization func under the specified name func Register(name string, initializer func()) { if _, exists := registeredInitializers[name]; exists { panic(fmt.Sprintf("reexec func already registered under name %q", name)) } registeredInitializers[name] = initializer }
|
可以看出,reexec包中有一个全局变量registeredInitializers,记录了注册的子命令及处理函数,这里要注意下,处理函数不能带参数,只能到参数列表中去取相应的内容。而Register()就是向registeredInitializers注册内容。
Init()
那么,Docker是如何把子命令关联到reexec中的呢,毕竟Docker有第三方的子命令管理工具。答案时Init(),定义在/pkg/reexec/reexec.go中:
1 2 3 4 5 6 7 8 9 10 11 12
| func Init() bool { initializer, exists := registeredInitializers[os.Args[0]] if exists { initializer() return true } return false }
|
Init()会去找子命令是否注册,如果注册,那么则立即执行处理函数。所以Docker只要在第三方包命令管理工具执行之前,就调用Init(),就可以很好地处理两种命令。来看下/cmd/dockerd/docker.go中的dockerd启动函数main():
1 2 3 4 5 6 7
| func main() { if reexec.Init() { return } ...... }
|
系统中的使用
那么,Docker中有多少地方使用了reexec机制呢?可以来看下:
1 2 3 4 5 6 7 8 9
| ./pkg/chrootarchive/init_unix.go:15: reexec.Register("docker-applyLayer", applyLayer) ./pkg/chrootarchive/init_unix.go:16: reexec.Register("docker-untar", untar) ./daemon/graphdriver/overlay2/mount.go:18: reexec.Register("docker-mountfrom", mountFromMain) ./daemon/graphdriver/windows/windows.go:44: reexec.Register("docker-windows-write-layer", writeLayer) ./vendor/src/github.com/docker/libnetwork/sandbox_externalkey.go:11: reexec.Register("libnetwork-setkey", processSetKeyReexec) ./vendor/src/github.com/docker/libnetwork/service_linux.go:28: reexec.Register("fwmarker", fwMarker) ./vendor/src/github.com/docker/libnetwork/service_linux.go:29: reexec.Register("redirecter", redirecter) ./vendor/src/github.com/docker/libnetwork/resolver_unix.go:19: reexec.Register("setup-resolver", reexecSetupResolver) ./vendor/src/github.com/docker/libnetwork/osl/namespace_linux.go:59: reexec.Register("netns-create", reexecCreateNamespace)
|
举个例子,我们可以通过dockerd libnetwork-setkey <container-id> <controller-id>
把新建的容器加入到已准备好的network namespace中。这就是Docker先准备好网络,然后runc通过prestart hook调用设置容器网络的方法,这些都定义在config.json的hooks.prestart中:
1 2 3 4 5 6 7 8 9 10 11 12
| "hooks": { "prestart": [ { "path": "/usr/local/bin/dockerd", "args": [ "libnetwork-setkey", "12a3c752fc752f0a9099bea56525a106092f78f411704e47eaf9c32520550025", "9ca45d808e4c577ae6dceef7259ca41561cf1e990a1ae41d370c5df458aab3f5" ] } ] }
|
关于容器网络,会在以后详细分析。
总结
reexec机制相当简洁,但非常重要,提供了一种不想让用户调用,或可以供第三方包注册的命令行机制,值得在项目中应用。