LoginSignup
3
4

More than 3 years have passed since last update.

Nuxt.js で作成したアプリを CloudFront で配信する

Posted at

やること

  • GitHub Actions で Nuxt.js で作成したアプリのビルド&デプロイを行う
    • アプリケーションは(静的な)HTML にビルドする
    • デプロイ先は S3 にする
  • CloudFront 経由で配信
    • SSL 証明書は Certificate Manager で管理
    • ドメイン管理は Route 53 を使う
    • WAF は使わない

完成版の GitHub Actions

先に完成版の Actions のコードを載せておきます

name: Deploy

on:
  push:
    branches:
      - 'main'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Cache dependencies
        uses: actions/cache@v2
        with:
          path: ~/.npm
          key: ${{ runner.os }}-v1-npm-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-v1-npm-
            ${{ runner.os }}-v1-
            ${{ runner.os }}-
      - name: Install dependencies
        run: npm ci
      - name: Build
        run: npm run generate
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1
          role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
          role-duration-seconds: 1200
      - name: Deploy
        env:
          DEPLOY_BUCKET: ${{ secrets.DEPLOY_BUCKET }}
          DISTRIBUTION_ID: ${{ secrets.DISTRIBUTION_ID }}
        run: |
          aws s3 sync ./dist s3://$DEPLOY_BUCKET --delete
          aws cloudfront create-invalidation --distribution-id $DISTRIBUTION_ID --paths '/*'

アプリケーションを静的ビルド

静的サイトホスティングするための Nuxt 側の設定として、以下をnuxt.config.jsに設定する

nuxt.config.js
{
  target: 'static'
}

npx create-nuxt-app(TypeScript 利用)で環境構築を行うと、@nuxt/typescript-runtimeパッケージがインストールされ、nuxt-tsというバイナリが使える
./node_modules/.bin/nuxt-ts generateを実行すると、ホスティングするとそのままブラウザが実行できる HTML へビルドしてくれる

上記はnpx create-nuxt-appを使って環境構築した場合は、最初から npm-scripts でエイリアスされている

package.json
{
  "scripts": {
    "generate": "nuxt-ts generate"
  }
}

GitHub Actions で書く

ビルドまでを Actions で書くと以下のようになる(抜粋)

steps:
  - uses: actions/checkout@v2
  - name: Cache dependencies
    uses: actions/cache@v2
    with:
      path: ~/.npm
      key: ${{ runner.os }}-v1-npm-${{ hashFiles('**/package-lock.json') }}
      restore-keys: |
        ${{ runner.os }}-v1-npm-
        ${{ runner.os }}-v1-
        ${{ runner.os }}-
  - name: Install dependencies
    run: npm ci
  - name: Build
    run: npm run generate # nuxt-ts generate

S3 にデプロイ

デプロイ用に IAM ユーザーを作成するが、今回このユーザーには AssumeRole の権限のみをアタッチし、実際に S3 を操作する権限は IAM ロールに持たせる

S3 バケットを作成

AWS コンソール上でバケットを非公開で作成する
公開設定等はそのまま(デフォルト非公開になっている)で、バケット名だけ入力し、作成する

IAM ロールを作成

今回は、--deleteオプションが使えるs3 syncコマンドを使って S3 にアップロードする
以下の S3 アクションのポリシーをアタッチした IAM ロールを作成する

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:ListBucket", "s3:DeleteObject"],
      "Resource": ["arn:aws:s3:::[バケット名]/*", "arn:aws:s3:::[バケット名]"]
    }
  ]
}

IAM ユーザーを作成

以下の AssumeRole のポリシーをアタッチした IAM ユーザーを作成する

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": ["sts:AssumeRole", "sts:TagSession"],
      "Resource": "arn:aws:iam::[AWSアカウントID]:role/[上記で作ったロール名]"
    }
  ]
}

後述するが、Actions でaws-actions/configure-aws-credentialsを使って、AWS 認証を行っている
この中で AssumeRole をリクエストする際にセッションタグを渡しているため、sts:TagSessionが必要になる

AssumeRole を使うと一時的な認証情報が発行されるが、この時に有効期限付きのセッションが生成される

ただ、role-skip-session-taggingというオプションをtrueにすれば、sts:TagSessionは不要にできると思う

この辺のコードは以下
https://github.com/aws-actions/configure-aws-credentials/blob/master/index.js

ロールの信頼関係の編集

上記で作ったロールの信頼関係タブから、以下を設定する

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::[AWSアカウントID]:user/[上記で作ったユーザー名]"
      },
      "Action": ["sts:AssumeRole", "sts:TagSession"]
    }
  ]
}

GitHub Actions で書く

デプロイまでを Actions で書くと以下のようになる(抜粋)

steps:
  - name: Build
    run: npm run generate # nuxt-ts generate
  # 以下を追加
  - name: Configure AWS Credentials
    uses: aws-actions/configure-aws-credentials@v1
    with:
      aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
      aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      aws-region: ap-northeast-1
      role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
      role-duration-seconds: 1200
  - name: Deploy
    env:
      DEPLOY_BUCKET: ${{ secrets.DEPLOY_BUCKET }}
    run: aws s3 sync ./dist s3://$DEPLOY_BUCKET --delete

GitHub の Secrets

GitHub リポジトリのSettings -> Secretsで以下を設定している

キー名 設定値
AWS_ACCESS_KEY_ID アクセスキー
AWS_SECRET_ACCESS_KEY シークレットアクセスキー
DEPLOY_BUCKET デプロイ先の S3 バケット名
AWS_ROLE_TO_ASSUME 上記で作成したロールの ARN

role-duration-secondsについて

ロールの最大セッション時間は、デフォルト 1 時間になっている
これに対して、configure-aws-credentialsのデフォルトのセッション時間は 6 時間となっている
よって、role-duration-secondsオプションを指定し、configure-aws-credentialsのセッション時間を 1 時間以内にする必要がある
ここを超えるとバリデーションエラーで以下のようなエラーが出る

Error: The requested DurationSeconds exceeds the MaxSessionDuration set for this role.

CloudFront の設定

AWS コンソールの CloudFront から Create Distribution をクリック

Origin Settings

設定項目 設定値 備考
Origin Domain Name 上記で作成したバケットを選ぶ
Origin Path -
Enable Origin Shield No Edge とオリジン(ここでは S3 バケット)間にキャッシュレイヤーを設置する
リージョナルエッジキャッシュよりもオリジン寄りのキャッシュレイヤー
リージョナルエッジキャッシュからオリジンへのリクエストを集約できるので、オリジンの負荷を軽減できる
グローバル展開しているサービスだと使えそう
Origin ID 自動設定される
Restrict Bucket Access Yes
Origin Access Identity Create a New Identity CloudFront から S3 バケットへのアクセスを許可するための OAI を作成する
Grant Read Permissions on Bucket Yes, Update Bucket Policy S3 バケットに自動的に OAI のバケットポリシーを設定してくれる
Origin Connection Attempts 3 オリジンへの接続試行回数
Origin Connection Timeout 10 オリジンへの接続を確立する際のタイムアウト値(秒)
Origin Custom Headers - オリジンへのリクエストのカスタムヘッダー

Origin Shield を有効化した場合の構成

Origin Shield を有効化すると、ユーザーからオリジンまでの通信は以下になる

User -> Edge locations -> Regional edge caches -> Origin Shield -> Origin

  • オリジンへの通信はすべて Origin Shield に集約される
    • 世界中のリージョナルエッジキャッシュがそれぞれオリジンと通信していたのを Origin Shield に集約できる
  • キャッシュ効率が良い
    • 世界中のリージョナルエッジキャッシュがそれぞれにキャッシュを保持し、キャッシュがない場合にオリジンへ通信するのに比べて、Origin Shield がある場合は、Origin Shield にキャッシュが集約できるので、キャッシュヒット率が上がる

Default Cache Behavior Settings

設定項目 設定値 備考
Viewer Protocol Policy HTTPS Only HTTPS リクエストのみのアクセス可
HTTP リクエストが来た場合は 403 エラー
Allowed HTTP Methods GET, HEAD GET 以外の POST などのリクエストは 403 エラー
Field-level Encryption Config - HTTPS で POST された時に入力フォームなどフィールドレベルで暗号化するかどうか(今回は GET のみなので不要)
Cache and origin request settings Use a cache policy and origin request policy
Cache Policy Managed-CachingOptimized キャッシュの Default TTL が 24 時間のやつ
Origin Request Policy - オリジンにリクエストできるヘッダー、Cookie、クエリストリングを制限できる
Smooth Streaming No
Restrict Viewer Access (Use Signed URLs or Signed Cookies) No アクセスを署名付き URL または署名付き Cookie のみに限定する
Compress Objects Automatically Yes リクエストヘッダーにAccept-Encoding: brのように圧縮形式を指定してリクエストすると、CloudFront 側でよしなにコンテンツを圧縮して配信してくれる
圧縮されているかの確認は、レスポンスヘッダーにContent-Encoding: brなどが付与されていたら成功
Lambda Function Associations - Lambda@Edge 関数の設定
Enable Real-time Logs No リアルタイムログの有効化

Distribution Settings

今回は Certificate Manager 上で SSL 証明書の発行はしません
事前に SSL 証明書を発行してもらい、Certificate Manager でインポートしておきます
CloudFront で証明書を使用するには、Certificate Manager のリージョンをバージニア北部にする必要がある

設定項目 設定値 備考
Price Class Use U.S., Canada, Europe, Asia, Middle East and Africa どのエッジロケーション(CloudFront、Route 53、WAF が配置されているデータセンター)を使うかを設定
AWS WAF Web ACL None WAF の設定
Alternate Domain Names (CNAMEs) ドメイン名を設定
SSL Certificate Custom SSL Certificate
Certificate Manager でインポートした証明書を選択する
Custom SSL Client Support Clients that Support Server Name Indication (SNI) SNI をサポートするクライアントのみかすべてのクライアントにコンテンツを提供するかの設定
2010 年以降にリリースされたブラウザは SNI がサポートされているみたいなので、SNI をサポートするクライアントのみで良さそう
すべてのクライアントにすると月 6 万の請求が発生する
Security Policy TLSv1.2_2019
Supported HTTP Versions HTTP/2, HTTP/1.1, HTTP/1.0
Default Root Object index.html Nuxt を静的ビルドすると、dist ディレクトリ直下に index.html が生成されるので、これがルートになる
Standard Logging On CloudFront の標準ログを出力するかどうか
S3 Bucket for Logs S3 バケット名を選択 CloudFront のログ出力用に S3 バケットを作成しておく
Log Prefix 任意のディレクトリ名 ログのバケット内でディレクトリを分けて管理したいときに設定(たとえば本番用とステージング用で分けるとか)
Cookie Logging Off ログに Cookie を含めるかどうか
Enable IPv6 On IPv6 アドレスからのリクエストに応答するかどうか
Comment -
Distribution State Enabled この Distribution の作成後、すぐに有効するかどうか

Distribution の作成後、Status が Deployed になったら、使える状態になる
CloudFront のデフォルトドメイン.cloudfront.netでアクセスできたら成功

S3 バケットのリージョンをバージニア北部以外で作った場合、307 レスポンスが返ってくる可能性がある
しばらく待てば見れるようになるが、もし、すぐに確認したい場合は、以下のドキュメントで紹介されているようにオリジンドメイン名にリージョン名を追加するやり方で確認できる
https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-http-307-response/

自分の場合は、1 時間くらい放置しておいたら見れるようになっていた

CloudFront のキャッシュをクリア

デプロイした際は、CloudFront のキャッシュをクリアしたいので、以下を Actions に追記

steps:
  - name: Deploy
    env:
      DEPLOY_BUCKET: ${{ secrets.DEPLOY_BUCKET }}
      DISTRIBUTION_ID: ${{ secrets.DISTRIBUTION_ID }}
    run: |
      aws s3 sync ./dist s3://$DEPLOY_BUCKET --delete
      aws cloudfront create-invalidation --distribution-id $DISTRIBUTION_ID --paths '/*'

以下のポリシーをアタッチ

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": ["cloudfront:CreateInvalidation"],
      "Resource": [
        "arn:aws:cloudfront::[AWSアカウントID]:distribution/[CloudFrontのディストリビューションID]"
      ]
    }
  ]
}

Nuxt で複数のページコンポーネントがある場合

Nuxt で静的ビルドすると、ページコンポーネントごとにindex.htmlが作成される
ただし、CloudFront の Default Root Object の設定では、ルートのindex.htmlにしかこの設定は適用されないので、ルート(/)以外の/setting/reportなどでアクセスすると CloudFront は 403 エラーを返す

403 エラーは、CloudFront のエラーページの設定で一応回避できる

設定項目 設定値 備考
HTTP Error Code 403: Forbidden
Error Caching Minimum TTL (seconds) 10
Customize Error Response Yes
Response Page Path /index.html
HTTP Response Code 200: OK

CloudFront の 403 エラーを 200 にして、ルートのindex.htmlを返している

Route 53

CloudFront のデフォルトドメイン.cloudfront.netで公開しているので、Route 53 を設定し、任意のドメインで見れるようにする
任意のドメインはあらかじめ取得しておく

ホストゾーンの作成

Route 53 -> ホストゾーンからホストゾーンの作成

ドメイン名を入力し、パブリックホストゾーンを選んで作成

レコードを作成

上記で作成したホストゾーンを選択し、レコードを作成

設定項目 設定値 備考
ルーティングポリシー シンプルルーティング
レコード名 - サブドメインをルーティングする場合は、サブドメイン名を入れる
エイリアス On On にすると、対象の AWS リソースがデフォルトで持っているドメインを選択できる
Off にすると、対象の AWS リソースの IP アドレスを直接入力する
レコードタイプ A
トラフィックのルーティング先 CloudFront
バージニア北部
CloudFront のデフォルトドメイン名

しばらく待つと設定したドメイン名でアクセスできるようになる

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4