Content Addressable DockerイメージとRegistry2.0

Docker 1.6: Engine & Orchestration Updates, Registry 2.0, & Windows Client Preview | Docker Blog

Docker1.6が出た.コンテナやイメージのラベリング(RancherOSの“Adding Label Support to Docker 1.6”がわかりやすい)や,Logging Driversといった新機能が追加された.今回のリリースで自分的に嬉しいのはDockerイメージがContent-addressableになったこと(#11109).

今までDocker Regitryを介したイメージのやりとりはイメージの名前とタグ(e.g., tcnksm/golang:1.2)しか使うことができなかった.タグはイメージの作成者によって付与されるのもであり,同じタグであっても必ず同じイメージが利用できるという保証はなかった(Gitでいうとコミットハッシュが使えず,タグのみしか使えないという状況).

Docker1.6と同時に発表されたRegistry2.0(docker/distribution)によりイメージにユニークなID(digest)が付与されるようになり,確実に同じイメージを参照することができるようになった(immutable image references).

使ってみる

DockerHubはすでにRegistry2.0になっているのですぐにこの機能は使える.が,今回は自分でPrivate Registryを立ててこの機能を試してみる(環境はboot2docker on OSX).

まずはRegistryを立てる.v1と同じようにDockerイメージが提供されている.

$ docker run -p 5000:5000 registry:2.0

簡単なDockerfileを準備してtcnksm/test-digestイメージをビルドする.

FROM busybox
$ docker build -t $(boot2docker ip):5000/tcnksm/test-digest:latest .

imagesコマンドで確認する.--digestsオプションをつけるとdigestが表示されるようになる.Gitと同じように考えると直感とズレるかもしれないがbuildするだけではdigestは生成されない.

$ docker images --digests
REPOSITORY                               TAG                 DIGEST              IMAGE ID            CREATED             VIRTUAL SIZE
192.168.59.103:5000/tcnksm/test-digest   latest              <none>              8c2e06607696        3 days ago          2.433 MB

Registryにpushしてみる.pushするとdigestが生成される.

$ docker push $(boot2docker ip):5000/tcnksm/test-digest:latest
...
Digest: sha256:e4c425e28a3cfe41efdfceda7ccce6be4efd6fc775b24d5ae26477c96fb5eaa4

生成したイメージを削除しdigestを使ってイメージをpullしてみる.NAME:TAGではなく[email protected]という形式で指定する.

$ docker rmi $(boot2docker ip):5000/tcnksm/test-digest:latest
$ docker pull $(boot2docker ip):5000/tcnksm/[email protected]:e4c425e28a3cfe41efdfceda7ccce6be4efd6fc775b24d5ae26477c96fb5eaa4

imagesコマンドで確認する.今回はdigestが表示されているのが確認できる.

$ docker images --digests
REPOSITORY                               TAG                 DIGEST                                                                    IMAGE ID            CREATED             VIRTUAL SIZE
192.168.59.103:5000/tcnksm/test-digest   <none>              sha256:e4c425e28a3cfe41efdfceda7ccce6be4efd6fc775b24d5ae26477c96fb5eaa4   8c2e06607696        3 days ago          2.433 MB

Dockerfile

DockerfileFROMでのイメージ名の指定にもdigestは使える.気がついたら元のイメージ更新されていて完成イメージが意図しないものになっていたということが避けられる.

FROM 192.168.59.103:5000/tcnksm/[email protected]:e4c425e28a3cfe41efdfceda7ccce6be4efd6fc775b24d5ae26477c96fb5eaa4
$ docker build .
Step 0 : FROM 192.168.59.103:5000/tcnksm/[email protected]:e4c425e28a3cfe41efdfceda7ccce6be4efd6fc775b24d5ae26477c96fb5eaa4
---> 8c2e06607696
Successfully built 8c2e06607696

イメージの更新

Dockerfileを編集して新しいイメージをbuildする.

FROM busybox
MAINTAINER tcnksm
$ docker build -t $(boot2docker ip):5000/tcnksm/test-digest:latest .

Registryにpushする.すると今度は異なるdigestが生成される.

$ docker push $(boot2docker ip):5000/tcnksm/test-digest:latest
...
Digest: sha256:4675f7a9d45932e3043058ef032680d76e8aacccda94b74374efe156e2940ee5

仕組み

簡単に仕組みを説明する.digestは手元で生成されるわけではない.pushしてRegistry側で生成される.

まずclientはイメージと共にImage ManifestをRegistryに送る(署名する).Image ManifestはそのDocker Imageの内容をJSONで定義したもの.Golangのstructでいうと以下のようなものでイメージの名前やタグ,FSレイヤーといった情報が書かれる(Manifestはここに定義されている).

type ManifestData struct {
    Name          string             `json:"name"`
    Tag           string             `json:"tag"`
    Architecture  string             `json:"architecture"`
    FSLayers      []*FSLayer         `json:"fsLayers"`
    History       []*ManifestHistory `json:"history"`
    SchemaVersion int                `json:"schemaVersion"`
}

APIを叩くとManifestの中身を見ることができる.

$ curl $(boot2docker ip):5000/v2/tcnksm/test-digest/manifests/latest

そしてRegistryは以下の関数内でリクエストされたManifestを元にdigestを生成する(registry/handlers/images.go).

// PutImageManifest validates and stores and image in the registry.
func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http.Request) 

Docker-Content-Digestヘッダでそれをclientに送る(API doc

202 Accepted
Location: <url>
Content-Length: 0
Docker-Content-Digest: <digest>

Regitryが異なったら…?

送るManifestは同じなのでRegistryが違っても同じdigestが付与される.digestはRegistryをまたがってユニークになる.

上で作成したイメージをDockerHubにpushしてみる.同じdigestが付与される.

$ docker build -t tcnksm/test-digest:latest .
$ docker push tcnksm/test-digest:latest
...
Digest: sha256:e4c425e28a3cfe41efdfceda7ccce6be4efd6fc775b24d5ae26477c96fb5eaa4

Registry2.0

Faster and Better Image Distribution with Registry 2.0 and Engine 1.6 | Docker Blog

docker/distributionは新しいRegistryの実装で,APIやセキュリティなど今までのRegistryの問題を解決しようとしている.今まではPythonで実装されていたがGo言語で再実装されている.

特徴的なのは,

  • イメージManifestの再定義(Image Manifest Version 2, Schema 1) - #8093を参照.セキュリティの改善が主な目的.
  • APIの刷新(Docker Registry HTTP API V2, #Detail)- URIの改善,Manifest V2を利用できるようにする,Push/Pullが途中で死んでも終わったところから再開できるようにする,などなど(詳しく見てないけどclientはGo言語のinterfaceとして定義されていたので自分で独自のものをつくれる…?)
  • バックエンドのストレージをPluggable化(Docker-Registry Storage Driver)- 現在は,インメモリ,ファイルシステム,S3,Azure Blob Storageが選択できる.Go言語のinterfaceとして定義されてるので自分で実装することもできる.
  • Webhookの実装(Notifications)- Push/Pullといったイベントが発生するごとに設定したendopointにリクエストを送ることができる.

あとまだスケルトンしかないがdistコマンドというものを作ろうとしている(dist).これはDockerデーモンなしでDockerイメージのpull/pushを行うコマンド.Dockerの少し嫌な部分としてrumtimeとイメージのダウンロードが分かれていないというのがあったが,それをここで解決しようとしているっぽい.

References