docker命令之push

来源:互联网 发布:js判断两个字符串相等 编辑:程序博客网 时间:2024/06/07 22:24

1 背景

NAME
       docker-push - Push an image or a repository to the registry

SYNOPSIS
       docker push NAME[:TAG]
DESCRIPTION
       Push  an  image  or  a  repository  to a registry.  The default registry is the Docker Index located at index.docker.io
       (https://index.docker.io/v1/).  However the image can be pushed to another, perhaps private, registry  as  demonstrated
       in the example below.

本文以私有registry为例

registry地址:127.0.0.1

port:5000

namespace:name

repository:repo

tag:空

docker version:1.0

docker registry:0.9

2 代码分析

docker push 127.0.0.1:5000/namespace/repo

2.1 docker client发起push请求

代码位置:api/client/command.go:CmdPush

此方法是从docker命令行接收参数,并分析命令行所给的参数,给docker http server发送请求。主要解析的是127.0.0.1:5000/namespace/repo, 从这个参数中解析出registry的地址 127.0.0.1:5000,namespace:namespace,repository:repo,tag:空,另外从HOME命令下的.dockercfg中得到auth和email,其中auth是username和password的加密。两者用于在registry端进行认证,如果找不到.dockercfg文件,docker客户端会使用空间的user和password发送给registry端。

获取命令行参数,其中name=127.0.0.1:5000/namespace/repo

name := cmd.Arg(0)if name == "" {cmd.Usage()return nil}
解析HOME目录下的.dockercfg文件。首先根据命令行参数的name,解析出remote和tag,此例中,remote=127.0.0.1:5000/namespce/repo,tag=空

再从remote中解析出hostname=127.0.0.1:5000,然后从.dockercfg中找到hostname对应的项,.dockercfg这是以key:value方式存储的,key就是registry的hostname,value就是auth和email。根据“/”解析namespace和repository是否合法。

<span style="white-space:pre"></span>cli.LoadConfigFile()remote, tag := parsers.ParseRepositoryTag(name)// Resolve the Repository name from fqn to hostname + namehostname, _, err := registry.ResolveRepositoryName(remote)if err != nil {return err}// Resolve the Auth config relevant for this serverauthConfig := cli.configFile.ResolveAuthConfig(hostname)// If we're not using a custom registry, we know the restrictions// applied to repository names and can warn the user in advance.// Custom repositories can have different rules, and we must also// allow pushing by image ID.if len(strings.SplitN(name, "/", 2)) == 1 {username := cli.configFile.Configs[registry.IndexServerAddress()].Usernameif username == "" {username = "<user>"}return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", username, name)}
在获取的PUSH http请求的要素后,构建push动作的执行体。将认证信息放在“X-Registry-Auth”头中。

push := func(authConfig registry.AuthConfig) error {buf, err := json.Marshal(authConfig)if err != nil {return err}registryAuthHeader := []string{base64.URLEncoding.EncodeToString(buf),}return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{"X-Registry-Auth": ,})}

如果push请求失败,根据状态码是否是401来决定是否发起login请求。在login请求中同样携带auth认证信息。

if err := push(authConfig); err != nil {if strings.Contains(err.Error(), "Status 401") {fmt.Fprintln(cli.out, "\nPlease login prior to push:")if err := cli.CmdLogin(hostname); err != nil {return err}authConfig := cli.configFile.ResolveAuthConfig(hostname)return push(authConfig)}return err}

2.2 docker httpserver处理push请求

push命令在httpserver的路由项在api/server/server.go中有createRouter方法,它定义了各种请求对应的handler。其中"/images/{name:.*}/push":postImagesPush,在这个方法中,主要解析docker client过来的请求,并设置job环境变量,为job启动做准备。postImagesPush的主要任务是解析从客户端过来的请求,准备job的环境变量,启动一个job。


解析X-Rigestry-Auth头信息,如何没有此头,使用空的auth信息。

authConfig := &registry.AuthConfig{}authEncoded := r.Header.Get("X-Registry-Auth")if authEncoded != "" {// the new format is to handle the authConfig as a headerauthJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {// to increase compatibility to existing api it is defaulting to be emptyauthConfig = &registry.AuthConfig{}}} else {// the old format is supported for compatibility if there was no authConfig headerif err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {return err}}
构建job运行环境,并启动,这个job也会有engine router找到对应的handler,有handler做实际的执行动作。

job := eng.Job("push", vars["name"])job.SetenvJson("metaHeaders", metaHeaders)job.SetenvJson("authConfig", authConfig)job.Setenv("tag", r.Form.Get("tag"))if version.GreaterThan("1.0") {job.SetenvBool("json", true)streamJSON(job, w, true)} else {job.Stdout.Add(utils.NewWriteFlusher(w))}if err := job.Run(); err != nil {if !job.Stdout.Used() {return err}sf := utils.NewStreamFormatter(version.GreaterThan("1.0"))w.Write(sf.FormatError(err))}


</pre><h2><span style="font-family:Courier New">2.3 push job</span></h2>
push job的执行体放在graph/push.go文件中。在docker启动阶段,会注册push job的handler到engine中,关于graph的job处理handler都放在graph中的service.go的install方法中。
job handler的注册,push使用CmdPush handler。
 
func (s *TagStore) Install(eng *engine.Engine) error {for name, handler := range map[string]engine.Handler{"image_set":      s.CmdSet,"image_tag":      s.CmdTag,"tag":            s.CmdTagLegacy, // FIXME merge with "image_tag""image_get":      s.CmdGet,"image_inspect":  s.CmdLookup,"image_tarlayer": s.CmdTarLayer,"image_export":   s.CmdImageExport,"history":        s.CmdHistory,"images":         s.CmdImages,"viz":            s.CmdViz,"load":           s.CmdLoad,"import":         s.CmdImport,"pull":           s.CmdPull,"push":           s.CmdPush,} {if err := eng.Register(name, handler); err != nil {return fmt.Errorf("Could not register %q: %v", name, err)}}return nil}
 
CmdPush handler放在了graph/push.go文件中。这个handler主要完成想registry发起请求的任务。为构建项registry请求解析job环境变量中的参数。
 
var (localName   = job.Args[0]sf          = utils.NewStreamFormatter(job.GetenvBool("json"))authConfig  = &registry.AuthConfig{}metaHeaders map[string][]string)tag := job.Getenv("tag")job.GetenvJson("authConfig", authConfig)job.GetenvJson("metaHeaders", &metaHeaders)
解析registry的hostname和namespace、repository,hostname为registry的地址"127.0.0.1:5000", remoteName为"/namespace/repo"
 
 
hostname, remoteName, err := registry.ResolveRepositoryName(localName)
检查registry是否可用,将hostname组装成http或https的url,做ping操作,检查是否可用
 
 
endpoint, err := registry.ExpandAndVerifyRegistryUrl(hostname)
获取需要push的镜像的graph,graph会单独用一篇文章介绍。
 
 
img, err := s.graph.Get(localName)
 
构建发向registry的请求
 
r, err2 := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false)if err2 != nil {return job.Error(err2)}
 
如果没有命令行中没有tag,会push所有repository下的镜像,如果有tag,只push tag对应的repository的镜像
 
if err != nil {reposLen := 1if tag == "" {reposLen = len(s.Repositories[localName])}job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen))// If it fails, try to get the repositoryif localRepo, exists := s.Repositories[localName]; exists {if err := s.pushRepository(r, job.Stdout, localName, remoteName, localRepo, tag, sf); err != nil {return job.Error(err)}return engine.StatusOK}return job.Error(err)}
 
 
var token []stringjob.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", localName))if _, err := s.pushImage(r, job.Stdout, remoteName, img.ID, endpoint, token, sf); err != nil {return job.Error(err)}

3 协议流程

3.1偷偷的ping
3.2 client上传镜像的lay id和tag信息
3.3 client端registry请求lay id为5111的lay json数据,如果registry有此id的json数据,说明registry有此lay,registry给client返回200 OK
如果没有,registry返回给client 404,client会将此lay的json,lay和checksum上传给registry
对image的所有lay做上述操作。
3.4上传image的tag信息,将tag所在的lay id给registry
3.5client最后做put请求images,但里面什么也没有带,registry发现是空的,就返回给client 204 NO CONTENT,就此push结束(这个地方有点LOW)
0 0