CodeBuildのDownload Rate Limitエラー対策として、DockerIDを取得しユーザー認証してビルドする

# はじめに
CodePipelineのCodeBuildでの工程で、以下のエラーがたびたび発生しました。

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

DockerHubでは、匿名ユーザによるDockerHubからのコンテナイメージの呼び出し回数は、以下のような制限があります。

|設定  |意味|
|---|---|
| 無料プラン（匿名ユーザー）	 | 100 pull/6時間あたり|
| 無料プラン（認証ユーザー）	 | 200 pull/6時間あたり |
| Pro |  無制限 |
| Team  |  無制限 |

匿名ユーザーは、 IP アドレス単位で回数制限がかかるため、CodeBuildからの実行では自分が行った回数に関係なく制限に引っかかってしまうため、今回のようなエラーが出ていました。

そのため、今回は、無料プランの認証ユーザーとして、DockerHubにログインすることで、Buildのエラーになる確率をぐっと減らしたいと思います。
実際の本番環境ですと、有料プランに入るべきですが。。

# 前提条件
- CodepipelineでCodeBuild作成済み
作成方法はこちらの記事を参考にしてください。
[CodePipelineを利用したECSのローリングデプロイ](https://qiita.com/holdout0521/items/3aeeb9c9f9e663778ed7)

# 流れ
①DockerHubのアカウント作成
②SystemsManagerでパラメータ作成
③サービスロールにポリシーをアタッチ
④buildspec.ymlの修正
⑤CodeBuildに環境変数を追加



# DockerHubのアカウント作成
[こちらのリンクからDockerHubアカウント](https://hub.docker.com/)を作成します。
アカウント作成に必要なDockerID・メールアドレス・パスワードを設定します。
`DockerID`は、ユーザ名に近い使い方をします。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/2108b882-5869-d756-3479-8aa6bb4a9102.png">

### Codebuildで必要な値は、以下の2つです。
- `DockerID`
- `アカウントパスワード`

Docker Hub で Two-Factor Authentication（MFA）を有効にしている場合は、`アカウントパスワード` の代わりに`アクセストークン`を使用する必要があります。
`アカウント設定`に移動し、左タブ`Security`をクリックし、`New Access Tokens`をクリックすると、アクセストークンが発行されます。
  <img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/32a2a716-483d-05e7-9e52-b88c6db323f1.png">
  - `Access Token Description`：`ForCodeBuild`
  - `Access permissions`：`Read, Write, Delete`
  `Generate`をクリックします。
  <img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/78570fe6-8360-3969-877d-324053599b9e.png">

# SystemsManagerでパラメータ作成
以下の2つのパラメータを作成します。
- `DockerID`
- `アカウントパスワード`

SystemsManagerのマイパラメータに遷移し、`パラメータの作成`をクリックします。
- `名前`：`DOCKER_HUB_ID`（何でも可）
- `利用枠`：`標準`
- `タイプ`：`安全な文字列`
- `KMSキーソース`：`現在のアカウント`
- `KMSキーID`：`alias/aws/ssm`
- `値`：`DockerIDの値を入れる`
![スクリーンショット 2022-04-06 17.19.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/3832f063-dc47-099a-05a9-ac9a34db6bd4.png)

パラメータを作成をクリックします。

同様にアカウントパスワード用も作成します。
- `名前`：`DOCKER_HUB_PASSWORD`（何でも可）
- `利用枠`：`標準`
- `タイプ`：`安全な文字列`
- `KMSキーソース`：`現在のアカウント`
- `KMSキーID`：`alias/aws/ssm`
- `値`：`Dockerアカウントのパスワードの値を入れます`
  ![スクリーンショット 2022-04-06 17.19.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/9d62efd5-39c1-2175-cdff-88e2357a21fd.png)

# サービスロールにポリシーをアタッチ
ビルドプロジェクトがSystems Managerのパラメータストアに保存されているパラメータを参照する場合、ビルドプロジェクトのサービスロールに`ssm:GetParameters`アクションを許可する必要があります。

サービスロールは、`CodeBuild`コンソールのビルドプロジェクト→`編集`→`環境`から確認できます。


<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/41c7db8a-2a64-ac71-b3f5-870b96b6aaec.png">

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/c643af4a-4be5-0f9d-b3fc-2cd0c9b315b3.png">

IAMのコンソールからサービスロールに遷移し、ポリシー名をクリックします。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/4a76e918-cfe9-be72-cd32-f65b9116b797.png">

`さらにアクセス許可を追加する`をクリックします。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/84551036-2fdb-9621-9d32-dd5d74d324a2.png">
以下の通りに、GetParametersを追加します。

- `サービス`：`Systems Manager`
- `アクション`：`GetParameters`
- `リソース`：`全てのリソース`
`ポリシーの確認`をクリックし、`変更の保存`をクリックすると設定は完了です。
![スクリーンショット 2021-10-23 0.14.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/20126661-41ef-4657-55f6-a186dd7d7771.png)

# buildspec.ymlの修正
CodePipelineでのBuildステージにおいて、DockerHubにログインし、認証ユーザーとしてイメージをpullするために、以下の一行を`buildspec.yml`に加えます。

`echo $DOCKER_PASSWORD | docker login -u $DOCKER_USER --password-stdin`



```diff_yaml:buildspec.yml修正後
version: 0.2

phases:
  pre_build:
    commands:
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
+      - echo $DOCKER_PASSWORD | docker login -u $DOCKER_USER --password-stdin
  build:
    commands:
```



# CodeBuildに環境変数を追加
Buildステージに環境変数を追加する方法は、以下の2つあります。
・buildspec.ymlに環境変数を追加する。
・CodePipelineのCodeBuildのステージに環境変数を追加する。

## 方法①buildspec.ymlに環境変数を追加する
以下のenvにパラメータを入れます。

```diff_yaml:buildspec.ymlに追加後
version: 0.2

+ env:
+  parameter-store:
+    DOCKER_USER: DOCKER_HUB_ID
+    DOCKER_PASSWORD: DOCKER_HUB_PASSWORD

phases:
  pre_build:
    commands:
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
      - echo $DOCKER_PASSWORD | docker login -u $DOCKER_USER --password-stdin
    commands:
```

:::note notice
`DOCKER_HUB_ID`と`DOCKER_HUB_PASSWORD`は、パラメータストアで設定した名前になります。
:::

## 方法②CodePipelineのCodeBuildのステージに環境変数を追加する

:::note warn
こちらの方法で行う場合、`DOCKER_HUB_ID`と`DOCKER_HUB_PASSWORD`の名前は、`/CodeBuild/`を頭につける必要があるため、あまりおすすめしません。
:::
パラメータストアで作り直します。
`DOCKER_HUB_ID` → `/CodeBuild/DOCKER_HUB_ID`
`DOCKER_HUB_PASSWORD` → `/CodeBuild/DOCKER_HUB_PASSWORD`

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/dd45b8ba-9980-249b-d3de-83ed75430b9e.png">

環境変数の`名前`と`値`、`タイプ`を以下のように追加します。
・`DOCKER_USER`：`/CodeBuild/DOCKER_HUB_ID`：`パラメータ`
・`DOCKER_PASSWORD`：`/CodeBuild/DOCKER_HUB_PASSWORD`：`パラメータ`

これによって、環境変数に設定した`値`には、パラメーターストアで設定した値が入ります。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/8b4dc260-2c99-680b-45aa-67d999cc66cf.png">

これで、ビルドで失敗する確率がぐっと下がりました。



# ビルド時のエラー集①

サービスロールに`Systems Manager`の`ssm:GetParameters `がアタッチされていない場合に発生します。

```
Phase context status code: Decrypted Variables Error Message: AccessDeniedException: User: arn:aws:sts::xxxxxxxxx:assumed-role/codebuild-wordpress-service-role/AWSCodeBuild-1b8f0170-e973-4b24-8b03-f61cfb95a403 is not authorized to perform: ssm:GetParameters on resource: arn:aws:ssm:ap-northeast-1:xxxxxxxxxx:parameter/
```

# ビルド時のエラー集②
環境変数の値が間違っています。

```
Phase context status code: Decrypted Variables Error Message: parameter does not exist: 
```

# ビルド時のエラー集③
CodeBuildに環境変数が設定されていない場合に発生します。

```
Error: Cannot perform an interactive login from a non TTY device
```

# ビルド時のエラー集④
パラメータに設定しているユーザ名かパスワードが間違っています。

```
Error response from daemon: Get https://registry-1.docker.io/v2/: unauthorized: incorrect username or password
```


#参考
[ビルドプロジェクトの設定の変更 (コンソール)](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/change-project-console.html)
[CodeBuildのIPガチャを回避](https://hhhhhskw.hatenablog.com/entry/2020/11/25/180920)
[Docker Hub の Rate Limitに引っかかったのでdocker loginで対策](https://fu3ak1.hatenablog.com/entry/2020/11/22/122241)