内容简介:Docker更新的速度太快了。网上很多分析已经过时了。今天我翻了一下Docker的源码,发现其实Docker的源码挺 简单易懂的。当然前提是,你已经熟练掌握了Docker,经常用,知道有哪些概念,了解其原理。有了这些做基础, 就很容易读了。建议先读一下这篇文章:自己写一个容器 和入口在
Docker更新的速度太快了。网上很多分析已经过时了。今天我翻了一下 Docker 的源码,发现其实Docker的源码挺 简单易懂的。当然前提是,你已经熟练掌握了Docker,经常用,知道有哪些概念,了解其原理。有了这些做基础, 就很容易读了。
建议先读一下这篇文章:自己写一个容器 和 这个项目 的源代码。
目录结构
$ tree -d -L 1 . ├── api ├── builder ├── cli ├── client ├── cmd # 命令行的入口,我们就要从这里跳进去看 ├── container # 容器的抽象 ├── contrib # 杂物堆,啥杂七杂八的都往这里丢 ├── daemon # 今天的主角,dockerd这个daemon ├── distribution # 看起来发布相关的 ├── dockerversion ├── docs # 文档 ├── errdefs # 一些常见的错误 ├── hack ├── image # 镜像的抽象概念 ├── integration ├── integration-cli ├── internal ├── layer # 层。怎么说呢,就是对各种layer fs的抽象 ├── libcontainerd ├── migrate ├── oci # https://blog.docker.com/2017/07/demystifying-open-container-initiative-oci-specifications/ 容器标准库相关 ├── opts # 一些配置和配置校验相关的 ├── pkg # 类似于我们平时写的utils或者helpers等 ├── plugin # 插件相关的东西 ├── profiles ├── project ├── reference ├── registry ├── reports ├── restartmanager # 负责容器的重启,比如是否设置了"always"呀 ├── runconfig ├── vendor # go的vendor机制 └── volume # volume相关
-
负责存储配置的一般都叫
xx Store
- Docker的设计是单机的,不是分布式的
- Docker的设计是Client-Server模式的,平时我们用的docker这个命令被分散到 https://github.com/docker/cli 这个仓库去了
从命令行进入
入口在 cmd/dockerd/docker.go
:
func newDaemonCommand() *cobra.Command { opts := newDaemonOptions(config.New()) cmd := &cobra.Command{ Use: "dockerd [OPTIONS]", Short: "A self-sufficient runtime for containers.", SilenceUsage: true, SilenceErrors: true, Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { if opts.version { showVersion() return nil } opts.flags = cmd.Flags() return runDaemon(opts) // 真正的入口 }, } cli.SetupRootCommand(cmd) flags := cmd.Flags() flags.BoolVarP(&opts.version, "version", "v", false, "Print version information and quit") flags.StringVar(&opts.configFile, "config-file", defaultDaemonConfigFile, "Daemon configuration file") opts.InstallFlags(flags) installConfigFlags(opts.daemonConfig, flags) installServiceFlags(flags) return cmd }
然后跳到 cmd/dockerd/daemon.go
:
func (cli *DaemonCli) start(opts *daemonOptions) (err error) { stopc := make(chan bool) defer close(stopc) // warn from uuid package when running the daemon uuid.Loggerf = logrus.Warnf // 设置一些默认配置例如TLS啊blabla opts.SetDefaultOptions(opts.flags) // 加载配置 if cli.Config, err = loadDaemonCliConfig(opts); err != nil { return err } cli.configFile = &opts.configFile cli.flags = opts.flags if cli.Config.Debug { debug.Enable() } if cli.Config.Experimental { logrus.Warn("Running experimental build") } logrus.SetFormatter(&logrus.TextFormatter{ TimestampFormat: jsonmessage.RFC3339NanoFixed, DisableColors: cli.Config.RawLogs, FullTimestamp: true, }) // LCOW: Linux Containers On Windows. ref: https://blog.docker.com/2017/09/preview-linux-containers-on-windows/ system.InitLCOW(cli.Config.Experimental) // 设置默认的umask。022。也就是说,创建的文件权限都是755 if err := setDefaultUmask(); err != nil { return fmt.Errorf("Failed to set umask: %v", err) } // Create the daemon root before we create ANY other files (PID, or migrate keys) // to ensure the appropriate ACL is set (particularly relevant on Windows) // 设置daemon的执行时候的根目录 if err := daemon.CreateDaemonRoot(cli.Config); err != nil { return err } if cli.Pidfile != "" { pf, err := pidfile.New(cli.Pidfile) if err != nil { return fmt.Errorf("Error starting daemon: %v", err) } defer func() { if err := pf.Remove(); err != nil { logrus.Error(err) } }() } // TODO: extract to newApiServerConfig() serverConfig := &apiserver.Config{ Logging: true, SocketGroup: cli.Config.SocketGroup, Version: dockerversion.Version, CorsHeaders: cli.Config.CorsHeaders, } // 是否走TLS if cli.Config.TLS { tlsOptions := tlsconfig.Options{ CAFile: cli.Config.CommonTLSOptions.CAFile, CertFile: cli.Config.CommonTLSOptions.CertFile, KeyFile: cli.Config.CommonTLSOptions.KeyFile, ExclusiveRootPools: true, } if cli.Config.TLSVerify { // server requires and verifies client's certificate tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert } tlsConfig, err := tlsconfig.Server(tlsOptions) if err != nil { return err } serverConfig.TLSConfig = tlsConfig } if len(cli.Config.Hosts) == 0 { cli.Config.Hosts = make([]string, 1) } cli.api = apiserver.New(serverConfig) var hosts []string // 设置API server for i := 0; i < len(cli.Config.Hosts); i++ { var err error if cli.Config.Hosts[i], err = dopts.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)", proto, addr) hosts = append(hosts, protoAddrParts[1]) cli.api.Accept(addr, ls...) } registryService, err := registry.NewService(cli.Config.ServiceOptions) if err != nil { return err } rOpts, err := cli.getRemoteOptions() if err != nil { return fmt.Errorf("Failed to generate containerd options: %s", err) } containerdRemote, err := libcontainerd.New(filepath.Join(cli.Config.Root, "containerd"), filepath.Join(cli.Config.ExecRoot, "containerd"), rOpts...) if err != nil { return err } signal.Trap(func() { cli.stop() <-stopc // wait for daemonCli.start() to return }, logrus.StandardLogger()) // Notify that the API is active, but before daemon is set up. preNotifySystem() pluginStore := plugin.NewStore() // 初始化中间件 if err := cli.initMiddlewares(cli.api, serverConfig, pluginStore); err != nil { logrus.Fatalf("Error creating middlewares: %v", err) } // 实例化daemon d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote, pluginStore) if err != nil { return fmt.Errorf("Error starting daemon: %v", err) } d.StoreHosts(hosts) // validate after NewDaemon has restored enabled plugins. Dont change order. if err := validateAuthzPlugins(cli.Config.AuthorizationPlugins, pluginStore); err != nil { return fmt.Errorf("Error validating authorization plugin: %v", err) } // TODO: move into startMetricsServer() if cli.Config.MetricsAddress != "" { if !d.HasExperimental() { return fmt.Errorf("metrics-addr is only supported when experimental is enabled") } if err := startMetricsServer(cli.Config.MetricsAddress); err != nil { return err } } // TODO: createAndStartCluster() name, _ := os.Hostname() // Use a buffered channel to pass changes from store watch API to daemon // A buffer allows store watch API and daemon processing to not wait for each other watchStream := make(chan *swarmapi.WatchMessage, 32) // 默认就初始化Docker Swarm? c, err := cluster.New(cluster.Config{ Root: cli.Config.Root, Name: name, Backend: d, ImageBackend: d.ImageService(), PluginBackend: d.PluginManager(), NetworkSubnetsProvider: d, DefaultAdvertiseAddr: cli.Config.SwarmDefaultAdvertiseAddr, RaftHeartbeatTick: cli.Config.SwarmRaftHeartbeatTick, RaftElectionTick: cli.Config.SwarmRaftElectionTick, RuntimeRoot: cli.getSwarmRunRoot(), WatchStream: watchStream, }) if err != nil { logrus.Fatalf("Error creating cluster component: %v", err) } d.SetCluster(c) err = c.Start() if err != nil { logrus.Fatalf("Error starting cluster component: %v", err) } // Restart all autostart containers which has a swarm endpoint // and is not yet running now that we have successfully // initialized the cluster. d.RestartSwarmContainers() logrus.Info("Daemon has completed initialization") cli.d = d // 注册handler到router routerOptions, err := newRouterOptions(cli.Config, d) if err != nil { return err } routerOptions.api = cli.api routerOptions.cluster = c // 初始化router initRouter(routerOptions) // process cluster change notifications watchCtx, cancel := context.WithCancel(context.Background()) defer cancel() go d.ProcessClusterNotifications(watchCtx, watchStream) cli.setupConfigReloadTrap() // The serve API routine never exits unless an error occurs // We need to start it as a goroutine and wait on it so // daemon doesn't exit serveAPIWait := make(chan error) // 开始执行 go cli.api.Wait(serveAPIWait) // after the daemon is done setting up we can notify systemd api // 还要通知systemd? notifySystem() // Daemon is fully initialized and handling API traffic // Wait for serve API to complete // api server停了,daemon就跟着退出 errAPI := <-serveAPIWait c.Cleanup() shutdownDaemon(d) containerdRemote.Cleanup() if errAPI != nil { return fmt.Errorf("Shutting down due to ServeAPI error: %v", errAPI) } return nil }
这里做的事情可就多了,加载配置,设置相关的变量和配置,监听端口,设置信号handler, 起了API server,等待接受请求。
创建容器
上面我们看到了daemon的启动过程。启动完之后就等待请求了。那我们要找一个新的入口点去跟踪
代码,所以我选择 docker run
。从 docker/cli
库翻了翻,发现最后是调用 containers/create
这样一个接口。然后就在本仓库里搜索,我们的目标是找到处理这个请求的mux所在地,然后翻出对应的
handler api/server/router/container/container.go
,然后跳到 api/server/router/container/container_routes.go
:
ccr, err := s.backend.ContainerCreate(types.ContainerCreateCo nfig{ Name: name, Config: config, HostConfig: hostConfig, NetworkingConfig: networkingConfig, AdjustCPUShares: adjustCPUShares, })
跳进去,发现是个接口。而 s.backend
其实就是daemon。具体代码在上一节就可以跟到。所以我打开fzf进行
泛搜索 func daemon ContainerCreate
,找到了 daemon/create.go
:
// ContainerCreate creates a regular container // 创建一个容器 func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig) (containertypes.ContainerCreateCreatedBody, error) { return daemon.containerCreate(params, false) } func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, managed bool) (containertypes.ContainerCreateCreatedBody, error) { start := time.Now() if params.Config == nil { return containertypes.ContainerCreateCreatedBody{}, errdefs.InvalidParameter(errors.New("Config cannot be empty in order to create a container")) } os := runtime.GOOS // 不知道这个是干啥的。。。Windows和 Linux 怎么混来混去的。。。 if params.Config.Image != "" { // 拉取镜像 img, err := daemon.imageService.GetImage(params.Config.Image) if err == nil { os = img.OS } } else { // This mean scratch. On Windows, we can safely assume that this is a linux // container. On other platforms, it's the host OS (which it already is) if runtime.GOOS == "windows" && system.LCOWSupported() { os = "linux" } } // 验证容器的配置,如果有问题,就不能创建 warnings, err := daemon.verifyContainerSettings(os, params.HostConfig, params.Config, false) if err != nil { return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, errdefs.InvalidParameter(err) } // 验证网络配置 err = verifyNetworkingConfig(params.NetworkingConfig) if err != nil { return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, errdefs.InvalidParameter(err) } if params.HostConfig == nil { params.HostConfig = &containertypes.HostConfig{} } // 调整一些配置,例如CPU如果超量了,就设置成系统允许的最大的。等。 err = daemon.adaptContainerSettings(params.HostConfig, params.AdjustCPUShares) if err != nil { return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, errdefs.InvalidParameter(err) } // 创建容器 container, err := daemon.create(params, managed) if err != nil { return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err } containerActions.WithValues("create").UpdateSince(start) return containertypes.ContainerCreateCreatedBody{ID: container.ID, Warnings: warnings}, nil } // Create creates a new container from the given configuration with a given name. // 创建容器 func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (retC *container.Container, retErr error) { var ( container *container.Container img *image.Image imgID image.ID err error ) os := runtime.GOOS if params.Config.Image != "" { img, err = daemon.imageService.GetImage(params.Config.Image) if err != nil { return nil, err } if img.OS != "" { os = img.OS } else { // default to the host OS except on Windows with LCOW if runtime.GOOS == "windows" && system.LCOWSupported() { os = "linux" } } imgID = img.ID() if runtime.GOOS == "windows" && img.OS == "linux" && !system.LCOWSupported() { return nil, errors.New("operating system on which parent image was created is not Windows") } } else { // 没搞懂这个分支在这干啥。。。 if runtime.GOOS == "windows" { os = "linux" // 'scratch' case. } } // 再次检查配置 if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil { return nil, errdefs.InvalidParameter(err) } if err := daemon.mergeAndVerifyLogConfig(¶ms.HostConfig.LogConfig); err != nil { return nil, errdefs.InvalidParameter(err) } // 创建容器。。。。这个嵌套的有点多。。。此处返回的是内存中对容器的一个抽象 `Container` if container, err = daemon.newContainer(params.Name, os, params.Config, params.HostConfig, imgID, managed); err != nil { return nil, err } defer func() { if retErr != nil { if err := daemon.cleanupContainer(container, true, true); err != nil { logrus.Errorf("failed to cleanup container on create error: %v", err) } } }() if err := daemon.setSecurityOptions(container, params.HostConfig); err != nil { return nil, err } container.HostConfig.StorageOpt = params.HostConfig.StorageOpt // Fixes: https://github.com/moby/moby/issues/34074 and // https://github.com/docker/for-win/issues/999. // Merge the daemon's storage options if they aren't already present. We only // do this on Windows as there's no effective sandbox size limit other than // physical on Linux. if runtime.GOOS == "windows" { if container.HostConfig.StorageOpt == nil { container.HostConfig.StorageOpt = make(map[string]string) } for _, v := range daemon.configStore.GraphOptions { opt := strings.SplitN(v, "=", 2) if _, ok := container.HostConfig.StorageOpt[opt[0]]; !ok { container.HostConfig.StorageOpt[opt[0]] = opt[1] } } } // Set RWLayer for container after mount labels have been set // 要创建一个RWLayer,这样容器里面才可以读写。前面的layer都包含在image里。 rwLayer, err := daemon.imageService.CreateLayer(container, setupInitLayer(daemon.idMappings)) if err != nil { return nil, errdefs.System(err) } container.RWLayer = rwLayer rootIDs := daemon.idMappings.RootPair() if err := idtools.MkdirAndChown(container.Root, 0700, rootIDs); err != nil { return nil, err } if err := idtools.MkdirAndChown(container.CheckpointDir(), 0700, rootIDs); err != nil { return nil, err } if err := daemon.setHostConfig(container, params.HostConfig); err != nil { return nil, err } if err := daemon.createContainerOSSpecificSettings(container, params.Config, params.HostConfig); err != nil { return nil, err } var endpointsConfigs map[string]*networktypes.EndpointSettings if params.NetworkingConfig != nil { endpointsConfigs = params.NetworkingConfig.EndpointsConfig } // Make sure NetworkMode has an acceptable value. We do this to ensure // backwards API compatibility. runconfig.SetDefaultNetModeIfBlank(container.HostConfig) daemon.updateContainerNetworkSettings(container, endpointsConfigs) if err := daemon.Register(container); err != nil { return nil, err } stateCtr.set(container.ID, "stopped") daemon.LogContainerEvent(container, "create") return container, nil }
然后跟到 daemon.create
,仔细看,然后继续跟到了 daemon.newContainer
:
func (daemon *Daemon) newContainer(name string, operatingSystem string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) { var ( id string err error noExplicitName = name == "" ) id, name, err = daemon.generateIDAndName(name) if err != nil { return nil, err } if hostConfig.NetworkMode.IsHost() { if config.Hostname == "" { config.Hostname, err = os.Hostname() if err != nil { return nil, errdefs.System(err) } } } else { // 原来容器的hostname默认是容器id的前12位 daemon.generateHostname(id, config) } entrypoint, args := daemon.getEntrypointAndArgs(config.Entrypoint, config.Cmd) base := daemon.newBaseContainer(id) // 瞧。。。又一个创建容器 base.Created = time.Now().UTC() base.Managed = managed base.Path = entrypoint base.Args = args //FIXME: de-duplicate from config base.Config = config base.HostConfig = &containertypes.HostConfig{} base.ImageID = imgID base.NetworkSettings = &network.Settings{IsAnonymousEndpoint: noExplicitName} base.Name = name base.Driver = daemon.imageService.GraphDriverForOS(operatingSystem) base.OS = operatingSystem return base, err }
整个流程就跟完了。
以上所述就是小编给大家介绍的《Docker CE 18.03源码分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 以太坊源码分析(36)ethdb源码分析
- [源码分析] kubelet源码分析(一)之 NewKubeletCommand
- libmodbus源码分析(3)从机(服务端)功能源码分析
- [源码分析] nfs-client-provisioner源码分析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机程序设计艺术(第1卷)
[美] Donald E. Knuth / 清华大学出版社 / 2002-9 / 80.00元
第1卷首先介绍编程的基本概念和技术,然后详细讲解信息结构方面的内容,包括信息在计算机内部的表示方法、数据元素之间的结构关系,以及有效的信息处理方法。此外,书中还描述了编程在模拟、数值方法、符号计算、软件与系统设计等方面的初级应用。此第3版增加了数十项简单但重要的算法和技术,并根据当前研究发展趋势在数学预备知识方面做了大量修改。一起来看看 《计算机程序设计艺术(第1卷)》 这本书的介绍吧!