docker v1.11 源码重构分析
来源:互联网 发布:风险评价矩阵图 编辑:程序博客网 时间:2024/05/23 22:58
基于docker v1.12的源代码,对docker engine v1.11中重构后的源码结构进行分析,涵盖dockerd, containerd, containerd-shim, runC。
docker1.11新特性
docker在v1.11版本进行了重大的重构,对docker engine和container进行了解耦,docker engine运行在containerd上,containerd运行在runC上,通过containerd-shim中间层进行了解耦。之前的docker engine分为四个组件:dockerd、containerd、containerd-shim、runC,之间关系如下图所示:
containerd是一个守护进程,它可以使用runC的接口管理容器,使用gRPC暴露容器的其他功能。由于容器运行时是孤立的引擎,引擎最终能够启动和升级而无需重新启动容器。在v1.12版本中,在dockerd的启动参数或者配置文件中,开启live-store
参数即可,具体参考 官方live-store升级dockerd文档。
runC是一个轻量级的工具,它是用来运行容器的,runC实际上就是在libcontainer上进行了接口封装,这是一个独立的二进制文件。
关于runC和containerd的更详细的源码分析,请参考我的相应博文。
docker1.12四个组件的介绍
编译
可按照官网的方式直接编译docker:https://docs.docker.com/v1.5/contributing/devenvironment/
注意,containerd和runc的源码并不直接在docker的源码中,而是已经拆分出来。
在docker编译的时候,会在github上拉取containerd和runc的源码并编译生成containerd和runc的二进制文件,包括docker-containerd、docker-containerd-shim、docker-containerd-ctr这三个可执行文件。代码在docker的Dockerfile文件中:
同样,会从github上拉取runc的源码并编译生成runc的二进制文件docker-runc。代码在docker的Dockerfile中:
docker本身编译后会生成dockerd、docker、docker-proxy这三个二进制文件。其中,dockerd是docker的daemon,docker是client。
运行
启动dockerd后,会自动启动containerd,如下所示:
docker1.12源码结构分析
dockerd和docker的主函数入口分别在cmd/dockerd和cmd/docker。首先来分析dockerd的启动流程。
dockerd启动流程
在cmd/dockerd/docker.go的main函数中,进行一些参数的初始化工作后,会调用到cmd/dockerd/daemon.go中的start()函数:
if !stop { err = daemonCli.start() notifyShutdown(err) if err != nil { logrus.Fatal(err) }}
在start()函数中,会通过以下源码,根据启动参数中的tcp、unix socket等,分别创建接收client端请求的api server:
api := apiserver.New(serverConfig)cli.api = apifor i := 0; i < len(cli.Config.Hosts); i++ { var err error if cli.Config.Hosts[i], err = opts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil { return fmt.Errorf("error parsing -H %s : %v", cli.Config.Hosts[i], err) } protoAddr := cli.Config.Hosts[i] protoAddrParts := strings.SplitN(protoAddr, "://", 2) if len(protoAddrParts) != 2 { return fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr) } proto := protoAddrParts[0] addr := protoAddrParts[1] // It's a bad idea to bind to TCP without tlsverify. if proto == "tcp" && (serverConfig.TLSConfig == nil || serverConfig.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert) { logrus.Warn("[!] DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING [!]") } ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig) if err != nil { return err } ls = wrapListeners(proto, ls) // If we're binding to a TCP port, make sure that a container doesn't try to use it. if proto == "tcp" { if err := allocateDaemonPort(addr); err != nil { return err } } logrus.Debugf("Listener created for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1]) api.Accept(protoAddrParts[1], ls...)}
接下来,会通过以下源码调用到libcontainerd/remote_linux.go当中的New()函数,创建containerdRemote,与containerd建立联系:
containerdRemote, err := libcontainerd.New(cli.getLibcontainerdRoot(), cli.getPlatformRemoteOptions()...)
在这个New()函数当中,会通过以下代码调用到runContainerdDaemon()函数:
if r.startDaemon { if err := r.runContainerdDaemon(); err != nil { return nil, err }}
在runContainerdDaemon()函数中,会启动containerd进程:
// Start a new instanceargs := []string{ "-l", fmt.Sprintf("unix://%s", r.rpcAddr), "--shim", "docker-containerd-shim", "--metrics-interval=0", "--start-timeout", "2m", "--state-dir", filepath.Join(r.stateDir, containerdStateDir),}if r.runtime != "" { args = append(args, "--runtime") args = append(args, r.runtime)}if r.debugLog { args = append(args, "--debug")}if len(r.runtimeArgs) > 0 { for _, v := range r.runtimeArgs { args = append(args, "--runtime-args") args = append(args, v) } logrus.Debugf("libcontainerd: runContainerdDaemon: runtimeArgs: %s", args)}cmd := exec.Command(containerdBinary, args...)
回到cmd/dockerd/daemon.go中的start()函数中,通过以下代码调用daemon/daemon.go中的NewDaemon()函数,创建daemon:
d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote)
在NewDaemon()函数中,处理一些基本配置后,通过以下代码调用layer/layer_store.go中的NewStoreFromOptions(options StoreOptions)函数。
d.layerStore, err = layer.NewStoreFromOptions(layer.StoreOptions{ StorePath: config.Root, MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"), GraphDriver: driverName, GraphDriverOptions: config.GraphOptions, UIDMaps: uidMaps, GIDMaps: gidMaps, })
在NewStoreFromOptions(options StoreOptions)函数中,会调用graphdriver/driver.go中的New函数,在New函数中,会通过以下代码获取到rootfs driver。rootfs driver或者是dockerd启动参数中指定的,或者是系统推荐的:
func New(root string, name string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) { if name != "" { logrus.Debugf("[graphdriver] trying provided driver %q", name) // so the logs show specified driver return GetDriver(name, root, options, uidMaps, gidMaps) } // Guess for prior driver driversMap := scanPriorDrivers(root) logrus.Debugf("driversMap is v%", driversMap) for _, name := range priority { if name == "vfs" { // don't use vfs even if there is state present. continue } if _, prior := driversMap[name]; prior { // of the state found from prior drivers, check in order of our priority // which we would prefer driver, err := getBuiltinDriver(name, root, options, uidMaps, gidMaps) if err != nil { // unlike below, we will return error here, because there is prior // state, and now it is no longer supported/prereq/compatible, so // something changed and needs attention. Otherwise the daemon's // images would just "disappear". logrus.Errorf("[graphdriver] prior storage driver %q failed: %s", name, err) return nil, err } // abort starting when there are other prior configured drivers // to ensure the user explicitly selects the driver to load if len(driversMap)-1 > 0 { var driversSlice []string for name := range driversMap { driversSlice = append(driversSlice, name) } return nil, fmt.Errorf("%q contains several valid graphdrivers: %s; Please cleanup or explicitly choose storage driver (-s <DRIVER>)", root, strings.Join(driversSlice, ", ")) } logrus.Infof("[graphdriver] using prior storage driver %q", name) return driver, nil }
docker命令行执行分析
docker client端docker的入口函数在cmd/docker/docker.go当中,在main函数中,会对命令行参数进行处理,以docker create为例,最终会调用到api/client/container/create.go中的NewCreateCommand(dockerCli *client.DockerCli)函数,然后继续调用到runCreate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *createOptions, copts *runconfigopts.ContainerOptions)函数,进而调用到createContainer函数。然后通过以下源码调用到vendor/src/github.com/docker/engine-api/client/container_create.go中的ContainerCreate函数。
//create the containerresponse, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
在ContainerCreate函数中,会向api server端发起post请求:
serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)if err != nil { if serverResp != nil && serverResp.statusCode == 404 && strings.Contains(err.Error(), "No such image") { return response, imageNotFoundError{config.Image} } return response, err}
在api/server/router/container/container.go的initRoutes()函数中,定义了以下http router:
……// POSTrouter.NewPostRoute("/containers/create", r.postContainersCreate),router.NewPostRoute("/containers/{name:.*}/kill", r.postContainersKill),router.NewPostRoute("/containers/{name:.*}/pause", r.postContainersPause),……
因而,server端会把由api/server/router/container/container_routes.go中的postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string)函数处理该post请求。
在postContainersCreate函数中,会通过以下代码调用到daemon/create.go中的ContainerCreate(params types.ContainerCreateConfig, validateHostname bool)函数,最后由docker daemon来创建container:
// ContainerCreate creates a regular containerfunc (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error) { return daemon.containerCreate(params, false, validateHostname)}
- docker v1.11 源码重构分析
- Docker源码分析:Docker架构
- ARM64从源码编译docker(v1.9.1)
- Docker源码分析
- docker daemon 源码分析
- Docker源码分析
- docker volume源码分析
- docker logger 源码分析
- freemodbus-v1.5.0 源码分析
- Docker源码分析(一):Docker架构
- Docker源码分析(一):Docker架构
- Docker源码分析(一):Docker架构
- Docker源码分析(一):Docker架构
- Docker源码分析(一):Docker架构
- Docker源码分析(一):Docker架构
- Docker源码分析(一):Docker架构
- Docker源码分析(一):Docker架构
- DOCKER源码分析(一):DOCKER架构
- 每天一个 Linux 命令(7):mv命令
- STL之deque源码剖析
- TOMCAT设置能访问软连接映射的文件及软连接的创建
- 数据库SQL优化
- 58. Length of Last Word
- docker v1.11 源码重构分析
- 用Python写一段发邮件的小程序
- 集体智慧编程学习笔记--第二章
- 第二章作业
- vi编辑器进阶教程
- 阿里云使用经验
- Hadoop案例之单表关联输出祖孙关系
- UCOSIII 中断
- Spring MVC 之 参数绑定