docker架构速看(1)-启动

2024-01-15 01:50
文章标签 启动 docker 架构 速看

本文主要是介绍docker架构速看(1)-启动,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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.Configcli.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.apirouterOptions.cluster = cinitRouter(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 completeerrAPI := <-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初始化HTTPServerfor _, 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,管理volumesd.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 maskedcheckpointrouter.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 {imageBackendimportExportBackendregistryBackend
}// initRoutes initializes the routes in the image router
func (r *imageRouter) initRoutes() {r.routes = []router.Route{// GETrouter.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),// POSTrouter.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),// DELETErouter.NewDeleteRoute("/images/{name:.*}", r.deleteImages),}
}

总结

​ 最后,Docker服务端成功启动,处理请求架构如下:

在这里插入图片描述

​ 下一节介绍Docker中的镜像存储。

这篇关于docker架构速看(1)-启动的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/607254

相关文章

mybatis的整体架构

mybatis的整体架构分为三层: 1.基础支持层 该层包括:数据源模块、事务管理模块、缓存模块、Binding模块、反射模块、类型转换模块、日志模块、资源加载模块、解析器模块 2.核心处理层 该层包括:配置解析、参数映射、SQL解析、SQL执行、结果集映射、插件 3.接口层 该层包括:SqlSession 基础支持层 该层保护mybatis的基础模块,它们为核心处理层提供了良好的支撑。

百度/小米/滴滴/京东,中台架构比较

小米中台建设实践 01 小米的三大中台建设:业务+数据+技术 业务中台--从业务说起 在中台建设中,需要规范化的服务接口、一致整合化的数据、容器化的技术组件以及弹性的基础设施。并结合业务情况,判定是否真的需要中台。 小米参考了业界优秀的案例包括移动中台、数据中台、业务中台、技术中台等,再结合其业务发展历程及业务现状,整理了中台架构的核心方法论,一是企业如何共享服务,二是如何为业务提供便利。

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

springboot3打包成war包,用tomcat8启动

1、在pom中,将打包类型改为war <packaging>war</packaging> 2、pom中排除SpringBoot内置的Tomcat容器并添加Tomcat依赖,用于编译和测试,         *依赖时一定设置 scope 为 provided (相当于 tomcat 依赖只在本地运行和测试的时候有效,         打包的时候会排除这个依赖)<scope>provided

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud

SpringBoot项目是如何启动

启动步骤 概念 运行main方法,初始化SpringApplication 从spring.factories读取listener ApplicationContentInitializer运行run方法读取环境变量,配置信息创建SpringApplication上下文预初始化上下文,将启动类作为配置类进行读取调用 refresh 加载 IOC容器,加载所有的自动配置类,创建容器在这个过程