这一篇文章分析一下docker push的过程;docker push是将本地的镜像上传到registry service的过程;

根据前几篇文章,可以知道客户端的命令是在api/client/push.go中,CmdPush()函数:

基本思路就是将通过解析cmd.Arg(0)参数,提取去要push的镜像的repository 和 tag,通过registry 和 repository获得repostoryInfo;如果需要安全验证,那么还要设置一下authConfig;接着通过POST:/images/xxxx/push? 请求调用server端的postImagesPush()函数;(在api/server/image.go)中,主要来分析一下这个函数:

func (s *Server) postImagesPush(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string)   error {

if vars == nil {

return fmt.Errorf("Missing parameter")

}

metaHeaders := map[string][]string{}

for k, v := range r.Header {

if strings.HasPrefix(k, "  ") {

metaHeaders[k] = v

}

}

if err := parseForm(r); err != nil {

return err

}

authConfig := &cliconfig.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 = &cliconfig.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 fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err)

}

}

name := vars["name"]

output := ioutils.NewWriteFlusher(w)

imagePushConfig := &graph.ImagePushConfig{

MetaHeaders: metaHeaders,

AuthConfig:  authConfig,

Tag:         r.Form.Get("tag"),

OutStream:   output,

}

w.Header().Set("Content-Type", "application/json")

if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil {

if !output.Flushed() {

return err

}

sf := streamformatter.NewJSONStreamFormatter()

output.Write(sf.FormatError(err))

}

return nil

}

最主要的流程是前面是封装好 imagePushConfig 这个数据结构,imagePushConfig主要包括http request中的header信息、镜像tag、认证信息以及一个回写客户端的output Writer(),然后调用s.daemon.Repositories().Push(name, imagePushConfig) 来进行镜像文件的上传;之前的文章中介绍过,deamon的Repositories()函数返回的其实是TagStore结构(/graph/tags.go 文件中);直接去看一下TagStore()的Push函数:

// Push initiates a push operation on the repository named localName.

func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) error {

// FIXME: Allow to interrupt current push when new push of same image is done.

var sf = streamformatter.NewJSONStreamFormatter()

// Resolve the Repository name from fqn to RepositoryInfo

repoInfo, err := s.registryService.ResolveRepository(localName)

if err != nil {

return err

}

endpoints, err := s.registryService.LookupPushEndpoints(repoInfo.CanonicalName)

if err != nil {

return err

}

reposLen := 1

if imagePushConfig.Tag == "" {

reposLen = len(s.Repositories[repoInfo.LocalName])

}

imagePushConfig.OutStream.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.        CanonicalName, reposLen))

// If it fails, try to get the repository

localRepo, exists := s.Repositories[repoInfo.LocalName]

if !exists {

return fmt.Errorf("Repository does not exist: %s", repoInfo.LocalName)

}

var lastErr error

for _, endpoint := range endpoints {

logrus.Debugf("Trying to push %s to %s %s", repoInfo.CanonicalName, endpoint.URL, endpoint.Version)

pusher, err := s.NewPusher(endpoint, localRepo, repoInfo, imagePushConfig, sf)

if err != nil {

lastErr = err

continue

}

if fallback, err := pusher.Push(); err != nil {

if fallback {

lastErr = err

continue

}

logrus.Debugf("Not continuing with error: %v", err)

return err

}

s.eventsService.Log("push", repoInfo.LocalName, "")

return nil

}

if lastErr == nil {

lastErr = fmt.Errorf("no endpoints found for %s", repoInfo.CanonicalName)

}

return lastErr

}

首先通过localName获取repositoryInfo,然后通过repositoryInfo获取endpoints,然后针对每一个endpoint,实例化一个pusher,通过pusher.Push()来实现镜像的上传,程序的流程和结构很好理解;看一下NewPusher()的代码:

func (s *TagStore) NewPusher(endpoint registry.APIEndpoint, localRepo Repository, repoInfo *registry.RepositoryInfo,       imagePushConfig *ImagePushConfig, sf *streamformatter.StreamFormatter) (Pusher, error) {

switch endpoint.Version {

case registry.APIVersion2:

return &v2Pusher{

TagStore:  s,

endpoint:  endpoint,

localRepo: localRepo,

repoInfo:  repoInfo,

config:    imagePushConfig,

sf:        sf,

}, nil

case registry.APIVersion1:

return &v1Pusher{

TagStore:  s,

endpoint:  endpoint,

localRepo: localRepo,

repoInfo:  repoInfo,

config:    imagePushConfig,

sf:        sf,

}, nil

}

return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)

}

主要是根据endpoint中version的不同,来决定实例化哪个pushser;暂且以v2 pusher()为例来进行分析;

type v2Pusher struct {

*TagStore

endpoint  registry.APIEndpoint

localRepo Repository

repoInfo  *registry.RepositoryInfo

config    *ImagePushConfig

sf        *streamformatter.StreamFormatter

repo      distribution.Repository

}

这个是v2Pusher的定义;接下来是v2Pusher的Push(/graph/push_v2.go文件)函数:

func (p *v2Pusher) Push() (fallback bool, err error) {

p.repo, err = NewV2Repository(p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig)

if err != nil {

logrus.Debugf("Error getting v2 registry: %v", err)

return true, err

}

return false, p.pushV2Repository(p.config.Tag)

}

首先实例化出来NewV2Repository实例(/graph/registry.go文件中)复制给pusher的repo实例,然后调用pusher的pushV2Repository函数进行镜像的上传;新的pusher.repo实例,也就是NewV2Repository返回的其实是一个新的repository(vendor/src/github.com/docker/distribution/registry/client/repository.go文件中),它主要新建了一个提供了超时设定、权限验证和对远程endpoint的api version进行验证的http transport,负责镜像的上传;接着看一下最重要的pusher的pushV2Repository函数;

func (p *v2Pusher) pushV2Repository(tag string) error {

localName := p.repoInfo.LocalName

if _, err := p.poolAdd("push", localName); err != nil {

return err

}

defer p.poolRemove("push", localName)

tags, err := p.getImageTags(tag)

if err != nil {

return fmt.Errorf("error getting tags for %s: %s", localName, err)

}

if len(tags) == 0 {

return fmt.Errorf("no tags to push for %s", localName)

}

for _, tag := range tags {

if err := p.pushV2Tag(tag); err != nil {

return err

}

}

return nil

}

调用TagStore的poolAdd函数,将要下载的的名字加入的TagStore的pushingPool里面,保证同一时刻只有一个同名镜像在下载;通过getImageTags(tag)获取对应tag的要下载的镜像,实际上使用pusher的localRepo中查找对应tag的镜像,localRepo 是Repository类型,更确切说是map[string]string类型,key为tag,value为layerID;如果没有传过来的tag为空的话,则返回所有的镜像;接着分析p.pushV2Tag()函数

func (p *v2Pusher) pushV2Tag(tag string) error {

logrus.Debugf("Pushing repository: %s:%s", p.repo.Name(), tag)

layerID, exists := p.localRepo[tag]

if !exists {

return fmt.Errorf("tag does not exist: %s", tag)

}

layersSeen := make(map[string]bool)

layer, err := p.graph.Get(layerID)

if err != nil {

return err

}

m := &manifest.Manifest{

Versioned: manifest.Versioned{

SchemaVersion: 1,

},

Name:         p.repo.Name(),

Tag:          tag,

Architecture: layer.Architecture,

FSLayers:     []manifest.FSLayer{},

History:      []manifest.History{},

}

var metadata runconfig.Config

if layer != nil && layer.Config != nil {

metadata = *layer.Config

}

out := p.config.OutStream

for ; layer != nil; layer, err = p.graph.GetParent(layer) {

if err != nil {

return err

}

if layersSeen[layer.ID] {

break

}

logrus.Debugf("Pushing layer: %s", layer.ID)

if layer.Config != nil && metadata.Image != layer.ID {

if err := runconfig.Merge(&metadata, layer.Config); err != nil {

return err

}

}

jsonData, err := p.graph.RawJSON(layer.ID)

if err != nil {

return fmt.Errorf("cannot retrieve the path for %s: %s", layer.ID, err)

}

var exists bool

dgst, err := p.graph.GetDigest(layer.ID)

switch err {

case nil:

_, err := p.repo.Blobs(context.Background()).Stat(context.Background(), dgst)

switch err {

case nil:

exists = true

out.Write(p.sf.FormatProgress(stringid.TruncateID(layer.ID), "Image already exists", nil))

case distribution.ErrBlobUnknown:

// nop

default:

out.Write(p.sf.FormatProgress(stringid.TruncateID(layer.ID), "Image push failed", nil))

return err

}

case ErrDigestNotSet:

// nop

case digest.ErrDigestInvalidFormat, digest.ErrDigestUnsupported:

return fmt.Errorf("error getting image checksum: %v", err)

}

// if digest was empty or not saved, or if blob does not exist on the remote repository,

// then fetch it.

if !exists {

if pushDigest, err := p.pushV2Image(p.repo.Blobs(context.Background()), layer); err != nil {

return err

} else if pushDigest != dgst {

// Cache new checksum

if err := p.graph.SetDigest(layer.ID, pushDigest); err != nil {

return err

}

dgst = pushDigest

}

}

m.FSLayers = append(m.FSLayers, manifest.FSLayer{BlobSum: dgst})

m.History = append(m.History, manifest.History{V1Compatibility: string(jsonData)})

layersSeen[layer.ID] = true

}

logrus.Infof("Signed manifest for %s:%s using daemon's key: %s", p.repo.Name(), tag, p.trustKey.KeyID())

signed, err := manifest.Sign(m, p.trustKey)

if err != nil {

return err

}

manifestDigest, manifestSize, err := digestFromManifest(signed, p.repo.Name())

if err != nil {

return err

}

if manifestDigest != "" {

out.Write(p.sf.FormatStatus("", "%s: digest: %s size: %d", tag, manifestDigest, manifestSize))

}

manSvc, err := p.repo.Manifests(context.Background())

if err != nil {

return err

}

return manSvc.Put(signed)

}

pushV2Tag()函数比较长,从上到下一点点分析下:首先根据tag从localRepo中找到LayerID,然后通过graph.Get()通过layerID返回layer,实际是一个image结构;layerSeen是一个map,表示的哪些已经被push过了,以免重复;接下来实例化一个manifest结构,manifest主要记录镜像的所有layer信息(包括镜像的校验和以及镜像jsondata中的信息);

接着就是一个for 循环,从一个layer中不断的取得这个layer的父镜像;然后上传;上传镜像之前,先通过graph获取这个镜像的jsondata和digest校验和,通过 p.repo.Blobs(context.Background()).Stat(context.Background(), dgst) 判断校验和在将要上传的地点是否已经存在,存在的话则打印出相关提示信息,不存在的情况则会调用  p.pushV2Image() 去上传镜像;上传完镜像后会生成新的digest,接着调用

m.FSLayers = append(m.FSLayers, manifest.FSLayer{BlobSum: dgst})

m.History = append(m.History, manifest.History{V1Compatibility: string(jsonData)})

layersSeen[layer.ID] = true

这三行代码,将layer信息放到manifest文件中,并且设置layerSeen标明这个layer已经上传过了;

当所有的镜像上传完毕后,接着对manifest文件进行签名、生成manifest文件的digest信息、然后通过repo将manifest信息上传;

以上就是docker push的大致步骤

docker push 实现过程的更多相关文章

  1. docker push到私有仓库

    1.登录 docker login http://xxxxx.com 2.登录私有hub创建项目 例如项目叫:abc-dev 2.给镜像打tag docker tag 2e25d8496557 xxx ...

  2. s11 Docker+DevOps实战--过程和工具

    开发人员本地提交代码,本地使用容器模拟生产环境测试,测试通过提交到git master 分支,就会触发pipeline执行集成构建.集成工具: gitlab-vi,travis,或Jenkins.自动 ...

  3. 通过容器提交镜像(docker commit)以及推送镜像(docker push)笔记

    在本地创建一个容器后,可以依据这个容器创建本地镜像,并可把这个镜像推送到Docker hub中,以便在网络上下载使用. 查看镜像 [root@docker-test1 ~]# docker image ...

  4. docker push images login -u harbor 问题记录 https 证书

    1.[root@dev-100 Desktop]# docker login -u clouder -p engine harbor.xiaowei.com 2.docker tag busybox: ...

  5. “unauthorized: authentication required” -- openshift3.9 docker push 报错

    1.docker tag mybank-tomcat:latest 172.30.25.185:5000/mybank-tomcat:latest 2.当往registrypush镜像的时候, doc ...

  6. docker push 镜像到本地仓库

    root@ubuntu:# uname -a Linux ubuntu --generic #-Ubuntu SMP Mon Feb :: UTC x86_64 x86_64 x86_64 GNU/L ...

  7. Docker Push 镜像到公共仓库

    首选需要在https://hub.docker.com/上注册用户. 1.登录docker账号主要命令:docker login sudo docker login 2.推送镜像主要命令:docker ...

  8. docker push

    一.确保docker hub上有账号 二.确认要提交的镜像的命名空间为自己账号名称 三.在本地登录docker: docker login 四.提交镜像: docker push zhengchuzh ...

  9. docker push dial tcp *.*.*.*:443 getsockopt: connection refused

    docker 在提交镜像的时候出现以下错误. 我用的是本地的仓库,所以tcp后面是我的ip地址. 错误信息: #docker push ubuntu docker push  dial tcp 192 ...

随机推荐

  1. laraver mongo 查询操作

    1,mongo 不支持特殊where条件(&,|) 2,mongo 可以连接mysql的表查询,但不支持连表的where查询

  2. js页面跳转(含框架跳转)整理

    js方式的页面跳转1.window.location.href方式    <script language="javascript" type="text/java ...

  3. 如何在VISIO 2010/2013 中关闭Shape protection(图形保护)

    最近在画UML图,用到MS visio 2010, 在使用一些网络查找到的图形的时候发现无法编辑,在网上找了找,翻译了下. Visio 2013 的图形保护功能,可以锁定图形的某些特定属性,使其无法被 ...

  4. ADSafe净网大师----所谓的去广告神器竟然在偷偷推送广告

    今天刚开发完的网站上线联调, 偶然发现<head>里多了一个脚本引用: <script async src="http://c.cnzz.com/core.php" ...

  5. java实现的类和表持久化

    //映射的过程: package com.ly.orm; import java.lang.reflect.Field; import java.util.ArrayList; import java ...

  6. [ios学习笔记之视图、绘制和手势识别]

    一 视图 二 绘制 三 手势 00:31 UIGestureRecognizer 抽象类 两步 1添加识别器(控制器或者视图来完成) 2手势识别后要做的事情 UIPanGestureRecognize ...

  7. PHP5.3.3+Apache2.2.16+MySQL5.1.49

    轻松配置PHP5.3.3+Apache2.2.16+MySQL5.1.49,下面是有详细的步骤说明.   第一步:下载安装的文件 1. MySQL:下载地址mysql-5.1.49-win32.msi ...

  8. 不断弹出svchost.exe错误框

    同事的一台电脑,xp系统,启动后就弹出svchost错误的对话框,不论确定还是取消,关闭后立刻又弹出. 打开任务管理器,尝试对一些后安装的软件结束进程,结束一个,关闭一次,看看结束哪一个,关闭后不再弹 ...

  9. vbscript input select 添加个option根据value值到指定位置--相当于排序

    '添加option到指定位置(按value排序) dim valindex valindex=-1 for i=0 to selcom.length-1 if selcom.Options(i).va ...

  10. maven+springmvc+spring+mybatis+velocity整合

      一.ssmm简介 ssmm是当下企业最常用的开发框架架构 maven:管理项目jar包,构建项目 spring:IOC容器,事务管理 springmvc:mvc框架 myBatis:持久层框架 v ...