Circle CIでDockerHubのDownload rate limitを調べた

## 結論

2020/11/03 17:00時点で、まだ 100回/6時間 の制限は適用されていませんでした。 
[公式のアナウンス](https://www.docker.com/increase-rate-limits?utm_source=docker&utm_medium=web%20referral&utm_campaign=pull%20limits%20home%20page&utm_budget=) によると ~~日本時間で11/05 02:00-05:00に適用完了するようです。~~
(2020/11/08追記)　誤訳でした。ごめんなさい。公式のアナウンスは11/02を境に段階的に制限を適用していき、その過程で時々完全に制限をかける時間帯を設けるという趣旨でした。([追記にて詳細](#結局いつ完全に制限がかかるのか))

(2020/11/03 17:00時点)
![rate_limit2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/6a485957-aba4-fdd6-d5fe-20f3513b8a2c.png)


上が認証情報なし(匿名ユーザ)、下が自分の個人のDockerHubアカウントで確認した結果です。 
どちらも制限はかかっているものの、6時間で5000回まででした。  
ちなみにCircleCIは[一部の例外を除きユーザが今すぐ対応すべきことはない](https://support.circleci.com/hc/en-us/articles/360050623311-Docker-Hub-rate-limiting-FAQ)と発表しています。 Download rate limitが完全に適用された後、このジョブでは(匿名ユーザの場合)100回までと表示されるものの問題なくジョブを回せるような感じになるのかなあと想像しています。


## Download rate limitとCircleCI
[DockerHubのpull回数制限](https://matsuand.github.io/docs.docker.jp.onthefly/docker-hub/download-rate-limit/)が順次適用されているようです。 
公式には2020/11/01に導入というアナウンスはあったものの、ドキュメントにあるとおり11/02から徐々に導入していくようです。 
私は手元でもCircleCI上でも 11/03 17:00時点で6時間で100回までという制限が有効になったことを確認できていません。

**CircleCIユーザは多くの場合、いますぐ対応しなければならないことはありません。**
**ただ、以下のケースに該当する場合は[公式FAQ](https://support.circleci.com/hc/en-us/articles/360050623311-Docker-Hub-rate-limiting-FAQ)を一読した方が良いです。**
(なぜか2020/11/03時点で[日本語ページ](https://support.circleci.com/hc/ja/articles/360050623311-Docker-Hub%E3%81%AE%E3%83%AC%E3%83%BC%E3%83%88%E5%88%B6%E9%99%90%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8BFAQ)は情報が古いので英語ページを参照した方が良いです)

* Machine Executor を利用している
* オンプレミスサーバ(CircleCI Enterprise)を利用している 

ただしCircleCIは今後この制限がユーザに適用される可能性があることを否定しておらず、適切な認証情報を使ってイメージをpullすることを推奨しています。 
認証情報の設定方法は[公式ガイド](https://circleci.com/docs/2.0/private-images/)や[詳細に解説されている記事](https://qiita.com/sot528/items/30a9728973b3dbca3f76)をご覧ください。

## Download rate limitの調べ方
詳細な説明は[公式の解説](https://matsuand.github.io/docs.docker.jp.onthefly/docker-hub/download-rate-limit/)にお任せします。 
ざっくり言うとトークンを取得して特定のURLにHTTPリクエストを投げると今のダウンロード制限情報を教えてくれるということを言っています。

```
$ TOKEN=$(curl "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token
$ curl -v -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest 2>&1 | grep RateLimit
```

**※注意　2行目のcurlリクエストは1回分のイメージダウンロードと見なされます。無闇に実行しないようにしましょう。**
DockerHubはパス`/v2/*/manifests/*` へのGETメソッドによるリクエストを1回のダウンロードと見なしているようです。

実行すると以下のような結果が返ってきます。

```
< RateLimit-Limit: 5000;w=21600
< RateLimit-Remaining: 4999;w=21600
```

限界が5000回、残り回数が4999回、wは時間窓で21600s(6時間)です。
消費された1回は残り回数を問い合わせるリクエスト自体によるものです。

## CircleCIの設定ファイル

``` .circleci/config.yml
version: 2.1

workflows:
  workflow:
    jobs:
      - build:
          context:
            - dockerhub-context
jobs:
  build:
    docker:
      - image: circleci/node:latest
        auth:
          username: $DOCKERHUB_USERNAME
          password: $DOCKERHUB_PASSWORD
    resource_class: small
    steps:
      - checkout
      - run: 
          name: Check rate limits with Circle CI credentials
          command: "curl -v -H \"Authorization: Bearer `curl 'https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull' | jq -r .token`\" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest 2>&1 | grep RateLimit"
      - run:
          name: Check rate limits with my own credentials
          command: "curl -v -H \"Authorization: Bearer `curl --user \"$DOCKERHUB_USERNAME:$DOCKERHUB_PASSWORD\" 'https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull' | jq -r .token`\" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest 2>&1 | grep RateLimit"
```

こんな感じで書きました。 
コマンドは変数設定が面倒だったのでトークン取得と残り回数問い合わせを同じ行でやっています。
また、`dockerhub-context`なるcontextを作成して環境変数`DOCKERHUB_USERNAME`と`DOCKERHUB_PASSWORD`を設定しています。パスワードはWebページのログインで使用するものではなくDockerHubのアカウント設定から作成できるAPIキーを使うと良いです。
結果は冒頭の画像です。制限が完全に有効化した頃にもう一回試してみたいですね。

**※もちろんですがこのスクリプト内のcurlリクエストも1回のダウンロードと見なされます(合計2回)。CircleCI内ならおそらく大丈夫とは言えあまりブン回さないようにしましょう。**


## 余談
`https://registry-1.docker.io/v2/*/manifests/*`への問い合わせであってもHEADメソッドならばダウンロード回数の消費はないようです。 
ということは `RateLimit-Limit` とかはヘッダ情報だし `curl --head` で問い合わせればいいんじゃね！？　と思いましたが駄目でした。

```
$ curl --head -H "Authorization: Bearer `curl 'https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull' | jq -r .token`" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest 2>&1
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, 03 Nov 2020 08:55:59 GMT
```

## 結局いつ完全に制限がかかるのか
11/08時点では不明です。ただし、[Understanding Docker Hub Rate Limiting](https://www.docker.com/increase-rate-limits?utm_source=docker&utm_medium=web%20referral&utm_campaign=pull%20limits%20home%20page&utm_budget=) にて現在の制限および一時的に制限が完全に適用される時間が分かります。

>Current Docker Hub Usage Limit Status (updated 11/6, 12:00pm Pacific)
Unauthenticated requests:	2,500 per six hours
Free tier requests:	2,500 per six hours
Temporary full enforcement windows (100 per six hours for unauthenticated requests, 200 per six hours for free accounts): November 9, 9am-3pm Pacific time and November 10, 3am-9am Pacific time.

以上の例だと、日本時間05:00時点では匿名ユーザ、認証済みユーザともに2500回/6hの制限がかかっていることが分かります。また、日本時間11/10 02:00-08:00、20:00-翌02:00に **一時的に完全に制限する**(匿名：100回/6h、認証済：200回/6h)と書いています。

Temporary full enforcement windows を暫定的な完全施行時間帯と誤読していました。ごめんなさい。

実務的には既に完全に制限がかかったものとして対策するのがよいでしょう。

## 参考
* [Docker Hubのレート制限に関するFAQ](https://support.circleci.com/hc/en-us/articles/360050623311-Docker-Hub-rate-limiting-FAQ)  
CircleCI公式のFAQ
* [Using Docker Authenticated Pulls](https://circleci.com/docs/2.0/private-images/)  
CircleCIのジョブ内で認証済ユーザでDockerイメージをプルする方法
* [ダウンロードレート制限](https://matsuand.github.io/docs.docker.jp.onthefly/docker-hub/download-rate-limit/)  
DockerHub公式のダウンロード制限についての説明 
* [Resource Consumption Updates FAQ](https://www.docker.com/pricing/resource-consumption-updates)  
ダウンロード制限のことに加えてイメージの保持期限についても記載がある公式FAQ