docker push 過程 distribution源碼 分析

docker push 過程 distribution源碼分析

承接上一篇“distribution structure and start up 分析”本文分析一下distribution在docker push時候的處理流程。所寫內容爲個人對distribution registry的理解,如有錯誤還請各位指出以便更正。
本文所涉及的存儲信息是以本地文件系統爲例進行分析說明。

在分析之前我先根據我的理解以本地存儲爲例對distribution的存儲目錄結構進行簡單說明。

distribution 存儲目錄分析說明

這裏以我之前搭建的一個鏡像倉庫爲例,有一個鏡像兩個tag, 簡單說明distribution的存儲目錄信息:

    ├── blobs
    │   └── sha256
    │       ├── 12
    │       │   └── 124a9bb461f0106581e9e0e76a1edd39076b62ff80c3608676f5a6e057326b10
    │       │       └── data
    │       ├── 19
    │       │   └── 194529caae55f1ec32d99ae611bd52a5a25b2d20f4c49094598b3cfecb459f55
    │       │       └── data
    │       ├── 27
    │       │   └── 276470c32842665461864314d57dd44bb3c56f074794ac3535493f329c258239
    │       │       └── data
    │       ├── 29
    │       │   └── 298839ed00f4384909cc62cb14ea994a6d170efac760c304f7e28662b970ba0d
    │       │       └── data
    │       ├── 34
    │       │   └── 3458458e7203e6bdc1e1ae94d9ba554af73db82334c5851d599dd435cc6bd301
    │       │       └── data
    │       ├── 3c
    │       │   ├── 3ca07c56cae5ba32650b237f4263cb382241373ef8bdb4fdf3159f8f7aa222eb
    │       │   │   └── data
    │       │   ├── 3cb08336d912fcc35c3cd618459c2e6803b20f505da1255a75b7fea4819b8a1d
    │       │   │   └── data
    │       │   └── 3cebd0f4bc415e136cda07dd211c759deadec4e95ee0740fefce5c0b70b28a55
    │       │       └── data
    │       ├── 42
    │       │   ├── 42a9aa022025cae4f696bb839f61adbf04fe7e4fbd9e600ed6fd784c4e27ef5f
    │       │   │   └── data
    │       │   └── 42ec43196b29fd4e3b17c6d04ff2fdb1069f8f7d21a2c44da9cc29d401f751a1
    │       │       └── data
    │       ├── 4f
    │       │   └── 4fd5388148cb60d9180d862df0440aa66cb1e330aab7cce785218f761ef24061
    │       │       └── data
    │       ├── 53
    │       │   └── 53b2590d0c3fef44882e645c79542b05caab1b5acf21968ba162e28a1b02d1a4
    │       │       └── data
    │       ├── 54
    │       │   └── 5461c243803b776a461d0eac87018660c977af1d10bf4e5ce95911b82054685d
    │       │       └── data
    │       ├── 65
    │       │   └── 65e4379b47f129da1cc399e68388c590ef024e55290a0ef5b82b63a1f18baf13
    │       │       └── data
    │       ├── 67
    │       │   └── 67e8f3430c3f11993877ca7fcf200fcb6b6514020c5f202a700dd28b6a122a2a
    │       │       └── data
    │       ├── 79
    │       │   └── 796c35f1fde2fcf458b1f25ebb68534f6bc028bad43c58172b240420b254b75d
    │       │       └── data
    │       ├── 8f
    │       │   └── 8f8a7dd64b5eabfb8aa1aa20ac6cb5db1ae79769f7bb69177c35979fae8714fa
    │       │       └── data
    │       ├── 94
    │       │   └── 941ac47011daa91b282b2a1b5e2cfd31a2a533295aa0d01e48fc41484658d764
    │       │       └── data
    │       ├── a1
    │       │   └── a1cd489c1ab8e51df8547a534524fd557ef83c00da88d970f241432347fb4bfe
    │       │       └── data
    │       ├── a5
    │       │   └── a52d7cc561ce3f840778d712a9458b3bd9f4f5cf9d9a0fa421d58eae10a46be4
    │       │       └── data
    │       ├── b2
    │       │   └── b296475482e5a3cbf3b2d21a4eb3c198dca9a638d1ad7cb548fd5eb9df38fb18
    │       │       └── data
    │       ├── b4
    │       │   └── b4fa55a79843e5e711d313127221832295cfc62a2b2de3f8e72c77c851b65201
    │       │       └── data
    │       ├── b5
    │       │   └── b5fa36c7626337446af4dd66d6ca7040c5061a708502b25c7e2a315b0dc0fd2d
    │       │       └── data
    │       ├── b9
    │       │   └── b975216c4476e4120537f4b04c080fbf8f71e5a8a303d920d23ed43d1a291723
    │       │       └── data
    │       ├── c2
    │       │   └── c2a8c6084659ec5616e58986f94f59fc0f8d1537300854b70420262d370a14df
    │       │       └── data
    │       ├── c3
    │       │   ├── c30f55aa7914fdce4ccca196601df27b168f04bdd26f067fb2be55e9ca7d3cc2
    │       │   │   └── data
    │       │   └── c3683c0954721b5a7f7932673024f818a6b1dd09f3d923d11f80710d22a37a8b
    │       │       └── data
    │       ├── c4
    │       │   └── c4db43cd7e2080e418138b4fd25e10e540f48eee55906b1332df7986bfc07320
    │       │       └── data
    │       ├── c5
    │       │   └── c5ee94bc35bc65a59cf7fb650a9a57ce9fdb79176c2f707251cb4cdd9f1ead0a
    │       │       └── data
    │       ├── c6
    │       │   └── c6d4161c3d2db5a0dff55e67665ad252dc6a244745d4d28748a33b0eb9016ac5
    │       │       └── data
    │       ├── d8
    │       │   └── d83791fefdeb24a924f16bf4b1e4e6a555f68a167e730a5a61e5b123d31114f4
    │       │       └── data
    │       ├── dd
    │       │   └── dd5c8c14a21cf99b3a69ba520d71058630d35ad474251358700830a4596c62aa
    │       │       └── data
    │       ├── e7
    │       │   └── e7c637000dfa093cd047d63321b0d722540152f2fe23edc4726893dc55afc89f
    │       │       └── data
    │       ├── f1
    │       │   └── f158dd228292a8c1eab511d3f05d50260a589d12f21c1a960e91de8dac600d6d
    │       │       └── data
    │       └── fc
    │           └── fce479c28d5681384afbf8e7c4bb5cb3e41c20b6e8d359eb4b8d7527f79e1002
    │               └── data
    └── repositories
        └── ddcmao
            └── java8
                ├── _layers
                │   └── sha256
                │       ├── 124a9bb461f0106581e9e0e76a1edd39076b62ff80c3608676f5a6e057326b10
                │       │   └── link
                │       ├── 194529caae55f1ec32d99ae611bd52a5a25b2d20f4c49094598b3cfecb459f55
                │       │   └── link
                │       ├── 276470c32842665461864314d57dd44bb3c56f074794ac3535493f329c258239
                │       │   └── link
                │       ├── 298839ed00f4384909cc62cb14ea994a6d170efac760c304f7e28662b970ba0d
                │       │   └── link
                │       ├── 3458458e7203e6bdc1e1ae94d9ba554af73db82334c5851d599dd435cc6bd301
                │       │   └── link
                │       ├── 3ca07c56cae5ba32650b237f4263cb382241373ef8bdb4fdf3159f8f7aa222eb
                │       │   └── link
                │       ├── 3cb08336d912fcc35c3cd618459c2e6803b20f505da1255a75b7fea4819b8a1d
                │       │   └── link
                │       ├── 3cebd0f4bc415e136cda07dd211c759deadec4e95ee0740fefce5c0b70b28a55
                │       │   └── link
                │       ├── 42a9aa022025cae4f696bb839f61adbf04fe7e4fbd9e600ed6fd784c4e27ef5f
                │       │   └── link
                │       ├── 42ec43196b29fd4e3b17c6d04ff2fdb1069f8f7d21a2c44da9cc29d401f751a1
                │       │   └── link
                │       ├── 4fd5388148cb60d9180d862df0440aa66cb1e330aab7cce785218f761ef24061
                │       │   └── link
                │       ├── 53b2590d0c3fef44882e645c79542b05caab1b5acf21968ba162e28a1b02d1a4
                │       │   └── link
                │       ├── 5461c243803b776a461d0eac87018660c977af1d10bf4e5ce95911b82054685d
                │       │   └── link
                │       ├── 65e4379b47f129da1cc399e68388c590ef024e55290a0ef5b82b63a1f18baf13
                │       │   └── link
                │       ├── 67e8f3430c3f11993877ca7fcf200fcb6b6514020c5f202a700dd28b6a122a2a
                │       │   └── link
                │       ├── 796c35f1fde2fcf458b1f25ebb68534f6bc028bad43c58172b240420b254b75d
                │       │   └── link
                │       ├── 8f8a7dd64b5eabfb8aa1aa20ac6cb5db1ae79769f7bb69177c35979fae8714fa
                │       │   └── link
                │       ├── 941ac47011daa91b282b2a1b5e2cfd31a2a533295aa0d01e48fc41484658d764
                │       │   └── link
                │       ├── a1cd489c1ab8e51df8547a534524fd557ef83c00da88d970f241432347fb4bfe
                │       │   └── link
                │       ├── a52d7cc561ce3f840778d712a9458b3bd9f4f5cf9d9a0fa421d58eae10a46be4
                │       │   └── link
                │       ├── b296475482e5a3cbf3b2d21a4eb3c198dca9a638d1ad7cb548fd5eb9df38fb18
                │       │   └── link
                │       ├── b4fa55a79843e5e711d313127221832295cfc62a2b2de3f8e72c77c851b65201
                │       │   └── link
                │       ├── b5fa36c7626337446af4dd66d6ca7040c5061a708502b25c7e2a315b0dc0fd2d
                │       │   └── link
                │       ├── b975216c4476e4120537f4b04c080fbf8f71e5a8a303d920d23ed43d1a291723
                │       │   └── link
                │       ├── c2a8c6084659ec5616e58986f94f59fc0f8d1537300854b70420262d370a14df
                │       │   └── link
                │       ├── c30f55aa7914fdce4ccca196601df27b168f04bdd26f067fb2be55e9ca7d3cc2
                │       │   └── link
                │       ├── c3683c0954721b5a7f7932673024f818a6b1dd09f3d923d11f80710d22a37a8b
                │       │   └── link
                │       ├── c4db43cd7e2080e418138b4fd25e10e540f48eee55906b1332df7986bfc07320
                │       │   └── link
                │       ├── c5ee94bc35bc65a59cf7fb650a9a57ce9fdb79176c2f707251cb4cdd9f1ead0a
                │       │   └── link
                │       ├── c6d4161c3d2db5a0dff55e67665ad252dc6a244745d4d28748a33b0eb9016ac5
                │       │   └── link
                │       ├── d83791fefdeb24a924f16bf4b1e4e6a555f68a167e730a5a61e5b123d31114f4
                │       │   └── link
                │       ├── dd5c8c14a21cf99b3a69ba520d71058630d35ad474251358700830a4596c62aa
                │       │   └── link
                │       ├── e7c637000dfa093cd047d63321b0d722540152f2fe23edc4726893dc55afc89f
                │       │   └── link
                │       ├── f158dd228292a8c1eab511d3f05d50260a589d12f21c1a960e91de8dac600d6d
                │       │   └── link
                │       └── fce479c28d5681384afbf8e7c4bb5cb3e41c20b6e8d359eb4b8d7527f79e1002
                │           └── link
                ├── _manifests
                │   ├── revisions
                │   │   └── sha256
                │   │       ├── 08ffaab8c710dd9cf03d880e11e8f3def1907fe53ea57e198f7f1ac7a19e4848
                │   │       │   └── link
                │   │       ├── 24256410da5fedb15166c150e98ff9e348dd029bfe35077a3862051594f29919
                │   │       │   └── link
                │   │       ├── 400462eab4cbbb446e676e07af9df536afc59e7f47f1f2441d3241b1942d7b84
                │   │       │   └── link
                │   │       ├── 92b5198f48e84aa0d5212715550d2761368207f8522b107e63c74cd43c6e8ec3
                │   │       │   └── link
                │   │       └── f415c274bbc1da0847014ffd37aaeff1385b9234504a6804e7e88f0fe310dd1c
                │   │           └── link
                │   └── tags
                │       ├── 0.1
                │       │   ├── current
                │       │   │   └── link
                │       │   └── index
                │       │       └── sha256
                │       │           ├── 08ffaab8c710dd9cf03d880e11e8f3def1907fe53ea57e198f7f1ac7a19e4848
                │       │           │   └── link
                │       │           └── 400462eab4cbbb446e676e07af9df536afc59e7f47f1f2441d3241b1942d7b84
                │       │               └── link
                │       └── 0.2
                │           ├── current
                │           │   └── link
                │           └── index
                │               └── sha256
                │                   ├── 24256410da5fedb15166c150e98ff9e348dd029bfe35077a3862051594f29919
                │                   │   └── link
                │                   ├── 92b5198f48e84aa0d5212715550d2761368207f8522b107e63c74cd43c6e8ec3
                │                   │   └── link
                │                   └── f415c274bbc1da0847014ffd37aaeff1385b9234504a6804e7e88f0fe310dd1c
                │                       └── link
                └── _uploads

以上結構是tree 鏡像倉庫根目錄後的一個樹狀結構圖, 基本的結構是:

├── blobs
│   └── sha256
│       ├── 12
│       │   └── 124a9bb461f0106581e9e0e76a1edd39076b62ff80c3608676f5a6e057326b10
│       │       └── data
│       └── 19
│           └── 194529caae55f1ec32d99ae611bd52a5a25b2d20f4c49094598b3cfecb459f55
│               └── data
└── repositories
    └── ddcmao
        └── java8
            ├── _layers
            │   └── sha256
            │       ├── 124a9bb461f0106581e9e0e76a1edd39076b62ff80c3608676f5a6e057326b10
            │       │   └── link
                ……
            │       └── 194529caae55f1ec32d99ae611bd52a5a25b2d20f4c49094598b3cfecb459f55
            │           └── link
            ├── _manifests
            │   ├── revisions
            │   │   └── sha256
                    ……
            │   │       └── 08ffaab8c710dd9cf03d880e11e8f3def1907fe53ea57e198f7f1ac7a19e4848
            │   │           └── link
            │   └── tags
            │       ├── 0.1
            │       │   ├── current
            │       │   │   └── link
            │       │   └── index
            │       │       └── sha256
            │       │           └── 400462eab4cbbb446e676e07af9df536afc59e7f47f1f2441d3241b1942d7b84
            │       │               └── link
            │       └── 0.2
            │           ├── current
            │           │   └── link
            │           └── index
            │               └── sha256
            │                   └── f415c274bbc1da0847014ffd37aaeff1385b9234504a6804e7e88f0fe310dd1c
            │                       └── link
            └── _uploads

以上結構是一個鏡像倉庫的基本樹狀目錄結構。 在根目錄下存有兩個目錄 blobs 跟repositories。
blobs目錄是存放每層數據以及一個鏡像的manifests信息的具體文件,其目錄結構簡單明瞭,data中存放的是真是的數據信息。
repositories目錄則是存放鏡像倉庫中的鏡像的組織信息,相當於是分佈式存儲中的matadata, 期目錄結構也不復雜。

  1. 首先repositories目錄下是ddcmao目錄,該目錄是鏡像倉庫裏的邏輯分組,有沒有都可以,如果要進行分組管理,則該目錄就是分組這裏稱之爲項目。
  2. 項目下面是java8目錄,該目錄是一個鏡像repository目錄。
  3. java8目錄下面有_layers、_manifests和_uploads目錄,其中_uploads目錄是一個臨時目錄,是鏡像上傳的過程中的目錄,一旦鏡像上傳完成,該目錄下的文件就被刪除。
  4. _layers目錄類似於blobs目錄,但是它不存儲真是數據僅僅以link文件保存每個layer的sha256編碼。保存該repository長傳過得所有layer的sha256編碼信息。
  5. _manifests目錄存放的信息就是該repository的上傳的所有版本(tag)的manifest信息。其目錄下有revisions目錄和tags目錄。
  6. _tags目錄很明顯每個tag一組記錄,例如tag 0.1 0.2, 每個tag下面有current目錄和index目錄, current目錄下的link文件保存了該tag目前的manifest文件的sha256編碼,而index目錄則列出了該tag歷史上傳的所有版本的sha256編碼信息。
  7. _revisions目錄裏存放了該repository歷史上上傳版本的所有sha256編碼信息。

以第一組tree目錄結構給大家簡單解釋, 我們的鏡像名爲ddcmao/java8, 現有0.2和0.1 兩個tag, 可以看到0.1tag目錄下index目錄有兩條記錄,而0.2tag目錄下index有三條記錄,這個表示了0.1tag被上傳了兩個歷史版本同時0.2tag共被上傳了三個歷史版本, 具體限制的0.1tag使用的是哪個版本的信息需要看0.1tag目錄下current目錄的index文件,該文件表明了0.1tag鏡像的manifest文件的sha256編碼,因此確認0.1tag的真是版本情況。0.2tag也是同樣。

我們再看revisions目錄下總共有5條記錄,而且編碼是0.1tag目錄下index子目錄的編碼跟0.2tag index子目錄編碼的總和,這表示java8總共上傳過5個版本的信息,但現在只有兩個tag,其中三條記錄已經無效,但由於registry的layer共享機制,目前不刪除。

docker push 過程中distribution處理

distribution 實質上是一個http服務,在docker push的時候實際上是在處理http 請求的。我們結合一個例子來說明一下,先看一下我們下面關於docker push過程中抓獲的tcpdum 信息。
以 docker push library/busybox 爲例,信息如下:


這裏寫圖片描述

從以上的圖片中可以看到,image push的核基本流程是先check 該鏡像的layer是否存在, 在post patch put blob–push每個layer, 最後put manifests。因此我們可以根據流程進行單獨的分析。
我們將上面的流程分爲check–HEAD blobs, Post Blobs, Patch Blobs, Put Blobs, Put Manifests 這幾部來分析。

check-HEAD Blob分析

從上面的圖片中看到每一次上傳一個layer之前都會先發送一條HEAD blobs的請求,該http request是查看該layer是否已經存在,如果存在則不再上傳,如果不存在那麼接下來進行上傳。

request example:
http://lalalala.com/v2/library/busybox/blobs/sha256:04176c8b224aa0eb9942af765f66dae866f436e75acef028fe44b8a98e045515

對應的方法:

  • request function: HEAD

  • request URL: /v2/*/blobs/sha256:*********

  • request handler dispatch:func blobDispatcher(ctx *Context, r *http.Request)

  • request handler: blobHandler.GetBlob

根據app.register(v2.RouteNameBlob, blobDispatcher),/v2/*/blobs/sha256:***沒有uploads,因此其對應的dispatch是blobDispatcher。
而 func blobDispatcher(ctx *Context, r *http.Request)中僅僅分配了對應的http handlerFunc,具體內容如下:

mhandler := handlers.MethodHandler{
            "GET":  http.HandlerFunc(blobHandler.GetBlob),
            "HEAD": http.HandlerFunc(blobHandler.GetBlob),
        }

我們來看一下對應的func (bh *blobHandler) GetBlob(w http.ResponseWriter, r *http.Request):

func (bh *blobHandler) GetBlob(w http.ResponseWriter, r *http.Request) {
    context.GetLogger(bh).Debug("GetBlob")
    blobs := bh.Repository.Blobs(bh)
    desc, err := blobs.Stat(bh, bh.Digest)
    if err != nil {
        if err == distribution.ErrBlobUnknown {
            bh.Errors = append(bh.Errors, v2.ErrorCodeBlobUnknown.WithDetail(bh.Digest))
        } else {
            bh.Errors = append(bh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
        }
        return
    }

    if err := blobs.ServeBlob(bh, w, r, desc.Digest); err != nil {
        context.GetLogger(bh).Debugf("unexpected error getting blob HTTP handler: %v", err)
        bh.Errors = append(bh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
        return
    }
}

該函數中先獲取desc = blobs.stat 即獲取對應的blob的組織信息。

func (bs *blobs) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
    return bs.statter.Stat(ctx, dgst)

}

func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
    path, err := pathFor(blobDataPathSpec{
        digest: dgst,
    })

    if err != nil {
        return distribution.Descriptor{}, err
    }

    fi, err := bs.driver.Stat(ctx, path)
    if err != nil {
        switch err := err.(type) {
        case driver.PathNotFoundError:
            return distribution.Descriptor{}, distribution.ErrBlobUnknown
        default:
            return distribution.Descriptor{}, err
        }
    }

    if fi.IsDir() {
        // NOTE(stevvooe): This represents a corruption situation. Somehow, we
        // calculated a blob path and then detected a directory. We log the
        // error and then error on the side of not knowing about the blob.
        context.GetLogger(ctx).Warnf("blob path should not be a directory: %q", path)
        return distribution.Descriptor{}, distribution.ErrBlobUnknown
    }

    // TODO(stevvooe): Add method to resolve the mediatype. We can store and
    // cache a "global" media type for the blob, even if a specific repo has a
    // mediatype that overrides the main one.

    return distribution.Descriptor{
        Size: fi.Size(),

        // NOTE(stevvooe): The central blob store firewalls media types from
        // other users. The caller should look this up and override the value
        // for the specific repository.
        MediaType: "application/octet-stream",
        Digest:    dgst,
    }, nil
}

調用bs.driver.Stat(ctx, path)最後調用到filesystem的driver的Stat函數即堅持對應路徑的文件信息,如果有則返回信息,否則返回錯誤信息。
之後GetBlob調用blobs.ServeBlob(bh, w, r, desc.Digest),調用到這裏說明對應的文件存在,但是不知道文件是否一致,該函數的調用信息如下:

func (bs *blobServer) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error {
    desc, err := bs.statter.Stat(ctx, dgst)
    if err != nil {
        return err
    }

    path, err := bs.pathFn(desc.Digest)
    if err != nil {
        return err
    }

    if bs.redirect {
        redirectURL, err := bs.driver.URLFor(ctx, path, map[string]interface{}{"method": r.Method})
        switch err.(type) {
        case nil:
            // Redirect to storage URL.
            http.Redirect(w, r, redirectURL, http.StatusTemporaryRedirect)
            return err

        case driver.ErrUnsupportedMethod:
            // Fallback to serving the content directly.
        default:
            // Some unexpected error.
            return err
        }
    }

    br, err := newFileReader(ctx, bs.driver, path, desc.Size)
    if err != nil {
        return err
    }
    defer br.Close()

    w.Header().Set("ETag", fmt.Sprintf(`"%s"`, desc.Digest)) // If-None-Match handled by ServeContent
    w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%.f", blobCacheControlMaxAge.Seconds()))

    if w.Header().Get("Docker-Content-Digest") == "" {
        w.Header().Set("Docker-Content-Digest", desc.Digest.String())
    }

    if w.Header().Get("Content-Type") == "" {
        // Set the content type if not already set.
        w.Header().Set("Content-Type", desc.MediaType)
    }

    if w.Header().Get("Content-Length") == "" {
        // Set the content length if not already set.
        w.Header().Set("Content-Length", fmt.Sprint(desc.Size))
    }

    http.ServeContent(w, r, desc.Digest.String(), time.Time{}, br)
    return nil
}

又執行了一遍bs.statter.Stat, 之後獲取對應的文件路徑,在之後根據是否是連接文件來重新獲取信息,也就是說鏡像倉庫支持分佈式的link文件。 再往後創建了一個FileReader,執行http.ServerContent 即檢查文件是否修改,文件大小文件的sha256編碼信息返httpresponse。 該函數的具體內容自己去看源碼,我粗略的看了一下級別上是追蹤文件的修改、大小、sha256碼。
如果這裏沒有找到對應的layer的文件——blobs目錄下沒有對應的sha256文件夾以及data文件,就需要執行下面的Post Blobs/uploads Patch、Put請求了,如果完全存在則繼續下一個layer。

上傳一個layer

上傳一個layer 分爲Post Patch Put三個步驟,我們一個一個分析:

Post Blobs/uploads分析

繼續上面的分析,如果push image的一個layer時發現鏡像倉庫裏面沒有改layer的信息,則就執行Post blobs請求。

request example:
http://reg.lalalalal.com/v2/library/busybox/blobs/uploads/?from=library%2Fbusybox&mount=sha256%3A04176c8b224aa0eb9942af765f66dae866f436e75acef028fe44b8a98e045515

對應的方法:

  • request function: POST

  • request URL: /v2/*/blobs/uploads/?:*********

  • request handler dispatch:func blobUploadDispatcher(ctx *Context, r *http.Request)

  • request handler: buh.StartBlobUpload

    buh : blobUploadHandler
    根據app.register(v2.RouteNameBlobUpload, blobUploadDispatcher) 所以該請求對應的dispatch是blobUploadDispatcher
    而 func blobUploadDispatcher(ctx *Context, r *http.Request)中僅僅分配了對應的http handlerFunc,具體內容如下:

    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)
    }
    

    ……

上面是構建blobUploadHandler結構以及針對request請求的方法來指定對應的http.handlerFunc。

我們來看一下buh.StartBlobUpload函數:

// StartBlobUpload begins the blob upload process and allocates a server-side
// blob writer session, optionally mounting the blob from a separate repository.

func (buh *blobUploadHandler) StartBlobUpload(w http.ResponseWriter, r *http.Request) {
    var options []distribution.BlobCreateOption

    fromRepo := r.FormValue("from")
    mountDigest := r.FormValue("mount")

    if mountDigest != "" && fromRepo != "" {
        opt, err := buh.createBlobMountOption(fromRepo, mountDigest)
        if opt != nil && err == nil {
            options = append(options, opt)
        }
    }

    blobs := buh.Repository.Blobs(buh)
    upload, err := blobs.Create(buh, options...)

    if err != nil {
        if ebm, ok := err.(distribution.ErrBlobMounted); ok {
            if err := buh.writeBlobCreatedHeaders(w, ebm.Descriptor); err != nil {
                buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
            }
        } else if err == distribution.ErrUnsupported {
            buh.Errors = append(buh.Errors, errcode.ErrorCodeUnsupported)
        } else {
            buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
        }
        return
    }

    buh.Upload = upload

    if err := buh.blobUploadResponse(w, r, true); err != nil {
        buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
        return
    }

    w.Header().Set("Docker-Upload-UUID", buh.Upload.ID())
    w.WriteHeader(http.StatusAccepted)
}

首先根據是否有from參數跟mount參數創建mount信息。
其次獲取Blobs對象再調用blobs.Create來生產upload信息。我們來看一下Blobs跟Blobs.Create函數:

func (repo *repository) Blobs(ctx context.Context) distribution.BlobStore {
    var statter distribution.BlobDescriptorService = &linkedBlobStatter{
        blobStore:   repo.blobStore,
        repository:  repo,
        linkPathFns: []linkPathFunc{blobLinkPath},
    }

    if repo.descriptorCache != nil {
        statter = cache.NewCachedBlobStatter(repo.descriptorCache, statter)
    }

    if repo.registry.blobDescriptorServiceFactory != nil {
        statter = repo.registry.blobDescriptorServiceFactory.BlobAccessController(statter)
    }

    return &linkedBlobStore{
        registry:             repo.registry,
        blobStore:            repo.blobStore,
        blobServer:           repo.blobServer,
        blobAccessController: statter,
        repository:           repo,
        ctx:                  ctx,

        // TODO(stevvooe): linkPath limits this blob store to only layers.
        // This instance cannot be used for manifest checks.
        linkPathFns:            []linkPathFunc{blobLinkPath},
        deleteEnabled:          repo.registry.deleteEnabled,
        resumableDigestEnabled: repo.resumableDigestEnabled,
    }
}

func (lbs *linkedBlobStore) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) {
    context.GetLogger(ctx).Debug("(*linkedBlobStore).Writer")

    var opts createOptions

    for _, option := range options {
        err := option.Apply(&opts)
        if err != nil {
            return nil, err
        }
    }

    if opts.Mount.ShouldMount {
        desc, err := lbs.mount(ctx, opts.Mount.From, opts.Mount.From.Digest())
        if err == nil {
            // Mount successful, no need to initiate an upload session
            return nil, distribution.ErrBlobMounted{From: opts.Mount.From, Descriptor: desc}
        }
    }

    uuid := uuid.Generate().String()
    startedAt := time.Now().UTC()

    path, err := pathFor(uploadDataPathSpec{
        name: lbs.repository.Named().Name(),
        id:   uuid,
    })

    if err != nil {
        return nil, err
    }

    startedAtPath, err := pathFor(uploadStartedAtPathSpec{
        name: lbs.repository.Named().Name(),
        id:   uuid,
    })

    if err != nil {
        return nil, err
    }

    // Write a startedat file for this upload
    if err := lbs.blobStore.driver.PutContent(ctx, startedAtPath, []byte(startedAt.Format(time.RFC3339))); err != nil {
        return nil, err
    }

    return lbs.newBlobUpload(ctx, uuid, path, startedAt, false)
}

這裏根據配置信息生產linkedBlobStore信息,然後調用linkedBlobStore的Create方法,該方法中處理mount信息,生產uuid,blob目錄,然後PutContext寫入後端存儲, 再調用newBlobUpload
mount信息處理其實還是蠻簡單的就是在生產對應layer的信息放在_layers目錄下。
我們來看一下startedAtPath的文件路徑: repoPrefix/current_image_name/_uploads/repository.id/startedat,是前面分析的鏡像名下的repositories目錄下的_uploads目錄。
其中 lbs.blobStore.driver.PutContent 是調用後端存儲驅動的PutContext函數真是的寫入數據,只不過目前沒有真是信息需要寫入,統一寫入[]byte(startedAt.Format(time.RFC3339)。
最後調用 lbs.newBlobUpload生產對應distribution.BlobWriter信息並返回,裏面指定了fileWriter這個對象爲對應driver的FileWrite對象。具體內容如下:

func (lbs *linkedBlobStore) newBlobUpload(ctx context.Context, uuid, path string, startedAt time.Time, append bool) (distribution.BlobWriter, error) {
    fw, err := lbs.driver.Writer(ctx, path, append)
    if err != nil {
        return nil, err
    }

    bw := &blobWriter{
        ctx:        ctx,
        blobStore:  lbs,
        id:         uuid,
        startedAt:  startedAt,
        digester:   digest.Canonical.New(),
        fileWriter: fw,
        driver:     lbs.driver,
        path:       path,
        resumableDigestEnabled: lbs.resumableDigestEnabled,
    }

    return bw, nil
}

返回到StartBlobUpload之後給buh.Upload = upload,並調用 buh.blobUploadResponse(w, r, true)並返回。 該函數時間是生成http reesponse相關的信息,裏面具體內容比較複雜,這裏先不做分析。

Patch Blobs/uploads分析

繼續上面的分析,如果push image的一個layer時發現鏡像倉庫裏面沒有改layer的信息,則就執行Patch blobs請求。

request exapmle :
http://reg.lalalala.com/v2/library/busybox/blobs/uploads/75816304-18cb-4fcf-af57-98f5508f0eb8?_state=nCtc8O54DzVTJSg5WI-WHVO8qF-F23Q9Murrfv67QqR7Ik5hbWUiOiJsaWJyYXJ5L2J1c3lib3giLCJVVUlEIjoiNzU4MTYzMDQtMThjYi00ZmNmLWFmNTctOThmNTUwOGYwZWI4IiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDE3LTA1LTI0VDA4OjA1OjQ3Ljk3NjQxMDU3OFoifQ%3D%3D

對應的方法:

  • request function: Patch

  • request URL: /v2/*/blobs/uploads/{uuid:}?:*********

  • request handler dispatch:func blobUploadDispatcher(ctx *Context, r *http.Request)

  • request handler: buh.PatchBlobData

    buh : blobUploadHandler
    根據app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher) 所以該請求對應的dispatch是blobUploadDispatcher
    而 func blobUploadDispatcher(ctx *Context, r *http.Request)中僅僅分配了對應的http handlerFunc,具體內容已經分析過了,這裏不再重複。

由於該request中有UUID信息因此blobUploadDispatcher在指定request handler之後還進行了一次轉換和處理,我們簡單來看一下:

if buh.UUID != "" {
    state, err := hmacKey(ctx.Config.HTTP.Secret).unpackUploadState(r.FormValue("_state"))
    if err != nil {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctxu.GetLogger(ctx).Infof("error resolving upload: %v", err)
            buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadInvalid.WithDetail(err))
        })
    }
    buh.State = state

    if state.Name != ctx.Repository.Named().Name() {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctxu.GetLogger(ctx).Infof("mismatched repository name in upload state: %q != %q", state.Name, buh.Repository.Named().Name())
            buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadInvalid.WithDetail(err))
        })
    }

    if state.UUID != buh.UUID {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctxu.GetLogger(ctx).Infof("mismatched uuid in upload state: %q != %q", state.UUID, buh.UUID)
            buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadInvalid.WithDetail(err))
        })
    }

    blobs := ctx.Repository.Blobs(buh)
    upload, err := blobs.Resume(buh, buh.UUID)
    if err != nil {
        ctxu.GetLogger(ctx).Errorf("error resolving upload: %v", err)
        if err == distribution.ErrBlobUploadUnknown {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadUnknown.WithDetail(err))
            })
        }

        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
        })
    }
    buh.Upload = upload

    if size := upload.Size(); size != buh.State.Offset {
        defer upload.Close()
        ctxu.GetLogger(ctx).Errorf("upload resumed at wrong offest: %d != %d", size, buh.State.Offset)
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadInvalid.WithDetail(err))
            upload.Cancel(buh)
        })
    }
    return closeResources(handler, buh.Upload)
}

上面的代碼主要解析了了http request的_state信息,這個信息是上一次請求結束的時候帶回去的信息, 然後根據相關的信息進行校驗,校驗之後執行了blobs.Resume(buh, buh.UUID),之後 調用了closeResources(handler, buh.Upload)相當於是重定向了http handler。
我們首先來看一下blobs.Resume(buh, buh.UUID):
我們再來看一下重定向的handler:

func closeResources(handler http.Handler, closers ...io.Closer) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        for _, closer := range closers {
            defer closer.Close()
        }
        handler.ServeHTTP(w, r)
    })
}

該處理函數只是將原來的UPload預約關閉。調用了buh.Upload.Close–>func (bw *blobWriter) Close()–>bw.fileWriter.Close()實際上是調用了後端存儲上的文件的close函數。

我們看一下blobs.Resume(buh, buh.UUID) Resume跟create非常類似,調用newBlobUpload新建了一個BlobWriter對象,文件的目錄由原來的 repoPrefix/current_image_name/_uploads/repository.id/startedat 變更爲 repoPrefix/current_image_name/_uploads/repository.id/data了,而且屬性append之前是false這次變更爲true,爲後面的數據寫入做準備。

接下來我們回到dispatach指定的處理函數PatchBlobData中:

// PatchBlobData writes data to an upload.
func (buh *blobUploadHandler) PatchBlobData(w http.ResponseWriter, r *http.Request) {
    if buh.Upload == nil {
        buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadUnknown)
        return
    }

    ct := r.Header.Get("Content-Type")
    if ct != "" && ct != "application/octet-stream" {
        buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(fmt.Errorf("Bad Content-Type")))
        // TODO(dmcgowan): encode error
        return
    }

    // TODO(dmcgowan): support Content-Range header to seek and write range

    if err := copyFullPayload(w, r, buh.Upload, buh, "blob PATCH", &buh.Errors); err != nil {
        // copyFullPayload reports the error if necessary
        return
    }

    if err := buh.blobUploadResponse(w, r, false); err != nil {
        buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
        return
    }

    w.WriteHeader(http.StatusAccepted)
}

從上面代碼來看主要執行了兩個調用,copyFullPayload跟blobUploadResponse,我們先來看一下copyFullPayload,該函數僅僅是將request中所帶的data拷貝到buh.Upload中,調用的是io.Copy(destWriter, r.Body)方法, 該方法跟後端的blobwriter有關,需要寫驅動的人仔細研究一下。

之後調用blobUploadResponse方法, 該函數的具體內容如下:

// blobUploadResponse provides a standard request for uploading blobs and
// chunk responses. This sets the correct headers but the response status is
// left to the caller. The fresh argument is used to ensure that new blob
// uploads always start at a 0 offset. This allows disabling resumable push by
// always returning a 0 offset on check status.
func (buh *blobUploadHandler) blobUploadResponse(w http.ResponseWriter, r *http.Request, fresh bool) error {
    // TODO(stevvooe): Need a better way to manage the upload state automatically.
    buh.State.Name = buh.Repository.Named().Name()
    buh.State.UUID = buh.Upload.ID()
    buh.Upload.Close()
    buh.State.Offset = buh.Upload.Size()
    buh.State.StartedAt = buh.Upload.StartedAt()

    token, err := hmacKey(buh.Config.HTTP.Secret).packUploadState(buh.State)
    if err != nil {
        ctxu.GetLogger(buh).Infof("error building upload state token: %s", err)
        return err
    }

    uploadURL, err := buh.urlBuilder.BuildBlobUploadChunkURL(
        buh.Repository.Named(), buh.Upload.ID(),
        url.Values{
            "_state": []string{token},
        })
    if err != nil {
        ctxu.GetLogger(buh).Infof("error building upload url: %s", err)
        return err
    }

    endRange := buh.Upload.Size()
    if endRange > 0 {
        endRange = endRange - 1
    }

    w.Header().Set("Docker-Upload-UUID", buh.UUID)
    w.Header().Set("Location", uploadURL)

    w.Header().Set("Content-Length", "0")
    w.Header().Set("Range", fmt.Sprintf("0-%d", endRange))

    return nil
}

裏面比較明顯的是調用buh.Upload.Close()–>func (bw *blobWriter) Close()–>bw.fileWriter.Close()實際上是調用了後端存儲上的文件的close函數,刷緩存關閉文件。
之後調用生產response信息。其中比較重要的是uploadURL的生產,爲嚇一條http請求生產URL,前面說的http的_state信息是前一條http的response帶回來的,就是這個,首先生產token, 然後在BuildBlobUploadChunkURL。

至此Patch請求分析完畢。

Put Blobs/uploads分析

繼續上面的分析,如果push image的一個layer時發現鏡像倉庫裏面沒有改layer的信息,則就執行Put blobs請求。

request example:
http://reglalalala.com/v2/library/busybox/blobs/uploads/75816304-18cb-4fcf-af57-98f5508f0eb8?_state=4GUuNB-bRVYFnJ36MKo7aMCDdcTg8NX0e0OAt7CNR1N7Ik5hbWUiOiJsaWJyYXJ5L2J1c3lib3giLCJVVUlEIjoiNzU4MTYzMDQtMThjYi00ZmNmLWFmNTctOThmNTUwOGYwZWI4IiwiT2Zmc2V0Ijo3MDExMDIsIlN0YXJ0ZWRBdCI6IjIwMTctMDUtMjRUMDg6MDU6NDdaIn0%3D&digest=sha256%3A04176c8b224aa0eb9942af765f66dae866f436e75acef028fe44b8a98e045515

對應的方法:

  • request function: Put

  • request URL: /v2/*/blobs/uploads/{uuid:}?:*********

  • request handler dispatch:func blobUploadDispatcher(ctx *Context, r *http.Request)

  • request handler: buh.PutBlobUploadComplete

    buh : blobUploadHandler
    根據app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher) 所以該請求對應的dispatch是blobUploadDispatcher
    而 func blobUploadDispatcher(ctx *Context, r *http.Request)中分配了對應的http handlerFunc並進行了一次轉換,具體內容已經分析過了,這裏不再重複。

這裏直接看PutBlobUploadComplete函數:

// PutBlobUploadComplete takes the final request of a blob upload. The
// request may include all the blob data or no blob data. Any data
// provided is received and verified. If successful, the blob is linked
// into the blob store and 201 Created is returned with the canonical
// url of the blob.
func (buh *blobUploadHandler) PutBlobUploadComplete(w http.ResponseWriter, r *http.Request) {
    if buh.Upload == nil {
        buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadUnknown)
        return
    }

    dgstStr := r.FormValue("digest") // TODO(stevvooe): Support multiple digest parameters!

    if dgstStr == "" {
        // no digest? return error, but allow retry.
        buh.Errors = append(buh.Errors, v2.ErrorCodeDigestInvalid.WithDetail("digest missing"))
        return
    }

    dgst, err := digest.ParseDigest(dgstStr)
    if err != nil {
        // no digest? return error, but allow retry.
        buh.Errors = append(buh.Errors, v2.ErrorCodeDigestInvalid.WithDetail("digest parsing failed"))
        return
    }

    if err := copyFullPayload(w, r, buh.Upload, buh, "blob PUT", &buh.Errors); err != nil {
        // copyFullPayload reports the error if necessary
        return
    }

    desc, err := buh.Upload.Commit(buh, distribution.Descriptor{
        Digest: dgst,

        // TODO(stevvooe): This isn't wildly important yet, but we should
        // really set the mediatype. For now, we can let the backend take care
        // of this.
    })

    if err != nil {
        switch err := err.(type) {
        case distribution.ErrBlobInvalidDigest:
            buh.Errors = append(buh.Errors, v2.ErrorCodeDigestInvalid.WithDetail(err))
        case errcode.Error:
            buh.Errors = append(buh.Errors, err)
        default:
            switch err {
            case distribution.ErrAccessDenied:
                buh.Errors = append(buh.Errors, errcode.ErrorCodeDenied)
            case distribution.ErrUnsupported:
                buh.Errors = append(buh.Errors, errcode.ErrorCodeUnsupported)
            case distribution.ErrBlobInvalidLength, distribution.ErrBlobDigestUnsupported:
                buh.Errors = append(buh.Errors, v2.ErrorCodeBlobUploadInvalid.WithDetail(err))
            default:
                ctxu.GetLogger(buh).Errorf("unknown error completing upload: %#v", err)
                buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
            }

        }

        // Clean up the backend blob data if there was an error.
        if err := buh.Upload.Cancel(buh); err != nil {
            // If the cleanup fails, all we can do is observe and report.
            ctxu.GetLogger(buh).Errorf("error canceling upload after error: %v", err)
        }

        return
    }
    if err := buh.writeBlobCreatedHeaders(w, desc); err != nil {
        buh.Errors = append(buh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
        return
    }
}

從代碼看這次需要獲取blob的sha256編碼,而此編碼request中已經有了,之後直接調用copyFullPayload跟buh.Upload.Commit,copyFullPayload不做分析了, 我們先分析一下writeBlobCreatedHeaders而後在分析buh.Upload.Commit。
我們先看一下writeBlobCreatedHeaders:

func (buh *blobUploadHandler) writeBlobCreatedHeaders(w http.ResponseWriter, desc distribution.Descriptor) error {
    ref, err := reference.WithDigest(buh.Repository.Named(), desc.Digest)
    if err != nil {
        return err
    }
    blobURL, err := buh.urlBuilder.BuildBlobURL(ref)
    if err != nil {
        return err
    }

    w.Header().Set("Location", blobURL)
    w.Header().Set("Content-Length", "0")
    w.Header().Set("Docker-Content-Digest", desc.Digest.String())
    w.WriteHeader(http.StatusCreated)
    return nil
}

內容很簡單僅僅生產了對應BLob的URL信息。
我們再看buh.Upload.Commit函數:

func (bw *blobWriter) Commit(ctx context.Context, desc distribution.Descriptor) (distribution.Descriptor, error) {
    context.GetLogger(ctx).Debug("(*blobWriter).Commit")

    if err := bw.fileWriter.Commit(); err != nil {
        return distribution.Descriptor{}, err
    }

    bw.Close()
    desc.Size = bw.Size()

    canonical, err := bw.validateBlob(ctx, desc)
    if err != nil {
        return distribution.Descriptor{}, err
    }

    if err := bw.moveBlob(ctx, canonical); err != nil {
        return distribution.Descriptor{}, err
    }

    if err := bw.blobStore.linkBlob(ctx, canonical, desc.Digest); err != nil {
        return distribution.Descriptor{}, err
    }

    if err := bw.removeResources(ctx); err != nil {
        return distribution.Descriptor{}, err
    }

    err = bw.blobStore.blobAccessController.SetDescriptor(ctx, canonical.Digest, canonical)
    if err != nil {
        return distribution.Descriptor{}, err
    }

    bw.committed = true
    return canonical, nil
}

從代碼來看先調用了bw.fileWriter.Commit()將文件內容的buf刷新。然後在檢驗sha256編碼與文件本身是否合法匹配,再調用bw.moveBlob跟bw.blobStore.linkBlob,完成存儲後端的信息更新與文件組織,之後調用bw.removeResources刪除相關臨時(_uploads 目錄下的)文件。

bw.moveBlob在做了一組校驗之後調用bw.blobStore.driver.Move(ctx, bw.path, blobPath)將_uplaod下面的數據文件move到blobs/sha256/Hex[0:2]/Hex/data文件。
bw.blobStore.linkBlob則是創建了repositorys/current_image_name/_layers/sha256/hex/link文件,文件內容爲對應sha256:$hex
至此一個layer上傳成功。

上傳完成以上所有的layer之後需要最有一個image的組織信息——即manifest文件上傳,上面的所有layer都可以不上傳(例如僅僅是新加了一個Tag信息),但是該Manifest文件必須上傳。

Put Manifests分析

繼續上面的分析,如果push image的所有layer之後,還需要將該鏡像的描述和組織文件上傳,否則就找不到該鏡像以及其對應的各層組織信息——manifest文件。

request example:
http://reg.lalalala.com/v2/library/busybox/manifests/latest

對應的方法:

  • request function: POST

  • request URL: /v2/**/manifests/$taginfo

  • request handler dispatch:func imageManifestDispatcher(ctx *Context, r *http.Request)

  • request handler: imageManifestHandler.PutImageManifest

根據app.register(v2.RouteNameManifest, imageManifestDispatcher) 所以該請求對應的dispatch是imageManifestDispatcher

這裏我們之間看 PutImageManifest函數:

// PutImageManifest validates and stores an image in the registry.
func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http.Request) {
    ctxu.GetLogger(imh).Debug("PutImageManifest")
    manifests, err := imh.Repository.Manifests(imh)
    if err != nil {
        imh.Errors = append(imh.Errors, err)
        return
    }

    var jsonBuf bytes.Buffer
    if err := copyFullPayload(w, r, &jsonBuf, imh, "image manifest PUT", &imh.Errors); err != nil {
        // copyFullPayload reports the error if necessary
        return
    }

    mediaType := r.Header.Get("Content-Type")
    manifest, desc, err := distribution.UnmarshalManifest(mediaType, jsonBuf.Bytes())
    if err != nil {
        imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
        return
    }

    if imh.Digest != "" {
        if desc.Digest != imh.Digest {
            ctxu.GetLogger(imh).Errorf("payload digest does match: %q != %q", desc.Digest, imh.Digest)
            imh.Errors = append(imh.Errors, v2.ErrorCodeDigestInvalid)
            return
        }
    } else if imh.Tag != "" {
        imh.Digest = desc.Digest
    } else {
        imh.Errors = append(imh.Errors, v2.ErrorCodeTagInvalid.WithDetail("no tag or digest specified"))
        return
    }

    var options []distribution.ManifestServiceOption
    if imh.Tag != "" {
        options = append(options, distribution.WithTag(imh.Tag))
    }
    _, err = manifests.Put(imh, manifest, options...)
    if err != nil {
        ……
        return
    }

    // Tag this manifest
    if imh.Tag != "" {
        tags := imh.Repository.Tags(imh)
        err = tags.Tag(imh, imh.Tag, desc)
        if err != nil {
            imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
            return
        }

    }

    // Construct a canonical url for the uploaded manifest.
    ref, err := reference.WithDigest(imh.Repository.Named(), imh.Digest)
    if err != nil {
        imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
        return
    }

    location, err := imh.urlBuilder.BuildManifestURL(ref)
    if err != nil {
        // NOTE(stevvooe): Given the behavior above, this absurdly unlikely to
        // happen. We'll log the error here but proceed as if it worked. Worst
        // case, we set an empty location header.
        ctxu.GetLogger(imh).Errorf("error building manifest url from digest: %v", err)
    }

    w.Header().Set("Location", location)
    w.Header().Set("Docker-Content-Digest", imh.Digest.String())
    w.WriteHeader(http.StatusCreated)
}

從上面的代碼中可以看出首先調用manifests, err := imh.Repository.Manifests(imh)構建manifests對象,然後調用distribution.UnmarshalManifest(mediaType, jsonBuf.Bytes())解析manifest,因爲有shema1跟shema2兩種因此是有區別的,之後manifests.Put(imh, manifest, options…)寫入manifest文件並建立revisions目錄下link文件, 最後調用 tags := imh.Repository.Tags(imh)獲取tagservice,之後調用tags.Tag(imh, imh.Tag, desc)寫入manifests目錄裏面tags目錄下的相關link文件。
這裏imh.Repository.Manifests(imh)是非常關鍵的,其內容決定了後面很多函數的入口,我們來看一下imh.Repository.Manifests(imh)函數:

func (repo *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
    manifestLinkPathFns := []linkPathFunc{
        // NOTE(stevvooe): Need to search through multiple locations since
        // 2.1.0 unintentionally linked into  _layers.
        manifestRevisionLinkPath,
        blobLinkPath,
    }

    manifestDirectoryPathSpec := manifestRevisionsPathSpec{name: repo.name.Name()}

    var statter distribution.BlobDescriptorService = &linkedBlobStatter{
        blobStore:   repo.blobStore,
        repository:  repo,
        linkPathFns: manifestLinkPathFns,
    }

    if repo.registry.blobDescriptorServiceFactory != nil {
        statter = repo.registry.blobDescriptorServiceFactory.BlobAccessController(statter)
    }

    blobStore := &linkedBlobStore{
        ctx:                  ctx,
        blobStore:            repo.blobStore,
        repository:           repo,
        deleteEnabled:        repo.registry.deleteEnabled,
        blobAccessController: statter,

        // TODO(stevvooe): linkPath limits this blob store to only
        // manifests. This instance cannot be used for blob checks.
        linkPathFns:           manifestLinkPathFns,
        linkDirectoryPathSpec: manifestDirectoryPathSpec,
    }

    ms := &manifestStore{
        ctx:        ctx,
        repository: repo,
        blobStore:  blobStore,
        schema1Handler: &signedManifestHandler{
            ctx:        ctx,
            repository: repo,
            blobStore:  blobStore,
        },
        schema2Handler: &schema2ManifestHandler{
            ctx:        ctx,
            repository: repo,
            blobStore:  blobStore,
        },
        manifestListHandler: &manifestListHandler{
            ctx:        ctx,
            repository: repo,
            blobStore:  blobStore,
        },
    }

    // Apply options
    for _, option := range options {
        err := option.Apply(ms)
        if err != nil {
            return nil, err
        }
    }

    return ms, nil
}

函數內容比較長,但是非常簡單明瞭,不做具體的解析。
我們再看一下manifests.Put(imh, manifest, options…):

這裏以shema2爲例。

func (ms *schema2ManifestHandler) Put(ctx context.Context, manifest distribution.Manifest, skipDependencyVerification bool) (digest.Digest, error) {
    context.GetLogger(ms.ctx).Debug("(*schema2ManifestHandler).Put")

    m, ok := manifest.(*schema2.DeserializedManifest)
    if !ok {
        return "", fmt.Errorf("non-schema2 manifest put to schema2ManifestHandler: %T", manifest)
    }

    if err := ms.verifyManifest(ms.ctx, *m, skipDependencyVerification); err != nil {
        return "", err
    }

    mt, payload, err := m.Payload()
    if err != nil {
        return "", err
    }

    revision, err := ms.blobStore.Put(ctx, mt, payload)
    if err != nil {
        context.GetLogger(ctx).Errorf("error putting payload into blobstore: %v", err)
        return "", err
    }

    // Link the revision into the repository.
    if err := ms.blobStore.linkBlob(ctx, revision); err != nil {
        return "", err
    }

    return revision.Digest, nil
}

調用ms.blobStore.Put(ctx, mt, payload)函數將manifest文件內容寫入blobs目錄,調用ms.blobStore.linkBlob(ctx, revision)創建manifests/current_image_name/revisions/目錄下的link文件。

我們在看一下tags.Tag(imh, imh.Tag, desc)函數:

func (ts *tagStore) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error {
    currentPath, err := pathFor(manifestTagCurrentPathSpec{
        name: ts.repository.Named().Name(),
        tag:  tag,
    })

    if err != nil {
        return err
    }

    lbs := ts.linkedBlobStore(ctx, tag)

    // Link into the index
    if err := lbs.linkBlob(ctx, desc); err != nil {
        return err
    }

    // Overwrite the current link
    return ts.blobStore.link(ctx, currentPath, desc.Digest)
}

函數中調用lbs := ts.linkedBlobStore(ctx, tag)來指定tag目錄下index目錄的Path轉義信息。調用lbs.linkBlob(ctx, desc)生成tag目錄下index目錄中的link信息。
調用ts.blobStore.link(ctx, currentPath, desc.Digest)來生成tags目錄下current目錄的link文件。
至此 Manifests文件上傳完成。

至此一個鏡像上傳的所有request請求處理完畢。

distribution push完成後有notify信息發送,留待以後再做分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章