ユユユユユ

webエンジニアです

静的コンテンツを S3 バケットに継続的にデプロイする

  jnsato.com  でホストしている自己紹介ページについて、連続して書いてきた。昨日はホスティングのインフラ構成について書いた。

jnsato.hateblo.jp

 こちらの記事にて、このウェブページはサーバーレスな構成を取っていて、保守にリソースを割く必要はほとんどない、と総括した。

 この記事では、自動デプロイのパイプラインを用意することで、わずかに残った作業すらも自動化したことについて書く。これにより、いよいよインフラをいっさい意識せずにウェブサイトを管理できるようになっている。

やったこと

 全体の流れとしては次の図のようになる。

f:id:jnsato:20200531170842p:plain

 左上の GitHub アイコンが起点となる。やっていることとしては次の通り。

  1. GitHub へのプッシュをトリガーに webhook で CodePipeline が起動
  2. CodePipeline が CodeBuild を呼び出しビルドを開始
  3. CodeBuild は SystemsManager から API key を取得
  4. 静的ジェネレータが API 呼び出しを行い結果を静的ファイルに書き出す
  5. 静的ファイルが S3 バケットに同期される

 以下にそれぞれのステップについて詳しくみていくことにする。

1. GitHub へのプッシュをトリガーに webhook で CodePipeline が起動

 GitHub の webhook を登録しているわけであるが、 GitHub が推奨する方式1でエンドポイントを保護する必要がある。やや面倒ではあるが、 CloudFormation で管理してしまえばなんということはない。

 GitHub 側で発行したシークレットトークンを AWS 側では SecretsManager に保管しておいて、 CloudFormation で次のように展開している。細かく解説するとなると長くなってしまうため、該当のコードを抜粋だけして掲げた上で、詳細は公式ドキュメントにゆずる。

Resources:
  ...
  DeployPipelineWebhook:
    Type: AWS::CodePipeline::Webhook
    Properties:
      Authentication: GITHUB_HMAC
      AuthenticationConfiguration:
        SecretToken: '{{resolve:secretsmanager:<your_secret_name>:SecretString:WebhookSecretToken}}'
      Filters:
        - JsonPath: "$.ref"
          MatchEquals: "refs/heads/{Branch}"
      RegisterWithThirdParty: true
      TargetAction: Source
      TargetPipeline: !Ref DeployPipeline
      TargetPipelineVersion: !GetAtt DeployPipeline.Version

2. CodePipeline が CodeBuild を呼び出しビルドを開始

 ここはシンプルに CodePipeline に CodeBuild プロジェクトを登録するだけ。あるいはスクリーンショットを見てもらう方が話が早いかもしれない。

f:id:jnsato:20200531171001p:plain

3. CodeBuild は SystemsManager から API key を取得

 あらかじめ SystemsManager に API Key を登録しておき、 CodeBuild が参照可能なように払い出す。 CodeBuild コンテナはこれを環境変数として登録する。

 これについてはこのプロジェクトに固有の要件にすぎないから、読み飛ばしてもらって構わない。

4. 静的ジェネレータが API 呼び出しを行い結果を静的ファイルに書き出す

 フロントエンドアプリは TypeScript と next.js で書いている。

 またページ内コンテンツは一部 DynamoDB に格納しており、 API Gateway と Lambda 経由で一覧取得できるように構成している。これについては別の記事で詳しく触れた。

jnsato.hateblo.jp

 ここでは、ビルドプロセス内で API を呼び出し、その結果を静的ファイルとして固定化させている、ということになる。コンテンツの管理も自分で行っているから、更新する際には任意のタイミングで再ビルドすれば最新のコンテンツが反映される方式になっている。

5. 静的ファイルが S3 バケットに同期される

 デプロイである。ビルドのアウトプットを S3 バケットに同期することが目標となる。

 シンプルに CLI を使って次のように同期させている。

aws s3 sync --exact-timestamp --delete ./frontend/out s3://${S3_BUCKET}

 --exact-timestamp--deleteオプションは、正確な同期を保証するために与えている。前者はファイルサイズが同じでもタイムスタンプが異なっていれば別ファイルとみなして同期させること、後者は存在しなくなったファイルを削除することを指示している。

おわりに

 以上、 jnsato.com の裏で動いている継続的デリバリーの仕組みについて公開した。

 このほか、 CodePipeline に Chatbot を連携させていたこともあった。ただこれはわざわざ通知するまでもないことで、かえって煩わしいので切ってしまった。

 同じように、必ずしも AWSアーキテクチャを設計するのが最適ではないところもあったかもしれない。例えば、わざわざ CodeBuild を使わずとも、何か別の SaaS を使ってもよかったんじゃないか、とか。

 確かに、気軽に使えるツールとか、使い慣れたツールでもさらに使いこなすのは大事。しかし、使ったことのないツールを試してみるのもやっぱり楽しいし、よくも悪くも学べるものは多い。自分の興味本位であれこれ試行錯誤しながら進められること、これもまた個人的なプロジェクトのやりがいのひとつであることは間違いない。