在开发docker app的时候,为了共享这个镜像,导出再分发给团队成员是比较麻烦的做法。Docker 提供了一个 Registry 给我们上传开发好的镜像(类似 maven 的私有仓库),这样需要使用的团队成员去 registry 上下载即可。

下面介绍下如何使用

1. 下载官方镜像 docker pull registry

[root@localhost ~]# docker pull registry
Using default tag: latest
latest: Pulling from library/registry
ab7e51e37a18: Pull complete 
c8ad8919ce25: Pull complete 
5808405bc62f: Pull complete 
f6000d7b276c: Pull complete 
f792fdcd8ff6: Pull complete 
Digest: sha256:9d295999d330eba2552f9c78c9f59828af5c9a9c15a3fbd1351df03eaad04c6a
Status: Downloaded newer image for registry:latest
[root@localhost ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
registry            latest              177391bcf802        6 days ago          33.3MB
[root@localhost ~]#

 

2. 按照官方说明(传送门)启动

[root@localhost ~]# docker run -d -p 5000:5000 --name registry registry
acff7e5e012f996a7a03c7b7bf26773755cfa908cde3ae1dbdcd8ec970e2dff1
[root@localhost ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
acff7e5e012f        registry            "/entrypoint.sh /e..."   6 seconds ago       Up 6 seconds        0.0.0.0:5000->5000/tcp   registry
[root@localhost ~]#

这样私有的 docker registry 就建好了。

 

接下来作为 demo 下载一个简单的 hello-world

[root@localhost ~]# docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
ca4f61b1923c: Pull complete 
Digest: sha256:be0cd392e45be79ffeffa6b05338b98ebb16c87b255f48e297ec7f98e123905c
Status: Downloaded newer image for hello-world:latest
[root@localhost ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
registry            latest              177391bcf802        6 days ago          33.3MB
hello-world         latest              f2a91732366c        2 weeks ago         1.85kB
[root@localhost ~]#

重命名镜像名称 docker tag <镜像名> <仓库地址:端口>/新的镜像名

[root@localhost ~]# docker tag hello-world localhost:5000/my-hello-world
[root@localhost ~]# docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
registry                        latest              177391bcf802        6 days ago          33.3MB
hello-world                     latest              f2a91732366c        2 weeks ago         1.85kB
localhost:5000/my-hello-world   latest              f2a91732366c        2 weeks ago         1.85kB
[root@localhost ~]#

上传镜像 docker push <仓库地址:端口>/新的镜像名

[root@localhost ~]# docker push localhost:5000/my-hello-world
The push refers to a repository [localhost:5000/my-hello-world]
f999ae22f308: Pushed 
latest: digest: sha256:8072a54ebb3bc136150e2f2860f00a7bf45f13eeb917cca2430fcd0054c8e51b size: 524
[root@localhost ~]#

删除本地镜像

[root@localhost ~]# docker rmi localhost:5000/my-hello-world
Untagged: localhost:5000/my-hello-world:latest
[root@localhost ~]# docker rmi hello-world
Untagged: hello-world:latest
Untagged: hello-world@sha256:be0cd392e45be79ffeffa6b05338b98ebb16c87b255f48e297ec7f98e123905c
Deleted: sha256:f2a91732366c0332ccd7afd2a5c4ff2b9af81f549370f7a19acd460f87686bc7
Deleted: sha256:f999ae22f308fea973e5a25b57699b5daf6b0f1150ac2a5c2ea9d7fecee50fdf
[root@localhost ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
registry            latest              177391bcf802        6 days ago          33.3MB
[root@localhost ~]#

从仓库下载镜像 docker pull <仓库地址:端口>/镜像名

[root@localhost ~]# docker pull localhost:5000/my-hello-world
Using default tag: latest
latest: Pulling from my-hello-world
ca4f61b1923c: Pull complete 
Digest: sha256:8072a54ebb3bc136150e2f2860f00a7bf45f13eeb917cca2430fcd0054c8e51b
Status: Downloaded newer image for localhost:5000/my-hello-world:latest
[root@localhost ~]# docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
registry                        latest              177391bcf802        6 days ago          33.3MB
localhost:5000/my-hello-world   latest              f2a91732366c        2 weeks ago         1.85kB
[root@localhost ~]#

重命名一下就可以使用了

[root@localhost ~]# docker tag localhost:5000/my-hello-world my-hello-world
[root@localhost ~]# docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
registry                        latest              177391bcf802        6 days ago          33.3MB
my-hello-world                  latest              f2a91732366c        2 weeks ago         1.85kB
localhost:5000/my-hello-world   latest              f2a91732366c        2 weeks ago         1.85kB
[root@localhost ~]#

 

基本概念就是这样,但是还有一些问题。

1. 这样的启动方式,上传的数据都是作为一个 docker volume 存储在宿主机上面。如果这个 registry 容器被删除,那么容器内的数据也会丢失,即我们上传的镜像。

所以我们需要将 registry 的镜像地址映射到宿主机上面,registry 在容器内部的存储地址是 /var/lib/registry/, 通过添加 -v 映射到宿主机。

[root@localhost ~]# docker rm -f registry
registry
[root@localhost ~]# docker run --name registry -d -p 5000:5000 -v /home/saul/registry:/var/lib/registry registry
a107eda18508d791085ebcec5cbb9378d496239fba0f7347e998eeda5966dc18
[root@localhost ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
a107eda18508        registry            "/entrypoint.sh /e..."   4 seconds ago       Up 3 seconds        0.0.0.0:5000->5000/tcp   registry
[root@localhost ~]#

将原来的容器删除,重新建一个。这里我映射到了 /home/john/registry 这个目录下。

再次上传镜像

[root@localhost registry]# docker push localhost:5000/my-hello-world
The push refers to a repository [localhost:5000/my-hello-world]
f999ae22f308: Pushed 
latest: digest: sha256:8072a54ebb3bc136150e2f2860f00a7bf45f13eeb917cca2430fcd0054c8e51b size: 524

2. 如何查看镜像

官网提供了一套操作 registry 的 api (传送门)

查看仓库 GET /v2/_catalog

[root@localhost registry]# curl http://127.0.0.1:5000/v2/_catalog
{"repositories":["my-hello-world"]}

查看镜像标签 GET /v2/<name>/tags/list

[root@localhost registry]# curl http://127.0.0.1:5000/v2/my-hello-world/tags/list
{"name":"my-hello-world","tags":["latest"]}

现在只有一个 latest 标签,这里 run 下 hello-world,然后提交一个新的版本1.0

[root@localhost registry]# docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
registry                        latest              177391bcf802        6 days ago          33.3MB
localhost:5000/my-hello-world   latest              f2a91732366c        2 weeks ago         1.85kB
my-hello-world                  latest              f2a91732366c        2 weeks ago         1.85kB
[root@localhost registry]# docker run -it my-hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://cloud.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/engine/userguide/

[root@localhost registry]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS                    NAMES
e023cad89c0d        my-hello-world      "/hello"                 6 seconds ago       Exited (0) 5 seconds ago                            determined_keller
a107eda18508        registry            "/entrypoint.sh /e..."   8 minutes ago       Up 8 minutes               0.0.0.0:5000->5000/tcp   registry
[root@localhost registry]# docker commit -m "1.0" e023cad89c0d my-hello-world:1.0
sha256:d9f41037af5b7648d4a1bfb0a95b903f16ebc52a23a0883896ae71d7831ee97c
[root@localhost registry]# docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
my-hello-world                  1.0                 d9f41037af5b        4 seconds ago       1.85kB
registry                        latest              177391bcf802        6 days ago          33.3MB
localhost:5000/my-hello-world   latest              f2a91732366c        2 weeks ago         1.85kB
my-hello-world                  latest              f2a91732366c        2 weeks ago         1.85kB
[root@localhost registry]#

上传镜像

[root@localhost registry]# docker tag my-hello-world:1.0 localhost:5000/my-hello-world:1.0
[root@localhost registry]# docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED              SIZE
my-hello-world                  1.0                 d9f41037af5b        About a minute ago   1.85kB
localhost:5000/my-hello-world   1.0                 d9f41037af5b        About a minute ago   1.85kB
registry                        latest              177391bcf802        6 days ago           33.3MB
my-hello-world                  latest              f2a91732366c        2 weeks ago          1.85kB
localhost:5000/my-hello-world   latest              f2a91732366c        2 weeks ago          1.85kB
[root@localhost registry]# docker push localhost:5000/my-hello-world:1.0
The push refers to a repository [localhost:5000/my-hello-world]
f999ae22f308: Layer already exists 
1.0: digest: sha256:69b12f1aeee0355bcf803b5159f96f69738f6b002ea3b6861b802aff337d26cf size: 524
[root@localhost registry]#

再次查看仓库的标签

[root@localhost registry]# curl http://127.0.0.1:5000/v2/my-hello-world/tags/list
{"name":"my-hello-world","tags":["latest","1.0"]}

可以看到有 latest, 1.0 2个标签

3. 那么怎么删除仓库里的镜像

DELETE /v2/<name>/manifests/<reference>

注:registry 2.3 版本以上 需要在头部添加 

Accept: application/vnd.docker.distribution.manifest.v2+json

[root@localhost registry]# curl --header "Accept: application/vnd.docker.distribution.manifest.v2+json" -X DELETE http://127.0.0.1:5000/v2/my-hello-world/manifests/latest
{"errors":[{"code":"UNSUPPORTED","message":"The operation is unsupported."}]}
[root@localhost registry]#

提示 UNSUPPORTED! 为什么?

查阅资料得知 registry 有个配置文件 config.yml 内容如下

version: 0.1
log:
  accesslog:
    disabled: true
  level: debug
  formatter: text
  fields:
    service: registry
    environment: staging
  hooks:
    - type: mail
      disabled: true
      levels:
        - panic
      options:
        smtp:
          addr: mail.example.com:25
          username: mailuser
          password: password
          insecure: true
        from: sender@example.com
        to:
          - errors@example.com
loglevel: debug # deprecated: use "log"
storage:
  filesystem:
    rootdirectory: /var/lib/registry
    maxthreads: 100
  azure:
    accountname: accountname
    accountkey: base64encodedaccountkey
    container: containername
  gcs:
    bucket: bucketname
    keyfile: /path/to/keyfile
    rootdirectory: /gcs/object/name/prefix
    chunksize: 5242880
  s3:
    accesskey: awsaccesskey
    secretkey: awssecretkey
    region: us-west-1
    regionendpoint: http://myobjects.local
    bucket: bucketname
    encrypt: true
    keyid: mykeyid
    secure: true
    v4auth: true
    chunksize: 5242880
    multipartcopychunksize: 33554432
    multipartcopymaxconcurrency: 100
    multipartcopythresholdsize: 33554432
    rootdirectory: /s3/object/name/prefix
  swift:
    username: username
    password: password
    authurl: https://storage.myprovider.com/auth/v1.0 or https://storage.myprovider.com/v2.0 or https://storage.myprovider.com/v3/auth
    tenant: tenantname
    tenantid: tenantid
    domain: domain name for Openstack Identity v3 API
    domainid: domain id for Openstack Identity v3 API
    insecureskipverify: true
    region: fr
    container: containername
    rootdirectory: /swift/object/name/prefix
  oss:
    accesskeyid: accesskeyid
    accesskeysecret: accesskeysecret
    region: OSS region name
    endpoint: optional endpoints
    internal: optional internal endpoint
    bucket: OSS bucket
    encrypt: optional data encryption setting
    secure: optional ssl setting
    chunksize: optional size valye
    rootdirectory: optional root directory
  inmemory:  # This driver takes no parameters
  delete:
    enabled: false
  redirect:
    disable: false
  cache:
    blobdescriptor: redis
  maintenance:
    uploadpurging:
      enabled: true
      age: 168h
      interval: 24h
      dryrun: false
    readonly:
      enabled: false
auth:
  silly:
    realm: silly-realm
    service: silly-service
  token:
    realm: token-realm
    service: token-service
    issuer: registry-token-issuer
    rootcertbundle: /root/certs/bundle
  htpasswd:
    realm: basic-realm
    path: /path/to/htpasswd
middleware:
  registry:
    - name: ARegistryMiddleware
      options:
        foo: bar
  repository:
    - name: ARepositoryMiddleware
      options:
        foo: bar
  storage:
    - name: cloudfront
      options:
        baseurl: https://my.cloudfronted.domain.com/
        privatekey: /path/to/pem
        keypairid: cloudfrontkeypairid
        duration: 3000s
  storage:
    - name: redirect
      options:
        baseurl: https://example.com/
reporting:
  bugsnag:
    apikey: bugsnagapikey
    releasestage: bugsnagreleasestage
    endpoint: bugsnagendpoint
  newrelic:
    licensekey: newreliclicensekey
    name: newrelicname
    verbose: true
http:
  addr: localhost:5000
  prefix: /my/nested/registry/
  host: https://myregistryaddress.org:5000
  secret: asecretforlocaldevelopment
  relativeurls: false
  tls:
    certificate: /path/to/x509/public
    key: /path/to/x509/private
    clientcas:
      - /path/to/ca.pem
      - /path/to/another/ca.pem
    letsencrypt:
      cachefile: /path/to/cache-file
      email: emailused@letsencrypt.com
  debug:
    addr: localhost:5001
  headers:
    X-Content-Type-Options: [nosniff]
  http2:
    disabled: false
notifications:
  endpoints:
    - name: alistener
      disabled: false
      url: https://my.listener.com/event
      headers: <http.Header>
      timeout: 500
      threshold: 5
      backoff: 1000
      ignoredmediatypes:
        - application/octet-stream
redis:
  addr: localhost:6379
  password: asecret
  db: 0
  dialtimeout: 10ms
  readtimeout: 10ms
  writetimeout: 10ms
  pool:
    maxidle: 16
    maxactive: 64
    idletimeout: 300s
health:
  storagedriver:
    enabled: true
    interval: 10s
    threshold: 3
  file:
    - file: /path/to/checked/file
      interval: 10s
  http:
    - uri: http://server.to.check/must/return/200
      headers:
        Authorization: [Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==]
      statuscode: 200
      timeout: 3s
      interval: 10s
      threshold: 3
  tcp:
    - addr: redis-server.domain.com:6379
      timeout: 3s
      interval: 10s
      threshold: 3
proxy:
  remoteurl: https://registry-1.docker.io
  username: [username]
  password: [password]
compatibility:
  schema1:
    signingkeyfile: /etc/registry/key.json
validation:
  enabled: true
  manifests:
    urls:
      allow:
        - ^https?://([^/]+\.)*example\.com/
      deny:
        - ^https?://www\.example\.com/

可以看到 storage 节点下 delete enabled 默认为 false

storage:
  delete:
    enabled: false

那么我们开启它,docker 允许通过 -e 传入环境变量(变量的格式为 REGISTRY_variable),我们传入一个   REGISTRY_STORAGE_DELETE_ENABLED=true 。官网说明(传送门

那么删除原来的 registry 启动方式改为 

[root@localhost registry]# docker rm -f registry
registry
[root@localhost registry]# docker run --name registry -d -p 5000:5000 -v /home/john/registry:/var/lib/registry -e REGISTRY_STORAGE_DELETE_ENABLED=true registry
8af585efa1914c2354def81eb0cbf3f30bf4fe0d5cab24ba7ce2491cfbc0678b

查看仓库

[root@localhost registry]# curl  http://127.0.0.1:5000/v2/_catalog
{"repositories":["my-hello-world"]}

刚才上传的镜像还在,并没有随着容器删除而删除,证明我们刚才将镜像存在宿主机上的修改是有效的。

再次删除镜像

[root@localhost registry]# curl --header "Accept: application/vnd.docker.distribution.manifest.v2+json" -X DELETE http://127.0.0.1:5000/v2/my-hello-world/manifests/latest
{"errors":[{"code":"DIGEST_INVALID","message":"provided digest did not match uploaded content"}]}

提示 DISGEST_INVALID,删除api是 DELETE /v2/<name>/manifests/<reference>,查看官网 For deletes, reference must be a digest or the delete will fail. 一定要是一个 digest。

查阅资料 通过 curl -I 可以查得

[root@localhost registry]# curl --header "Accept: application/vnd.docker.distribution.manifest.v2+json" -I -X GET http://localhost:5000/v2/my-hello-world/manifests/latest
HTTP/1.1 200 OK
Content-Length: 524
Content-Type: application/vnd.docker.distribution.manifest.v2+json
Docker-Content-Digest: sha256:8072a54ebb3bc136150e2f2860f00a7bf45f13eeb917cca2430fcd0054c8e51b
Docker-Distribution-Api-Version: registry/2.0
Etag: "sha256:8072a54ebb3bc136150e2f2860f00a7bf45f13eeb917cca2430fcd0054c8e51b"
X-Content-Type-Options: nosniff
Date: Fri, 08 Dec 2017 08:44:59 GMT

[root@localhost registry]#

Docker-Content-Digest 这个就是我们需要的 digest

再次调用api 删除

[root@localhost registry]# curl --header "Accept: application/vnd.docker.distribution.manifest.v2+json" -X DELETE http://127.0.0.1:5000/v2/my-hello-world/manifests/sha256:8072a54ebb3bc136150e2f2860f00a7bf45f13eeb917cca2430fcd0054c8e51b
[root@localhost registry]# curl http://127.0.0.1:5000/v2/my-hello-world/tags/list
{"name":"my-hello-world","tags":["1.0"]}
[root@localhost registry]#

可以看到 lastest 已经没有了,删除成功。

其实我们可以在文件系统看到tags的信息

[root@localhost tags]# pwd
/home/john/registry/docker/registry/v2/repositories/my-hello-world/_manifests/tags
[root@localhost tags]# ll
total 0
drwxr-xr-x. 4 root root 34 Dec  8 16:21 1.0
[root@localhost tags]#

我们可以浏览一下这些目录

[root@localhost v2]# pwd
/home/john/registry/docker/registry/v2
[root@localhost v2]# ll
total 0
drwxr-xr-x. 3 root root 20 Dec  8 11:28 blobs
drwxr-xr-x. 3 root root 28 Dec  8 16:13 repositories
[root@localhost v2]#

在这个目录下有个blobs目录

这个目录在删除完镜像也要清理一下,2.4以上registry才有次功能

docker exec -it <registry_container_id> bin/registry garbage-collect <path_to_registry_config>

 

[root@localhost v2]# docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml
my-hello-world
my-hello-world: marking manifest sha256:69b12f1aeee0355bcf803b5159f96f69738f6b002ea3b6861b802aff337d26cf 
my-hello-world: marking blob sha256:d9f41037af5b7648d4a1bfb0a95b903f16ebc52a23a0883896ae71d7831ee97c
my-hello-world: marking blob sha256:ca4f61b1923c10e9eb81228bd46bee1dfba02b9c7dac1844527a734752688ede

3 blobs marked, 2 blobs eligible for deletion
blob eligible for deletion: sha256:8072a54ebb3bc136150e2f2860f00a7bf45f13eeb917cca2430fcd0054c8e51b
time="2017-12-08T08:56:15Z" level=info msg="Deleting blob: /docker/registry/v2/blobs/sha256/80/8072a54ebb3bc136150e2f2860f00a7bf45f13eeb917cca2430fcd0054c8e51b" go.version=go1.7.6 instance.id=fccb2a78-43b9-4542-9f9d-7bbf0ec0b044 
blob eligible for deletion: sha256:f2a91732366c0332ccd7afd2a5c4ff2b9af81f549370f7a19acd460f87686bc7
time="2017-12-08T08:56:15Z" level=info msg="Deleting blob: /docker/registry/v2/blobs/sha256/f2/f2a91732366c0332ccd7afd2a5c4ff2b9af81f549370f7a19acd460f87686bc7" go.version=go1.7.6 instance.id=fccb2a78-43b9-4542-9f9d-7bbf0ec0b044 
[root@localhost v2]#

这样删除就差不多了。

粗暴一点可以直接(不知道有没有副作用)

rm -rf /home/john/registry/docker/registry/v2/repositories/<镜像名>

4. 随服务启动

docker run 的时候添加 --restart=always 可以保证 docker 服务启动的时候,容器随服务一起启动。

docker run --name registry -d -p 5000:5000 --restart=always -v /home/john/registry:/var/lib/registry -e REGISTRY_STORAGE_DELETE_ENABLED=true registry

 

总结

启动

docker run --name registry -d -p 5000:5000 --restart=always -v /xxx/registry:/var/lib/registry -e REGISTRY_STORAGE_DELETE_ENABLED=true registry

上传

docker push <registry_ip:port>/<name>

下载

docker pull <registry_ip:port>/<name>

查看 docker registry

GET /v2/_catalog

查看镜像tags

GET /v2/<name>/tags/list

删除镜像

配置config.yml,获取镜像 digest

DELETE /v2/<name>/manifests/<reference>