Laravelのデプロイ
かつてLaravelの開発環境を設定していろいろ学んできましたが、いよいよデプロイです。
Windows10で開発したLaravel9のプロジェクトをDebian11にデプロイしました。Laravelのデプロイは基本的にはプロジェクト単位でコピーをして設定を変更するだけなのでさほど難しくはないものです。
ただLaravel9がPHPのバージョン8.0以上を要求しているのに対し、執筆時点のDebian11においてはAPTで取得できるPHPのバージョンは7.4だったので、apacheとPHPをmakeすることになりました。
手順は公式のガイドに従います。
Apacheのビルド
APTでbuild-essentialsインストールを終えているものとし、Apacheをビルドします。
Apacheから、httpd-2.4.54.tar.gz をダウンロードして展開します。
# cd /usr/local/src # wget https://dlcdn.apache.org/httpd/httpd-2.4.54.tar.gz # tar vzxf httpd-2.4.54.tar.gz # cd httpd-2.4.54 # ./configure --enable-so ... checking for APR... no
configureを実行すると、APRがないということです。INSTALLファイルを読むと、apr.apache.orgからダウンロードするようにとの事でした。
APRはプラットフォーム間の挙動差異を吸収してくれるツールのようです。configure -hで見てみても、これがない状態でビルドする方法はなさそうでした。
ダウンロードしたのち展開し、httpd-2.4.54ディレクトリにあるsrclibディレクトリの中に、バージョンを除去した形で移動させます。その後、--with-inculded-aprオプションをつけてconfigureを実行します。
# wget https://dlcdn.apache.org//apr/apr-1.7.0.tar.gz # wget https://dlcdn.apache.org//apr/apr-util-1.6.1.tar.gz # tar vzxf apr-1.7.0.tar.gz # tar vzxf apr-util-1.6.1.tar.gz # mv apr-1.7.0 httpd-2.4.54/srclib/apr # mv apr-util-1.6.1 httpd-2.4.54/srclib/apr-util # ./configure --enable-so --with-inculded-apr
PCREもが存在しないというエラーも出ました。PCREはPerl 5と同じ構文で正規表現を行うためのライブラリです。aptで「libpcre2-dev」をインストールしたところconfigureは通りましたが、ビルドでエラーになりました。PCRE2Project/pcre2から最新版をダウンロードしてビルドするのがいいと思いまいます。
make時に「 expat.h: そのようなファイルやディレクトリはありません 」というメッセージでエラーとなりました。こちらは「libexpat1-dev」をaptでインストールしたところ解消しました。。
デフォルトだとApacheバイナリ群は「/usr/local/apache2/」にインストールされます。設定ファイルはその中のconf/httpd.confとなっており、設定はすべてその中に記述します。APTからインストールした際のようなDebianのApache2の設定ディレクトリ群では設定をしないため、a2enmod等のコマンドは利用できません。
サーバーはbinディレクトリのapachectlから「start」や「stop」といった引数を渡してコントロールします。apachectlの他のコマンドについては公式ページに掲載されています。
公開するファイルの保存先であるhtdocsディレクトリも同じ場所に存在します。
コマンドを利用すれば起動はできますが、おそらくサービス(Systemd)として動かしたいと思います。これはAPT経由でインストールしたapacheサーバーのファイルを参考に次のようにしました。
apache2.service
[Unit]
Description=The Apache HTTP Server
After=network.target remote-fs.target nss-lookup.target
Documentation=https://httpd.apache.org/docs/2.4/
[Service]
Type=forking
Environment=APACHE_STARTED_BY_SYSTEMD=true
ExecStart=/usr/local/apache2/bin/apachectl start
ExecStop=/usr/local/apache2/bin/apachectl stop
ExecReload=/usr/local/apach2/apachectl graceful
PrivateTmp=true
Restart=on-abort
[Install]
WantedBy=multi-user.target
このファイルを/etc/systemd/system/ディレクトリに配置します。その後「systemctl enable apache2」を実行して自動化させます。
デフォルト以外の場所にインストールした場合は、Execのパスに注意してください。
またAPTでインストールした際は、apacheはwww-dataというユーザー権限で動くのでその設定もします。
ユーザーを作成します。
次に、httpd.confのUserとGroupにwww-dataを設定します。
httpd.conf
# User daemon # Group daemon User www-data Group www-data
サービスを再起動させて状態を確認します。httpのプロセスのユーザーが設定したwww-dataになっているものがあれば正常です。一部のプロセスはrootになっています。
# ps aux | grep www-data root ... /usr/local/apache2/bin/httpd -k start www-data ... /usr/local/apache2/bin/httpd -k start ...
PHPのビルド
PHPをビルドします。まず公式のインストールガイドにあるように--with-apxs2=/usr/local/apache2/bin/apxs指定します。apacheをビルドしたのはこのためです。
他LaravelのPHP要件であるもののうち、デフォルトで有効にならない次の拡張ライブラリを設定します。
- BCMath
--enable-bcmath
- cURL
--with-curl
- Mbstring
--enable-mbstring
- OpenSSL
--with-openssl
- PCRE
公式のWebのドキュメントにはデフォルトでソースにバンドルされているPCREが利用されるとの記述があったので、なにも指定しなかったところ「 Unable to start pcre module in Unknown on line 0 」というエラーに遭遇しました。そこでapacheインストール時にビルドしたPCREを利用するように「--with-external-pcre=[ライブラリのディレクトリ」と指定しています。
- PDO
PDO自体は有効になっていますが、MySQL(MariaDB)を使うなら、--with-pdo-mysqlを指定する必要があります。Postgreの場合は--with-pdo-pgsqlを指定します。
MariadbやopenSSLの他必要なアプリをインストールしておきます。筆者の環境では他にpkg-configやlibxml2-dev、libssl-dev(openssl)、libsqlite3-dev、libcurl4-openssl-dev(curl)、libonig-dev(Oniguruma正規表現ライブラリ)、zlib1g-dev(圧縮ライブラリ)が必要になりました。
# apt install mariadb-server openssl pkg-config ... # wget https://www.php.net/distributions/php-8.1.9.tar.gz # tar vzxf php-8.1.9.tar.gz # cd php-8.1.9.tar.gz # ./configure --with-apxs2=/usr/local/apache2/bin/apxs --enable-bcmath --with-curl --enable-mbstring --with-openssl --with-pdo-mysql --with-zlib --with-external-pcre=/usr/local/lib # make # make test # make install
make testを事項するように促されますので、実行します。おそらくいくつかは失敗するのではないかと思います。
これらの対処方法は@IT:「PHPテスト失敗の原因を追究する」が参考になります。
例えば次のようなエラーが出たとします。
この時ソースディレクトリにある「ext/opcache/tests/bug78014.phpt」に定義されているテストが実行されます。その内容から状況を判断します。
今回は、opcache関連のエラーがいくつか出ました。make testを管理者権限で実行したためだと判断しました。opcacheのコードの事前ロードではroot権限での実行が禁止されています。ユーザー権限でテストをすると該当のエラーは出ませんでした(代わりroot権限でテストした際には出ないエラーが出ましたが)。
PHPの設定
httpd.confにおけるPHPのLoadModuleのエントリーは--with-apxs2オプションをを付けてビルド・インストールした流れの中で自動的に加わっていると思います。それが出力されているのが確認できたら、.phpの拡張子をもつファイルをPHPとしてパースする設定の記述をします。
httpd.conf
...
<FilesMatch \.php$>
SetHandler application/x-httpd-php
</FilesMatch>
...
PHPの設定ファイルはソースディレクトリからphp.ini-developmentまたはphp.ini-production、ファイルをコピー・リネームして配置します。デフォルトだと/usr/local/lib/php.iniに配置する決まりになっています。
Laravelプロジェクトのデプロイ
前置きが長くなりましたが、本番サーバーにLaravelプロジェクトをデプロイします。
サーバーにcomposerがない場合はリンク先を参考にインストールします。
# php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" # php -r "if (hash_file('sha384', 'composer-setup.php') === '55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" # php composer-setup.php # php -r "unlink('composer-setup.php');"
作業後に作業ディレクトリ「composer.phar」という名前で実行ファイルができます。これは後からオートローダ―を最適化する際に利用します。binディレクトリなどパスの通った場所から、composerというシンボリックリンクを張ります。
同様にNode.js環境もインストールします。Node.jsのインストールはNVMを使って行うのがいいと思います。
LinuxにおけるNVMは実行したユーザーのホームディレクトリに.nvmとして保存されます。実行ファイルへのパスはスクリプトが.bashrcファイルに記述してくれますが、初回時はうまく反映されていないことがあります。which nvm等の応答がなかったら再ログインしてみてください。またroot環境でインストールしてしまうと、ユーザー環境からは利用できない場所にインストールされてしまいますので注意が必要です。
$ wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash $ nvm install --lts $ nvm use インストールされたバージョン
nvmをアンインストールしたい場合は.nvmディレクトリを削除するだけです。npmの実行ファイルは.nvmディレクトリの下流にありますが、エラーログ等はユーザーディレクトリ内の.npmディレクトリに保存されるようですので、nvmアンインストール時にはそれも削除します。
この後の手順は次のようになります。
- ファイルのコピー
基本的にプロジェクトディレクトリをコピーします。「/node_modules,/public/build,/storage/*.key,/vendor,.phpunit.result.cache」など通常.gitignoreに含まれるディレクトリ以外のすべてが対象となります。
後々のことを考えるとgitを利用して開発側でアップロードしたgitレポジトリを本番側でcloneした方が便利だと思いますが、この時デフォルトだと、.envファイルは含まれないので別途生成するかコピーします。また、composerやnodeで設定したパッケージもコピーされませんので、それぞれ「npm update」「composer install」を実行します。
コピーしたファイルとディレクトリの所有権はこのあとcomposerやnpmを実行するユーザーにしておきます。
- APP_KEYの再生成
.envファイルに設定してあるAPP_KEYを生成または再生成します。
$ php artisan key:generate
- 環境をプロダクションモードに
.envファイル中の「APP_ENV」の値を「local」から「production」にします。
- デバッグモードの解除
.envファイル中の「APP_DEBUG」の値をfalseにします。APP_ENVの後に出現する設定なのでここで紹介していますが、この値をfalseに設定すると実行時に詳細なエラーメッセージが出なくなりますので、稼働を確認してからの方がいいかもしれません。
- APP_URLの設定
デフォルトでapp.testとなっているURLを正規のものに変えます。
- データベースの設定
初回のデプロイ時は環境によってデータベースにユーザー登録等が必要になります。またマイグレーションやシードも必要に応じて実行します。
bootstrapに相当する場所(サービスなど)にデータベーステーブルが存在することが前提のコードが書かれていると、例外がスローされてphp artisanコマンドを実行できない場合があります。コードを直すか、それが難しければ開発側のデータベースのテーブルをダンプしたものを本番側でインポートします。MySQL(MariaDB)におけるダンプ・インポートの方法は次の通りです。
$ mysqldump -u ユーザー名 -p データベース名 --default-character-set=binary>dump.sql
$ mysql-u ユーザー名 -p データベース名 --default-character-set=binary<dump.sql
- JavaScriptのビルド
JavaScriptの環境自体は既に設定済みだと思いますので、ビルドのコマンドを実行するだけです。buildしてから本番環境にコピーする方法も試してみたのですが、やり方が悪かったのか@viteディレクティブが本番環境にならずうまくいきませんでした。
$ npm run build
ビルド時に、次のようなエラーが出た場合はstack overflowを参考に、/etc/sysctl.confのファイルウォッチャーの制限を変更します。
System limit for number of file watchers reached/etc/sysctl.conf
... fs.inotify.max_user_watches = 16384 ...
ここで設定した値は適当です。筆者の環境ではデフォルト値が8192だったので倍にしました。変更後、「sysctl -p」で設定を反映させます。
- オートローダの最適化
Composerのクラスオートローダの最適化をします。ここで先ほどインストールしたcomposer.pharを使います。composerは管理者権限で実行すると警告がでます。
$ composer.phar install --optimize-autoloader --no-dev
- configの最適化
構成ファイルをひとつのキャッシュファイルにまとめます。新しい環境でconfigのキャッシュしないと古い環境のものが使われてしまい、思わぬエラーに悩まされることになります。
$ php artisan config:cache
- ルートの最適化
すべてのルート登録をキャッシュ ファイル内の 1 つのメソッド呼び出しに減らします。
$ php artisan route:cache
- ビューの最適化
すべての Blade ビューをプリコンパイルします。
$ php artisan view:cache
すべて終えたら、エントリーポイントであるpublicディレクトリ内のindex.phpにアクセスして意図したように表示できるかチェックします。
開発環境がWindowsでデプロイはLinuxという場合は、ファイルの大文字と小文字の違いが識別されるようになる上に権限の概念も加わりますので注意が必要です。
筆者が実際に遭遇した事例を挙げると、はphp artisanコマンド等でautoload_real.phpのコードでエラーとなって何もできなかったのですが、composer.jsonのautoload上のパスの中のappのaが大文字になっていました。
...
"autoload": {
"files": [
"app/helpers/CustomHelper.php"
]
},
...
ちなみにこれは自作のヘルパー関数を追加する設定だったので、値の修正後は「composer dump-autoload」を実行してautoloaderを更新することも必要です。
細かな設定
稼働が確認できたら細かな設定をします。
htdocs以下のファイルやディレクトリの所有権の設定の仕方はいろいろな考え方があるとは思いますが、stack overflow:「How to set up file permissions for Laravel?」に答えのひとつがありました。
ただ先のサイトのまま従ってしまうと、本番環境でnpm run buildが実行できなくなってしまいます。なので筆者は次のようにしました。
- ファイルのコピー
筆者の環境のumaskの値は0022です。
- 「composer install」「npm update」を実行
- 所有権を、編集ユーザー:apacheグループに変更
# chown -R edit-user:www-data プロジェクトdir/
- すべてのファイルから「その他」の書き込み権限と実行権限を削除
# find プロジェクトdir/ -type f | xargs chmod o-wx
- すべてのディレクトリから「その他」の書き込み権限を削除
# find プロジェクトdir/ -type d | xargs chmod o-w
- すべてのファイルに「グループ」の書き込み権限を追加
# find プロジェクトdir/ -type f | xargs chmod g+w
- すべてのディレクトリに「グループ」の書き込み権限を追加
# find プロジェクトdir/ -type d | xargs chmod g+w
そのほかphp.iniを修正してエラーの出力と止めたり、apacheの設定でディレクトリ指定時のファイルのリストを止めたりします。
apacheのhttpd.confのdir_moduleを使って、publicディレクトリにアクセスした際にindex.phpを表示させようとすると、次のようなエラーになりうまくいきませんでした。
それに対する回避策は、index.htmlファイルを作ってidnex.phpにリダイレクトするようにするのが簡単です。
index.html
...
<head>
<!-- metaで書くパターン-->
<meta http-equiv="refresh" content="0;URL='./index.php'"/>
<!-- jsで書くパターン-->
<script>
window.onload = function () {
location.replace("./index.php");
};
</script>
ただ、他に設定しなければならないものもありますので、.htaccessファイルとmod_rewriteを使って次のようにするのがいいと思います。
.htaccessファイルを利用するために、httpd.confでは設定の上書きを許可します。同時に、ファイルのリストの出力をやめ、mod_rewriteを有効にします。
httpd.conf
...
# mod_rewriteを有効に(コメントアウトを外す)
LoadModule rewrite_module modules/mod_rewrite.so
...
<Directory "/usr/local/apache2/htdocs">
...
# Indexesを消してファイル一覧を出さないようにする
Options FollowSymLinks
# NoneからAllに変えて設定の上書きを許可する
AllowOverride All
</Directory>
プロジェクトディレクトリ直下のデータを読み込まれるのはよくないので、下流のディレクトリを参照できないようにします。
プロジェクトデイレクトリ/.htaccess
...
<Files "*">
deny from all
</Files>
publicディレクトリだけは例外に指定します。プロジェクトのpublicディレクトリにはLaravelが生成した.htaccessファイルが存在しますので、それに追記します。
ここにルートアクセスの際のリダイレクトも追加します。
プロジェクトデイレクトリ/public/.htaccess
...
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
# pulicディレクトリアクセス時のリダイレクト設定
RewriteRule ^$ %{REQUEST_URI}index.php [L,R]
</IfModule>
# ファイル読み込み例外設定
<Files "*">
allow from all
</Files>
参考にさせていただきましたサイトの皆様、ありがとうございました。