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

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

## 要件

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

## PhalconでCLI実行

CLIエントリポイントとバッチ処理を実装する。
下記のコードスニペットがそのまま使えました。ありとうございます。

https://techtech-note.com/707/#google_vignette

**CLIモードのスタンドアロン実行**

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

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

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

こんな感じ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417736/a7e6339e-6db7-4c4d-abbe-e32cd5d88b71.png)

**Dockerfile**

```dockerfile:Dockerfile
...

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

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

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


**entrypoint.sh**

```shell: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 "$@"
```


**実行**

```shell:shell
# サーバー起動モード
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](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/launch_types.html)
  - デバッグ、モニタリングためCloudWatchLogは必ずオンしておく。
- `クラスタ > スケジュールされたタスク`では主に`タスク定義`、`cron`式および、`コンテナの上書き > コマンドの上書き`でCMDをCLI起動用に上書きする。
  - スケジュール機能はEventBridgeに統合されていて、EventBridge流のcron式で記述する(例:`cron(0 0 * * ? *)`)。タイムゾーンはUTC。
  [cron 式と rate 式を使用して Amazon EventBridge でルールをスケジュールする - Amazon EventBridge](https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-scheduled-rule-pattern.html)
  - コマンドの上書きはカンマ区切りで指定する
    コマンドラインなのりでスペース区切りで指定すると、ひとつの文字列として認識され、そのようなコマンドはない的なエラーになる。
```shell:コマンドの上書き
# だめ　ひとつのコマンドとして認識される
# 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
```

こんな感じ
![image 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417736/d640fc7f-3e4f-4d6b-9853-41d7c8391db9.png)

### つまずき

#### 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で実行結果を確認する。