docker源码分析(1)(持续更新)

2023-05-16

docker源码分析(1)(持续更新)

  • daemon
    • docker DaemonClient的启动
    • 内部函数分析
      • newAPIServerConfig
      • loadListeners
      • initContainerD

daemon

docker DaemonClient的启动

代码位于cmd\dockerd\daemon.go

func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
	//创建一个关闭的通道
	stopc := make(chan bool)
	defer close(stopc)

	opts.SetDefaultOptions(opts.flags)

	if cli.Config, err = loadDaemonCliConfig(opts); err != nil {
		return err
	}
	warnOnDeprecatedConfigOptions(cli.Config)

	if err := configureDaemonLogs(cli.Config); err != nil {
		return err
	}

	logrus.Info("Starting up")

	cli.configFile = &opts.configFile
	cli.flags = opts.flags

	if cli.Config.Debug {
		debug.Enable()
	}

	if cli.Config.Experimental {
		logrus.Warn("Running experimental build")
	}

	if cli.Config.IsRootless() {
		logrus.Warn("Running in rootless mode. This mode has feature limitations.")
	}
	if rootless.RunningWithRootlessKit() {
		logrus.Info("Running with RootlessKit integration")
		if !cli.Config.IsRootless() {
			return fmt.Errorf("rootless mode needs to be enabled for running with RootlessKit")
		}
	}

	// return human-friendly error before creating files
	// 需要root权限,Geteuid去找0号UID也就是root
	if runtime.GOOS == "linux" && os.Geteuid() != 0 {
		return fmt.Errorf("dockerd needs to be started with root. To see how to run dockerd in rootless mode with unprivileged user, see the documentation")
	}

	//LCOW=Linux Containers on Windows,在window上运行docker的能力
	system.InitLCOW(cli.Config.Experimental)

	//设置Umask为0022,这个后续文件的权限就是755了
	if err := setDefaultUmask(); err != nil {
		return 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)
	if err := daemon.CreateDaemonRoot(cli.Config); err != nil {
		return err
	}

	if err := system.MkdirAll(cli.Config.ExecRoot, 0700); err != nil {
		return err
	}

	//这个记录一些重要的文件路径,现在把cli.Config.ExecRoot放了进来
	//后续还会把PID等文件放到这个[]中
	potentiallyUnderRuntimeDir := []string{cli.Config.ExecRoot}

	if cli.Pidfile != "" {
		pf, err := pidfile.New(cli.Pidfile)
		if err != nil {
			return errors.Wrap(err, "failed to start daemon")
		}
		potentiallyUnderRuntimeDir = append(potentiallyUnderRuntimeDir, cli.Pidfile)
		defer func() {
			if err := pf.Remove(); err != nil {
				logrus.Error(err)
			}
		}()
	}

	if cli.Config.IsRootless() {
		// Set sticky bit if XDG_RUNTIME_DIR is set && the file is actually under XDG_RUNTIME_DIR
		if _, err := homedir.StickRuntimeDirContents(potentiallyUnderRuntimeDir); err != nil {
			// StickRuntimeDirContents returns nil error if XDG_RUNTIME_DIR is just unset
			logrus.WithError(err).Warn("cannot set sticky bit on files under XDG_RUNTIME_DIR")
		}
	}

	//根据clinet的中的config属性配置一个serverConfig
	serverConfig, err := newAPIServerConfig(cli)
	if err != nil {
		return errors.Wrap(err, "failed to create API server")
	}
	//根据serverConfig创建一个cli.api(New函数返回一个Server对象)
	cli.api = apiserver.New(serverConfig)

	//配置主机
	hosts, err := loadListeners(cli, serverConfig)
	if err != nil {
		return errors.Wrap(err, "failed to load listeners")
	}

	ctx, cancel := context.WithCancel(context.Background())
	waitForContainerDShutdown, err := cli.initContainerD(ctx)
	if waitForContainerDShutdown != nil {
		defer waitForContainerDShutdown(10 * time.Second)
	}
	if err != nil {
		cancel()
		return err
	}
	defer cancel()

	//Trap信号
	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.
	// windows下用的,unix下没有找到相关函数 linux下是空函数
	preNotifySystem()

	//插件
	pluginStore := plugin.NewStore()

	if err := cli.initMiddlewares(cli.api, serverConfig, pluginStore); err != nil {
		logrus.Fatalf("Error creating middlewares: %v", err)
	}

	//创建一个真正的damon
	d, err := daemon.NewDaemon(ctx, cli.Config, pluginStore)
	if err != nil {
		return errors.Wrap(err, "failed to start daemon")
	}

	d.StoreHosts(hosts)

	// validate after NewDaemon has restored enabled plugins. Don't change order.
	if err := validateAuthzPlugins(cli.Config.AuthorizationPlugins, pluginStore); err != nil {
		return errors.Wrap(err, "failed to validate authorization plugin")
	}

	cli.d = d

	if err := startMetricsServer(cli.Config.MetricsAddress); err != nil {
		return errors.Wrap(err, "failed to start metrics server")
	}

	c, err := createAndStartCluster(cli, d)
	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")

	routerOptions, err := newRouterOptions(cli.Config, d)
	if err != nil {
		return err
	}
	routerOptions.api = cli.api
	routerOptions.cluster = c

	initRouter(routerOptions)

	go d.ProcessClusterNotifications(ctx, c.GetWatchStream())

	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
	notifySystem()

	// Daemon is fully initialized and handling API traffic
	// Wait for serve API to complete
	errAPI := <-serveAPIWait
	c.Cleanup()

	shutdownDaemon(d)

	// Stop notification processing and any background processes
	cancel()

	if errAPI != nil {
		return errors.Wrap(errAPI, "shutting down due to ServeAPI error")
	}

	logrus.Info("Daemon shutdown complete")
	return nil
}

内部函数分析

newAPIServerConfig

func newAPIServerConfig(cli *DaemonCli) (*apiserver.Config, error) {
	//serverConfig为最终返回的对象
	//serverConfig中配置比较多的就是关于TLS的内容,如CA Cert文件等
	serverConfig := &apiserver.Config{
		Logging:     true,
		SocketGroup: cli.Config.SocketGroup,
		Version:     dockerversion.Version,
		CorsHeaders: cli.Config.CorsHeaders,
	}

	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 nil, err
		}
		serverConfig.TLSConfig = tlsConfig
	}

	if len(cli.Config.Hosts) == 0 {
		cli.Config.Hosts = make([]string, 1)
	}

	return serverConfig, nil
}

loadListeners

func loadListeners(cli *DaemonCli, serverConfig *apiserver.Config) ([]string, error) {
	//这个是最终返回的hosts列表
	var hosts []string
	seen := make(map[string]struct{}, len(cli.Config.Hosts))

	//遍历cli.Config.Hosts这个列表
	for i := 0; i < len(cli.Config.Hosts); i++ {
		var err error
		if cli.Config.Hosts[i], err = dopts.ParseHost(cli.Config.TLS, honorXDG, cli.Config.Hosts[i]); err != nil {
			return nil, errors.Wrapf(err, "error parsing -H %s", cli.Config.Hosts[i])
		}
		//如果seen已经有了某个host就跳过,去找下一个
		if _, ok := seen[cli.Config.Hosts[i]]; ok {
			continue
		}
		//把当前的放到seen中作为key,一个空的struct作为value
		seen[cli.Config.Hosts[i]] = struct{}{}

		protoAddr := cli.Config.Hosts[i]
		//通过SplitN分割字符串,并取前两个
		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
		if len(protoAddrParts) != 2 {
			return nil, 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 [!]")
		}
		//初始化一个listeners
		ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig)
		if err != nil {
			return nil, err
		}
		// 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 nil, err
			}
		}
		logrus.Debugf("Listener created for HTTP on %s (%s)", proto, addr)
		hosts = append(hosts, protoAddrParts[1])
		cli.api.Accept(addr, ls...)
	}

	return hosts, nil
}

initContainerD

unc (cli *DaemonCli) initContainerD(ctx context.Context) (func(time.Duration) error, error) {
	//返回值是一个函数,函数的入参是一个时间
	//结合函数waitForShutdown名,可以猜出函数的作用shutdown等待的时间
	//此函数还有一个重要的功能就是initContainerD
	var waitForShutdown func(time.Duration) error
	if cli.Config.ContainerdAddr == "" {
		systemContainerdAddr, ok, err := systemContainerdRunning(honorXDG)
		if err != nil {
			return nil, errors.Wrap(err, "could not determine whether the system containerd is running")
		}
		if !ok {
			logrus.Debug("Containerd not running, starting daemon managed containerd")
			opts, err := cli.getContainerdDaemonOpts()
			if err != nil {
				return nil, errors.Wrap(err, "failed to generate containerd options")
			}

			//此处Start开始一个containerd daemon
			r, err := supervisor.Start(ctx, filepath.Join(cli.Config.Root, "containerd"), filepath.Join(cli.Config.ExecRoot, "containerd"), opts...)
			if err != nil {
				return nil, errors.Wrap(err, "failed to start containerd")
			}
			logrus.Debug("Started daemon managed containerd")
			cli.Config.ContainerdAddr = r.Address()

			// Try to wait for containerd to shutdown
			//这个地方也印证了我们的想法,WaitTimeout就是一个计时函数
			waitForShutdown = r.WaitTimeout
		} else {
			cli.Config.ContainerdAddr = systemContainerdAddr
		}
	}

	return waitForShutdown, nil
}

觉得有帮助的可以支持下!谢谢!
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

docker源码分析(1)(持续更新) 的相关文章

随机推荐

  • Gradle-Could not determine java version from ‘11‘

    问题描述 因为换工作 xff0c 需要新配置的环境 xff0c 所以遇到了各种问题 xff0c 例如下面这个 xff1a 发生错误的相关环境配置 JDK 11Gradle 4 4 如果不知道自己安装的 Java 版本 xff0c 可以根据一
  • 重装双系统中的 Ubuntu

    我的电脑是 Windows 43 Ubuntu 双系统 xff08 UEFI启动 xff09 xff0c 最近 Ubuntu 被我玩坏了 xff0c 正好 15 04 alpha 2 发布了嘛 xff0c 我就想干脆重装 Ubuntu 算了
  • Markdown:插入图片和图注

    文章目录 在Markdown中插入图片 xff0c 调节位置 xff0c 大小 xff0c 以及插入图注 并排插入图片并排插入图片但无缝隙简单的插入可调整大小图片使用图床 xff08 腾讯云cos 43 picgo xff09 在markd
  • Vmware Esxi在线管理虚拟机

    目录 特征 占地面积小 可靠的性能 增强的安全性 生态系统卓越 用户友好的体验 管理截图 登录系统 主机虚拟机 虚拟机操作 What is ESXI Bare Metal Hypervisor ESX VMware 特征 通过将多台服务器整
  • 孤立森林(Isolation Forest)

    最近正好在做孤立森林的项目 xff0c 写下这篇推文记录下项目进程 xff0c 也给需要做这个项目的同学一点参考 1 什么叫异常点 图中红色的点偏离大部分的数据 xff0c 可以发现这些点分布稀疏且离密度高的群体较远 xff0c 这样的点称
  • 修改docker容器端口映射

    大家都知道docker run可以指定端口映射 xff0c 但是容器一旦生成 xff0c 就没有一个命令可以直接修改 通常间接的办法是 xff0c 保存镜像 xff0c 再创建一个新的容器 xff0c 在创建时指定新的端口映射 有没有办法不
  • MaxWelll常用命令

    文章目录 1 启动maxwell xff1a 1 1 控制台为输出 xff1a 1 2 kafka为输出 xff1a 1 3 配置指定某些表输出 xff1a 1 4 配置输出到kafka 分区规则为表的主键 xff1a 1 5 配置文件启动
  • Linux系统--进程与线程的区别

    看了一遍排在前面的答案 xff0c 类似 进程是资源分配的最小单位 xff0c 线程是CPU调度的最小单位 这样的回答感觉太抽象 xff0c 都不太容易让人理解 做个简单的比喻 xff1a 进程 61 火车 xff0c 线程 61 车厢 线
  • 关于锁的理解

    本文打算写一些和锁有关的东西 xff0c 谈一谈我对锁的原理和实现的理解 xff0c 主要包含以下方面 信号量互斥量条件变量 同步与互斥 其实同步与互斥都是计算机科学里面概念性的东西 xff0c 它们和什么编程语言 操作系统其实都没什么关系
  • Android 适配ipv6

    Android8 0开始支持ipv6 okhttp支持ipv6 glide支持ipv6方法 ijkplayer
  • java自我学习第一阶段的收获和感受

    1 首先对于编程的学习第一步都是基本语法的学习 xff0c 语法学习的特点有 xff1a 所有编程语言的语法定义都大体相似 xff0c 大概的是编程发展的延续 xff0c 没有人语言设计人员会标新立异 xff0c 只有站在别人的肩上不断完善
  • docker常用命令及配置

    最近使用了docker xff0c 像是发现了新大陆 真是简洁好用的东西 xff0c 为啥原来没发现 docker的安装不再赘述 xff0c 在此记录一下常用的命令 xff0c 以便于将来查询 查看docker容器版本 docker ver
  • 数字图像处理读后感

    数字图像处理读后感 前言 初步写完比赛代码后 看了机器学习看了数字图像处理还有其他的一些乱七八糟的东西 然后又觉得如果只是看完没有总结回顾那就是很失败的一件事情 所以就想写一写自己的读后感 我自己看得是B站的视频 这是西安电子科技大学的任老
  • 第七天 树莓派+计算棒配置过程

    一 xff1a 安装过程参考 xff1a https blog csdn net qqqzmy article details 85213414 1 材料硬件 树莓派3B 43 intel movidius 神经元计算棒2代显示器 鼠标键盘
  • Docker镜像推送(push)到Docker Hub

    镜像构建成功后 xff0c 只要有docker环境就可以使用 xff0c 但必须将镜像推送到Docker Hub上去 我们之前创建的镜像不符合Docker Hub的tag要求 xff0c 因为 在Docker Hub注册的用户名是boony
  • Altera FPGA 远程更新

    文章目录 1 概述2 如何生成factory jic xff1f 3 如何使用ASMI IP4 如何使用Remote updata IP xff1f 5 UART命令 以下内容均参考自官方手册 手册下载地址 xff1a ug altremo
  • 将ros的深度图像 转换成 opencv的图像 并求距离

    将ros的图像 转换成 opencv的图像并求距离 参考的博客概念将ROS图像消息转换为OpenCV图像示例计算距离 参考的博客 ROS学习笔记47 xff08 ROS图像和OpenCV图像之间的转换 xff08 C 43 43 xff09
  • ROS入门总结(二):Python定义发布器Publisher与订阅器Subscriber

    节点ROS通信系统中就是一个可执行的程序 xff0c 这边博客主要记录如何在Python代码节点中定义发布器和订阅器 发布器 xff1a import rospy from std msgs msg import String 载入Stri
  • 计算机视觉中的传统特征提取方法总结(转载)

    前言 本文对计算机视觉传统方法中的一些特征提取方法进行了总结 xff0c 主要包括有 xff1a SIFT 尺度不变特征变换 HOG 方向梯度直方图 SURF ORB LBP HAAR 目录 1 SIFT xff08 尺度不变特征变换 xf
  • docker源码分析(1)(持续更新)

    docker源码分析 xff08 1 xff09 xff08 持续更新 xff09 daemondocker DaemonClient的启动内部函数分析newAPIServerConfigloadListenersinitContainer