docker架构速看(1)-启动

news2024/12/23 13:52:08

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()函数,函数简单概括如下:

  1. 根据传入的参数设置守护进程的配置信息。
  2. 加载listener,监听hosts
  3. 创建一个新的守护进程(daemon)对象,并启动它。
  4. 初始化路由,将不同的请求路由到不同的后端服务进行处理。
  5. 启动API服务,开始监听客户端的请求,并将请求路由到正确的后端服务进行处理。
  6. 等待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

  1. 根据传入的参数,为每个需要处理的host进行相关设置,例如TLS。
  2. 调用daemon/listeners包中的listeners.Init()函数,该函数会根据不同的协议(TCP Socket、Unix Socket等)来初始化对应的listener,并将listener绑定到对应的host。
  3. 使用初始化好的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中的镜像存储。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/595544.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vector-常用CAN工具 - VN5000接口以太网包过滤

目录 一、什么是硬件过滤&#xff1f; 1、什么时候应该过滤硬件&#xff1f; 2、需要注意什么&#xff1f; 3、如何过滤VN5000接口上的以太网报文&#xff1f; &#xff08;1&#xff09;Vector Hardware Manager &#xff08;2&#xff09;Vector Hardware Config 一、…

Benewake(北醒) 快速实现 TF03-485 与电脑通信操作说明

目录 一、前言二、工具准备1. USB-RS485 转接板或北醒 TF 系列专用转接板2. TF03-4853. PC&#xff1a;Windows 系统4. 串口助手软件、上位机 WINCC 三、连接方式方案一&#xff1a;USB-RS485 连接电脑与雷达(1) USB-RS485 转接板接口说明(2) TF03-485 引脚定义(3) 连接方式 方…

Go中的并发是困难的

我明白标题可能有些令人困惑&#xff0c;因为一般来说&#xff0c;Go被认为在并发方面有很好的内置支持。然而&#xff0c;我并不认为在Go中编写并发软件是容易的。让我向您展示我是什么意思。 使用全局变量 第一个例子是我们在项目中遇到的问题。直到最近&#xff0c;sarama…

【致敬未来的攻城狮计划】打卡1:rcsa+keil环境搭建

前言 这回参加的是csdn李肯老师的攻城狮计划&#xff0c;简单说就是我白嫖板子&#xff0c;输出学习笔记。 板子是瑞萨的CPK_RA2E1&#xff0c;还有触摸元件&#xff0c;看起来很有意思hh。 环境搭建 一开始决定采取vscode搭建的方式。后期进行到最后一步——cmake build的时…

SQL-计算留存率cohort

目录 1、留存率cohort介绍及其业务价值 2、计算思路 3、实操 3.1、日对日留存cohort 3.2、周对周留存cohort 3.3、月对月留存cohort 1、留存率cohort介绍及其业务价值 留存率cohort也叫做同期群留存分析&#xff0c;将同一时间范围内的用户分为一组&#xff0c;计算这批…

Linux命令(26)之uptime

Linux命令之uptime 1.uptime介绍 linux命令uptime是用来为用户提供系统从开启到当前运行uptime命令时系统已运行的时长信息&#xff0c;除此之外&#xff0c;还提了系统启动时间&#xff0c;当前登录用户&#xff0c;系统平均负载信息。 2.uptime用法 uptime [参数] uptime…

华为OD机试真题(Java),四则运算(100%通过+复盘思路)

一、题目描述 输入一个表达式&#xff08;用字符串表示&#xff09;&#xff0c;求这个表达式的值。 保证字符串中的有效字符包括[‘0’-‘9’],‘’,‘-’, ‘*’,‘/’ ,‘(’&#xff0c; ‘)’,‘[’, ‘]’,‘{’ ,‘}’。且表达式一定合法。 数据范围&#xff1a;表达…

gitlab记录

1、docker方式部署启动 参考文档&#xff1a; https://blog.csdn.net/weixin_53443677/article/details/125518696 https://blog.csdn.net/weixin_39034012/article/details/119211630 1.1、docker启动gitlab 前期准备 > # 拉镜像 > docker pull gitlab/gitlab-ce:late…

chatgpt赋能python:Python代码30行:提高网站SEO的最佳实践

Python 代码 30 行&#xff1a;提高网站 SEO 的最佳实践 搜索引擎优化&#xff08;SEO&#xff09;是网站成功的重要因素&#xff0c;它可以让网站排名更高并吸引更多的流量。Python 代码可以帮助您实现最佳的 SEO 实践&#xff0c;并提高网站的可见性和排名。下面是一个包含 …

Tugraph的设计和源码初步解析

1. Tugraph Tugraph是一款开源的性能优秀的图数据库&#xff0c;该图数据库使用多版本的BTree作为数据的存储引擎&#xff0c;同时设置了点边数据在这个存储引擎上的布局&#xff08;主要考虑数据的局部性&#xff09;&#xff0c;从而达到高性能查询的目的。本文主要从Tugrap…

ubuntu系统登录密码重置方法

公司搬家&#xff0c;需要更改git服务器地址&#xff0c;发现服务器密码也忘记了&#xff0c;折腾了下&#xff0c;通过如下方法修改成功。 一、重启计算机并进入GRUB菜单&#xff08;如果您的计算机没有显示GRUB菜单&#xff0c;请尝试按住Shift键或Esc键&#xff0c;直到出现…

手机安卓Termux搭建Hexo博客网站,并发布公网访问。

文章目录 1. 安装 Hexo2. 安装cpolar内网穿透3. 公网远程访问4. 固定公网地址 转载自cpolar极点云的文章&#xff1a;安卓手机使用Termux搭建Hexo个人博客网站【内网穿透公网访问】 Hexo 是一个用 Nodejs 编写的快速、简洁且高效的博客框架。Hexo 使用 Markdown 解析文章&#…

DAY04_JDBC快速入门JDBC API详解SQL防注入数据库连接池JDBC综合练习

目录 1 JDBC1.1 JDBC概念1.2 JDBC本质 1.3 JDBC好处 2 JDBC快速入门2.1 编写代码步骤2.2 具体操作 3 JDBC API详解3.1 DriverManager3.2 Connection3.2.1 获取执行对象3.2.2 事务管理 3.3 Statement3.4 ResultSet3.4.1 ResultSet案例 3.5 PreparedStatement3.5.1 SQL注入3.5.2 …

基于opencv实现两路yuv数据拼接合成一张大图

背景 实时音视频通话&#xff08;RTC&#xff09;越来越注重安全审核&#xff0c;特别是在1v1娱乐社交场景中&#xff0c;对于视频反垃圾的需求也越来越大。随之而来的是客户对审核成本降低的诉求日益强烈。针对1v1场景&#xff0c;将两路视频拼接成一张图片进行审核相比于分别…

大数据Doris(三十一):Broker Load导入HDFS json格式数据和注意事项

文章目录 Broker Load导入HDFS json格式数据和注意事项 一、导入HDFS json格式数据 1、创建Doris表

nginx(八十一)rewrite模块指令再探之(三)重定向

一 return和rewrite重定向再探 ① 前言 多种重定向跳转方式的差异 nginx与Location响应头细节探讨 本为不涉及讨论如下的绝对重定向1) return 301 http://www.wzj.com:6443/url?namewzj2) rewrite ... http://www.wzj.com:6443/url 2) rewrite ... http://www.wzj.com:64…

一分钟学一个 Linux 命令 - pwd

前言 大家好&#xff0c;我是 god23bin。欢迎大家继续围观《一分钟学一个 Linux 命令》&#xff0c;每天只需一分钟&#xff0c;记住一个 Linux 命令不成问题。本篇文章将聚焦于 pwd 命令&#xff0c;一个超级简单又常用的命令。在接下来的内容中&#xff0c;我将快速介绍 pwd…

Elasticsearch总结

详细描述一下 Elasticsearch 搜索的过程&#xff1f; 1、搜索被执行成一个两阶段过程&#xff0c;我们称之为 Query Then Fetch&#xff1b; 2、在初始查询阶段时&#xff0c;查询会广播到索引中每一个分片拷贝&#xff08;主分片或者副本分片&#xff09;。 每个分片在本地执…

chatgpt赋能python:使用Python关闭所有子进程

使用Python关闭所有子进程 如果您使用Python编写了多进程应用程序&#xff0c;那么您可能会遇到一些关闭所有子进程的问题。这种情况可能是您的主进程已经完成了&#xff0c;但是子进程却没有关闭&#xff0c;从而导致资源浪费和程序崩溃。在这篇文章中&#xff0c;我们将讨论…

STM32F1xx -- Systick 系统滴答定时器

1. SysTick 是一个向 CPU 提供定时中断信号的计数器&#xff0c;其计数速率是由 Cortex-M 系列处理器的系统时钟频率和 SysTick 计数器的重载值共同决定的。 1.1 Systick 时钟来源之一&#xff0c;Systick 一般设置为1ms 中断一次&#xff0c;为系统任务调度提供服务&#xff…