CodeCommit から エラーコード 429 を受け取った原因と対策

記事タイトルとURLをコピーする

こんにちは😸
カスタマーサクセス部の山本です。

前提

複数の CodeBuild プロジェクトが CodeCommit のリポジトリにあるソースコードを同時に クローンする構成があるとします。

エラー

私の検証では、CodeBuild プロジェクトを 7, 8 個同時に実行したあたりから、いくつかのプロジェクトで以下のようなエラーとなりました。
エラーは、CodeCommit からのクローンに失敗したという趣旨のものです。

git fetch failed with exit status 128: POST git-upload-pack (106 bytes)
error: RPC failed; HTTP 429 curl 22 The requested URL returned error: 429
fatal: the remote end hung up unexpectedly
for primary source and source version refs/heads/main

和訳:

git fetch が終了ステータス 128 で失敗しました: POST git-upload-pack (106 バイト)
エラー: RPC 失敗; HTTP 429 curl 22 要求された URL がエラー 429 を返しました
致命的: リモート側が予期せずハングアップしました
プライマリソースとソースバージョンについては、refs/heads/main を参照してください

ビルドログ抜粋:

同時に実行した 11 プロジェクト中、4 プロジェクトが失敗しました。

原因

error: RPC failed; HTTP 429 curl 22 The requested URL returned error: 429

HTTP ステータス 429クライアントからサーバーへのリクエストの過多 を意味します。

The 429 status code indicates that the user has sent too many requests in a given amount of time ("rate limiting"). ステータス コード 429 は、ユーザーが指定された時間内に送信したリクエストが多すぎることを示します (「レート制限」)。

AWS ドキュメントにおいても、 CodeCommit からのステータスコード 429に関する記載があります。

  • 1 秒あたりのリクエスト数
  • 実行中のリクエストの合計数

これらが一定数以上になると、リクエスト過多となるようです。
その場合、リクエストに時間がかかるか、失敗するようです。
リクエスト過多となる数値については公開されていないようです。

参考: アクセスエラーと のトラブルシューティング AWS CodeCommit - AWS CodeCommit

対策

リクエスト過多となる場合は、以下の対策をすることが推奨されています。

  • 複数の実行が同時に行われないような実装にする
    • 実行前にランダムな遅延時間を設けるようにする。タイミングをずらすことで、同時実行を避ける。
      • プロジェクト A からの実行リクエスト:1 秒待ってから実行
      • プロジェクト B からの実行リクエスト:15 秒待ってから実行
      • プロジェクト C からの実行リクエスト:30 秒待ってから実行
    • 各ビルドプロジェクトをイベント駆動で実行するようにして、同時実行を避ける
    • エラー時の再試行を実装する
      • 1 回目の再試行は 10 秒後、2 回目の再試行は 15 秒後など指数関数的に増加するようにする。増加する秒数は毎回ランダムに決まるようにする。

CodeBuild で実施可能な対策

CodeBuild プロジェクトには、自動再試行機能があり、失敗時に 10 回まで再試行することができます

対象のビルドプロジェクトを選択し「編集」を押します。

「環境」枠内の「追加設定」を開いて「自動再試行の上限」を設定し「プロジェクトを更新する」を押します。

自動再実行した場合は、「ビルド履歴」画面で、「送信者」がビルドプロジェクトに設定した「サービスロール」になります。(黄色い枠部分)
同時実行は Step Functions で検証していたため、1 回目の実行の「送信者」は Step Functions のステートマシン名になっています。

時と場合によると思いますが、10 プロジェクトある場合、3回目のリトライですべて成功になっていました。

まとめ

CodeCommit からのクローンを同時に複数行う場合は、タイミングをずらすように工夫したり、再試行を設定しておくとよさそうです。

おまけ

Step Functions で検証した際のコードです。
test1 , test2 とプロジェクトがあるとき、それぞれに異なる待ち時間を設定して、ビルドプロジェクトの開始時間をずらしています。
これによってエラーは起きなくなりました。

  1. すべてのCodeBuildプロジェクトを取得
  2. 各プロジェクトに対して:
    • プロジェクト名が"test"で始まるかチェック
    • "test"で始まらない場合は以降の処理をスキップ
    • "test"で始まる場合、プロジェクト名 (test1 , test2 ) に基づいて待機時間を決定
      • test1 10 秒
      • test2 15 秒
    • 指定された時間待機
    • ビルドを開始
{
  "QueryLanguage": "JSONata",
  "StartAt": "ListProjects",
  "States": {
    "ListProjects": {
      "Arguments": {},
      "Next": "Map",
      "Output": "{% $states.result.Projects %}",
      "Resource": "arn:aws:states:::aws-sdk:codebuild:listProjects",
      "Type": "Task"
    },
    "Map": {
      "End": true,
      "ItemProcessor": {
        "ProcessorConfig": {
          "Mode": "INLINE"
        },
        "StartAt": "namecheck",
        "States": {
          "CodeBuild StartBuild": {
            "Arguments": {
              "ProjectName": "{% $states.input %}"
            },
            "End": true,
            "Resource": "arn:aws:states:::codebuild:startBuild",
            "Type": "Task"
          },
          "Pass": {
            "End": true,
            "Type": "Pass"
          },
          "Calculate Wait Time": {
            "Type": "Choice",
            "Choices": [
              {
                "Condition": "{% $contains($states.input, /test2/) %}",
                "Next": "Wait 15s"
              },
              {
                "Condition": "{% $contains($states.input, /test3/) %}",
                "Next": "Wait 15s"
              },
              {
                "Condition": "{% $contains($states.input, /test4/) %}",
                "Next": "Wait 30s"
              },
              {
                "Condition": "{% $contains($states.input, /test5/) %}",
                "Next": "Wait 30s"
              },
              {
                "Condition": "{% $contains($states.input, /test6/) %}",
                "Next": "Wait 10s"
              },
              {
                "Condition": "{% $contains($states.input, /test7/) %}",
                "Next": "Wait 15s"
              },
              {
                "Condition": "{% $contains($states.input, /test8/) %}",
                "Next": "Wait 15s"
              },
              {
                "Condition": "{% $contains($states.input, /test9/) %}",
                "Next": "Wait 30s"
              },
              {
                "Condition": "{% $contains($states.input, /test10/) %}",
                "Next": "Wait 30s"
              }
            ],
            "Default": "Wait 10s"
          },
          "Wait 10s": {
            "Type": "Wait",
            "Seconds": 10,
            "Next": "CodeBuild StartBuild"
          },
          "Wait 15s": {
            "Type": "Wait",
            "Seconds": 15,
            "Next": "CodeBuild StartBuild"
          },
          "Wait 30s": {
            "Type": "Wait",
            "Seconds": 30,
            "Next": "CodeBuild StartBuild"
          },
          "namecheck": {
            "Choices": [
              {
                "Condition": "{% $contains($states.input, /test*/)%}",
                "Next": "Calculate Wait Time"
              }
            ],
            "Default": "Pass",
            "Type": "Choice"
          }
        }
      },
      "Type": "Map"
    }
  }
}

単純に各プロジェクトの実行を 1 秒ずつずらすことでも解決できそうです。
ListProject の結果を順番に処理していく方法になります。
JsonNata を使用することで実現できます。

{
  "QueryLanguage": "JSONata",
  "StartAt": "ListProjects",
  "States": {
    "ListProjects": {
      "Type": "Task",
      "Resource": "arn:aws:states:::aws-sdk:codebuild:listProjects",
      "Arguments": {},
      "Output": "{% $states.result.Projects %}",
      "Next": "Map"
    },
    "Map": {
      "Type": "Map",
      "End": true,
      "MaxConcurrency": 1,
      "ItemProcessor": {
        "ProcessorConfig": {
          "Mode": "INLINE"
        },
        "StartAt": "namecheck",
        "States": {
          "namecheck": {
            "Type": "Choice",
            "Choices": [
              {
                "Condition": "{% $contains($states.input, /test*/)%}",
                "Next": "CodeBuild StartBuild"
              }
            ],
            "Default": "Pass"
          },
          "CodeBuild StartBuild": {
            "Type": "Task",
            "Resource": "arn:aws:states:::codebuild:startBuild",
            "Arguments": {
              "ProjectName": "{% $states.input %}"
            },
            "End": true
          },
          "Pass": {
            "Type": "Pass",
            "End": true
          }
        }
      }
    }
  }
}

余談

一人でのんびりハイキングに行きたいです。
去年の浅間山です。

山本 哲也 (記事一覧)

カスタマーサクセス部のインフラエンジニア。

山を走るのが趣味です。