docker命令之push

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 + name
	hostname, _, err := registry.ResolveRepositoryName(remote)
	if err != nil {
		return err
	}
	// Resolve the Auth config relevant for this server
	authConfig := 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()].Username
		if 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 header
		authJson := 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 empty
			authConfig = &registry.AuthConfig{}
		}
	} else {
		// the old format is supported for compatibility if there was no authConfig header
		if 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 := 1
		if 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 repository
		if 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 []string
	job.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)

發佈了17 篇原創文章 · 獲贊 4 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章