“Too Many Requests.” でビルドが失敗する…。AWS CodeBuild で IP ガチャを回避するために Docker Hub ログインしよう!という話
最近、AWS CodeBuild で Deckerfile をビルドしていると以下のような Too Many Requests.
というメッセージでビルドが失敗することがないでしょうか?
error pulling image configuration: toomanyrequests: Too Many Requests. Please see https://docs.docker.com/docker-hub/download-rate-limit/
この原因と対策について本記事にまとめました。
Download Rate Limit(ダウンロード制限)とは?
そもそも何故 Too Many Requests.
が最近になって出るようになったかというと、2020年8月に Docker Hub ではコンテナイメージの Pull 回数にレート制限を設けることを発表しています。
レート制限は以下のとおりです。
プラン | レート制限 |
---|---|
無料プラン(匿名ユーザー) | 100 pull/6時間あたり |
無料プラン(認証ユーザー) | 200 pull/6時間あたり |
Pro | 無制限 |
Team | 無制限 |
buildspec.yml
内で docker login -u *** -p ***
といった処理をしていなければ、それは匿名ユーザーで利用していることになります。
(ちなみに aws ecr get-login
のログイン処理は ECR へのログインであり、Docker Hub のログインではありませんのでお間違えないように)
「匿名ユーザーで使ってるけど 6 時間あたり 100 pull も出来るならウチの環境では十分やなー」
と思ってスルーされた方も少なくないと思いますが、ちょっと待ってください。リンク先のブログで言及されているとおり、匿名ユーザーは IP アドレスに基づいて制限されます。
For anonymous (unauthenticated) users, pull rates are limited based on the individual IP address.
東京リージョンの CodeBuild のグローバル IP は 8 つ
CodeBuild を非 VPC 環境のプロジェクトとして作成している場合、CodeBuild のグローバル IP は共通アドレスが利用されています。
つまり、自分のビルド環境が 6 時間以内で 1 回目の pull だったとしても、そのときに割り当てられていた CodeBuild のグローバル IP が 6時間以内で 101 回目の pull であった場合、そのリクエストは Too Many Requests.
になります。
それでは、東京リージョンの CodeBuild ってグローバル IP 何個で回してるのか?というと ip-ranges.json を参照することで判ります。
$ jq .createDate < ip-ranges.json "2020-10-08-23-41-17" $ jq -r '.prefixes[] | \ select(.region=="ap-northeast-1") | \ select(.service=="CODEBUILD") | \ .ip_prefix' < ip-ranges.json 13.112.191.184/29
執筆時点の情報では 13.112.191.184/29
ですので、IP レンジとしては 13.112.191.184 ~ 13.112.191.191
です。つまり 8 アドレスということになります。
ビルド処理のなかで curl https://ipconfig.io/ip
で確認した結果がこちら。
[Container] 2020/10/09 10:13:29 Running command echo CodeBuild IP... CodeBuild IP... [Container] 2020/10/09 10:13:29 Running command curl https://ipconfig.io/ip % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 100 15 100 15 0 0 39 0 --:--:-- --:--:-- --:--:-- 38 13.112.191.188
10回ほど試してみましたが確認できたのは、13.112.191.185
、13.112.191.188
、13.112.191.191
の 3 つでした。いずれも上記レンジ内の IP が表示されていますので、間違いなさそうです。
Download Rate Limit の対策
それでは本題の Download Rate Limit 対策についてですが、私が検討したパターンは以下の 4 つです。
リトライ
先述のとおり、幾つかのグローバル IP がありますので、リトライによって制限を受けないアドレスを引けたならば pull は成功します。しかし毎回変わる保証はありませんので、立て続けに制限を受けているアドレスを引き続けることもありますので、あまり望ましい対策ではないと考えます。
IP ガチャだと理解したうえで、いずれリトライで成功すれば良いと割り切れる環境ならば利用しても良いかとは思います。
ビルドプロジェクトを VPC に接続する
ビルドプロジェクトは VPC に接続することが可能です。この場合、CodeBuild から Docker Hub への接続はあなたの NAT Gateway の EIP になりますので、匿名ユーザーであってもあなたの環境で 6時間あたり 100 pull まで利用できます。
既に NAT Gateway をお持ちの環境であれば VPC 接続で回避するのも良いかと思いますが、このためだけに NAT Gateway を設置するのであればオススメしません。(1つの NAT Gateway 起動料金だけで 30 日で $44.64。2 AZ でおよそ $90 になります)
NAT Gateway のコストを払った匿名ユーザーの 100 pull を勝ち取るくらいなら、Docer Hub の有料プランのほうが安いです。。
ECR にイメージを集約する
ビルド内で Docker Hub から取得しているイメージを事前に ECR に保管し、ECR から取得するようにすれば DockerHub の制限はもう気になりません。
・・・が、ECR のイメージ管理がめちゃ大変になりそうですね。
ビルド内でユーザー認証する
結局のところ認証ユーザーとして利用するのが一番良いと思います。無料プランでもユーザー認証するだけで、送信元 IP で制限されることなく 6 時間あたり 200 pull まで利用できます。
ということで、今回はビルド処理内で Docker Hub にログインする手順をご紹介します。
ビルド処理内で Docker Hub にログイン
事前準備
以下の環境は既にあるものとして進めます。
- AWS CodeBuild ビルドプロジェクト
- Dockerfile をビルドし、ECR に push できる適切な IAM がアタッチされている
- Docker Hub のユーザー ID/Password または AccessToken
ユーザー認証情報の保管
Docker Hub にログインするための ID や Password または AccessToken を保管するには、AWS Secrets Manager か AWS Systems Manager パラメータストア を利用します。パスワードのローテーションを利用しないのであれば無料で利用可能なパラメータストアで十分と思います(KMS の API 呼出料金はいずれも掛かります)
が、今回は AWS Secrets Manager を使った手順にしました。理由は AWS Secrets Manager だとキーバリューが複数登録できるからです。(複数のパラメータストア登録するのが面倒だっただけです、、すみません)
AWS Secrets Manager 管理コンソールから [シークレット] - [新しいシークレットを保存する] を開き、その他のシークレット
を選択します。シークレットキー/値
に Docker Hub のユーザー認証情報を登録します。今回は暗号化キーは DefaultEncryptionKey
とします。任意の名前、自動ローテーションを無効にして作成します。
上図では username
、password
としていますが、Docker Hub で Two-Factor Authentication(MFA)を有効にしている場合は、password
の代わりに AccessToken を利用してください。
ビルドプロジェクトの IAM 追加
今回は AWS Secrets Manager を利用しますので、ビルドプロジェクトが利用している IAM ロールに secretsmanager:GetSecretValue
の権限を追加してください。
ビルドプロジェクトから Secret を参照する
Secret の参照は buildspec.yml の env:
で以下のように記述します。DOCKERHUB_USER:
の部分が環境変数名になり、arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:docker-hub-eRuNNr
は Secret-ID
です。シークレットには複数のキーバリューがある場合、値を抽出するキーを :username
のように指定します。
env: secrets-manager: DOCKERHUB_USER: arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:docker-hub-eRuNNr:username DOCKERHUB_PASS: arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:docker-hub-eRuNNr:password
詳細は公式ガイド(Build specification reference for CodeBuild)を参照ください。
Docker Hub へのログイン
冒頭申し上げたとり aws ecr get-login
は ECR へのログインですので、Docker Hub へのログインを別途記述します。Docker Hub で MFA を利用されている場合は DOCKERHUB_PASS
を AccessToken の値に置き換えるだけで、ログイン方法としては同じです。
phases: pre_build: commands: # ECR へのログイン - echo Logging in to Amazon ECR... - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION) # Docker Hub へのログイン - echo Logging in to Docker Hub... - echo $DOCKERHUB_PASS | docker login -u $DOCKERHUB_USER --password-stdin # コミット ID をイメージタグに設定 - IMAGE_TAG=$CODEBUILD_RESOLVED_SOURCE_VERSION
このように記述してビルドを実行すると、、
[Container] 2020/10/10 02:39:37 Running command echo Logging in to Docker Hub... Logging in to Docker Hub... [Container] 2020/10/10 02:39:37 Running command echo $DOCKERHUB_PASS | docker login -u $DOCKERHUB_USER --password-stdin WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded
Login Succeeded
と表示されていますので、ログインできていますね!
試しにビルド内で ~/.docker/config.json
を参照してみると、以下のように "https://index.docker.io/v1/"
が追加されています。
[Container] 2020/10/10 02:39:39 Running command cat ~/.docker/config.json { "auths": { "0123456789012.dkr.ecr.ap-northeast-1.amazonaws.com": { "auth": "*****" }, "https://index.docker.io/v1/": { "auth": "*****" } }, "HttpHeaders": { "User-Agent": "Docker-Client/19.03.3 (linux)" } }
検証は以上です!
さいごに
CodeBuild で Too Many Requests
でエラーになった、というお問い合わせをちょこちょこ見かけるようになったので原因と対策についてまとめました。
匿名ユーザー且つ非VPCのビルドプロジェクトは IP ガチャです。不要なビルド失敗を回避されたい場合は、ユーザー認証するように設定いただくのが無難かと思います。設定も簡単ですので、転ばぬ先の杖としてご検討ください。
以上!大阪オフィスの丸毛(@marumo1981)でした!
サンブル
buildspec.yml
version: 0.2 env: secrets-manager: DOCKERHUB_USER: arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:docker-hub-eRuNNr:username DOCKERHUB_PASS: arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:docker-hub-eRuNNr:password phases: pre_build: commands: # ECR へのログイン - echo Logging in to Amazon ECR... - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION) # DockerHub へのログイン - echo Logging in to Docker Hub... - echo $DOCKERHUB_PASS | docker login -u $DOCKERHUB_USER --password-stdin # コミット ID をイメージタグに設定 - IMAGE_TAG=$CODEBUILD_RESOLVED_SOURCE_VERSION build: commands: - echo Build started on `date` - echo Building the Docker image... - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG . - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG post_build: commands: - echo Build completed on `date` - echo Pushing the Docker image... - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG