CloudFront+Amazon S3 で 親ドメインを移行せずに Web アプリをデプロイした

AWS

とあることをきっかけに Flutter を学習しようと思いたち、最近 Flutter での開発に没頭していました。

開発したものはこちら。

MapleStory 防御率計算ツール

メイプルストーリーというオンラインゲームでのなんやかんやするための計算ツールです。この記事では技術的な面での体験などを語りたいので、どういうアプリなのかとかは省略します。

この記事で主に語りたいこと

この記事で主に語ることは以下のとおりです。

  • サブドメインを使って AWS リソース(CloudFront+Amazon S3)にルーティングする方法
  • Route 53 のホストゾーンは使わないでサブドメインにルーティングする方法
  • ACM で発行した証明書を外部 DNS で DNS 認証する方法
  • デプロイするにあたって使う IAM ユーザーの IAM ポリシー作成

親ドメインを AWS に移管せずにサブドメインだけ AWS で使いたいけどなんかうまくいかない……! という人の足がかりになればいいなと思います。

技術選定

  1. Flutter の学習なので、 Flutter を使うことは当然決まっている。
  2. とりあえず Firebase を使えばいいだろう

と当初は考えていたのですが、Firebase との連携がうまくいきませんでした。

いろいろな記事を見て回る限り、とっても簡単に見えたのですが、Flutter は未だ進化している技術のようなので、バージョンがうまく噛み合わなかったのかもしれません。

とくに認証周りの仕組みを作ったりする予定はなかったので、一旦そのあたりの学習は後回しにして、以前開発したものをそのまま Flutter でリメイクする形となりました。

前々から Amazon Web Service を使い方覚えておきたいなと思っていたので、今回は以下の構成に。

  • Amazon S3 にデプロイ
  • AWS Certificate Manager(ACM)で証明書を発行
  • CloudFront で配信
  • Mixhost cPanel の DNS を使って 証明書の DNS 認証・ルーティング

AWS Amplify を使う手もあったのですが、認証周りが不要なのと、AWS でよく使われるサービスと言っても過言ではない S3 と CloudFront に触っておきたかったのでこのような選択しています。

Route 53 を使っていない理由は後述します。

ACM で証明書の発行~CloudFront でディストリビューションの作成

今回語りたいのは主に DNS 周りのお話なので、詳しくは解説しません。

ただ、これだけは言っておきたい。ACM での証明書発行はバージニア北部でやりましょう。バージニア北部で発行した証明書しか使えないみたいです。ここで証明書を発行すると cname 名と cname 値がもらえます。

CloudFront ディストリビューションを作成するとディストリビューションドメイン名というものが発行されます。

ここで出てきた以下のものはあとで必要になります。(簡単にコピーできる仕組みになっているので、メモはしなくても大丈夫です)

  • 証明書の cname 名(_xxxxxxx123456.example.com みたいなやつ)
  • 証明書の cname 値(_xxxxxxx123456.aws みたいなやつ)
  • CloudFront の ディストリビューションドメイン名(xxxxxxxxxx.cloudfront.net みたいなやつ)

DNS の設定

Route 53 は使わずに DNS(ホストゾーン) の設定をしていきます。

なぜ Route 53 を使わなかったのか?

この記事の本題はここです。

Route 53 の使い方の候補はいくつか見つかりました。以下の4パターンです。

  1. Route 53 で新しいドメインを取得し、普通にホストゾーンを作成・設定する
  2. Route 53 でサブドメイン用のホストゾーンを作成
  3. Route 53 で親ドメインのホストゾーンを作成し、サブドメインのAレコードだけ設定する
  4. 外部の DNS(ホストゾーン) を使い、レコードを設定する

タイトルにもある通り、 AWS の Route 53 を使わなかったのは、親ドメインを移行せずにサブドメインで配信したかったにほかなりません。

パターン1も一応候補ではあったのですが、やはりドメインの料金がネックなのと、どうせなら app ドメインがいいなと思っていたと思っていたんですよね。

しかし、どうやら AWS では app ドメインが取得できないらしい。なので1番は候補から消えました。(一応外部で app ドメインを取得してホストゾーンに登録する方法もアリかなと思っているので、もしかしたらそのうち app ドメインにちゃっかり変わっているかもしれません)

パターン2とパターン3の方法でのやり方は失敗

次にパターン2とパターン3ですが、結論から言って無理でした。できるのではないかと思うのですが理由は不明。

ドメインに AWS の NS レコードを登録すると、親ドメインもどうしてか名前解決できたりできなくなったりしてしまうみたいでした。なんか親ドメインまで AWS のホストゾーンの設定に引っ張られているっぽい。

すでに NS レコードが登録されているので、 AWS の NS レコードを追記した感じです。(これがいけないのかなあ)

ドメインや DNS の設定は反映に時間がかかります。なので、イマイチ原因特定にいたらなかったのです。ページを開きっぱなしにしていたら突然解決されたり、何もしていないのに突然名前解決できない状態になったり……。

僕のブログは Mixhost のネームサーバーを通しているのですが、そこに AWS のネームサーバーが追加されることで、バグったのかな? とか思っています(適当)。

うっかりこのブログが名前解決できない期間が生まれてしまったりして、結構疲弊しました。

ただし、この方法でやっている人もいるので、できないというわけでもないらしい。

Route 53 にサブドメインを設定する2種類の方法|ふじい|note

Mixhost の DNS (ホストゾーン)を使うことに

そこで僕は第4の方法として、そもそも Route 53 を使うのを諦め、別の方法を試すことにしました。ここまでいろいろ失敗することで、DNS に関する知見が溜まってきたからです。

今回サブドメインとして AWS に登録したかったドメインはバリュードメインで取得し、Mixhost の DNS を通しています。

ちなみに、AWS のホストゾーンとは DNS のことらしいです。

Mixhost の DNS のレコードは Mixhost で使う分には自動的にレコードが追記されたりしていくので普段気にすることはめったにありませんが DNS に関する知識が少しだけ身についた僕は「Mixhost の DNS があるのなら、AWS の ホストゾーンみたいにレコードを追加する機能が存在するのでは?」と思い、 Mixhost の管理画面である cPanel を探ってみました。

どうやらこの Zone Editor というのが Mixhost で DNS の設定ができるものらしいです。

証明書の DNS 認証と cloudfront 連携の設定

証明書を DNS 認証するために、CNAME Record をクリックします。

CNAME レコードを追加を押してレコードを追加。少し待つと認証されて、証明書が利用可能になります。

次にサブドメインへのトラフィックが CloudFront へ行くように CNAME レコードを追加。

証明書のときと同じように入力。

手探りでやっているのでイマイチ確証がないのですが、上の画像のようなルーティングになると思うのです。

Route 53 のホストゾーンを使ったルーティングの場合は CloudFront とネームサーバーの繋がりは CNAME ではなく Aレコード(IP アドレスでのルーティング)でおこないますが、cloudfront の IP アドレスは複数存在するので、CNAME でつなぎました。

Github Actions を使ってデプロイ

今回は S3 にデプロイする際、CI/CD のデファクトスタンダードとなりつつある Github Actions を使いました。

どうやらアクセスキーを預けずにデプロイする方法もあるみたいですが、今回は IAM ユーザーなどの IAM 関係への理解を深めるべく、アクセスキーを使ったデプロイを行ないました。

デプロイ用の IAM ポリシーの作成

ここでもっとも気を遣ったのは「アクセスキーの漏洩への注意」です。当然といえば当然ですね。

初心者がAWSでミスって不正利用されて$6,000請求、泣きそうになったお話。 - Qiita

上記のような失敗談を共有してくださるありがたい先人たちのおかげで、セキュリティには十分注意を払うことができました。

ここではあまり触れられることのないポリシー関連の話をしたいと思うので、バケットの作成方法などのお話は割愛します。

バケットを作成後、静的ホスティングを有効にしたら、とりあえずいろいろな制限のもとバケットに HTTP アクセスできるようになります。一般的には CloudFront を経由したアクセスのみを リソースポリシー(バケットポリシー) を使って許可しているので、パブリックアクセスはすべてブロックした状態です。

バケットポリシーについてはこの記事で触れていません。CloudFront の OAI の設定からできると思います。

で、ここから本題のデプロイ準備ですが、すべてにアクセスできる権限であるAdministratorAccessポリシーなどの強力な権限を持つ IAM ユーザーのアクセスキーを生成するのはもってのほかです。

かといって、AmazonS3FullAccessをアタッチするのもどうかなと思って、カスタマー管理ポリシーを作ることにしました。

よくS3バケットへのデプロイ方法を解説する記事ではAmazonS3FullAccessが使われていますが、今回は「ただ動けばいい」ではなく、できるだけ業務運用を考えた発想をしたいと思い避けることにした感じです。

ミスは起きてはいけないものですが、起こるものです。万が一アクセスキーが漏洩する事態になったとき、新たにバケットが作成されたり削除されたり、あるいはバケットのアクセス管理が変更されたりすると大変なことになります。なので「アクセスキーは漏洩する可能性がある」という前提で考えを巡らせました。

AmazonS3FullAccess はS3に限ったフルアクセスなので、EC2インスタンスが大量に建てられて不正にマイニングに使われてしまうなんてことは起こりません。その面では安心できます。

しかし、権限を最低限に絞るのは、セキュリティ上重要です。そこで、今回は以下のようなポリシーを作成しました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "S3AllObjectCURD",
            "Effect": "Allow",
            "Action": "s3:*Object",
            "Resource": "*"
        }
    ]
}

このポリシーで許可しているのはオブジェクトそのものに対する権限だけです。主に「作成・更新・読み取り・削除」があればいいかなと思い、上記のようになっています。

S3 のアクセスについては以下の記事がわかりやすかったです。

S3のIAM権限で使えるActionをまとめてみた - かべぎわブログ

改めて見ると、FullAccess だと結構いろいろできるんだなと思います。s3:*Object もいらないかもしれません。CreateObject,PutObject,DeleteObject の3つだけでいいのかな?

なにはともあれ、これなら万が一アクセスキーが漏洩したとして、バケットそのものが作成・削除されたり、バケットのポリシーが変更されたりすることはありません。本当ならResourceも絞り込んだほうがいいのですが、面倒なので今回は見送りました。

IAM グループと IAM ユーザーの作成

ポリシーを作成後、以下のような流れでアクセスキーを Github のリポジトリに登録しました。

  1. 「S3Uploader」という名前でIAM グループを作成
  2. 上で作成したIAM ポリシーをグループにアタッチ
  3. アクセスキーを生成した IAM ユーザーを新規作成
  4. 新規作成した IAM ユーザーを S3Uploader に所属させる
  5. Github リポジトリのシークレット情報としてアクセスキーを設定
  6. デプロイ用の workflow を書く

これで無事 CI/CD できるように。

ちなみに、今回書いたデプロイ用 workflow はこんな感じ。

name: Build Flutter on S3

on:
  push:
      branches:
        - master

      workflow_dispatch:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      # Flutter をインストール
      - name: Setup Flutter
        run: git clone https://github.com/flutter/flutter.git

      # パスを通す
      - name: Add Path
        run: echo "$(pwd)/flutter/bin" >> $GITHUB_PATH

      # パッケージをダウンロード
      - name: Download Package
        run: flutter pub get

      # WEB 用にビルド
      - name: Build
        run: flutter build web

      # S3にデプロイ
      - name: Deploy
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_BUCKET_NAME: ${{secrets.AWS_BUCKET_NAME}}
        run: aws s3 cp --recursive --region ap-northeast-1 build/web s3://${AWS_BUCKET_NAME}

workflow を書く上で以下の記事を参考にしました。

GitHub ActionsでウェブサイトをAmazon S3にデプロイする | DevelopersIO
【Flutter】GitHub Actions で Android 向けに自動デプロイする

正直バケット名を secrets で隠す必要性があるかはわかりませんが、追加でバケット名も隠しています。

広告

関連記事

新着記事

広告