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, 期目錄結構也不復雜。
- 首先repositories目錄下是ddcmao目錄,該目錄是鏡像倉庫裏的邏輯分組,有沒有都可以,如果要進行分組管理,則該目錄就是分組這裏稱之爲項目。
- 項目下面是java8目錄,該目錄是一個鏡像repository目錄。
- java8目錄下面有_layers、_manifests和_uploads目錄,其中_uploads目錄是一個臨時目錄,是鏡像上傳的過程中的目錄,一旦鏡像上傳完成,該目錄下的文件就被刪除。
- _layers目錄類似於blobs目錄,但是它不存儲真是數據僅僅以link文件保存每個layer的sha256編碼。保存該repository長傳過得所有layer的sha256編碼信息。
- _manifests目錄存放的信息就是該repository的上傳的所有版本(tag)的manifest信息。其目錄下有revisions目錄和tags目錄。
- _tags目錄很明顯每個tag一組記錄,例如tag 0.1 0.2, 每個tag下面有current目錄和index目錄, current目錄下的link文件保存了該tag目前的manifest文件的sha256編碼,而index目錄則列出了該tag歷史上傳的所有版本的sha256編碼信息。
- _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 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 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 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信息發送,留待以後再做分析。