WebアプリをDocker構成にした場合にフロントエンドリソースを扱うためのデザインパターン

この記事はCyberAgent エンジニア Advent Calendar 2015の7日目の記事です。

www.adventar.org

6日目はkaelaelaさんの「なぜデザイナーとエンジニアは分業するのか」でした。

弊社のAdvent Calendarは2年連続の参戦です。思えば昨年も12月2週の月曜だった気がしますね。

普段何をしているかというと、業務中のTwitterだったり、プロジェクトメンバーを煽ったり、事業部長が連発する80年代の死語を矯正したり、競馬予想をしたり、新しくできた頭のおかしいエンジニア評価制度をボロクソに批判したりというのが主であります(完全にクズですね)。

とはいえたまには真面目に弊社エンジニアブログに寄稿したりもしているんですよ(^ω^) ameblo.jp

上のエントリもそうですが、今年1年は本当にDockerと向き合った年だったのでこの記事もDockerネタでいこうと思います。社内・社外含め、DockerのProduction導入に二の足を踏んでる人がその一歩を踏み出す一助になれば幸いであります。

はじめに

弊社エンジニアブログにも書いたように、今のプロジェクトではDockerをフル活用しています。Webアプリケーション側の構成はこんな感じで、PCやネイティブアプリからのリクエストを受け付けます。

f:id:a-yamada:20151207003933p:plain

WebサーバとしてNginxがいて、SSR(サーバサイドレンダリング)としてNode.jsでReactを利用。ビジネスドメインレイヤーのAPIを提供するのがGolangのGojiで書かれたRESTFul API。Nginxが受けたリクエストがAPIのURLであれば、APIに直接Proxyするし、それ以外であればNodeにProxyしてHTMLをレンダリングして返します。

しかし、これだけではWebアプリケーションとしては成立しません。HTMLからはJavaScriptcssや画像といったフロントエンドのassetsや、ユーザーが投稿した画像(UGCと呼ぶ)へのリクエストが当然あります。本エントリでは、Docker構成でWebアプリケーションを構成した場合、assetsの管理にどのような手法があるかについて述べようかと思います。

Webの構成(SSRとSPA)とDocker

パターン紹介の前にWeb(Node+React)側の構成についてもう少し説明しておきましょう。基本はSSRですが、SSRをしながらもSPA(Single-page Application)らしい表現も取り入れていて、SSRとSPAを共存させた構成になっています。この辺の詳細については弊社ahomuがまさに当事者なのでとても詳しく解説してくれています。

havelog.ayumusato.com

WebのGitリポジトリは1つで、SSRとSPAのコードを管理しています。当然、フロントエンドのassetsもこのリポジトリで管理されます。このリポジトリのビルドフローは・・・

  • CircleCIで諸々のテスト+ビルド
  • フロントエンドをビルドしてassetsを生成する
  • Nodeをビルド
  • NodeアプリケーションとしてのDockerイメージを生成、Private RegistryへPush

です。ここで課題にあがるのがまさにassetsの扱いです。いくつかパターンがあって、それぞれメリット・デメリットが存在します。

Pattern: 全部S3(or CDN)に置く

完全にS3(or CDN)に置いてしまうパターンです。assetsはビルド時にS3にアップロードされ、ビルド時に発行したtimestampのディレクトリ配下にまるっとアップロードします。このとき、このtimestampをWebのDockerコンテナ内にconfigとして追加しておいて、SSRするときにassetsを参照できるようにしておきます(timestampを利用することでcache breakにもなる)。

s3://your-assets-bucket/(timestamp)/xxxx
  • (+) assetsへのリクエストをS3やCDNに完全に丸投げできる
  • (-) Nginxを通さないので、assetsの動的gzipができない。S3にアップロードする際に、gzipされたものも同時にアップロードする必要がある(タルすぎ)
  • (※) S3やCDNが障害を起こした場合、assetsだけがごっそり逝って間抜けなことになる

Pattern: Nginxコンテナにassetsを同梱する

f:id:a-yamada:20151207021342p:plain

S3(or CDN)パターンだと動的gzipができないため、フロントエンドのパフォーマンスチューニングの面ではちょっと扱いにくい面は否めません。このパターンでは、WebのassetsをNginxにコピーして同梱してしまうパターンです。こうすることでNginxの動的gzipや、静的リソース配信の様々な機能の恩恵を受けられます。

  • (+) assetsの動的gzipができる
  • (-) WebのassetsをNginxにコピーする必要が有るため、Webをビルドする際にNginxのコンテナもビルドする必要がある
  • (※) assetsリクエストもNginxで捌くので、S3(or CDN)パターンよりはリクエストが増える

Pattern: NginxコンテナからWebコンテナをボリュームマウントする

f:id:a-yamada:20151207021112p:plain

Dockerにはコンテナ間でボリュームをマウントできるという機能があります(docker-composeでいうとこの、volumes_from)。これは何かというと、とあるコンテナ内のディレクトリを他のコンテナからも参照できるというものです。 つまり、Webコンテナ内のassetsをNginxコンテナからも参照できるようになります。コンテナ間ボリュームマウントによって、NginxはWebコンテナ内にあるaseetsを配信できるようになります。

  • (+) assetsの動的gzipができる
  • (+) assetsを同梱したNginxコンテナを毎度ビルドする必要はなくなる(Webのビルドだけでassetsは事実上更新される)
  • (-) コンテナ間マウントによってコンテナが密結合になり、ポータビリティは多少損なわれる。2つのコンテナが同一ホストに存在している必要があるため、スケジューリングでコンテナを分散する場合効率的なリソースの利用も多少損なわれる。

今のプロジェクトでの方式

今のプロジェクトではNginxコンテナからWebコンテナをボリュームマウントするパターンを採用し、さらに画像Assetsに関しては弊社が開発している画像配信サービスであるHayabusaを介して取得しています。

f:id:a-yamada:20151207030624p:plain

Hayabusaは動的リサイズやクロップ等の機能を備えており、またCDNとしての働きもしています。この方式にすることで、画像AssetsはHayabusaの恩恵を受け、JSやCSSといったテキスト系assetsはNginx側の動的gzipの恩恵も受けられるという算段です。ちょっとNginxが仕事しすぎ感があるので、assetsリクエストだけを捌くNginxを別途置くのもあり。あと、何でHayabusaに直接アクセスするパターンもありますが、一度Nginxを介してキャッシュしておくことで、Hayabusaに障害があったとしてもキャッシュで動き続けることが可能ってのがあります(画像assetsだからこそなせる技)。

Hayabusaについては今回のAdvent Calendarで既に紹介されているので、興味のある方はご覧くださいませ。 ameblo.jp

まとめ

と、こんな感じでDockerでフロントエンドを運用する上で重要なassets配置パターンについていくつか紹介しました。今年はコンテナ芸人として色々とチョロチョロしていたわけですが、フロントエンド寄りのDockerの話あんまりしてないなぁと思ったのでこのネタを選んだ次第です。来年もポータビリティ高い1年を送りたいものです。

明日8日目はkuro_m88さんです。