Docker HubのRate Limitにやられた話

はじめまして、SREに所属している福谷といいます。
普段はKubernetes, Terraform, CI/CD等インフラに近いモノと格闘していることが多いです。
よろしくおねがいします。

ところで皆様、Docker Hubはお使いでしょうか。
かなり時間が経ってしまいましたが、Docker HubのRate Limitにまつわる失敗談を共有したいと思います。

あらすじ

  • 2020年、Docker HubからのpullにRate Limitが適用された
  • Kubernetesやアプリケーションの設定によっては、不必要なコンテナイメージのpullが発生する場合がある
  • ミラーサーバの活用や、各々Docker Hubに対して認証を行うことで対策可能
  • pullが集中する条件の把握はしっかり行いましょう

何が起こったのか

改めまして、以下のようなメッセージに見覚えはないでしょうか。

You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limits

これは docker pull などによりDocker Hubからコンテナイメージをpullしようとした際、一定の条件下で実行が制限された場合に発生するエラーメッセージです。
2020年、段階的に適用されたRate Limitが原因であり Docker社の発表 を参考にまとめると以下のようなものです。

適用日 匿名ユーザの制限[/6時間] Freeユーザの制限[/6時間]
2020/11/12 500 500
2020/11/16 250 250
2020/11/18 100 200

ここでポイントとなるのは、匿名ユーザの制限がアクセス元IPアドレス単位で計算されることです。
匿名ユーザによるDocker Hubからのpullが多数実施された結果、IPアドレスを共有するオフィス内で制限によるエラーが発生する事態となりました。

問題の経緯

KubernetesのimagePullPolicy

時を戻し2018年初頭のこと、自分はインフラや運用面に関わっていたこともあり CIサーバなどとして活用するためKubernetes(以下、K8sと表記)を、おうちK8sならぬ社内K8sとして構築していました。

このときKuberentesの仕様を勘違いしており、ImageのTagが省略されている場合は imagePullPolicy=IfNotPresentが適用されているものと思っていました。
※正しくは、imagePullPolicy未指定 かつ imageのtag指定なし の場合、 imagePullPolicy=Always扱いになります。
また、間の悪いことにカオスエンジニアリングもどきを稼働させており、一定期間ごとに殆どのPodは再起動する状態でした。
つまり、この頃からこのK8sはコンテナイメージをpullし続ける状態だったのです。

Rate Limitの適用予告

2020年8月末、Rate Limitについての予告がDocker社からありました。

この頃には件のK8sは500コンテナ程度を抱えるそこそこに大きなクラスタになっていましたが、(言い訳でしかないですが)自分は主たる担当ではなくなっていました。
まずい状況になっていることはわかったため、ひとまずはK8sの設定としてimagePullPolicy=IfNotPresent、もしくは明示的なTag指定を行うよう に変更をするよう現行の担当チームに伝えたつもりでした。(が、調整不足で流れてしまっていたのです。。。)

他にもGitLab CI/CD Runnerの設定pull_policyというものがありデフォルトでalwaysになっていたのですが、こちらはif-not-presentに変更して対処していました。

X Day

2020年11月に入って暫く経った頃、Rate Limitのエラーメッセージが発生し始めました。
先述のK8sは対策したと思い込んでいたことから、他のCIサーバや個人端末でのpull量が想定より多かったのだろうと考え、対策を検討しました。

ミラーサーバによる対応

対策として最も手軽なのは各々でDocker Hubにdocker loginすることですが、 不用意にアカウントを作成するのも考えものですし レイテンシ/トラフィック削減の点で有用さが見込めたことから Registryをpull-throughなミラーサーバとして構築することにしました。
しかしながら、この時点ではミラー使用を呼びかけpull数の激しいユーザがミラーを使ってくれることを祈るしかありませんでした。

Googleの用意したミラーについての説明が全体的にわかりやすかったのでリンクしておきます: Container Registry の Docker Hub ミラーの使用

ミラーサーバの構築

設定の詳細はRegistryのドキュメントを参照してください。
自前で用意する場合、サーバ側の設定は以下のようなものになります。

Docker Composeでの例

version: "3"
services:
  registry:
    image: registry:2.7
    ports
      - 443:5000
    volumes:
      - ./docker-registry-data:/var/lib/registry
      - ./path/to/crt.pem:/tls/crt.pem:ro
      - ./path/to/key.pem:/tls/key.pem:ro
    environment:
      REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR: inmemory
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
      REGISTRY_HTTP_ADDR: :5000
      REGISTRY_HTTP_TLS_CERTIFICATE: /tls/crt.pem
      REGISTRY_HTTP_TLS_KEY: /tls/key.pem
      REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io
      REGISTRY_PROXY_USERNAME: xxx
      REGISTRY_PROXY_PASSWORD: xxx

Docker Daemonの設定

Docker Daemonが動作しているホスト側でも設定が必要になります。 基本的にConfigure the Docker daemonの案内に従い、registry-mirrors を指定すればOKです。

設定例

{
  "registry-mirrors": ["https://example.com"]
}

設定の場所は以下のようなものです。

  • Docker Desktop: Preference > Docker Engine
  • Linux: /etc/docker/daemon.json
  • Windows: %programdata%\docker\config\daemon.json

設定されていると、 docker info コマンドで取得できる情報の末尾辺り Registry Mirrorsとして確認できます。

$ docker info | tail -4
 Registry Mirrors:
  https://example.com
 Live Restore Enabled: false

ポイントとして、ミラーサーバ側がHTTPSに対応していたほうがよいです。
非対応の場合セキュリティ的にも良くないですし、リスクを許容したとしても追加の設定が必要になります。

ミラーサーバに接続できない場合の挙動

ミラーが設定されている場合、イメージの有無やネットワーク状態によってDocker Hubからのpullはどのように挙動が変化するのでしょうか。
確認したところ、以下のような動作を行うことがわかりました

ホストにイメージが存在する ミラーサーバとの接続 Docker Hubのイメージをdocker pullした際の挙動
Yes OK pullされない
Yes NG pullされない
No OK ミラーサーバからpullされる。Rate Limitへの影響はミラーサーバにイメージが存在しない場合のみ
No NG ミラーサーバへの接続試行後、暫くしてDocker Hub本体からpullされる

ミラーサーバがダウンした場合でもうまく切り替えてくれるようですので、副作用はあまり気にせず設定して良さそうでした。
10秒程度でのタイムアウトが設定されているようです。(おそらくこれでしょうか?)

状態を可視化

並行して、状態の把握のためPrometheus + Grafanaでpull可能な残数を可視化してみることにしました。
暫く観察したところ、どうやら時間帯に関わらず継続して一定の頻度でpull数が消費されていることがわかりました。

犯人を探せ

一定の頻度で24時間活動していることから、どうやら人間ではなくサーバが犯人である目星がつきました。
しかしオフィス内で動作しているサーバ的なものはいくつかありますが、思い当たるところにはミラーの設定を投入済であり謎は深まるばかりでした。

そしてここに至って、ようやく某K8sについて再確認しました。 pullしないように設定したつもりだったので、きちんと確認をしていなかったのです。
犯人は最初から近くに潜んでいました。。。

ひとまずの対応

問題のK8sでpull数が多くなる原因となっていた部分には ひとまずimagePullPolicy=IfNotPresentの設定を実施し、問題は嘘のように解消しました。

情シスチームによる社内公式ミラーサーバの運用も行われ始め、問題は終息したと考えていました。
しかしこの時点では、設定を作り込むのもコストが掛かったため、某K8sそのものにはミラーの設定を入れていませんでした。

再発と恒久対応

2021年に入り暫く経った頃、またRate Limitエラーが発生し始めました。
調べると、今度はK8sのNodeを定期再起動するようにした対応が原因であろうということがわかりました。
これまで定期killされておらず、imagePullPolicy=IfNotPresentな設定が入っていない コンテナが相当数存在し、 Nodeが再起動するたびにDocker Hubからpullする状態になっていたようです。
ようやく重い腰をあげK8s各Nodeからミラーサーバを参照する設定を投入、沈静化しました。

まとめ

商用サーバのような立ち位置ではなく、在宅勤務で社内に居ないことも多かったため
正直なところあまり優先度をあげて対応できていなかったのですが
あとから考えると、いくつか事前に被害を抑えられるポイントがありました。

以下の点を教訓としつつ、チャレンジを行う姿勢は忘れないようにしたいと思います。

  • 正常性バイアスの存在を意識し、ニュートラルな視点で課題解決することの大切さ
  • 可視化はやはり状態の把握に有効
  • 対応内容は恒久的に機能するか、懸念点はないかの把握を行い、憂いは残さないこと

オプティムではエンジニアを随時募集中です
www.optim.co.jp

SRE, 技術的なプロダクトの改善に興味がある方はこちらも御覧ください
recruit.jobcan.jp