AWS CodeBuildを用いてVue CLI製ウェブアプリケーションをビルドからテスト、静的解析、デプロイ、通知まで全部自動にする

Automate the Boring Stuff with… Cloud?

nakamura.ka
WingArc1st Inc.
19 min readOct 1, 2019

--

2016/12/31 主催したクラブイベントにて photo by threehouse

ある種のジャンルのDJ⁰は一曲一曲が短いため、オーディエンスからのフィードバックを受けるとすぐさま新しい曲を試すことができる — —

[0]: つまり私のことです

はじめに

みなさま、はじめまして。今年度から新卒でウイングアークに入社したnakamura.kaと申します。
今回は、新人研修で取り組んだVue CLI製ウェブアプリケーションの開発をベースに、プロジェクトが配置されているリモートリポジトリへの変更(つまりpush)をトリガーとした

  • 自動スタイルチェック(静的解析)
  • 自動テスト
  • 自動ビルド
  • 自動デプロイ
  • 自動通知

まで一貫する自動システムの構築を行った記録を、備忘録もかねてご紹介したいと思います。

開発したVue CLI製アプリ「Cloud Kanban」、現在も仲のいい同期との間で使っています¹

今回用いたサービス

  • Atlassian Bitbucket
  • AWS CodeBuild
  • Amazon S3
  • Amazon CloudWatch
  • AWS Lambda
  • Slack

自動化したシステムの全体像

AWS公式風コンポーネント図
シーケンス図ではこんな感じ
  1. Bitbucket上のリポジトリにpushされた際、CodeBuildに対しWebHookが飛ぶ
  2. CodeBuild上で記述された方法によりビルドが行われる
  3. ビルド結果が正常であればS3上に成果物を配置する
  4. ビルド結果をCloud Watchで受け取り、Lambdaを叩いてSlack上でビルド結果の通知を行う

[1]: お互いまったく別の業務に関わっていて、顔も合わせないのにどういうタスクを抱え込んでいるのかなんとなくわかるのは面白い こういうカンバンの使い方もデジタルならではでアリなのかなと思う

1. CodeBuildでビルドプロジェクトを作成する

前提条件

今回は

  • デフォルトでBitbucketのリポジトリを用いていた
  • IAM²の関係上CodeCommit³を扱えない

という上記の2点からCodeBuildのみを用いたビルドプロジェクトを作成します。

なお、2019/09現在CodePipeline⁴が対応しているリポジトリプロバイダにBitbucketが含まれていないため、今回はCodeBuild上でビルドからデプロイまで(無理矢理)一貫しておこなっていますが、本来業務として一連のシステムを構築するのであれば、パイプラインシステムの構築できるCodePipelineの利用をおすすめします。⁵

[2]: AWS Identity and Access Management、AWS上のサービスにおけるアクセス権の制御を行うサービス 今回は後述するCodeCommit上でのアクセス権を与えられなかった
[3]: AWS上でGitリポジトリを利用できるサービス
[4]: CodeBuild、CodeDeploy等のAWS開発者ツールサービスを統合し、AWS上でワークフローを構築できるサービス
[5]: それを言い出すと業務としてはもう一段上の自動化および業務システムの構築としてTerraform⁶等を用いたほうがよいと思います
[6]: Kubernetesなどのようにクラウド上のインフラストラクチャを宣言的に定義できるツール

ビルドプロジェクトの作成

基本的には特にカスタムしなくても動くはずです。

ソースプロバイダにBitbucketを設定する
今回はそのままpush時に自動ビルドを行う設定にしましたが、この際にプルリクエストの状態やBranch名等の正規表現を用いたフィルターを設定することで、柔軟に自動ビルドのタイミングを設定することができます。

ビルド環境を設定する
OSイメージやタイムアウト、使用する計算機資源、環境変数等を設定することができます。
今回はすべてデフォルトですが、実業務ではDockerを用いたカスタムイメージ⁷やタイムアウトの指定、計算機資源を選定⁸する必要があると思います。

[7]: どのような環境でもビルドできるほうがよいと考えたため今回はデフォルトのイメージを使用していますが、外部サービスに依存するプロジェクトなどの場合Docker等コンテナ化技術も利用していくことも視野に入れるべきだと思います
[8]: ただし完全マネージド型であるため、Auto Scalingは可能

buildspecの作成および設定
CodeBuildでのビルドには

  • BuildコマンドをまとめたbuildspecというYAML形式のファイルを用いる方法
  • ビルドコマンドを指定する方法

の二つの方法があります。
たとえばmavenでは、maven内にビルドフェーズという概念をすでに持っているためフェーズ内にテストやスタイルチェックを組み込むことが可能であり、今回のプロジェクト規模の場合後者のビルドコマンドで十分だと思います。⁹
しかし今回のプロジェクトで用いているVue-CLIのビルドにはそのような概念は存在せず

  • ビルド時にテストを組み込みたい
  • ビルド成功時にS3の既存ファイルを削除したい

等の理由から、前者のbuildspecファイルを用いたビルドを行っています。

[9]: ただし、前者のbuildspecファイルだとgitの管理対象に含まれるため、実業務では後者のほうがよいと思います¹⁰
[10]: が、それだとbuildspecファイルを作る作業やbuildspecファイルを維持管理する作業自体が存在してしまうため、Terraform等を用いて完全に構築してしまうのであれば後者のほうがよいのでしょうか……?(要検証)

ここがCodeBuildのコアとなる部分なので詳しく見ていきます。

phases

ここではビルドの際の処理手順を書いていきます。

install

Node.jsのアップデート、テストコードフレームワークや依存する外部ライブラリなどをインストールするビルド環境構築時の処理を書きます。
今回はすべて最新バージョンへのアップデートをしていますが、場合によっては指定バージョンへのダウングレード等もここで行います。

pre_build

buildの前に行う処理があればここに書きます。
今回のプロジェクトでは前述したとおりCodePipelineを用いていないため、このタイミングで静的解析(npm run lint)とテスト(npm run test)を行うように書いています。

build

ビルド時の処理をここで書きます。

ここは本来であればnpm run buildのみでよいのですが、CodeBuildは仕様上どのフェーズがfailedしても後続のフェーズが走ってしまいます。¹¹
そこでCodePipelineを用いない今回は、ビルド環境の環境変数であるCODEBUILD_BUILD_SUCCEEDINGを見て、これまでのビルドが成功しているかを確認しています。
pre_buildでもしビルドが失敗していた場合(つまりスタイルチェックやテストに失敗していた場合)、テストは行われず失敗の終了コードを返すようにしています。

[11]: と思ったのですがAWSの公式ドキュメント¹²だとPRE_BUILDが失敗した場合BUILDはされないような書かれ方になっています 開発当時はpre_build失敗時もbuildに移行していたように思ったのでこのような書き方をしていますが、もしかしたらnpm run buildだけでよいのかもしれません
[12]: https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/view-build-details.html#view-build-details-phases

post_build

新しい成果物を配置するため、ビルド成功時にデプロイおよびホスティング先となるS3バケット内の既存のプロジェクトを削除しています。

artifacts

成果物をどのように扱うか書いていきます。

files

どのようなファイルを配置するか正規表現で指定します。
ここでは指定したディレクトリ以下を再帰的に配置するよう、 **/* という指定を行っています。

base-directory

どこのディレクトリ以下を配置するか指定します。
npm run build でのビルドでは通常dist 以下に成果物が生成されるためこのように書いていますが、たとえば mvn package でのビルドの場合 target 以下に生成されるため、 target となります。

以上を書いた buildspec.ymlファイルを(デフォルトでは)リポジトリのルートディレクトリに配置します。

アーティファクトの設定
成果物をどこにアップロードするのか設定します。
今回の場合は生成先とデプロイ先とホスティング先がすべて同じなのでそれ用に用意してあるS3バケットに対して上記 dist以下をそのまま配置します。

ログの設定
ログをどこに吐くか指定します。
まったく吐かなくても後述する通知機能の作成は可能ですが、今回はおためしで上述のS3バケットとは別のバケットに吐くように設定しています。

2. ビルド通知の設定を行う

一応以上の設定ですでに pushした際に自動的にビルドからデプロイまでされるようになっていますが、このままではいつどのようなビルドがあったのか、どのようなフェイズでビルドが失敗したのか、コンソールを開かなければすぐにはわかりません。
なのでビルド後に結果を通知するシステムを作ってみます。

Slackの設定

まずはカスタムインテグレーションとして Incoming Webhookを追加します。
通知を行いたいチャンネルを選択し追加を行うと、Webhook URLが表示されます。基本的にはそこに対し POSTしてあげるだけでよいです。

AWS Lambdaの設定

後述するCloudWatchでトリガーが引かれた(つまりビルドが終了した)際に叩くLambdaを設定しておきます。

基本的にはWebhook URLを叩いてるだけです。
ただし、後述のCloudWatchから来るデータがどうなっているのかわかりづらく膨大であるため、ここである程度情報を絞る必要があります。¹⁹
詳しい解説は省きますが、今回は、 eventで受け取るビルド結果のjsonをパースし、ビルド結果が FAILEDだった場合にはメンションをつけてFAILEDフェーズのログを載せて POSTしています。なお、ビルド結果が SUCCEEDEDだった場合にはただ結果をPOSTしているだけです。¹³

[13]: これは、常にビルド結果が通知されると誰も見なくなると考えたからです¹⁹

CloudWatchの設定

CloudWatchではたとえば期間内にどれだけビルドが走ってどれだけfailしたかというのを確認、通知¹⁴する機能もありますが¹⁵、今回はビルドステータスが変化した時、つまりビルド結果がSUCCEEDEDFAILEDおよびSTOPPEDのいずれかになったとき(つまりビルド終了時)に結果を通知する仕組みにします。
これは一例なので例えばビルドに数時間かかるような実製品の場合は柔軟に、ビルド開始時やビルドフェーズの変更時(つまりビルドフェーズ移行時)などを通知することも視野に入ってくると思います。
しかし、あまりにも頻繁に通知が飛んできて誰も見なくなってしまうということも十分考えられるため、監視システムの構築には十分に検討を重ねる必要があるでしょう。¹⁹

[14]: 正しくはAWS Lambdaを叩く機能
[15]: また、AWS内のコンポーネントを定期実行するための機能も存在します これはたとえば実業務においてビルド時間が長くなりすぎるため平日深夜2時にビルドを走らせたいというときに有効です¹⁶
[16]: ただしビルド自動化においてはアンチパターン²⁰

ルールを作成する
ルールタブからルールの作成を行います。

  1. イベントソースにイベントパターンを指定し、サービス名にCodeBuild、イベントタイプにCodeBuild Build State Changeを選択します
  2. イベントパターンのプレビューとしてだいたい以下のように表示されると思いますので、編集を行います(具体的に編集する部分としては監視したい build-statusproject-nameを追加します)
  3. 以下から以下のようになると思います
  4. トリガーされたときに叩くAWS Lambdaの関数として上で作った関数を選択します

おめでとうございます!
以上でビルドから通知まですべて自動で行われるようになりました。

linus torvalds自らビルド結果を教えてくれる様子

ちなみにS3上に配置されたログを確認してみるとテストが失敗したというログが吐かれているのがわかると思います。

まとめ

DevOps、継続的インテグレーション・デリバリーとしてのメリット

  • 開発からリリースまで高速
  • 変更に対しすぐさま反応があるので変更者の修正が容易
  • ビルドという行為それ自体がテスト(ひいては開発者の安心)となる²⁰

クラウド・サーバレスとしてのメリット

  • ビルドサーバーの維持管理が不要
  • 抽象的でステートレスな仮想化環境でビルドを行える
  • Auto Scalingによりビルドの待ちが発生しない

デメリット…?

EC2上に常時ビルドサーバーを立てるというやり方に比べると料金は使ったときに使った分だけではあるが、このような自動ビルドの仕組みは頻繁に行ってこそ価値がある²⁰ため、時間ではなくビルドに対してお金が発生するのであればそれはビルドのインセンティブを減少させる……のかもしれない。

将来像

  • ビルドから通知までの流れがバラバラなのでTerraform等で自動化
  • また、CodePipelineでステップや依存関係などを設定、可視化できるとよい
  • テストケースを別で管理し、どのようなテストを行っているのか、カバレッジはどれくらいか、つまり、いま自分たちがどこにいるのか可視化できるとなおよい¹⁷
  • ビルド失敗時は失敗時でもう少し詳細なログ解析を行いたい¹⁸
  • ビルドの失敗、成功だけでなく実行回数、実行時間等のメトリクスもロギングすることでより開発および品質保証に活かしていきたい¹⁸

今回は新人研修という狭いスコープの中ですが、開発という立場から取り組めそうな品質保証への取り組みの一例として自動ビルドから通知までの一連のプロセスを自動化してみました。

この記事が誰かのための巨人の肩となれば幸いです。

[17]:たとえばmavenだとbuild時にテストレポートを出力する機能があるため、CloudWatchでのログ生成時にLambdaでパースするということが考えられると思います
[18]:たとえばCloudWatchにログを溜め、CloudWatch上で精査、必要な情報だけLambdaで通知するということが考えられると思います

参考文献

[19]: Mike Julian 著、松浦 隼人 訳「入門 監視 ――モダンなモニタリングのためのデザインパターン」オライリージャパン、2019年
[20]: David Scott Bernstein 著、吉羽 龍太郎、永瀬 美穂、原田 騎郎、有野 雅士 訳「レガシーコードからの脱却 ――ソフトウェアの寿命を延ばし価値を高める9つのプラクティス」オライリージャパン、2019年

--

--

nakamura.ka
WingArc1st Inc.

Tab people. Software engineering, cloud engineering(AWS), dj, electronics, keyboard DIY, subculture research. Python, C, Java, Vue.js. Stories are my own.