ログエクスポートのジョブは、正しく書いてある。
IAMも問題ない。
S3バケットも存在する。
ロググループも存在する。
なのに、本番運用で失敗する。
そういうことがあります。
今回起きたのは、CloudWatch Logsのログエクスポートジョブが、日次処理でエラーになるという事象です。
CloudWatch Logsのログを、定期的にS3へエクスポートする。
AWSを使ったシステムでは、よくある運用設計です。
ログをCloudWatch Logsに集約し、一定期間ごとにS3へ退避する。
監査、障害調査、保存期間、コスト、運用保守を考えると、自然な構成に見えます。
しかし、今回の本質は、ジョブの実装ミスではありませんでした。
問題は、AWSサービスの制約を、運用ジョブ設計とテスト設計に織り込めていなかったことです。
CloudWatch Logsのログエクスポートは、ジョブを作れば好きなだけ並列に流せるものではありません。
ここを知らないと、単体では正常に動いていたジョブが、本番運用で突然失敗します。
起きたこと
あるシステムで、日次ジョブとしてCloudWatch Logsのログエクスポートを実行していました。
処理内容はシンプルです。
CloudWatch Logsに出力されたログを、一定期間ごとにS3へエクスポートする。
S3側に保存しておけば、長期保管、調査、監査対応にも使える。
CloudWatch Logs側の保持期間も適切に管理できる。
よくある構成です。
ところが、日次ジョブで実行しているログエクスポートがエラーになりました。
確認すると、ロググループは存在している。
S3バケットも存在している。
IAMロールも大きく間違っていない。
以前は同じ処理が動いていた。
にもかかわらず、ジョブが失敗する。
この時点で、最初に疑うのは個別設定です。
権限が足りないのではないか。
出力先バケットのポリシーが変わったのではないか。
ロググループ名が間違っているのではないか。
対象期間の指定がおかしいのではないか。
Lambdaやシェルの実装に不備があるのではないか。
もちろん、それらの確認は必要です。
しかし、調べていくと、原因はもっと上位の設計にありました。
何が問題だったのか
問題は、CloudWatch Logsのログエクスポートには、AWS側の制約があるという点です。
CloudWatch Logsのログエクスポートでは、CreateExportTaskを使って、ロググループのログをS3へ出力します。
このExportTaskには制約があります。
1つのAWSアカウント、1つのリージョンにつき、アクティブなExportTaskは1つしか持てません。
ここでいうアクティブとは、実行中または保留中の状態です。
つまり、あるログエクスポートタスクがまだ実行中、または開始待ちの状態で残っている場合、同じアカウント・同じリージョン内で次のエクスポートタスクを作成しようとしても失敗する可能性があります。
これはAWS公式ドキュメントにも記載されているCloudWatch Logsのクォータです。
これが盲点でした。
1つのシステムだけを見ていると、この問題は見えにくいです。
1つのロググループを対象にしている。
手動で1回だけ実行する。
単体テストで1回だけエクスポートする。
この条件なら、正常に見えます。
しかし、同じAWSアカウントの同一リージョンの中に複数システムが存在し、それぞれが日次でCloudWatch Logsのエクスポートを実行していると、話が変わります。
Aシステムのログエクスポートが実行中。
そのタイミングで、Bシステムのログエクスポートが起動する。
さらに、Cシステムのログエクスポートも同じ時間帯に起動する。
それぞれのジョブは、単体で見れば正しい。
しかし、アカウント全体で見ると、同時に実行できない処理が重なっている。
これでは失敗します。
ジョブの中身が間違っていなくても、AWSサービスの制約に引っかかるからです。

個別には正しい。
しかし、共通のAWSアカウント/リージョン側で競合する。
今回の問題は、まさにここでした。
原因
「AWSの制約でした」で終わらせてはいけません。
それは調べれば分かる話です。
実務で問うべき原因は、もう一段深いところにあります。
それは、AWSサービスの制約を、設計時点でジョブ設計・スケジュール設計・テスト設計に落とせていなかったことです。
AWSの制約そのものは障害ではありません。
AWSが壊れていたわけではありません。
CloudWatch Logsが異常だったわけでもありません。
仕様どおりに動いていただけです。
問題は、その仕様を使う側が、運用条件に織り込めていなかったことです。
特に、複数システムを1つのAWSアカウントに載せる場合、各システムが独立してジョブを設計すると、こういう問題が起きます。
Aシステムの担当者は、Aシステムのログエクスポートだけを見る。
Bシステムの担当者は、Bシステムのログエクスポートだけを見る。
Cシステムの担当者は、Cシステムのログエクスポートだけを見る。
個別に見ると、それぞれの設計は間違っていない。
しかし、アカウント全体で見ると、同時実行できないジョブが同じ時間帯に並んでいる。
この状態では、本番運用で失敗します。
打った対策と運用の設計観点
対策を考えるときに、単純に「エラーになったらリトライすればよい」と考えるのは危険です。
もちろん、リトライは必要です。
しかし、何も考えずにリトライを入れると、別のシステムのExportTaskがまだ完了していない状態で、同じ失敗を繰り返すだけになります。
今回考えるべき対策は、少なくとも次の3つです。
1つ目は、ログエクスポートジョブの実行時間をずらすことです。
同じAWSアカウント・同一リージョン内で複数システムがCloudWatch Logsのエクスポートを行う場合、各ジョブの起動時刻を重ねないようにする必要があります。
たとえば、以下のように時間帯を分けます。
変更前:
01:00 Aシステム ログエクスポート
01:00 Bシステム ログエクスポート
01:00 Cシステム ログエクスポート
変更後:
01:00 Aシステム ログエクスポート
02:00 Bシステム ログエクスポート
03:00 Cシステム ログエクスポート
ただし、これは最低限の対策です。
2つ目は、ExportTaskの状態を確認してから、次のタスクを作成することです。
ログ量が多い日には、前のエクスポート処理が想定より長引くことがあります。
障害や遅延により、前日のタスクが残っていることもあります。
単純に時刻をずらしただけでは、処理時間の変動に対応できません。
そのため、既存のExportTaskの状態を確認し、実行中または保留中のタスクがある場合は、待機する、スキップする、後続へ回す、といった制御が必要になります。
処理の流れとしては、以下のような形です。
1. 既存のExportTaskを確認する
2. RUNNINGまたはPENDINGのタスクがあれば待機する
3. 一定時間待っても完了しなければスキップまたは異常終了する
4. 実行可能な状態であれば新しいExportTaskを作成する
5. 作成したタスクIDを記録する
6. 完了状態を確認する
7. 失敗時はリトライまたは運用通知する

ここで重要なのは、ジョブの成功条件を「APIを呼べたこと」にしないことです。
CreateExportTaskを呼び出せた。
タスクIDが返ってきた。
だから成功。
これだけでは不十分です。
運用上は、実際にエクスポートが完了し、S3に期待したログが出力されていることまで確認する必要があります。
3つ目は、失敗時の運用を決めておくことです。
エクスポートに失敗した場合、翌日にまとめて再実行するのか。
対象期間を分割して再実行するのか。
失敗したロググループだけを再実行するのか。
運用担当者にアラートを出すのか。
一定回数までは自動リトライするのか。
リトライしても失敗した場合、誰が判断するのか。
ここまで決めて、初めて運用ジョブの設計になります。
「毎日1時にジョブを起動する」だけでは、設計として足りません。
「その時間に実行してよい状態か」
「実行できなかった場合にどう扱うか」
「ログ欠損や二重出力をどう防ぐか」
ここまで考える必要があります。
失敗時の設計観点は、最低でも次のように整理します。
・リトライ回数
・リトライ間隔
・再実行対象期間
・二重出力の扱い
・ログ欠損時の対応
・通知先
・運用手順書への記載
ジョブは、処理を起動するだけではありません。
失敗したときに、運用担当者が判断できる状態にしておくところまで含めて、ジョブ設計です。
補足:そもそもExportTaskでよかったのか
ここまでの対策は、CloudWatch LogsのExportTaskを使い続ける前提の話です。
しかし、設計としては、そもそもS3へ日次エクスポートする方式でよかったのか、という確認も必要です。
障害調査や一時的なログ検索が目的であれば、CloudWatch Logs Insightsで足りるケースもあります。
CloudWatch Logsにログを保持し、必要なときに検索・分析できれば、必ずしもS3へエクスポートしなくてよい場合があります。
一方で、長期保管、監査対応、CloudWatch Logsの保持期間外の保存、S3を前提にしたログ管理が必要であれば、S3への保存は必要になります。
その場合も、日次のExportTaskだけが選択肢ではありません。
リアルタイム性が必要な場合や、ExportTaskの同時実行制約を避けたい場合は、CloudWatch LogsのサブスクリプションフィルターからAmazon Data Firehoseへ連携し、Firehose経由でS3へ配信する構成も検討対象になります。
つまり、対策は「ExportTaskの起動時間をずらす」だけではありません。
ログの用途、保存期間、検索頻度、監査要件、リアルタイム性、コスト、運用負荷を踏まえて、そもそものログ保存方式を選ぶ必要があります。
今回のようなトラブルは、単にジョブを直すだけでなく、ログ運用方式そのものを見直すきっかけにもなります。
テスト工程で見るべきだったこと
今回の話で重要なのは、設計だけではありません。
テスト工程でも、見方を間違えるとこの問題は見逃されます。
単体テストでは、CloudWatch LogsからS3へエクスポートできるかを確認します。
これは必要です。
ロググループを指定する。
対象期間を指定する。
S3バケットに出力される。
IAMロールで実行できる。
エラー時にログが出る。
ここまでは確認します。
しかし、単体テストで1回エクスポートが成功しても、本番運用で問題なく回ることは保証できません。
結合テストでは、ジョブスケジューラから起動できるか、LambdaやシェルからAWS APIを呼べるか、S3に出力されるか、異常時に通知されるかを確認します。
これも必要です。
しかし、それでもまだ足りません。
システムテストや運用テストでは、実際の運用に近い条件で確認する必要があります。
たとえば、次のような観点です。
・同じAWSアカウント内に複数システムが存在する
・同一リージョン内で複数のログエクスポートジョブが日次で動く
・ログ量が多い日にExportTaskの完了が遅れる
・前日のExportTaskが残っている状態で翌日のジョブが起動する
・他システムのExportTask実行中に自システムのジョブが起動する
・失敗した場合に再実行すると対象期間が重複する
・スキップした場合にログ欠損が起きる
・運用担当者が失敗に気づける
・通知を受けた担当者が、何を見て判断すればよいか分かる
ここまで見ないと、運用ジョブとしての品質は確認できません。
「1回エクスポートできた」ことと、
「毎日、運用として成立する」ことは別です。
これは、テスト工程で非常に重要な考え方です。
機能確認だけなら、1回エクスポートできれば合格に見えます。
しかし、運用確認として見るなら、複数システム、複数ジョブ、ログ量増加、遅延、再実行、通知、手順まで確認しなければいけません。
テスト工程は、単に「作ったものが動くか」を見る工程ではありません。
設計した運用が、実際の条件で破綻しないかを確認する工程です。
クラウドでも設計は省けない
今回の気づきは、CloudWatch LogsのExportTask制約そのものではありません。
それは調べれば分かります。
本当の気づきは、クラウドサービスを使う場合、設計対象は自分たちが作った処理だけではないということです。
AWSのマネージドサービスを使うと、OSやミドルウェアを自分たちで管理しなくてよい部分が増えます。
それは大きなメリットです。
しかし、その代わりに、サービスごとの仕様、クォータ、非同期処理、状態遷移、リトライ設計、料金体系を理解する必要があります。
CloudWatch Logsのエクスポートも同じです。
画面上は簡単に見えます。
APIも用意されています。
S3へ出力できるので、仕組みとしてはシンプルに見えます。
しかし、実運用では、同時実行数、処理時間、ログ量、再実行、監視、通知まで考える必要があります。
クラウドだから簡単になる部分はあります。
しかし、クラウドだから設計しなくてよい、という話ではありません。
むしろ、クラウドサービスの仕様を知らないまま設計すると、オンプレとは別の形で詰まります。
オンプレでは、サーバ、ディスク、ネットワーク、ジョブスケジューラ、ミドルウェアの制約を見ていました。
クラウドでは、そこにAWSサービスごとの制約が加わります。
サービスを使うだけなら簡単です。
しかし、業務システムとして運用するなら、サービスの制約まで含めて設計する必要があります。
再発防止として見るべきこと
同じ問題を繰り返さないためには、個別ジョブを修正するだけでは不十分です。
今回のような問題は、担当者がCloudWatch LogsのExportTask制約を知っていれば防げます。
しかし、知らない人が設計すれば、また同じことが起きます。
だからこそ、個人の知識や注意力に頼るのではなく、設計レビューやテスト計画の観点として残しておく必要があります。
以下は、設計レビューやテスト計画にそのまま転用できる形で整理したチェックリストです。
・使用するAWSサービスのクォータを確認したか
・アカウント単位、リージョン単位の制約を確認したか
・同一アカウント内の他システムと競合しないか
・同一リージョン内で競合するジョブがないか
・同時実行できない処理を並列に起動していないか
・ジョブの起動時刻だけでなく、完了時刻も考慮したか
・処理時間が長引いた場合の後続影響を確認したか
・前回処理が残っている場合の動作を決めたか
・リトライ時に二重処理やログ欠損が起きないか
・スキップ時にどのように検知するか決めたか
・失敗時に誰が、どの通知で、何を見て判断するか
・運用手順書に再実行方法を記載したか
・運用テストで複数システム同時稼働を確認したか
このような観点を、設計レビューやテスト計画に入れておく。
それが再発防止のために見るべきことです。
今回のような問題は、担当者個人の注意力だけに頼ると再発します。
CloudWatch LogsのExportTask制約を知っている人がいれば防げる。
知らない人が設計するとまた起きる。
それでは、組織として弱いです。
だから、レビュー観点にする。
テスト観点にする。
運用設計のチェックリストにする。
これが、実務では必要になります。
若手SEに伝えたいこと
若手SEのうちは、どうしても「処理が動くか」に意識が向きます。
コマンドが成功した。
APIが通った。
S3にファイルが出た。
ジョブが正常終了した。
もちろん、それは大事です。
しかし、実務ではそれだけでは足りません。
本番では、他のシステムも動いています。
他のジョブも動いています。
ログ量も日によって変わります。
AWSサービス側の制約もあります。
障害時には再実行もあります。
だから、設計では「単体で動くか」だけでなく、「運用全体の中で成立するか」を見なければいけません。
CloudWatch Logsのログエクスポート失敗は、小さなトラブルに見えるかもしれません。
しかし、ここには大事な教訓があります。
ジョブは、処理単体ではなく、運用全体で設計する。
そして、
テストは、機能確認ではなく、運用で破綻しないことを確認する工程である。
この視点がないと、クラウドでもオンプレでも、同じように運用で詰まります。
AWSを使うこと自体は難しくありません。
しかし、AWSを使って業務システムを安定運用するには、サービス仕様、制約、ジョブ設計、監視、再実行、テスト工程までつなげて考える必要があります。
「1回動いたから大丈夫」
この考え方が、本番運用では一番危ないです。
単体で動くことと、運用で回ることは違います。
この違いを理解しているかどうかが、実務のSEとしてかなり大きな差になります。
今回の話は、単なるCloudWatch Logsの制約の話ではなく、
「ジョブ単体では動く」ことと、
「本番運用で毎日回る」ことは別物である、という話でもあります。
このあたりは、以下の記事でも詳しく書いています。
【前編】詳細設計で決めること|基本設計を「構築・テスト・運用できる形」に落とし込む工程
シリーズ全体は「実務で使えるシステム開発方法論」マガジンにまとめています。


コメント