Docker架构速看(1)-启动
Docker是常用的容器管理工具,这篇文章对Docker架构结合源码做简要分析,由于也只使用过很少的命令,所以只分析image和container的相关部分。
源码准备
Docker源码可以在github上找到,当前已更名为Moby,这里采用22.06稳定版分支的代码。Moby项目使用vendor目录管理依赖,在vendor.mod开头指明了Moby项目的模块github.com/docker/docker,所以需要把moby更名为docker,并放置在GOPATH的对应目录下。
GOPATH
| src
| github.com
| doccker
| docker(moby)
| bin
| pkg
关于Docker项目代码结构可以参看这篇2017年的文章,Docker源码分析第一篇 JIMMY SONG
启动过程
Docker采用了C/S架构,客户端发送请求到服务端,由服务端docker daemon根据请求完成镜像查询,镜像拉取,容器启动等工作。在这里我们主要分析Docker服务端的启动过程。
cmd/dockerd/包下是命令行启动Docker的相关代码,docker.go是启动入口文件。其中newDaemonCommand()
函数处理命令行参数,并将参数传递到daemon.go中DaemonCli的start()函数,函数简单概括如下:
- 根据传入的参数设置守护进程的配置信息。
- 加载listener,监听hosts
- 创建一个新的守护进程(daemon)对象,并启动它。
- 初始化路由,将不同的请求路由到不同的后端服务进行处理。
- 启动API服务,开始监听客户端的请求,并将请求路由到正确的后端服务进行处理。
- 等待API服务结束,返回可能发生的错误。
func (cli *DaemonCli) start(opts *daemonOptions) (err error)
{
//根据参数设置cli.Config
cli.Config, err = loadDaemonCliConfig(opts)。
/*
加载listener,监听端口
*/
hosts, err := loadListeners(cli, serverConfig)
//初始化中间件
pluginStore := plugin.NewStore()
cli.initMiddlewares(cli.api, serverConfig, pluginStore)
//启动新守护进程
d, err := daemon.NewDaemon(ctx, cli.Config, pluginStore)
//初始化路由信息,将不同请求(image,cocntainer...)路由到不同后端服务处理
routerOptions, err := newRouterOptions(cli.Config, d)
routerOptions.api = cli.api
routerOptions.cluster = c
initRouter(routerOptions)
//开始接受请求了
serveAPIWait := make(chan error)
//其中根据路由信息为servers添加handler,之后循环处理请求,一个简单的服务器
go cli.api.Wait(serveAPIWait)
notifyReady()
// Daemon is fully initialized and handling API traffic
// Wait for serve API to complete
errAPI := <-serveAPIWait
}
loadListener
- 根据传入的参数,为每个需要处理的host进行相关设置,例如TLS。
- 调用daemon/listeners包中的listeners.Init()函数,该函数会根据不同的协议(TCP Socket、Unix Socket等)来初始化对应的listener,并将listener绑定到对应的host。
- 使用初始化好的listener初始化HTTPServer,并加入到servers中。
由此可见Docker客户端和服务器端可以通过TCP socket,Unix socket建立连接,上层使用HTTP协议通信。
//loadListener
func loadListeners(cli *DaemonCli, serverConfig *apiserver.Config)
{
//处理设置中所有host,格式: 协议:://地址
for i := 0; i < len(serverConfig.Hosts); i++ {
//处理host协议,地址
protoAddr := serverConfig.Hosts[i]
protoAddrParts := strings.SplitN(serverConfig.Hosts[i], "://", 2)
proto, addr := protoAddrParts[0], protoAddrParts[1]
//处理TLS config ...
ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig)
hosts = append(hosts, protoAddrParts[1])
//
cli.api.Accept(addr, ls...)
}
return hosts
}
// Init 初始化对应host的listener
func Init(proto, addr, socketGroup string, tlsConfig *tls.Config) ([]net.Listener, error)
{
// 根据协议不同,对listener做不同初始化
switch proto {
//
case "fd":
fds, err := listenFD(addr, tlsConfig)
ls = append(ls, fds...)
case "tcp":
l, err := sockets.NewTCPSocket(addr, tlsConfig)
ls = append(ls, l)
case "unix":
gid, err := lookupGID(socketGroup)
l, err := sockets.NewUnixSocket(addr, gid)
ls = append(ls, l)
default:
return nil, errors.Errorf("invalid protocol format: %q", proto)
}
}
func (s *Server) Accept(addr string, listeners ...net.Listener)
{
// 通过初始化好的listener初始化HTTPServer
for _, listener := range listeners
{
httpServer := &HTTPServer{
srv: &http.Server{
Addr: addr,
ReadHeaderTimeout: 5 * time.Minute, // "G112: Potential Slowloris Attack (gosec)"; not a real concern for our use, so setting a long timeout.
},
l: listener,
}
s.servers = append(s.servers, httpServer)
}
}
NewDaemon
daemon.NewDaemon()
新建Docker守护进程,NewDaemon设置了所有必要的内容,使得守护进程能够处理来自Web服务器的请求。让我们看看他做了什么,下面代码中忽略了很多设置验证内容,列出了主干部分。
func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.Store) (daemon *Daemon, err error)
{
// 初始化registrySservice,提供容器注册中心服务,从注册中心拉取,提交容器镜像等。
registryService, err := registry.NewService(config.ServiceOptions)
...
d.registryService = registryService
// Plugin system initialization should happen before restore. Do not change order.
d.pluginManager, err = plugin.NewManager(plugin.ManagerConfig{})
//初始化 layer.Store,
layerStore, err := layer.NewStoreFromOptions(layer.StoreOptions{})
//初始化 image.BackendStore,底层文件系统存储
ifs, err := image.NewFSStoreBackend(filepath.Join(imageRoot, "imagedb"))
//layerStore+fsStore 初始化image.Store,镜像存储
imageStore, err := image.NewImageStore(ifs, layerStore)
//初始化 VolumesService,管理volumes
d.volumes, err = volumesservice.NewVolumeService(config.Root, d.PluginStore, rootIDs, d)
//初始化imageService,提供镜像管理
d.imageService = images.NewImageService(imgSvcConfig)
}
可以看到daemon进程初始化中会初始化,启动镜像服务,容器服务,数据卷服务等,请求都是由这些服务来处理。
InitRouter
在之前可以看到daemon.NewDaemon()
完成后还有一步,初始化路由initRouter()
,在该函数中会初始化各种请求的路由,实际就是为不同请求路径添加不同handler,转发不同HTTP请求,交由后端服务处理。
func initRouter(opts routerOptions)
{
routers := []router.Router{
// we need to add the checkpoint router before the container router or the DELETE gets masked
checkpointrouter.NewRouter(opts.daemon, decoder),
//容器相关请求路由
container.NewRouter(opts.daemon, decoder, opts.daemon.RawSysInfo().CgroupUnified),
//镜像相关请求路由
image.NewRouter(
opts.daemon.ImageService(),
opts.daemon.ReferenceStore,
opts.daemon.ImageService().DistributionServices().ImageStore,
opts.daemon.ImageService().DistributionServices().LayerStore,
),
systemrouter.NewRouter(opts.daemon, opts.cluster, opts.buildkit, opts.features),
volume.NewRouter(opts.daemon.VolumesService(), opts.cluster),
build.NewRouter(opts.buildBackend, opts.daemon, opts.features),
sessionrouter.NewRouter(opts.sessionManager),
swarmrouter.NewRouter(opts.cluster),
pluginrouter.NewRouter(opts.daemon.PluginManager()),
distributionrouter.NewRouter(opts.daemon.ImageService()),
}
}
这里拿image router举例,看看image.NewRouter()
函数。其中backend实际提供后端服务,这里的backend实际上是一个接口,包含各种镜像操作。而在Docker中实现了该接口的就是之前初始化的ImageService。在看一下r.initRoutes()
就可以看到不同路径的路由信息,不同路径的请求交由不同函数处理,这些函数会解析请求参数,最后调用backend提供的函数进行真实处理。
// NewRouter initializes a new image router
func NewRouter(backend Backend, referenceBackend reference.Store, imageStore image.Store, layerStore layer.Store) router.Router {
r := &imageRouter{
backend: backend, //实际提供后端服务
referenceBackend: referenceBackend,
imageStore: imageStore,
layerStore: layerStore,
}
r.initRoutes()
return r
}
// /api/server/router/image/backend.go
// api包下定义的接口
type Backend interface {
imageBackend
importExportBackend
registryBackend
}
// initRoutes initializes the routes in the image router
func (r *imageRouter) initRoutes() {
r.routes = []router.Route{
// GET
router.NewGetRoute("/images/json", r.getImagesJSON),
router.NewGetRoute("/images/search", r.getImagesSearch),
router.NewGetRoute("/images/get", r.getImagesGet),
router.NewGetRoute("/images/{name:.*}/get", r.getImagesGet),
router.NewGetRoute("/images/{name:.*}/history", r.getImagesHistory),
router.NewGetRoute("/images/{name:.*}/json", r.getImagesByName),
// POST
router.NewPostRoute("/images/load", r.postImagesLoad),
router.NewPostRoute("/images/create", r.postImagesCreate),
router.NewPostRoute("/images/{name:.*}/push", r.postImagesPush),
router.NewPostRoute("/images/{name:.*}/tag", r.postImagesTag),
router.NewPostRoute("/images/prune", r.postImagesPrune),
// DELETE
router.NewDeleteRoute("/images/{name:.*}", r.deleteImages),
}
}
总结
最后,Docker服务端成功启动,处理请求架构如下:
下一节介绍Docker中的镜像存储。