Yappli Tech Blog

株式会社ヤプリの開発メンバーによるブログです。最新の技術情報からチーム・働き方に関するテーマまで、日々の熱い想いを持って発信していきます。

バッチ処理をECSタスクスケジュール化するときに注意したいポイント

はじめに

こんにちは、サーバーサイドエンジニアの佐野きよです。

最近、EC2 + Cron + Laravelで動いていたバッチサーバーをECSのタスクスケジュールへ移行しました。 その際に何点か注意したいポイントがあったので、同じ轍を踏まないようにこちらで共有します。 同様のことをしようとしている方の参考になりますと幸いです。

前提

  • クラウド: AWS
  • フレームワーク: Laravel
  • インフラ: EC2 (ECSタスクスケジュール化前)

この記事で触れないこと

  • ECSタスクスケジュール化の手順や方法
  • ECSのタスクスケジュールの仕組みや概要

なぜECSタスクスケジュールへ移行したのか

EC2とCronによるバッチ処理はシンプルで実績のある構成ですが、運用を続ける中でいくつかの課題が顕在化してきました。

  • スケーラビリティの問題
    • バッチ処理の数や種類が増えるにつれて、特定の時間に処理が集中し、サーバーのリソース(CPUやメモリ)が逼迫することがありました。これに対応するにはインスタンスタイプを上げる(スケールアップ)しかなく、柔軟なリソース調整が困難でした。
  • コスト効率
    • バッチ処理は特定の時間にしか実行されないにも関わらず、EC2インスタンスは24時間365日起動し続けており、アイドリング時間にもコストがかかっていました。

これらの課題を解決するため、私たちはAWS Fargateを利用したECSのタスクスケジュールへの移行を決定しました。

ECSへ移行することで、サーバー管理が不要になり、タスク実行時のみタスクが起動するようになるためコスト効率が向上します。また、処理ごとにコンテナが独立して実行されるため、スケーラビリティと耐障害性 が高くなります。

この記事は、こうした背景のもとで移行を進める中で得られた注意点をまとめたものになります。

同じバッチ処理が並列実行される可能性がある

時間がかかる処理や、毎分実行されるような処理では、前のタスクが終わる前に次のタスクが起動してしまい、コンテナが多重起動する状態になることがあります。

特に、既にスケジューラで重複実行されないような仕組みがある場合、並列実行は想定されておらず可能性が高いため注意が必要です。 例えばLaravelの場合、Kernel.phponOneServer() + withoutOverlapping() が記述されている場合は、並列実行が想定されていない可能性が高いため注意が必要です。

readouble.com

qiita.com

実行時間が正確になったことによるデッドロックの発生

これまでEC2で動かしていたとき、サーバー負荷などの影響で、例えば「8:00にスケジューリングしていた処理が実際には8:30に開始される」といったズレが発生していました。

タスクスケジュール化によって実行時刻が今までより正確になったことで、今まで時刻がズレていたことでたまたま同時に実行されていなかったバッチ処理と同時に実行されるようになりました。その結果、それらのバッチ間でデータベースのデッドロックが発生するという不具合が顕在化しました。(とは言えECSタスクスケジュールも厳密にスケジュール通り実行してくれるわけではないので、厳密な定期実行が求められる場合は要注意)

対策としては、事前にEC2環境での実行ログなどから実際の実行開始時刻を調査し、移行後もその時間に合わせてスケジュールを組むか、根本的な原因であるデッドロックが発生しないように処理を見直す必要があります。

ローカルストレージの処理

これはタスクスケジュール化というより、コンテナ化する際の注意点でもあります。

EC2サーバー上の特定のディレクトリにファイルや画像を出力するような処理があった場合、コンテナ環境ではタスクが終了するたびにそのデータが失われてしまいます。

そのため、永続化が必要なデータは、EFS (Elastic File System) のような共有ストレージを利用するか、すべてS3などに出力するように処理を変更する必要があります。

docs.aws.amazon.com

S3の権限エラー

こちらも、ECSタスクスケジュール化特有というより、コンテナ化する際の注意点となります。

EC2インスタンスで稼働させていたバッチ処理をコンテナ化してECSタスクとして実行する場合、EC2インスタンスに付与されていたIAMロールの権限を、ECSタスクが使用する「タスクロール」にも同様に付与し直す必要があります。

具体的なエラーの例

例えば、「S3バケットに置かれたCSVファイルを読み込み、処理結果を別のS3バケットにアップロードする」というバッチ処理があったとします。

EC2で稼働していた時

EC2インスタンスには、「S3バケットへの読み書きを許可する」ポリシーがアタッチされたIAMロール(例: EC2-S3-Access-Role)が設定されていました。そのため、バッチ処理は問題なくS3にアクセスできている状態にあります。

ECSへの移行で発生する問題

このバッチ処理をコンテナ化し、ECSのタスク定義を作成する際に、このIAMロールの設定を忘れてしまうケースがあります。 その状態でタスクスケジュールを実行すると、コンテナ内のアプリケーションはS3にアクセスしようとしますが、何の権限も持っていないため、 「Access Denied」 といったエラーが返され、処理が失敗してしまいます。

上記のようなことを防ぐためにも、EC2で稼動していたときと同様の権限をタスクロールにも付与してあげる必要があります。

ログが消失

こちらもコンテナ化する際の注意点になります。

EC2環境では、特定のファイルにログを書き出し、それをエージェント(CloudWatch Logs Agentなど)で収集する構成がよくあります。しかし、アプリケーションコード内でログの出力先をファイルに固定していると、ECS環境ではログが消失する原因になります。

コード例

例えば、以下のようなCSVを処理するバッチコマンドがあったとします。 handleメソッドの最初にLog::setDefaultDriver('batch');を記述することで、このコマンドのログだけを特定のチャンネル(ファイル)に出力するようにしています。(EC2サーバー上に出力される)

app/Console/Commands/ProcessCsvCommand.php

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;

class ProcessCsvCommand extends Command
{
    protected $signature = 'process:csv';
    protected $description = 'Process a CSV file';

    public function handle()
    {
        // ★問題のコード:ログドライバーを強制的に 'batch' に設定
        Log::setDefaultDriver('batch');

        Log::info('CSV処理を開始します。');

        // (何らかの処理...)
        for ($i = 1; $i <= 3; $i++) {
            Log::info("{$i}行目を処理中...");
            sleep(1);
        }

        Log::info('CSV処理が完了しました。');

        return 0;
    }
}

config/logging.php の設定

<?php

// ...
'channels' => [
    // ...
    'batch' => [
        'driver' => 'single',
        'path' => storage_path('logs/batch.log'), // ログファイル
        'level' => 'debug',
    ],
],
// ...

上記のようなLog::setDefaultDriver('batch');といったようなログドライバーの記述によってローカルストレージ上のファイルにログが出力されていると、コンテナ化した際にログが消失してしまうため、記述を削除するなどしてちゃんと標準出力されるようにしておく必要があります。

実行失敗時のことをある程度想定しておく

当たり前といえばそうですが、万が一、移行後に何らかの不具合が発生したケースを想定しておくことも重要です。

  • 移行対象のバッチ処理は再実行可能な冪等な作りになっているか?
  • バッチ処理が失敗したときのサービスへの影響範囲はどうか?
  • ECSタスクスケジュールから元のEC2でスケジュールングしていた状態に戻すための手順はどうなるか?(復旧のための切り戻しの手順はどうなるか?)
  • インシデントが起きたときのリスクはどれぐらいあるか?その際のコミュケーション方法は決まっているか

これらの点を事前に整理しておくことで、問題発生時にもスムーズに対応できる確率をあげることができます。

まとめ

今回は、EC2とCronで運用していたバッチ処理をECSのタスクスケジュールへ移行する際の注意点について解説しました。ECSへ移行することで、実行環境のコード化やスケーラビリティの向上など、多くのメリットが得られます。
しかし、意外と落とし穴があったり、インシデントに備えたリスクの把握が大変だったりするので、移行は余裕を持って慎重に行うことをおすすめします。

最後まで読んでいただきありがとうございました! 同じことをこれからしようとしている方の参考に少しでもなりますと幸いです。

さいごに

ヤプリではサーバーサイドエンジニアを随時募集しています! 興味を持った方、是非一度カジュアル面談を受けてみませんか…??

open.talentio.com