- 澁川喜規
- 約 3,400 文字
- 500 View
Vue.js連載です。ライトめなネタです。
Next.jsやNuxt.jsなどのサーバーサイドレンダリング必須なフレームワークであれば、Node.jsと一緒にコンテナ化するか、Vercelなどにデプロイする方法があります。こちらはJavaScriptのウェブアプリケーションなので実行環境を用意する必要があります。
一方、SPAとして作成したVue.jsなど、現代のフレームワークで作成したフロントエンドは、ビルドすると静的HTMLとJavaScriptコードになります。ただし、物理的なファイルが存在しないパスへのリクエストがあった場合にindex.htmlの内容をフォールバックとして返す必要があるため、動作させるにはそのあたりが設定可能なウェブサーバーを使う必要があります。index.htmlをロードするとブラウザ上でJavaScriptのコードが動作しますが、そのコードが内部で持っているURLのパス情報をみて適切なページを表示したり、それでも存在なければJavaScriptがエラー画面を出力します。
フューチャー作のガイドラインのWebフロントエンド設計ガイドラインのSPAのホスティングでは、いくつかホスティング方法を紹介しています。
- CloudFront+S3
- LB+S3
- LB+Webサーバー
このうち、LB+S3サーバーはSPAで必要なフォールバックができないのでSPA不可となっていますが、最近、ALBでパスのリライトができるようになったので、拡張子がないパスはindex.htmlにリライトとかやれば実はいけるのでは?という気が少ししていますが、それはまたの機会に試そうと思います。
- classmethodブログ: Application Load Balancer のリスナールールでトランスフォームを構成し、ターゲットにルーティングする前にホストヘッダーや URL パスを書き換えれるようになりました
これ以外には、ウェブアプリケーション側に配信機能を持たせてしまうというのも過去に技術ブログで紹介しました。比較的高速なGoとかRustならこれもありでしょう。
今回はガイドラインではSPA用によいとしている1. 3のうち、Dockerイメージを作った3番目の方法を全力で試そうと思います。
なぜDockerにするか
S3とかオブジェクトストレージにおいて配信というのがお手軽ですが、コンテナにまとめておくことでデプロイ時にまとめてフロントエンド資材を入れ替えたり、戻したりがしやすいのがメリットと考えています。また、CloudFrontはインターネット公開するサービスには良いのですが、社内システムでは使えません。加えて、ビルド済みフロントエンドを軽量なサーバーで配信すればリソース消費は少なくて済みます。開発時もフロントエンドを触らない人がローカルで動作検証するにはありがたいでしょう。
せっかく作るのであればセキュリティを意識したコンテナを目指します。近年、ランサムウェアが流行っています。静的なHTML/JSでアプリを作りフロントエンドを配信するだけのコンテナにしてバックエンドをプライベートネットワークの後ろ側に隠すことで攻撃面をかなり狭くできます。ですがHTMLなどが買い替えられると不正なプログラムを配る踏み台にされる可能性があるため、そうならないために次の項目にもチャレンジしてみます。
- シェルがないDistroless
- フロントエンドのartifactは読み込み専用で実行ユーザーでは書き換えられない
テスト用アプリケーション作成
Viteの標準的なサンプルです。
npm create vite@latest |
シングルページアプリケーションとして正しく動作することをテストするために、vue-routerを入れてページをいくつか足します。
import { createRouter, createWebHistory } from 'vue-router' |
ルーターを組み込みます。
import { createApp } from 'vue' |
アプリケーションのトップのレイアウト側にはページナビゲーションを起きます。
<script setup lang="ts"> |
ページを適当に作りました。全部紹介する必要性はあまりないと思うので2つだけ紹介します。
<template> |
<template> |
npm run devで動作させて動いたら次はDockerファイルを作っていきます。
Dockerfile作成
nginxの設定
Vueアプリができたところで次はサーバーです。Rust製のstatic-web-serverとか安全そうだし良さそうだなとも思ったのですが、APIサーバーへのリクエストをプロキシするような設定がなく、今後も入らなそうということもあり見送りました。このプロキシ機能があればウェブフロントエンドとバックエンドが同じドメイン(ポート番号も含めて)動作するので、CORSを機にする必要がなくなります。もちろん、作ったイメージを本番デプロイするだけならALBがやってくれるはずなのでstatic-web-serverでも良いかと思います。ここはありきたりですがnginxにしておきます。なお、今回はイメージサイズは60MBほどになりました。static-web-serverはシングルバイナリで4MBほどらしいので小ささを極めたい場合はstatic-web-serverで試すと良いでしょう。
設定ファイルとしては、SPAで必要なフォールバックを入れたのと、ログは/var/logとかではなく、コンソールに出力するようにしています。
設定ファイル上にuser www-data;と書けばユーザーが設定できます。ただし設定しなくてもワーカーはnobodyユーザーで動作します。最初はセキュリティ強化のためにサーバーは非ルートユーザーで動かすぞ!と設定していたのですが、ワーカーさえルートでなければ実用上は問題ないため、無視底のnobodyで十分だと判断しました。なお、別ユーザーで動かすと、特権ポートの1024以下は使えないため、80番ポートでサーバーを動かすことはできなくなります。
|
Dockerfile
Dockerfileは以下の通りです。最初Geminiに雛形をざっと作ってもらいましたが、いろいろ細かいところを後から修正しました。その際に心がけたポイントは以下の通りです。
- ビルドステージ
- bindマウント、cacheマウントを駆使してキャッシュフレンドリーな高速ビルド(生成AIはいつもやってくれない)
- nginxの設定
- こちらもbindマウント、cacheマウントで効率化
- 最終イメージのDistrolessはシェルがなくてmkdirとかもできないので、こちらのステージですべての必要なフォルダを作ったり、ユーザーやグループの設定を引っこ抜いたり、ディレクトリの権限を設定したり、nginxの動作に必要ライブラリをコピーしたりも含めて全て行なっています
- 実行イメージ
- Debianの新しいバージョンのtrixie(13)が使いたい→まだベータ扱いなのでいったん保留
- ビルド済みのHTML/JSを持ってきたり、nginxの設定を持ってきたり
- 実行ユーザー(nobody)から書き換えられないユーザーでHTML/JSを配置
# syntax=docker/dockerfile:1 |
実行するには次のようにします。
ビルド |
デバッグ実行
設定を変えてみたい場合のデバッグ方法も紹介しておきます。Distrolessではセキュリティのためにシェルがイメージに含まれていませんが、デバッグ用のイメージが提供されています。
- ランタイムをイメージを
gcr.io/distroless/base-debian12からgcr.io/distroless/base-debian12:debugに変更 docker run --rm -it -p 8080:80 --entrypoint=sh vue-spaで、shをnginxの代わりに実行
まとめ
Dockerfileは新旧の書き方がウェブには混在しているため、機会をみてはbind/cacheを使ったモダンなDockerfileの記法をブログに書くように日頃からしていました。今回はVue連載ということで、Vue製のSPAの静的HTMLのコンテナを作ってみました。単に作るだけでは世の中の有象無象の記事と変わらないので、最新の記法を使ったビルドの効率、実行効率、セキュリティ、どれも妥協しないDockerfileを作りました。Vue以外の方にも参考にしてもらえる記事になったと思います。
明日は松本朝香さんです。