AWS CodePipelineでReactをs3に保存してCloudFrontで公開する手順

## システム構成
以下の様なシステム構成イメージで準備します。
<img width="330" alt="Shot 2020-02-10 at 10.13.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/b19854b6-47f8-dc3a-f4ce-efc9eaad4712.png" />

*Branch:production*への*PR merge*が完了したら、Githubに設定されているwebhook経由でCodePipelineを呼んで、最終的にs3にReactのソースを保存します。

そして、**CloudFront**経由でアプリをブラウザーに起動する様にしておきます。

## Githubにレポジトリを作成
Reactの準備がされている前提で、まず、Githubにリポジトリを作成し、必要なソースコードをプッシュしておきます。

## productionブランチを作成
*Branch:master*から*Branch:production*に*Pull Request*(PR)がマージされた時に、CodePipelineを呼んで本番化をしたいので、*productionブランチ*を作成しておきます。

*Branch:master*に*production*と入力したら*Create branch: production from master*と表示されるので、そこをクリックして作成します。コマンドラインから作成しても構いません。
![Screen Shot 2020-02-07 at 16.06.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/ec903c68-9cc2-e239-2114-8d693dc9f650.png)

## IAMの設定
まず、[IAMのコンソール](https://console.aws.amazon.com/iam/home?region=ap-northeast-1#/policies)で*Create policy*を押してポリシーを追加します。以下の設定を追加します。以下のポリシーをもったユーザーのAccess key IDとSecretを取得しておきます。CodeBuildの環境変数設定の箇所で使います。

<img width="330" alt="Screen Shot 2020-02-07 at 19.14.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/b188325c-50e2-43e8-aa0c-8fd9b87c4d3f.png">

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:ListBucket",
                "s3:DeleteObject",
                "cloudfront:CreateInvalidation",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "arn:aws:s3:::あなたのバケット",
                "arn:aws:s3:::あなたのバケット/*",
                "arn:aws:cloudfront::数字:distribution/CloudFrontのID"
            ]
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::あなたのバケット/*"
        }
    ]
}
```

## s3の準備
[s3にアクセス](https://console.aws.amazon.com/s3/home?region=ap-northeast-1)して以下の様にある*create bucket*をクリックして、*bucket*の作成ウイザードを表示させます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/51089d75-3fae-d012-1b8f-7143b8cb0ae9.png" alt="" width="330" />

bucket名は自由に決め、後は`Next`を押してデフォルト設定で作成します。作成したbucketの詳細に入ります。

*Permissions*タグを選択して*PublicRead*を許可しておきます。黒く塗りつぶした箇所に設定したバケット名を設定し保存してください。
<img width="330" alt="Screen Shot 2020-02-07 at 16.28.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/c4786d22-8588-18f8-4c80-4767f82b629a.png">

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicRead",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::あなたのバケット名/*"
        }
    ]
}
```

## CloudFrontの設定
CloudFrontを利用することでSSLの利用も可能になるので、おすすめです。[CloudFrontのコンソール](https://console.aws.amazon.com/cloudfront/home?region=ap-northeast-1#distributions:)にアクセスして*Create Distribution*を選びます。そして、*Web*の*Get Started*を選びます。
<img width="330" alt="Screen Shot 2020-02-07 at 16.36.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/29cc9f43-2f85-5186-632b-4b7bff06876e.png">

Origin Domain Nameに先程作ったs3のbucketを指定します。
<img width="330" alt="Screen Shot 2020-02-07 at 16.41.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/7c8ebe84-423c-d7c4-7b40-41d17398be86.png">

Default Cache Behavior SettingsのViewer Protocol PolicyをRedirect HTTP to HTTPSを設定します。
<img width="330" alt="Screen Shot 2020-02-07 at 16.46.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/03bfcabb-0e5b-7f6a-3612-3402ae73b1ff.png">

Distribution Settingsでドメイン名とSSLを有効にするためにCustom SSLを設定します。ACMを設定しSSLを有効にします。ACMのドメイン（common name）とドメイン名は一致している必要があります。
<img width="330" alt="Screen Shot 2020-02-07 at 16.48.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/a726e00b-ca9a-f6df-2a56-fa08725e4e49.png">

Default Root Objectに*index.html*を指定します。こうすることでCloudFrontにアクセスした時にURLにindex.htmlを指定しない状態でもindex.htmlをデフォルトで呼び出すことができます。
<img width="330" alt="Screen Shot 2020-02-07 at 16.50.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/a7106100-9aaf-400f-1d92-2648ab0a4af6.png">

## Route53の設定
[Route53のコンソール](https://console.aws.amazon.com/route53/home?region=ap-northeast-1#hosted-zones:)にアクセスします。黒色に塗りつぶした内容を*Distribution Settings*のCNAMEsに設定します。*CloudFront Distributions*の一覧に表示されている*Domain Name*を*Alias Target*に指定します。
<img width="330" alt="Screen Shot 2020-02-07 at 16.55.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/a2c396ca-ec6e-01c4-e4e4-cdf4606989d4.png">

## CodePipelineの設定
[CodePipelineのコンソール](https://ap-northeast-1.console.aws.amazon.com/codesuite/codepipeline/start?region=ap-northeast-1)にアクセスしてオレンジ色の*Create project*ボタンをクリックします。

Pipeline名を設定し、次へ進みます。
<img width="330" alt="Screen Shot 2020-02-07 at 17.06.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/4992af2a-8c9b-7d7c-f9cd-97f9ac7d95b9.png">

SourceにGithubを選択、そして、認証して繋ぎます。最初に作っておいたRepositoryを選び、Branchは**production**を設定してください。
<img width="330" alt="Screen Shot 2020-02-07 at 17.07.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/21913596-4416-78e2-2170-6ddc602e53bf.png">
<img width="330" alt="Screen Shot 2020-02-07 at 17.08.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/853f30d7-113c-0cdb-77a8-82590d4684e8.png">

ビルドの設定をします。*AWS CodeBuild*を選択し*Create project*を選択してプロジェクトを作成します。
<img width="330" alt="Screen Shot 2020-02-07 at 17.16.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/686859ee-dc84-0ce0-d7f7-c2e137d551e4.png">

プロジェクト名を指定します。プロジェクト名は再設定ができないようです。
<img width="330" alt="Screen Shot 2020-02-07 at 17.15.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/bd42d63b-7685-3fa1-9f3b-43e274c4a3e8.png">

以下のような設定をしておきます。その他は、デフォルトの設定にします。[サポートしているDocker Images](https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html)はここで確認できます。
<img width="330" alt="Screen Shot 2020-02-07 at 17.15.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/60b232e0-6a3d-dd36-6e2b-f62c405bdd8a.png">

## CodeBuild環境変数の設定
[CodeBuildのプロジェクト一覧](https://ap-northeast-1.console.aws.amazon.com/codesuite/codebuild/projects?region=ap-northeast-1)から先程作成したプロジェクトを選択し環境変数を設定します。AWS_ACCESS_KEY_IDとAWS_SECRET_ACCESS_KEYにIAMで設定して取得したKEYとSECRETを設定します。

<img width="330" alt="Screen Shot 2020-02-07 at 17.37.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/ba8399e4-6984-0906-ae0b-0b80e3a6b258.png">
<img width="330" alt="Screen Shot 2020-02-07 at 18.50.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/4a040251-a020-0eb6-f5ec-ffc3a7942b6b.png">



## buildspec.ymlの設定
buildspec.ymlを作成しmaster branchにプッシュしておきます。ここでは、レポジトリルート以下に`react-app`を作成した場合の*buildspec.yml*の設定を紹介します。　Artifactsのbase-directoryの設定に関係するので、パスの関係性を正しく理解しておいたほうが良いです。

```bash
repository-root/
├── buildspec.yml
└── react-app
    ├── README.md
    ├── build
    ├── node_modules
    ├── package-lock.json
    ├── package.json
    ├── public
    ├── src
    ├── tsconfig.json
    └── yarn.lock
```

**処理の流れ**

npmの環境を準備
　↓
react-appをビルド
　↓
s3にビルドしたファイルをアップロード
　↓
CloudFrontのキャッシュを削除
　↓
Artifactsを保存（デフォルト自動設定、s3に自動でバケットが作成されている）

```yml
version: 0.2

phases:
  install:
    commands:
      - npm install -g n
      - n stable
      - npm update -g npm
      - npm install
      - node -v
      - npm -v
      - yarn -v
  pre_build:
    commands:
      - echo Entring app directory
      - cd react-app
      - yarn install
  build:
    commands:
      - echo Build started on `date`
      - yarn build
  post_build:
    commands:
      - aws s3 sync build/ s3://あなたのバケット名 --exclude '*.DS_Store' --acl public-read --cache-control public,max-age=604800
      - aws cloudfront create-invalidation --distribution-id CloudFrontのIDの --paths /index.html /service-worker.js"
artifacts:
  files:
    - '**/*'
  base-directory: react-app/build
```

##本番化

GithubでBranch:masterからBranch:productionで作成します。そして、PRをマージすると以下の様にPipelineが起動します。

<img width="330" alt="Screen Shot 2020-02-07 at 19.27.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18766/83e52f6b-a6f4-5f69-17bb-82d6e477f93c.png">