Dockerfile不要!Cloud Native BuildpacksでLaravelアプリケーションのコンテナイメージを作成する
こんにちは、ナカエです。 本日はCloud Native Buildpacksについての記事です。
Buildpacksは簡単に言えばDockerfileを書かずにアプリケーションのコードからコンテナイメージを作成できる仕組みです。元はPaaSで有名なHerokuで考案され、Cloud Foundryや他のPaaSでも利用されてきました。 Herokuにコードをデプロイするだけでアプリケーションが動くのは、裏でこのBuildpacksが活躍していたおかげなんですね。
HerokuとCloud Foundryで差異があった仕様を統一し、アプリケーションコードからOCIイメージを作成する仕様として標準化されたのが Cloud Native Buildpacks(CNB) です。2018年1月にPivotalとHerokuによってプロジェクトが開始され、2018年10月にCNCFのSandboxプロジェクトとなりました(2022年2月現在はIncubating)。
Cloud Native Buildpacksを利用したコンテナイメージの作成は簡単で、PackというCLIツールを使い、アプリケーションのソースコードがあるディレクトリで
pack build {image名} --builder={builder名}
というコマンドを打つだけです。
このCloud Native Buildpacksを使って、Laravelアプリケーションのコンテナイメージを作成するのが今回の記事の目標です。
Dockerfileを品質良く安全に保守し続けるのはコストがかかります。PHP界隈ではCloud Native Buildpacksの利用例はあまり聞きませんが、Dockerfileの秘伝のタレ化や属人的な運用を避けたり、更新し続けるコストを省きアプリケーション開発に集中したりという効用が期待できます。
参考:Cloud Native Buildpackで めんどうなコンテナイメージ作成を自動化しよう - Speaker Deck
packコマンドのインストール
PackはCloud Native Buildpacksプロジェクトによってメンテナンスされている、buildpacksの利用をサポートするCLIツールです。
Macの場合はHomebrewでインストールできます。
brew install buildpacks/tap/pack
※記事執筆時点でのpack CLIのバージョンは0.23.0でした。
Builderの選択肢
Builderはコンテナイメージのビルドを実行するのに必要なすべてのコンポーネントを含むイメージであり、pack buildコマンドで指定します。 PHPのビルドに対応したBuilderイメージを選択します。有名所の
- Cloud Foundaryプロジェクトによって提供されている Paketo Buildpacks
- Heroku Buildpack
のうち、Paketo Buildpacksを最初に試しました。
しかし、現時点(2022年2月)ではPaketo + Laravel の組み合わせはそのままでは上手く動かなかったため、HerokuのBuildpackを採用します。
( 該当のIssue: Buildpack support for Laravel 8 · Issue #366 · paketo-buildpacks/php)
Laravelアプリケーション作成
まずはComposerを使ってLaravelプロジェクトを作成します。
composer create-project laravel/laravel my-laravel-app
コマンド一発で……
cd my-laravel-app
pack build my-laravel-app --builder heroku/buildpacks:20
これでmy-laravel-appという名前でコンテナイメージが作成されます。
docker runコマンドでコンテナを起動します。
docker run --rm -e PORT=8080 -p 8080:8080 my-laravel-app
http://localhost:8080 にアクセスすると……
403だよチクショウ!
403の原因はWebサーバのドキュメントルートをpublicに指定していないせいでした。 基本的にはpack buildコマンドを打つだけとはいえ、事前に最低限の設定は必要なようです。
Webサーバの設定
プロジェクト直下にProcfile(大文字小文字に注意)という名前のファイルを作成し、起動コマンドに設定ファイルとWebサーバのドキュメントルートを指定します。WebサーバではデフォルトではApache httpdが利用されていますが、Nginxを利用することにします。
Procfile
web: heroku-php-nginx -C nginx.conf public
プロジェクト直下にnginx.confという名前で設定ファイルを作成します。
nginx.conf
location / {
try_files $uri @rewriteapp;
}
location @rewriteapp {
rewrite ^(.*)$ /index.php$1 last;
}
再ビルドして
pack build my-laravel-app --builder heroku/buildpacks:20
前のdocker runのプロセスを落としてから再びdocker run
docker run --rm -e PORT=8080 -p 8080:8080 my-laravel-app
http://localhost:8080 にアクセスするとLaravelのWelcomeページが今度こそ表示されます。
何もしていないのにまるで魔法のようにとはいかなかったものの、大変お手軽にコンテナイメージを作成することができました。
アセットのビルド
初期状態のLaravelアプリケーションを表示するだけなら問題はないのですが、実は現状のビルドプロセスではフロントエンド用のアセットが生成されていません。 明示的にbuildpackの指定をすることでPHPとProcfileのbuildpackに加え、Node.js用のbuildpackを利用します。
pack build my-laravel-app --builder heroku/buildpacks:20 --buildpack heroku/php --buildpack heroku/procfile --buildpack heroku/nodejs
buildpackの指定は設定ファイルに記述することもできます。
プロジェクトの直下にproject.tomlという名前でファイルを作成します。
project.toml
[project]
id = "my-laravel-app"
name = "My Laravel Application"
version = "1.0.0"
[build]
exclude = [
"/README.md",
".git"
]
[[build.buildpacks]]
uri = "heroku/php"
[[build.buildpacks]]
uri = "heroku/procfile"
[[build.buildpacks]]
uri = "heroku/nodejs"
上記のようにproject.tomlでbuildpackを指定しておけば、前と同じくbuildpackの指定を省いてpack buildを実行できるようになります。
pack build my-laravel-app --builder heroku/buildpacks:20
この状態でフロントエンドの依存パッケージのインストールまでは行ってくれるようになりました。アセットの生成をその後に実行するように指定します。
packages.jsonにnpm scriptとして"heroku-postbuild"を記述すると、ビルド時にその内容が実行されます。
{
// 略
"scripts": {
// 略
"heroku-postbuild": "npm run production"
},
// 略
}
再度pack buildを実行すると、npmでパッケージがインストールされた後にLaravel Mixが実行されている様子を確認できます。
pack build my-laravel-app --builder heroku/buildpacks:20
> heroku-postbuild
> npm run production
> production
> mix --production
ここまでで、Laravelアプリ用のコンテナイメージを作成することができました。 記事の後半ではより細かなカスタマイズ方法を見ていきましょう。
カスタマイズ方法
Heroku PHP Support の情報を見ると、各種ファイルを変更したり追加することでより細かな設定が可能となっていることがわかります。 普段からHerokuを利用している方にはおなじみかもしれません。
下記に一部を紹介します。
Webサーバの変更
すでにNginxに変更していましたが、Procfileの起動コマンドを変更することで、Webサーバを変更することが可能です。 また、オプションでWebサーバの設定ファイルを読み込むこともできます。
web: heroku-php-apache2 -C apache.conf public
web: heroku-php-nginx -C nginx.conf public
Apache2を指定してもNginxを指定しても、PHP-FPMとの組み合わせで動作します。 1コンテナ1プロセスという原則には反しているのですが、Herokuでの実績があるのでそこまで目くじらを立てないことにします。
本番用途ではありませんが、ビルトインWebサーバも選択肢の一つとなっています。
web: php -S 0.0.0.0:$PORT
php.ini設定
Webサーバのドキュメントルートに.user.iniファイルを作成して設定をカスタマイズする方法が推奨されています。
publicディレクトリ内に.user.iniファイルを下記の内容で作成します。
date.timezone = "Asia/Tokyo"
post_max_size = 20M
upload_max_filesize = 10M
PHP拡張の検出
PHP拡張をどのように追加するか調べると、composer.jsonのから依存を読み取ると説明されています。 composer.jsonのrequireに必要な拡張を追加し、composer.lockにも反映しておきます。
{
"require": {
"ext-apcu": "*"
}
}
この設定により、ビルド時に必要な拡張が検出されてインストールされます。
===> BUILDING
-----> Bootstrapping...
-----> Installing platform packages...
- php (8.1.2)
- ext-apcu (5.1.21)
- apache (2.4.52)
- composer (2.2.5)
- nginx (1.20.2)
- ext-mbstring (bundled with php)
拡張のバージョンを明示的に指定することも可能です。
利用できる拡張は下記リンク先の表を参考にしてください。
PHPバージョン指定
こちらもcomposer.jsonから読み取られます。 PHPのバージョンを固定したい場合は^8.0などの範囲指定をやめるといいでしょう
{
// 略
"require": {
"php": "8.0.14",
// 略
},
// 略
}
composer.jsonを直接編集した場合はcomposer.lockの更新をお忘れなく。
カスタムコンパイル
Composer scriptにcompileが存在すると、コンテナイメージのビルド時に実行されます。
composer compile --no-dev --no-interaction
FWに特有のキャッシュの生成などを行うことができます。
composer.json
{
"scripts": {
"compile": [
"php artisan route:cache"
]
}
}
この状態でpack buildを実行すると、composer installの後にcomposer compileが実行されていることが確認できます。
-----> Running 'composer compile'...
> php artisan route:cache
Route cache cleared!
Routes cached successfully!
まとめ
CNBのPackコマンドとHerokuのBuilpackを用いてLaravelアプリ用のコンテナイメージを作成することができました。 Dockerfileを書かない代わりに、各種ファイルから適切に設定を読み取れるように気を配る必要があるといったところでしょうか。 HerokuのBuildpack特有の設定も多いですが、Procfile、Composer scriptやnpm scriptなどなるべく標準的な仕組みで設定をカスタムできるようにという気概は感じられます。
2020年10月にはGoogle Cloud PlatformでもCNB v3をベースにしたGoogle Cloud Builpacksがリリースされるなどの動きもあり、Buildpacksの仕組みの利用は今後も広がっていきそうです。 PHPのサポートは若干薄い印象がありますが、PHP界隈でもより簡単にコンテナイメージを作成しアプリケーション開発自体に注力できるようになるといいですね。
- PHP , Buildpacks , CNCF , コンテナ