こんにちわSymfony7 with Flyio
Symfony Advent Calendar 2023 の14日目の記事です!
(遅くなってしまってすいません...😇)
はじめに
久しぶりにSymfonyを触っているんですが、気づいたらSymfony7になっていました...。
自分の知識はSymfony5.4で止まっています。
そこで、Symfony7を使ってflyioにデプロイするまでの諸々を書いていきたいと思います。
何が変わったのか?
最初にSymfony7を触った感想としては「あれ??5.4と比べると、ほとんど何も変わってない?」でした。
というか細かい実装は変わっているだろうけど、、Symfonyってメジャーバージョンアップしても、コントローラ作って、フォーム作って、DoctrineでEntity作ってみたいなのところはあんまり変わってなくてキャッチアップしやすいですね。
(これがタダで使えるとかまじでいいの?って気持ちになります。)
そもそも6.4と7.0の違いは?
雑に呟いたら@ttskchさんが教えてくました。
つまり機能としてはSymfony6.4と変わらないわけです。
ちなみに型定義が結構追加されているのは個人的に嬉しいです。
DoctrineのCollectionもこんな感じで書けるのでphpstanに怒られないように頑張ってコード書くみたいな本末転倒なことが起きにくくなっています。
/**
* @var Collection<int, Item>
*/
#[ORM\OneToMany(mappedBy: 'brand', targetEntity: Item::class, fetch: 'EXTRA_LAZY')]
private Collection $items;
使う側としては5.4のころからほとんど変わってない印象が強いです。
fly.ioにSymfonyをデプロイする
私の大好きなfly.ioにSymfony7をデプロイするまでの手順を書きます。
fly.ioにアカウントを登録済みで、クレジットカードも登録済みだという想定からはじめます。
まずはSymfonyのプロジェクトを作る
Symfony CLIを使うことでサクッとSymfonyプロジェクトが用意できます。
$ symfony new --webapp sf7-flyio-example
$ cd sf7-flyio-example
flyctl コマンドをインストールする
公式ドキュメントの通りにインストールします。
Install flyctl
$ curl -L https://fly.io/install.sh | sh
インストールしたらログインしておきましょう。
$ flyctl auth login
上記のコマンドを実行すると、ブラウザが立ち上がってflyioのページが表示されるのでそのままログインすれば大丈夫です。
無事にログインできたかを確認するには以下のコマンドを実行してください。
$ flyctl auth whoami
ログインしたアカウントのメールアドレスが表示されます。
flyctl launchを実行する
flyctl launch
コマンドでflyioにデプロイするための準備をします。
launchコマンドを実行すると言語・フレームワークをみていい感じにDockerfileやfly.tomlなどを生成してくれます。
あとは flyctl deploy
をすればすぐにデプロイできます。
ただし、サポートされている言語・フレームワークに限るわけです。
fly.ioの場合Laravelはサポートされているんですが、Symfonyはサポートされていないんですよね...
でも、大丈夫です公式ドキュメントにSymfonyをデプロイするための手順が書かれています。
ここからはlaunchするための手順を紹介します。
artisanを用意する
公式ドキュメントに書かれていますがartisanを用意します。
そうすることでlaunchコマンドはLaravelプロジェクトであると認識してくれるみたいです...w
We’ll start by tricking flyctl into thinking we have a Laravel application. The fly launch command just searches for a file named artisan. If it finds that, it will assume we have a Laravel application.
touch artisan
fly launch
用意できたらlaunchコマンドを実行します。
$ fly launch
Scanning source code
Detected a Laravel app
Creating app in /Users/polidog/s/github.com/polidog/sf7-flyio-example
We're about to launch your Laravel app on Fly.io. Here's what you're getting:
Organization: polidog (fly launch defaults to the personal org)
Name: sf7-flyio-example (derived from your directory name)
Region: Hong Kong, Hong Kong (this is the fastest region for you)
App Machines: shared-cpu-1x, 1GB RAM (most apps need about 1GB of RAM)
Postgres: <none> (not requested)
Redis: <none> (not requested)
? Do you want to tweak these settings before proceeding? (y/N)
「Do you want to tweak these settings before proceeding?」と聞かれるでy
を入力するとブラウザが立ち上がり、リージョンやマシンスペックを変更できます。
今回はリージョンを東京にしてメモリを1GBから256MBに変更しています。
上記の設定が完了すると勝手にDocker imageのビルドが始まりますが、失敗に終わります。
これは.envが存在しないことを起因としたエラーです。
#0 15.03 [KO]
#0 15.03 Script cache:clear returned with error code 255
#0 15.03 !! PHP Fatal error: Uncaught Symfony\Component\Dotenv\Exception\PathException: Unable to read the "/var/www/html/.env" environment file. in /var/www/html/vendor/symfony/dotenv/Dotenv.php:553
#0 15.03 !! Stack trace:
#0 15.03 !! #0 /var/www/html/vendor/symfony/dotenv/Dotenv.php(106): Symfony\Component\Dotenv\Dotenv->doLoad()
#0 15.03 !! #1 /var/www/html/vendor/symfony/dotenv/Dotenv.php(149): Symfony\Component\Dotenv\Dotenv->loadEnv()
#0 15.03 !! #2 /var/www/html/vendor/symfony/runtime/SymfonyRuntime.php(107): Symfony\Component\Dotenv\Dotenv->bootEnv()
#0 15.03 !! #3 /var/www/html/vendor/autoload_runtime.php(17): Symfony\Component\Runtime\SymfonyRuntime->__construct()
#0 15.03 !! #4 /var/www/html/bin/console(11): require_once('...')
#0 15.03 !! #5 {main}
#0 15.03 !! thrown in /var/www/html/vendor/symfony/dotenv/Dotenv.php on line 553
#0 15.03 !!
#0 15.03 Script @auto-scripts was called via post-install-cmd
環境変数の問題について
Symfonyの場合デフォルトではcomposer install時に .env
あることを前提しています。
しかしflyioでは.envは含めない方針となっています。
そのため、dockerignoreに.envが記述されています。
# 1. Ignore Laravel-specific files we don't need
bootstrap/cache/*
storage/framework/cache/*
storage/framework/sessions/*
storage/framework/views/*
storage/logs/*
*.env*
.rr.yml
rr
vendor
# 2. Ignore common files/directories we don't need
fly.toml
.vscode
.idea
**/*node_modules
**.git
**.gitignore
**.gitattributes
**.sass-cache
**/*~
**/*.log
**/.DS_Store
**/Thumbs.db
public/hot
回避策として公式ではSymfonyRuntimeを書き換えて.envがなくてもcomposer installできるようにする方法が書かれています。
<?php
namespace App;
use Symfony\Component\Runtime\SymfonyRuntime;
class FlySymfonyRuntime extends SymfonyRuntime
{
public function __construct(array $options = [])
{
if (isset($_ENV['FLY_APP_NAME'])) {
// production環境でflyio上で動いている状態なので.envを読み込まない
$options['disable_dotenv'] = true;
} else {
if (false === file_exists(__DIR__.'/../.env')) {
// .envがないのでflyio上のDocker Build時になるので.envを読み込まない
$options['disable_dotenv'] = true;
}
}
parent::__construct($options);
}
}
上記のコードを.fly/FlySymfonyRuntime.phpとして用意してください。
そして、Dockerfileを修正します。
# copy application code, skipping files based on .dockerignore
COPY . /var/www/html
COPY ./.fly/FlySymfonyRuntime.php /var/www/html/src/FlySymfonyRuntime.php
最後にfly.tomlを修正してAPP_RUNTIME
の環境変数の指定をします。
[env]
APP_RUNTIME = '\App\FlySymfonyRuntime'
PHPのバージョンの問題について
2023/12/16現在だとflyctl lunchで生成されたfly.tomlで指定されているphpのバージョンが8.1になっています。
今回はphp8.3を使いたいので変更します。
[build]
[build.args]
NODE_VERSION = "18"
PHP_VERSION = "8.3"
修正が完了したので fly deploy
を実行します。
fly launch
はfly.tomlやDockerfileを用意してくれるコマンドなので実際にデプロイする場合はfly deploy
コマンドを使います。
$ fly deploy
しかし、公式ドキュメントの方法で設定したはずなのにデプロイに失敗してしまいました。
composer installの後でエラーメッセージが表示されていたのでcomposer関連で何かしらの問題があったようです。
composerのpost-install-cmdでエラーになる
エラーメッセージを確認するとcomposer install後に実行される bin/console cache:clear
実行時にエラーが起きていました。
#0 12.79
#0 12.79 Run composer recipes at any time to see the status of your Symfony recipes.
#0 12.79
#0 12.80 Executing script cache:clear [KO]
#0 12.86 [KO]
#0 12.86 Script cache:clear returned with error code 255
#0 12.86 !! PHP Fatal error: Uncaught Symfony\Component\Dotenv\Exception\PathException: Unable to read the "/var/www/html/.env" environment file. in /var/www/html/vendor/symfony/dotenv/Dotenv.php:553
#0 12.86 !! Stack trace:
#0 12.86 !! #0 /var/www/html/vendor/symfony/dotenv/Dotenv.php(106): Symfony\Component\Dotenv\Dotenv->doLoad()
#0 12.86 !! #1 /var/www/html/vendor/symfony/dotenv/Dotenv.php(149): Symfony\Component\Dotenv\Dotenv->loadEnv()
#0 12.86 !! #2 /var/www/html/vendor/symfony/runtime/SymfonyRuntime.php(107): Symfony\Component\Dotenv\Dotenv->bootEnv()
#0 12.86 !! #3 /var/www/html/vendor/autoload_runtime.php(16): Symfony\Component\Runtime\SymfonyRuntime->__construct()
#0 12.86 !! #4 /var/www/html/bin/console(11): require_once('...')
#0 12.86 !! #5 {main}
#0 12.86 !! thrown in /var/www/html/vendor/symfony/dotenv/Dotenv.php on line 553
#0 12.86 !!
#0 12.86 Script @auto-scripts was called via post-install-cmd
ここの原因は簡単で cache:clear
を実行するが.envがないからエラーになっていました。
FlySymfonyRuntimeに差し替えてるはずなのになぜエラーが起きるのか?
vendor/autoroad_runtime.php
の中のソースコードを確認してみると理解でます。
bin/console実行時にvendor/autoroad_runtime.php
がrequireされています。
// /bin/consle
#!/usr/bin/env php
<?php
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
}
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
return new Application($kernel);
};
vendoer/autoroad_runtime.php
の中でどのSymfonyRuntimeが実行されるの決定されるのですが、その部分が以下のコードになります。
$runtime = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\SymfonyRuntime';
$_SERVER['APP_RUNTIME']
に値がなくてデフォルトの Symfony\Compnent\Runtime\SymfonyRuntime
が呼び出されて .env
が存在しないのでエラーになります。
実行時はfly.tomlのenvの情報が使われると思いますが、dockerビルド時はenvがセットされないためエラーになるということだと思います。
composer.jsonを使ってSymfonyRuntimeを切り替える
The Runtime Component #Selecting Runtimes
SymfonyRuntimeのドキュメントを読んでいたらcomposer.jsonでRuntimeを切り替えるということが書かれていました。
"extra": {
"symfony": {
"allow-contrib": false,
"require": "6.4.*"
},
"runtime": {
"class": "App\\FlySymfonyRuntime"
}
},
このように指定するとfly deploy
と FlySymfonyRuntime
で実行されていました。
しかしこれではローカル環境にエラーが出てしまいます。が、とりあえず一旦この方法で進めます。
Dockerfileの修正
再び flyctl deploy
するとまたエラーになります。
#0 15.88 sed: can't read app/Http/Middleware/TrustProxies.php: No such file or directory
このエラーはDockerfileがLaravel向けになっているためですね。。。
以下のようにDockerfileを修正します。
# syntax = docker/dockerfile:experimental
# Default to PHP 8.2, but we attempt to match
# the PHP version from the user (wherever `flyctl launch` is run)
# Valid version values are PHP 7.4+
ARG PHP_VERSION=8.2
ARG NODE_VERSION=18
FROM fideloper/fly-laravel:${PHP_VERSION} as base
# PHP_VERSION needs to be repeated here
# See https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact
ARG PHP_VERSION
LABEL fly_launch_runtime="laravel"
# copy application code, skipping files based on .dockerignore
COPY . /var/www/html
COPY ./.fly/FlySymfonyRuntime.php /var/www/html/src/FlySymfonyRuntime.php
RUN composer install --optimize-autoloader --no-dev \
&& chown -R www-data:www-data /var/www/html \
&& cp .fly/entrypoint.sh /entrypoint \
&& chmod +x /entrypoint
EXPOSE 8080
ENTRYPOINT ["/entrypoint"]
そしてもう一度deployします。
$ flyctl deploy
...
-------
Updating existing machines in 'sf7-flyio-example' with rolling strategy
-------
✔ Machine xxxxxxxxxxxxx [app] update succeeded
-------
ちなみにクレジットカード登録していないとエラーになるので気をつけてください😇
✖ Failed: error creating a new machine: failed to launch VM: To create more than 1 machine per app please add a payment method.
環境変数の設定について
無事にデプロイできたのでサイトにアクセスしてみたら、デバッグモードの画面が表示されてました...。
とうことでfly.tomlのenvを修正します。
[env]
APP_RUNTIME = '\App\FlySymfonyRuntime'
APP_ENV = "prod"
これでデバッグモードは表示されなくなります。
DATABASE_URLなどのクレデンシャルな環境変数について
fly.tomlにDBの接続情報などは記載できないですよね。
そういうときは flyctl secrets set
コマンドで設定するか、もしくはGUIからも設定ができます。
flyctl secrets set DATABASE_URL="postgresql://app:hoge@127.0.0.1:5432/app?serverVersion=15&charset=utf8"
おまけ
そもそもSymfonyRuntimeってなんだ?
Symfonyのpublic/index.phpはなぜ"無名関数"をreturnしているだけなのか
起動ロジックをRunnerに隠蔽し、Runnerを使ってアプリケーション起動する仕組みを使うことで、Symfony側のロジックを一切変更せず、SwooleやRoadrunnerなどでの実行を用意に切り替えれるようにするため
これはなかなかおもしろい仕組みですね。
いつの間にかこういう仕掛けになってし、ユーザーランドには影響ないのがまたすごいなぁと思いました。
まとめ
- Symfony7は機能的には6.4からそんなに変わってない
- flyioにデプロイするときはいろいろと工夫がいる
- SymfonyRuntimeすげー
最後に
今日のコードはリポジトリに上げてあります。
polidog/sf7-flyio-example
Discussion