ラベル phalcon の投稿を表示しています。 すべての投稿を表示
ラベル phalcon の投稿を表示しています。 すべての投稿を表示

DockerImageにCLI実行モードを実装してECSでバッチ運用する

ECSで稼働するphp:fpm-alpineベースのPHP/Phalconで構築したアプリケーションイメージを流用して、ECSでバッチ処理をスケジュール実行するためのアーキ。

要件

  • 運用中のアプリケーションのイメージを流用できること。
  • スケジュール実行したあと、exit終了すること。
  • ログはCloudWatchLogに出力すること。

PhalconでCLI実行

エントリポイントとバッチ処理を実装する。下記のコードスニペットがそのまま使えました。 【PHP Phalcon】バッチ処理の実装とcronの設定

実行

# cliモードで起動
php /path/to/CLIエントリポイント.php \
&& className(→ ClassNameTaskクラス) \
&& action_name(→ actionNameActionメソッド) \
&& param1(→ メソッドの引数1) \
&& param2(→ メソッドの引数2)

Dockerイメージのフレキシブル運用

ENTRYPOINTCMDに応じて サーバー起動 or CLI実行 を切り替えるシェルスクリプトを用意して、docker run イメージ {コマンド上書き}あるいは、ecsのコマンド上書き設定で起動モードをスイッチ出来るようにする。



Dockerfile

...

# エントリポイントスクリプトをコピー
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# エントリポイントを設定
ENTRYPOINT ["/entrypoint.sh"]

# デフォルトでは FPM を起動
CMD ["fpm"]

entrypoint.sh

#!/bin/sh
set -e  # エラー時に停止

# 引数がない場合、または "fpm" の場合は PHP-FPM を起動
if [ "$1" = "fpm" ] || [ -z "$1" ]; then
    echo "Starting PHP-FPM..."
    exec php-fpm
fi

# "cli" の場合は、引数を `php` に渡して実行(CLIモード)
if [ "$1" = "cli" ]; then
    shift # `cli` を削除
    echo "Running PHP CLI..."
    exec php "$@"
fi

# それ以外のコマンドは直接実行
exec "$@"

実行

# サーバー起動モード
docker run -d イメージ
#=> php-fpm

# CLI実行でワンショット起動
docker run --rm イメージ cli /path/to/cli.php arg1 arg2
#=> php /path/to/cli.php arg1 arg2

# メンテコマンドでワンショット起動
docker run --rm イメージ ls -l
#=> ls -l

ECS

初見はいろいろと問題が出るので、新しいタスクの実行でデバッグしてからスケジュールされたタスクを構築することを推奨。



  • バッチ用のタスク定義を作成して、上記のイメージを指定する。
    • 起動タイプ(Fagate?EC2?)クラスタレイヤーの話なのにここで選ぶのかい?と混乱していたら、フォームを想定環境に最適化してくる親切設計であった。Amazon ECS 起動タイプ - Amazon Elastic Container Service
    • デバッグ、モニタリングためCloudWatchLogは必ずオン
  • クラスタ > スケジュールされたタスクでは主にタスク定義cron式および、コンテナの上書き > コマンドの上書きでCMDをCLI起動用に上書きする。
# だめ ひとつのコマンドとして認識される
# cli /path/to/cli.php className action_name
#=> /entrypoint.sh: exec: line X: cli /path/to/cli.php className action_name: not found

# OK カンマでCMD引数としてプロットしてくれる(説明どおり)
cli,/path/to/cli.php,className,action_name
#=> exec php /path/to/cli.php className action_name


つまずき

ResourceInitializationError

クラスター > タスクの状態より ResourceInitializationError: unable to pull secrets or registry auth: The task cannot pull registry auth from Amazon ECR: There is a connection issue between the task and Amazon ECR. Check your task network configuration. RequestError: send request failed caused by: Post "https://api.ecr.ap-northeast-1.amazonaws.com/": dial tcp 99.77.58.41:443: i/o timeout

イメージのpull → イメージのECRエンドポイントに接続できていない。 → タスクのサブネットがプライベートサブネットだったのでNAT/IGWの準備がなくECRに接続できない。パブリックサブネットに設置して通過。

Essential container in task exited

クラスター > タスクの状態より Essential container in task exited

プロセスが終了してexit状態なのでOK。CloudWatchLogで実行結果を確認する。

phalconをv3からv4にアップグレードする


php:7.2-fpm-alpine3.12コンテナイメージが、vscode-v1.77以降のDevContainersでvscode-serverの起動エラーでコンテナに接続出来なくなったので、alpine-v3.16以降の
コンテナイメージへのアップグレードの必要に迫られた。

VsCode v1.77以降のDevContainers拡張でalpine v3.12コンテナに接続できない


php公式イメージで要件を満たすもっとも世代の近いイメージphp:7.4-fpm-alpine3.16php-v7.4に対応するphalconのバージョン要件がv4以降のため、phalconをv3からv4にメジャーアップデートする際の対応録。


イメージビルド編


ベースイメージの変更




# FROM php:7.2-fpm-alpine
FROM php:7.4-fpm-alpine3.16

oniguruma



docker-php-ext-installツールでmbstringをインストールしようとすると依存ライブラリのonigurumaがないといってビルドに失敗する。


が、php:7.4-fpm-alpine3.16にはそもそもoniguruma非依存のmbstringがセットアップ済みのため、追加インストールなど不要だったのでシンプルにdocker-php-ext-installからmbstringを削除して解決。


FROM php:7.4-fpm-alpine3.16
RUN docker-php-ext-install pdo_mysql mysqli mbstring < これ

#5 15.29 configure: error: Package requirements (oniguruma) were not met:
#5 15.29 
#5 15.29 Package 'oniguruma', required by 'virtual:world', not found
#5 15.29 
#5 15.29 Consider adjusting the PKG_CONFIG_PATH environment variable if you
#5 15.29 installed software in a non-standard prefix.
#5 15.29 
#5 15.29 Alternatively, you may set the environment variables ONIG_CFLAGS
#5 15.29 and ONIG_LIBS to avoid the need to call pkg-config.
#5 15.29 See the pkg-config man page for more details.

備忘録として


こちらさまによるとphp-v7.4らへんのmbstringビルドではonigurumaという荒々しい名の正規表現ライブラリに依存するようになったらしく、libonig-devというパッケージに収納されているとのこと。apkではこちらさまのとおりoniguruma-devというパッケージ名で配布されていた。oniguruma-devのインストール追加でイメージビルドは成功した。


# RUN docker-php-ext-install pdo_mysql mysqli mbstring
# docker-php-ext-installからmbstringを削除してapkでoniguruma-devをインストールする
RUN docker-php-ext-install pdo_mysql mysqli mbstring
apk add --no-cache oniguruma-dev < これ

phalcon



phalconのソースをバージョンを4.0系のlatestバージョンv4.0.6に変更


RUN cd /usr/local \
# phalconのビルドソースを3系から4系に変更
# && git clone https://github.com/phalcon/cphalcon.git -b v3.4.3 \
&& git clone https://github.com/phalcon/cphalcon.git -b v4.0.6 \
&& cd cphalcon/build \
&& sh install \
&& echo "extension=phalcon.so" > /usr/local/etc/php/conf.d/phalcon.ini \

psr




コンテナを起動してhttpアクセスするとphpワーニングでUncaught Error: Class 'Phalcon\Config' not found
え?メジャーアップデートでnamespaceがっつり変わった?と焦るも、公式見てもPhalcon\Configは健在だ。


ビルド失敗している?と思ってphpのロードモジュールを確認すると、psrモジュールがローディングできてない?からphalconモジュールがローディングできていないようだ。


php -m | grep phalcon
PHP Warning:  Cannot load module 'phalcon' because required module 'psr' is not loaded in Unknown on line 0

phalcon-v4からpsrに対応するためライブラリ依存するようだ。
Fatal error: Uncaught Error: Class 'Phalcon\Config' not found
Php can not load module phalcon
Installation - Phalcon Documentation


Dockerfileにpecl-psrのインストールコマンドを追加して、phalconモジュールのローディングの前にローディングする
Phalconをdocker imageに詰め込みたいとき


# xdebugのインストール
RUN pecl install psr \
&& docker-php-ext-enable psr \
&& echo "extension=psr.so" >> /usr/local/etc/php/conf.d/phalcon.ini \
&& echo "extension=phalcon.so" >> /usr/local/etc/php/conf.d/phalcon.ini \

xdebugバージョン

xdebug3.2.0でphp7がサポート対象外になったのでDockerfileを直した



xdebug-v3.2.0以降、php-v7がサポート対象外になったため、xdebugはバージョン指定でアーカイブDLする


[ 5/17] RUN pecl install xdebug && docker-php-ext-enable xdebug:
> #9 5.238 pecl/xdebug requires PHP (version >= 8.0.0, version <= 8.2.99), installed version is 7.3.29
> #9 5.238 No valid packages found
> #9 5.238 install failed

peclではxdebug-{バージョン}でアーカイブがディストリビュートされている


RUN pecl install xdebug-3.1.6 && docker-php-ext-enable xdebug

アプリケーション編

How to upgrade - Phalcon Documentation


Class 'Phalcon\Session\Adapter\Files' not found




サーバーセッションのファイルアダプタクラスはv4でPhalcon\Session\Adapter\Streamに置き換えられた
Phalcon 4.0 setting up Session


# => Class 'Phalcon\Session\Adapter\Files' not found
$di->setShared('session',function(){
    $session = new \Phalcon\Session\Adapter\Files();
    $session->start();
    return $session;
});

// 保存先`savePath`を指定する
$di->setShared('session',function(){
    $session = new Phalcon\Session\Manager();
    $files = new Phalcon\Session\Adapter\Stream( [
        'savePath' => '/tmp',
    ]);
    $session->setAdapter($files)->start();
    return $session;
});

Fatal error: Uncaught Error: Call to undefined method Phalcon\Logger\Adapter\Stream::log()




v3ではPhalcon\Logger\Adapter\Streamが出力log()と出力先のストリームアダプタを担っていたが、v4から出力はPhalcon\Loggerに分離されてPhalcon\Logger\Adapter\Streamを利用する形に変更となった。ログのインターフェースはpsr-3準拠。
ということでアダプタがlog()など知らんと言っている。


Fatal error: Uncaught Error: Call to undefined method Phalcon\Logger\Adapter\Stream::log() - Discussion - Phalcon Framework
Phalcon logger - Phalcon Documentation
PSR-3: Logger Interface 読了 #PHP - Qiita


$formatter = new Phalcon\Logger\Formatter\Line("[%date%] %message%");
$formatter->setDateFormat('Y/m/d H:i:s');

$adapter = new Phalcon\Logger\Adapter\Stream("php://stdout");
$adapter->setFormatter($formatter);

$logger = new Phalcon\Logger(
	// ロガーに名前をつけられるがマニュアルに書いてないのでとりあえず任意名
	// public function __construct( string $name, array $adapters = [] );
    'hoge', 
    [
		// 任意名のラベル名。Loggerからラベルに対してアダプタを選択したりできる。
        'stdout' => $adapter,
    ]
);

$logger->info('this is log message');


$application->handle(); triggers a "Wrong number of parameters"




フレームワークのエントリポイントapplication#handle()ではリクエストURIをとるようになった
$application->handle(); triggers a "Wrong number of parameters" error after last upgrade - Discussion - Phalcon Framework


// Handle the request
$application = new Phalcon\Mvc\Application();
# $application->handle(); triggers a "Wrong number of parameters"
$response = $application->handle();
$response->send();

// Handle the request
$application = new Phalcon\Mvc\Application();
$request = new Phalcon\Http\Request();
$response = $application->handle($request->getURI());
$response->send();

Phalcon\Mvc\Router#handle()も同じく


// triggers a "Wrong number of parameters"
$router->handle();

$request = new Phalcon\Http\Request();
$router->handle($request->getURI());

Class 'Phalcon\Mvc\Url' not found




Phalcon\Mvc\Urlはv4でPhalcon\Urlになった。そしてv5ではまさかのPhalcon\Mvc\Urlになった


VoltEngine#setOptionsのキーが変わった




compiledプレフィックスがなくなった
How to disable volt cache - Discussion - Phalcon Framework


$volt = new Phalcon\Mvc\View\Engine\Volt()
$volt->setOptions(
    [
        'compiledPath'      => BASE_PATH . $config->application->cacheDir,
        'compiledExtension' => '.compiled',
        'compiledSeparator' => '_',
        'stat' => true,
        'compiledAlways' => true
    ]
);

$volt = new Phalcon\Mvc\View\Engine\Volt()
$volt->setOptions(
    [
        'path'      => BASE_PATH . $config->application->cacheDir,
        'extension' => '.compiled',
        'separator' => '_',
        'stat' => true,
        'always' => true
    ]
);

Too few arguments to function {closure}(), 2 passed …




DIに渡すクロージャに設定していた引数がエラーになったので、キャプチャで渡す。変更になったのか元々バグっていてpsrで顕在化したのかな。
Task Too few arguments to function {closure}(), 2 passed and exactly 4 expected · Issue #4576 · swoole/swoole-src


// $di->setShared('voltService', function ($view, $di) use ( $config ){
$di->setShared('voltService', function () use ($config){

		$di = Phalcon\DI::getDefault();
        $volt = new Volt($di->get('view'), $di);
        $volt->setOptions(
            [
                'path'      => BASE_PATH . $config->application->cacheDir,
                'extension' => '.compiled',
                'separator' => '_',
                'stat' => true,
                'always' => true
            ]
        );
        return $volt;
    }
);

Fatal error: Cannot override final method Phalcon\Mvc\Model::getSource()




Model::getSource()はオーバーライド不可となったのでイニシャライザにModel::setSource('テーブル名')で指定する
Fatal error: Cannot override final method Phalcon\Mvc\Model::getSource() - Discussion - Phalcon Framework


public function initialize()
{       
    $this->setSchema("phalcon_demo-app");       
    $this->setSource("users");        
}

Fatal error: Uncaught Error: Class 'Phalcon\Validation\Validator' not found




カスタムバリデーションの実装で問題が発生


  • Phalcon\Validation\ValidatorはアブストラクトクラスPhalcon\Validation\Validatorに置き換えられた
  • PHP7型宣言の厳格化でオーバーライドに戻り値の宣言が必要

// phalcon v4でのカスタムバリデーションクラス実装
class CustomEmailValidator extends \Phalcon\Validation\AbstractValidator implements \Phalcon\Validation\ValidatorInterface
{
    public function validate(Validation $validation, $attribute): bool { ... }
}

Fatal error: Declaration of { 関数宣言 } must be compatible with { 関数宣言 }: string




PHP7型宣言の厳格化でオーバーライドに戻り値の宣言が必要


Phalcon\Model::findFirst to return null instead of false if no record was found




Phalcon\Model::findFirstで結果が無い場合に戻り値falsenullになった
変更箇所が多岐にわたるので、モデルクラスでfindFirstをオーバーライドしてnullの場合はfalseを返却して後方互換性を維持する


Model::saveのホワイトリストが効かない




The save() method no longer accepts parameters to set data. You can use assign instead.


// フィールドnullになる
$model->save($assign_key_val, $whitelist_key);

// assignに変更
$model->assign($assign_key_val);
$model->save();

phalcon - voltで変数の参照


voltで定数やらグローバル変数を参照するのに、知識不足でphp記述で妥協していたがvoltでいけるじゃないか。

クラス定数は

<?= ClassName::constant_field_name ?>

contantメソッドで定数の値をとってこれるじゃないか。

{{ constant('ClassName::constant_field_name') }}

global変数は

<?php global $GLOBAL_VARIABLE_NAME; ?>
<?= $GLOBAL_VARIABLE_NAME ?>

GLOBALSディレクティブで解決できるじゃないか。

{{ GLOBALS['GLOBAL_VARIABLE_NAME'] }}