Docker Hub のダウンロード制限を確認する

# はじめに

特定ユーザーに対する Docker の有料価が始まりましたが、それに先立って 2020 年ごろから Docker Hub のコンテナに対するプル要求数の制限がアカウント毎に行われています。

[制限の発表当初](https://www.docker.com/blog/what-you-need-to-know-about-upcoming-docker-hub-rate-limiting/)は無料、匿名ユーザーは 6 時間当たり 5,000 プル要求の制限が掛かっていましたが、2022 年 2 月現在では[価格表](https://www.docker.com/pricing) にも記載がある通り、下記の通りの制限になっているようです。

- 匿名ユーザー：6 時間当たり 100 プル要求
- 無料(Personal)ユーザー：6 時間あたり 200 プル要求
- Pro, Team, Business：24 時間当たり 50,000 プル要求

この記事では、改めて Docker Hub のプル要求数の制限がどのようなものなのかを確認していきます。

# ダウンロード制限の定義

Docker の[ダウンロード制限に関するドキュメント](https://docs.docker.com/docker-hub/download-rate-limit/) を確認すると、ダウンロード制限について次のような定義が乗っています。

- プル要求は、レジストリマニフェスト URL（GET/v2/_/manifests/_）に対する最大 2 つのリクエストとして定義されます。
- 通常のイメージのプルでは、1 つのマニフェスト要求が行われます。
- マルチアーチイメージのプルリクエストは、2 つのマニフェストリクエストを行います。
- HEAD リクエストはカウントされません。。

基本的には 1 イメージ 1 要求として考えれば良いようです。

# 現在の状況を確認する

次のようにアクセストークン取得後に何かしらのマニフェストファイルを取得すると、レスポンスヘッダーから現在の上限(`ratelimit-limit`)と利用状況(`ratelimit-remaining`)を確認できます。

```
$ TOKEN=$(curl --user 'username:password' "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)

$ curl --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest
HTTP/1.1 200 OK
content-length: 2782
content-type: application/vnd.docker.distribution.manifest.v1+prettyjws
docker-content-digest: sha256:767a3815c34823b355bed31760d5fa3daca0aec2ce15b217c9cd83229e0e2020
docker-distribution-api-version: registry/2.0
etag: "sha256:767a3815c34823b355bed31760d5fa3daca0aec2ce15b217c9cd83229e0e2020"
date: Mon, 31 Jan 2022 23:48:06 GMT
strict-transport-security: max-age=31536000
ratelimit-limit: 200;w=21600
ratelimit-remaining: 188;w=21600
docker-ratelimit-source: 12cc3a69-4489-446c-873c-xxxxxxxxxxxx
```

`docker-ratelimit-source` でプル数の上限を管理しているようです。匿名ユーザーの場合はこの値が接続元の IP アドレスになり、Docker Hub のユーザーでログインしている場合はユーザー毎、サイクル？毎 GUID が割り振られるようです。

現在のユーザーは、6 時間当たり 200 回の制限が掛かっていて、のこり 188 回のプル要求が許されているのが分かります。この状態で `mysql` のコンテナを一つプルしたとしたらどうなるのでしょうか。

```
$ docker pull mysql
Using default tag: latest
latest: Pulling from library/mysql
6552179c3509: Already exists
d69aa66e4482: Already exists
3b19465b002b: Already exists
7b0d0cfe99a1: Already exists
9ccd5a5c8987: Already exists
2dab00d7d232: Already exists
64d3afdccd4a: Already exists
82148d50b16c: Pull complete
8bb7d73a7d0c: Pull complete
74778cd68a75: Pull complete
d7e5f9309140: Pull complete
f2e376ecd59f: Pull complete
Digest: sha256:92d27b8222bbcf53bc42c70ca7cd1010d6c0527efc61f14980ce77c50932bef4
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest

$ curl --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest
HTTP/1.1 200 OK
... 略 ...
ratelimit-limit: 200;w=21600
ratelimit-remaining: 187;w=21600
docker-ratelimit-source: 12cc3a69-4489-446c-873c-xxxxxxxxxxxx
```

`ratelimit-remaining` の値が 1 つ減って 187 になりましたね。

マルチアーキテクチャなコンテナイメージをプルする場合は 2 つ要求を消費するということなので、`amazonlinux` をアーキテクチャ指定なしでプルしてみます。

```
$ docker pull amazonlinux:latest
latest: Pulling from library/amazonlinux
3a461b3ae562: Pull complete
Digest: sha256:ffc013f79b36a2c0352b444f5322ff43de25152a8ac1ad0fa473e90f1cbedcbe
Status: Downloaded newer image for amazonlinux:latest
docker.io/library/amazonlinux:latest
docker-ratelimit-source: 12cc3a69-4489-446c-873c-xxxxxxxxxxxx
```

ちゃんと 2 つ減っていますね。

```
$ curl --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest
HTTP/1.1 200 OK
... 略 ...
ratelimit-limit: 200;w=21600
ratelimit-remaining: 185;w=21600
docker-ratelimit-source: 12cc3a69-4489-446c-873c-xxxxxxxxxxxx
```

一度イメージを削除して、今度はアーキテクチャ指定でプルしてみます。

```
$ docker rmi amazonlinux
Untagged: amazonlinux:latest
Untagged: amazonlinux@sha256:ffc013f79b36a2c0352b444f5322ff43de25152a8ac1ad0fa473e90f1cbedcbe
Deleted: sha256:30156a0e4f040d255f30a8bf0b28540fce965398fe1c13ab29a7292e411dfde7
Deleted: sha256:4f765311acacb07fadae2ddcee2aa02ba56feb2f676f181adec48d215dd63663

$ docker pull amd64/amazonlinux:latest
latest: Pulling from amd64/amazonlinux
3a461b3ae562: Pull complete
Digest: sha256:9c4e7b374a7a9253764876520d867ba1f5bfddeed82b029b5054cce0f71faf20
Status: Downloaded newer image for amd64/amazonlinux:latest
docker.io/amd64/amazonlinux:latest
```

今度は 1 つ減りました。

```
$ curl --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest
HTTP/1.1 200 OK
... 略 ...
ratelimit-limit: 200;w=21600
ratelimit-remaining: 184;w=21600
docker-ratelimit-source: 12cc3a69-4489-446c-873c-xxxxxxxxxxxx
```

消して、プルしてを繰り返してリミットまでプルしてみます。

```
$ curl --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest
... 略 ...
ratelimit-limit: 200;w=21600
ratelimit-remaining: 0;w=21600
docker-ratelimit-source: 12cc3a69-4489-446c-873c-xxxxxxxxxxxx

$ docker pull amazonlinux
Using default tag: latest
Error response from daemon: toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit
```

tomanyrequest ではじかれましたね。

# 匿名ユーザーのリミット

匿名ユーザーでトークンを取得すると、レスポンスヘッダーの `docker-ratelimit-source` が IP アドレスになっていることを確認できます。
上限も少なくなっているので、同一ネットワーク内に匿名ユーザーが複数いる場合はすぐに上限に達してしまいそうですね。

```
$ TOKEN=$(curl "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
$ curl --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest
HTTP/1.1 200 OK
content-length: 2782
content-type: application/vnd.docker.distribution.manifest.v1+prettyjws
docker-content-digest: sha256:767a3815c34823b355bed31760d5fa3daca0aec2ce15b217c9cd83229e0e2020
docker-distribution-api-version: registry/2.0
etag: "sha256:767a3815c34823b355bed31760d5fa3daca0aec2ce15b217c9cd83229e0e2020"
date: Tue, 01 Feb 2022 03:04:11 GMT
strict-transport-security: max-age=31536000
ratelimit-limit: 100;w=21600
ratelimit-remaining: 96;w=21600
docker-ratelimit-source: 133.200.192.128
```

# 有料ユーザーのリミット

有料ユーザー(Pro, Team, Business)の場合、`ratelimit-limit` と `ratelimit-remaining` がレスポンスヘッダーに含まれなくなります。一応 24 時間で 50,000 回という制限はあるはずなのですが、匿名や無料ユーザーのように厳密には管理されないのかもしれませんね。

```
$ curl --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest
HTTP/1.1 200 OK
content-length: 2782
content-type: application/vnd.docker.distribution.manifest.v1+prettyjws
docker-content-digest: sha256:767a3815c34823b355bed31760d5fa3daca0aec2ce15b217c9cd83229e0e2020
docker-distribution-api-version: registry/2.0
etag: "sha256:767a3815c34823b355bed31760d5fa3daca0aec2ce15b217c9cd83229e0e2020"
date: Tue, 01 Feb 2022 03:14:48 GMT
strict-transport-security: max-age=31536000
docker-ratelimit-source: 2cbba8b1-5b0d-46e7-bde2-xxxxxxxxxxxx
```

# まとめ

制限の対象はレイヤー数ではなく、あくまでイメージ数です。
CI などで頻繁にイメージをプルする場合は有料アカウントを利用したほうが良いと思いますが、個人の開発端末の上で利用する場合は無料アカウントを利用すれば大きな問題にはならないでしょう。