distribution structure and start 分析

来源:互联网 发布:为实现数据的保密性 编辑:程序博客网 时间:2024/06/09 15:19

distribution structure and start 分析

本文简单介绍一下distribution的结构以及其启动过程。
这里分析的对象不包括存储驱动框架,hook框架以及认证体系。
本文应该是distribution系列文章中最早的一篇,但是由于各种原因其书写时间晚于pull push过程中distribution处理源码分析之后,因此新的架构图中有些是基于完全看完代码后才有的内容,这里大家先看着。

简介
distribution是docker体系的镜像仓库——即docker registry。 docker registry有两个版本,第一个是python写的 registry, 在docker1.6之前支持;第二个版本则是使用go语言写的distribution。
本文结合distribution的运行命令执行的过程监督分析一下distribution的框架与启动流程。

我们先看一下distribution的架构图:


这里写图片描述

前面我们展示了distribution的架构图,从该图可以明确看到其实distribution就是一个能提供特殊文件(Manifest,Blob,Tag,Catalog<这几个也是相互关联的>)存储的http服务,因此其启动过程主要是根据配置参数创建后端存储服务以及对外服务的http server的过程。
下面我们来看一下distribution的Start UP过程。

我们的分析过程中忽略Configuration的解析,这里统一以本地存储的filesystem为例做简单分析。

总览

我们先来看一下distribution的main函数:

    func main() {        registry.RootCmd.Execute()    }

该Execute函数是spf13/cobra包的Command对应的Execute, 该函数会调用对应cmd的excute直至调用对应cmd的Run函数,具体的调用与分析流程这里不做详细描述,我们直接进入distribution的cmd——ServeCmd 来看。
运行distribution的时候输入的参数是./registry serve 参考个命令字确定该命令对应的是ServeCmd.

ServeCmd的定义如下:

    var ServeCmd = &cobra.Command{        Use:   "serve <config>",        Short: "`serve` stores and distributes Docker images",        Long:  "`serve` stores and distributes Docker images.",        Run: func(cmd *cobra.Command, args []string) {            // setup context            ctx := context.WithVersion(context.Background(), version.Version)            config, err := resolveConfiguration(args)            if err != nil {                fmt.Fprintf(os.Stderr, "configuration error: %v\n", err)                cmd.Usage()                os.Exit(1)            }            if config.HTTP.Debug.Addr != "" {                go func(addr string) {                    log.Infof("debug server listening %v", addr)                    if err := http.ListenAndServe(addr, nil); err != nil {                        log.Fatalf("error listening on debug interface: %v", err)                    }                }(config.HTTP.Debug.Addr)            }            registry, err := NewRegistry(ctx, config)            if err != nil {                log.Fatalln(err)            }            if err = registry.ListenAndServe(); err != nil {                log.Fatalln(err)            }        },    }  

由此进入Run函数, 该函数非常简单命令,首先创建一个Contex结构,接下来解析配置文件, 这两部分我们不做分析,有兴趣的自己去分析。
接下来才是该函数的核心内容,调用NewRegistry创建registry,这是这个函数的核心。创建了registry之后调用registry的ListenAndServe()函数开始对我提供http服务,这个我们之前的http相关的文章中已经分析过了,本篇不做分析。

New Registry

接下来来看NewRegistry:

    // NewRegistry creates a new registry from a context and configuration struct.    func NewRegistry(ctx context.Context, config *configuration.Configuration) (*Registry, error) {        var err error        ctx, err = configureLogging(ctx, config)        if err != nil {            return nil, fmt.Errorf("error configuring logger: %v", err)        }        // inject a logger into the uuid library. warns us if there is a problem        // with uuid generation under low entropy.        uuid.Loggerf = context.GetLogger(ctx).Warnf        app := handlers.NewApp(ctx, config)        // TODO(aaronl): The global scope of the health checks means NewRegistry        // can only be called once per process.        app.RegisterHealthChecks()        handler := configureReporting(app)        handler = alive("/", handler)        handler = health.Handler(handler)        handler = panicHandler(handler)        handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler)        server := &http.Server{            Handler: handler,        }        return &Registry{            app:    app,            config: config,            server: server,        }, nil    }  

从上面的代码来看主要是NewAPP,以及处理Handler,最后构建了Registry的结构,结合上面Run函数的分析以及distribution的总加工,这里主要就是初始化生产APP,然后以APP来构建了以Http Server的handler。
那么这最终需要的分析的对象就是:

  • handlers.NewApp(ctx, config)
  • app.RegisterHealthChecks()
  • handler := configureReporting(app)
  • handler = alive(“/”, handler)
  • handler = health.Handler(handler)
  • handler = panicHandler(handler)

这几个中我们首先分析NewAPP,这个函数也是整个distribution启动过程中最为关键的。

NewAPP

这才是整个流程中最关键的部分。
我们首先来看一下app的结构定义:

    type App struct {        context.Context        Config *configuration.Configuration        router           *mux.Router                 // main application router, configured with dispatchers        driver           storagedriver.StorageDriver // driver maintains the app global storage driver instance.        registry         distribution.Namespace      // registry is the primary registry backend for the app instance.        accessController auth.AccessController       // main access controller for application        // httpHost is a parsed representation of the http.host parameter from        // the configuration. Only the Scheme and Host fields are used.        httpHost url.URL        // events contains notification related configuration.        events struct {            sink   notifications.Sink            source notifications.SourceRecord        }        redis *redis.Pool        // trustKey is a deprecated key used to sign manifests converted to        // schema1 for backward compatibility. It should not be used for any        // other purposes.        trustKey libtrust.PrivateKey        // isCache is true if this registry is configured as a pull through cache        isCache bool        // readOnly is true if the registry is in a read-only maintenance mode        readOnly bool    }

从上面来看, 主要的结构对象有route、driver、registry、events个需要重点关注,其他的不能说不重要,只是相对好理解,但是并不需要太多的解析关注。
上面提到了events这个结构,这个结构主要是用来注册各种hook信息的,很重要但是很抱歉,我没有分析透彻,这里先不做分析说明。
这样我们下面的分析中重点针对的就是route,driver跟registry对象来进行分析了。
我们来看一下NewApp函数的内容吧:

    func NewApp(ctx context.Context, config *configuration.Configuration) *App {        app := &App{            Config:  config,            Context: ctx,            router:  v2.RouterWithPrefix(config.HTTP.Prefix),            isCache: config.Proxy.RemoteURL != "",        }        // Register the handler dispatchers.        app.register(v2.RouteNameBase, func(ctx *Context, r *http.Request) http.Handler {            return http.HandlerFunc(apiBase)        })        app.register(v2.RouteNameManifest, imageManifestDispatcher)        app.register(v2.RouteNameCatalog, catalogDispatcher)        app.register(v2.RouteNameTags, tagsDispatcher)        app.register(v2.RouteNameBlob, blobDispatcher)        app.register(v2.RouteNameBlobUpload, blobUploadDispatcher)        app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher)        // override the storage driver's UA string for registry outbound HTTP requests        storageParams := config.Storage.Parameters()        ……        var err error        app.driver, err = factory.Create(config.Storage.Type(), storageParams)        ……        app.configureSecret(config)        app.configureEvents(config)        app.configureRedis(config)        app.configureLogHook(config)        ……        if app.registry == nil {            // configure the registry if no cache section is available.            app.registry, err = storage.NewRegistry(app.Context, app.driver, options...)            if err != nil {                panic("could not create registry: " + err.Error())            }        }        ……        authType := config.Auth.Type()        ……        return app    }

完整的NewApp代码非常长,这里将其中的一些option的配置信息删除了,简单的贴出了一些最关键最主要的逻辑部分。
接下来就一组一组的分析其初始化部分。
首先其定义了一个app的结构体,并对其中的部分进行了初始化。

route注册

这里的route注册主要使用了app.register的函数来进行注册的。
我们来看其中一个register的过程,以app.register(v2.RouteNameManifest, imageManifestDispatcher) 为例:

app.router.GetRoute(routeName).Handler(app.dispatcher(dispatch))

这个函数仅仅只是网app.router.namedRoutes里面添加指定的Handler。
具体这些RouteName的添加在router: v2.RouterWithPrefix(config.HTTP.Prefix)添加过了。

对应的route描述信息在 routeDescriptors = []RouteDescriptor{……}, 请大家仔细看这里。

app.dispatcher(dispatch) 返回了一个新的hppt handler的方法,该方法是在dispatch方法前面添加了一些处理特殊option信息以及authoration信息处理。

        app.register(v2.RouteNameBase, func(ctx *Context, r *http.Request) http.Handler {            return http.HandlerFunc(apiBase)        })        app.register(v2.RouteNameManifest, imageManifestDispatcher)        app.register(v2.RouteNameCatalog, catalogDispatcher)        app.register(v2.RouteNameTags, tagsDispatcher)        app.register(v2.RouteNameBlob, blobDispatcher)        app.register(v2.RouteNameBlobUpload, blobUploadDispatcher)        app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher)

这段代码描述该app仅仅提供 RouteNameBase、RouteNameManifest、RouteNameCatalog、RouteNameTags、RouteNameBlob、RouteNameBlobUpload和RouteNameBlobUploadChunk路径的访问服务,对应的路径定义如下:

    const (        RouteNameBase            = "base"        RouteNameManifest        = "manifest"        RouteNameTags            = "tags"        RouteNameBlob            = "blob"        RouteNameBlobUpload      = "blob-upload"        RouteNameBlobUploadChunk = "blob-upload-chunk"        RouteNameCatalog         = "catalog"    )

每个路径对应的请求的不通方法(Get、Put、Patch、Head、Delete等)的真正处理函数在对应的dispatch里面定义的, 我们以RouteNameBlobUploadChunk为例为大家简单show一下:

    func blobUploadDispatcher(ctx *Context, r *http.Request) http.Handler {        buh := &blobUploadHandler{            Context: ctx,            UUID:    getUploadUUID(ctx),        }        handler := handlers.MethodHandler{            "GET":  http.HandlerFunc(buh.GetUploadStatus),            "HEAD": http.HandlerFunc(buh.GetUploadStatus),        }        if !ctx.readOnly {            handler["POST"] = http.HandlerFunc(buh.StartBlobUpload)            handler["PATCH"] = http.HandlerFunc(buh.PatchBlobData)            handler["PUT"] = http.HandlerFunc(buh.PutBlobUploadComplete)            handler["DELETE"] = http.HandlerFunc(buh.CancelBlobUpload)        }        if buh.UUID != "" {            ……        }        return handler    }

该函数中根据不通的方法指定了不通的http.Handler。

当上面的代码执行完成后,route中仅仅也就RouteNameBase、RouteNameManifest、RouteNameTags、RouteNameBlob 、RouteNameBlobUpload、RouteNameBlobUploadChunk 和RouteNameCatalog七条路由,真正请求发来的时候,会跳转到对应到对应的dispatch中再根据对应的方法找到对应的出路函数。

driver 初始化

我们直接看代码吧:

    storageParams := config.Storage.Parameters()    if storageParams == nil {        storageParams = make(configuration.Parameters)    }    storageParams["useragent"] = fmt.Sprintf("docker-distribution/%s %s", version.Version, runtime.Version())    var err error    app.driver, err = factory.Create(config.Storage.Type(), storageParams)

核心代码就两行,第一行获取storageParams, 这里不做解析。
我们来看factory.Create(config.Storage.Type(), storageParams):

    func Create(name string, parameters map[string]interface{}) (storagedriver.StorageDriver, error) {        driverFactory, ok := driverFactories[name]        if !ok {            return nil, InvalidStorageDriverError{name}        }        return driverFactory.Create(parameters)    }

这里的代码中直接根据name 获取driverFactory, 然后调用driverFactory.Create来创建storage的驱动。
name参数是我们启动的时候在config.yml中配置的storage的项,会有 filesystem, swift, s3等可以指定。
driverFactory.Create是各自存储驱动的Create函数,属于存储驱动层的东西,这里也不做分析。

有人为问道driverFactories[name] 啥时候初始化的,为啥就能够直接获取到。 这里涉及到Golang 的init()的调用机制,在main()对应的文件中import了所有的后端驱动包,每个驱动包的init()函数都会自动执行,在init()函数中注册了各自的driverFactory。

我们用filesystem为例来看一下:

onst (    driverName           = "filesystem"    defaultRootDirectory = "/var/lib/registry"    defaultMaxThreads    = uint64(100)    // minThreads is the minimum value for the maxthreads configuration    // parameter. If the driver's parameters are less than this we set    // the parameters to minThreads    minThreads = uint64(25))func init() {    factory.Register(driverName, &filesystemDriverFactory{})}// filesystemDriverFactory implements the factory.StorageDriverFactory interfacetype filesystemDriverFactory struct{}

这样app.driver初始化成功。

app.registry 初始化

我们来看一下app.registry的初始化过程,从上面的额代码来看:

    app.registry=storage.NewRegistry(app.Context, app.driver, options...)

直接看storage.NewRegistry:

    NewRegistry(ctx context.Context, driver storagedriver.StorageDriver, options ...RegistryOption) (distribution.Namespace, error) {        // create global statter        statter := &blobStatter{            driver: driver,        }        bs := &blobStore{            driver:  driver,            statter: statter,        }        registry := &registry{            blobStore: bs,            blobServer: &blobServer{                driver:  driver,                statter: statter,                pathFn:  bs.path,            },            statter:                statter,            resumableDigestEnabled: true,        }        for _, option := range options {            if err := option(registry); err != nil {                return nil, err            }        }        return registry, nil    }

这个函数非常简单明了, 基本上没有啥函数调用,仅仅只是结构体赋值, 这里赋值所有对象前面都初始化过了。 这个函数的内容非常重要,是后期request处理的时候找到存储的最终的结构之一。

app.RegisterHealthChecks()

没做分析

configureReporting(app)

该函数的内容非常简单,但却非常重要且关键。 主要就是定义了handler = app, 然后将handler返回,从而完成了从app到handler的转换。 这里就不再贴出它的代码了。

alive(“/”, handler)

这个函数定义了一个”/”为请求URL路径的一个http request的handler函数,其实是registry的探活接口,非常简单。

health.Handler(handler)

没做分析

panicHandler(handler)

该函数定义了再http请求处理过程中出错的时候会抛出panic,从而重定向到这里。
该函数里面回调recover()从而不会panic。


至此APP, handler构建完成,包括其对应的各种request请求的handler,后端存储初始化都已完成。
接下来:

        server := &http.Server{            Handler: handler,        }        return &Registry{            app:    app,            config: config,            server: server,        }, nil

这几行代码定义了http server以及Registry对象,所有的初始化工作已全部做完,举例服务的运行仅仅剩下http server的ListenAndServe了。

registry.ListenAndServe()

这个函数的内容之前的”golang http源码解析”分析过,就不再重复了。


至此Distribution的Start Up过程分析完成。

原创粉丝点击