ECSのデプロイ時に一定確率で静的ファイルが404になる問題を回避する
ECSのデプロイ時に一定確率で静的ファイルが404になる問題を回避する:
結論:「s3-safety-deploy - npm」を使ってS3に静的ファイルをアップロードする。
ECSのデプロイ時における問題点が一つあります。それは、ページに必要な静的ファイル(cssやjs)が一時的に404エラーになる可能性がある、ということです。
ECSでは新しいタスクをデプロイする際、イミュータブルデプロイメント(Immutable、不変な)によってデプロイが行われます。このイミュータブルデプロイメントでは古いバージョンのタスクと新しいバージョンのタスクが同時に起動している状態が数分間続き、これは以下のような問題を引き起こします。
ロードバランサーレベルであれば、スティッキーセッションを利用してユーザーは一貫性のあるオリジンの参照ができます。しかしECSのサービスレベルでそのようなものは現状提供されていません。またこの問題は、ECSだけではなく、バチっとバージョンを切り替えるデプロイ手法(ブルーグリーンデプロイメントなど)でも発生するエラーです。
さらにCloudFrontを利用している場合、オリジンのエラーをデフォルトで5分間キャッシュするため、さらに404エラー時間が伸びてしまう可能性があります。またエラーのキャッシュを無効にしたとしても404エラーは数分間ものあいだ一定確率で発生し、根本的な解決になりません。またCDNのメリットであるDos攻撃の対策も無効になってしまいます。
この問題の対処法をAWSの技術サポートに問い合わせてみたところ、「そちらの解決方法としては、静的コンテンツをS3にアップロードしてCloudFrontで配信する方法はいかがでしょうか」、という返事をいただきました。要するに「そちら側で個別に対応して欲しい」ということです。
ただおそらくですが、このようなデプロイの問題は本来アプリケーション側で個別に対応するべき問題のようにも思えます。アプリケーションやインフラの構成によって対応するべき範囲は違いますし、どこまで許容するべきかといった所は運用方法によって変わってきます。そのため個別に各々が対応するしかないのでしょう。
ということで静的ファイルをS3にアップロードし、ユーザーが静的ファイルを参照する際はCloudFrontからS3を見にいくようにします。このS3へのデプロイ時のポイントとしては、S3に古いバージョンと新しいバージョンの静的ファイルを同時に存在させておく、ということです。
このデプロイを行うため、Node.jsでCLIツールを作ってnpmパッケージとして公開しました。「s3-safety-deploy - npm」
デプロイフローとしてはこのようになっています。
ちなみにライフサイクルの削除のタイミングは2日後以降に設定する必要があります。S3のライフサイクルによる削除のタイミングはUTC-00:00(日本時間で午前9時)と決まっているため、例えば削除が1日後だった場合、日本時間8:58にタグを付けたものは2分後に削除されてしまいます。そのため余裕を持って2日後以降にすることが望ましいです。
私はNuxt.jsで静的サイトを構築してブログを公開していますが、S3へアップロードする際に一時的にダウンタイムが生じてしまう問題がありました。そのための対応として、先ほどのnpmパッケージ(s3-safety-deploy - npm)を利用しています。
詳しくは「Nuxt.jsとContentfulでモダンなブログを構築してみた - Qiita」という記事の「安全なデプロイをどのようにするか」という部分で解説しています。
とりあえず現状だとnodeが使える環境なのでnodeで開発しましたが、実行環境ごとにビルドできるgoで開発するべきなんだろうなと思ってます。最近goを勉強し始めたので、またgoでも開発してみたいですね。
何か使いづらい箇所があればプルリク待っています。
s3-safety-deploy - npm
結論:「s3-safety-deploy - npm」を使ってS3に静的ファイルをアップロードする。
404エラーの概要
ECSのデプロイ時における問題点が一つあります。それは、ページに必要な静的ファイル(cssやjs)が一時的に404エラーになる可能性がある、ということです。ECSでは新しいタスクをデプロイする際、イミュータブルデプロイメント(Immutable、不変な)によってデプロイが行われます。このイミュータブルデプロイメントでは古いバージョンのタスクと新しいバージョンのタスクが同時に起動している状態が数分間続き、これは以下のような問題を引き起こします。
- ユーザーが
/
にアクセスする - ECSのサービスが新しいタスク(以下タスクA)にリクエストを流し、タスクAが
/
のHTMLを返す - ユーザーが返ってきたタスクAのHTMLで必要なjsファイル(
/task-a.js
)をリクエストする - ECSのサービスが古いタスク(以下タスクB)にリクエストを流すが、タスクBには
/task-a.js
が存在しないため404エラーを返す
ロードバランサーレベルであれば、スティッキーセッションを利用してユーザーは一貫性のあるオリジンの参照ができます。しかしECSのサービスレベルでそのようなものは現状提供されていません。またこの問題は、ECSだけではなく、バチっとバージョンを切り替えるデプロイ手法(ブルーグリーンデプロイメントなど)でも発生するエラーです。
さらにCloudFrontを利用している場合、オリジンのエラーをデフォルトで5分間キャッシュするため、さらに404エラー時間が伸びてしまう可能性があります。またエラーのキャッシュを無効にしたとしても404エラーは数分間ものあいだ一定確率で発生し、根本的な解決になりません。またCDNのメリットであるDos攻撃の対策も無効になってしまいます。
AWSに問い合わせた結果、「S3とCloudFrontを使ってね」
この問題の対処法をAWSの技術サポートに問い合わせてみたところ、「そちらの解決方法としては、静的コンテンツをS3にアップロードしてCloudFrontで配信する方法はいかがでしょうか」、という返事をいただきました。要するに「そちら側で個別に対応して欲しい」ということです。ただおそらくですが、このようなデプロイの問題は本来アプリケーション側で個別に対応するべき問題のようにも思えます。アプリケーションやインフラの構成によって対応するべき範囲は違いますし、どこまで許容するべきかといった所は運用方法によって変わってきます。そのため個別に各々が対応するしかないのでしょう。
S3へ安全なデプロイを行うために
ということで静的ファイルをS3にアップロードし、ユーザーが静的ファイルを参照する際はCloudFrontからS3を見にいくようにします。このS3へのデプロイ時のポイントとしては、S3に古いバージョンと新しいバージョンの静的ファイルを同時に存在させておく、ということです。このデプロイを行うため、Node.jsでCLIツールを作ってnpmパッケージとして公開しました。「s3-safety-deploy - npm」
デプロイフローとしてはこのようになっています。
- 最新バージョンのファイルをS3にアップロード
- 1でアップロードした以外のファイルにShouldDeleteタグを付ける
- S3のライフサイクルによって、ShouldDeleteタグの付いたものは数日後に削除する
ちなみにライフサイクルの削除のタイミングは2日後以降に設定する必要があります。S3のライフサイクルによる削除のタイミングはUTC-00:00(日本時間で午前9時)と決まっているため、例えば削除が1日後だった場合、日本時間8:58にタグを付けたものは2分後に削除されてしまいます。そのため余裕を持って2日後以降にすることが望ましいです。
S3の静的サイトのデプロイにも使えます
私はNuxt.jsで静的サイトを構築してブログを公開していますが、S3へアップロードする際に一時的にダウンタイムが生じてしまう問題がありました。そのための対応として、先ほどのnpmパッケージ(s3-safety-deploy - npm)を利用しています。詳しくは「Nuxt.jsとContentfulでモダンなブログを構築してみた - Qiita」という記事の「安全なデプロイをどのようにするか」という部分で解説しています。
おわり
とりあえず現状だとnodeが使える環境なのでnodeで開発しましたが、実行環境ごとにビルドできるgoで開発するべきなんだろうなと思ってます。最近goを勉強し始めたので、またgoでも開発してみたいですね。何か使いづらい箇所があればプルリク待っています。
s3-safety-deploy - npm
コメント
コメントを投稿