AWS ECS on Fargate構築の課題と解決策、採用してよかったこと

isdはじめに

先日、AWSで ECS on Fargate環境を構築する機会がありました。
ECSは、数年前に勉強がてら、簡単なサンプルアプリケーションを起動する程度のことをやってみただけで、お客様の本番環境として構築するのは初めてでした。

稼働するサービスは、医療・介護・福祉系SNSで、Microsoft Azure(Virtual Machines+SQL Database)からの移行となります。
大まかなAWS構成はこんな感じです。
(サブネットは省略)


 

コンテナイメージはアプリケーション開発担当の方が作成するということで、こちらでは、コンテナリポジトリとコンテナ実行環境を構築するのみとなり、スムーズに完了すると思っていました。
ですが、機能名にとまどったり、構築作業中にいくつか課題やリクエストが出てきたりで、なかなか大変でした。

このブログ記事では、2023年3月から5月にかけてのECS on Fargateの構築で出てきた課題と解決策、そして、アプリケーション開発担当の方にうかがった ECS on Fargateを採用してよかった点についてまとめます。

ECS on Fargateや、FireLens、ECS Exec等の細かい設定手順については、ネット上にわかりやすい記事がたくさんありますので、そちらを参照してください。

isdECS on Fargate構築における課題と解決策

1. ECSの構成要素を理解する
2. 環境変数の渡し方
3. タスクロールとタスク実行ロール
4. 稼働中のコンテナでデバッグしたい
5. FireLensを使用したログ出力
6. 外部アクセス時のアクセス元IPアドレスを固定したい

1. ECSの構成要素を理解する

いざ、AWSマネジメントコンソールで構築を始めようとしたとき、クラスター、サービス、タスク、タスク定義、コンテナといった構成要素の意味が分からず、とまどいました。
クラスター、サービス、タスクといったあたりは、他の文脈や機能でも使われる汎用的な用語ですし。

AWSのドキュメントのほか、以下の記事が参考になりました。

・Amazon EC2 Container Service(ECS)の概念整理 – Qiita
https://qiita.com/NewGyu/items/9597ed2eda763bd504d7

・Amazon EC2 Container Service(ECS)のデータモデルについて整理した – DevelopersIO
https://dev.classmethod.jp/articles/amazon-ecs-datamodel/

最初のQiita記事の「Serviceが中心」と考えると、理解しやすいと思います。

どの構成要素が、どの属性をもっているかについては、AWSCLIのドキュメントを読むと、理解が早いかもしれません。

・AWS CLI Command Reference
https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecs/index.html

2. 環境変数の渡し方

コンテナアプリケーションに環境変数を渡すには、以下の方法があります。

  1. タスク定義で指定
  2. Secrets Manager または Systems Managerパラメータストアを使用
  3. S3バケットに保存した環境変数ファイルを参照

今回は、アプリケーション開発担当の方のご要望で C. を採用しました。
S3バケットを用意し、タスク実行ロールに追加設定を行いました。

3. タスクロールとタスク実行ロール

タスク定義を設定する際に、AWSリソースへのアクセス制御のため、IAMロールを指定するのですが、「タスクロール」と「タスク実行ロール」の2つがあって、とまどいました。
AWSマネジメントコンソールのヘルプを読んでもわかりにくかったのですが、以下のように理解しました。

  • タスクロール

コンテナ化されたアプリケーションが、AWSリソースにアクセスするために必要な権限を管理する。

  • タスク実行ロール

ECSコンテナエージェントが、コンテナを起動・実行するときに、AWSリソースにアクセスするために必要な権限を管理する。

ややこしい。。

今回の環境では、それぞれのロールに、以下の権限(ポリシー)をアタッチしました。

  • タスクロールにアタッチしたポリシー
    • コンテンツ配信用S3バケットの読み書き
    • ジョブ管理用SQSキューの読み書き
    • ECS Exec実行に必要な権限(ログ出力含む)
    • FireLens Fluent BitコンテナからCloudWach Logsへのログ出力権限
  • タスク実行ロールにアタッチしたポリシー
    • S3バケットの環境変数ファイル取得
    • AmazonECSTaskExecutionRolePolicy(AWSがデフォルトで用意した、タスク実行に必要な最小限のポリシー。ECRからのコンテナイメージダウンロードやCloudWatch Logsへのログ出力など)

CloudWatch Logsへのログ出力については、デフォルトでは、コンテナアプリケーションではなくECS(のコンテナエージェント)がやってくれるので、「タスク実行ロール」への権限付与でよいのですが、FireLensを使用する場合は、「タスクロール」への権限付与が必要となります。
Fluent Bitコンテナが、(ECSコンテナエージェントを介さずに直接)CloudWatch Logsにログを書き出す、ということですね。
また、ECS Execのログ出力についても、「タスクロール」への権限付与が必要という情報がありました。

(参考)
・ECSのタスクロールとタスク実行ロールの違い – karakaram-blog
https://www.karakaram.com/difference-between-ecs-task-role-and-task-execution-role/
 

・ECS Exec のログ記録はタスクロールで行われるため注意しよう – DevelopersIO
https://dev.classmethod.jp/articles/ecs-exec-use-task-role-for-logging/

4. 稼働中のコンテナでデバッグしたい

これもアプリケーション開発担当の方からのリクエストでしたが、ECS Execを有効化することで解決しました。
ECS Execは、AWSCLIを使用して、実行中のコンテナに対してコマンドを実行できる機能で、Dockerのdocker execコマンドと同じようなものですね。

この機能のことは知らなくて、Fargateではホストサーバーにログインできませんから、デバッグやトラブルシューティングの際はログを確認するしかないと思っていたのですが、便利な機能ですね。

タスクロールに必要なIAMポリシーを追加し、AWSCLIで ECS Exec機能を有効化しました。

※AWSマネジメントコンソールは、ECS Execを設定できないとか、新バージョンのコンソールではFireLensを設定できないとか、なぜか設定できない項目があるのが不思議です。

(参考)
・[アップデート] 実行中のコンテナに乗り込んでコマンドを実行できる「ECS Exec」が公開されました – DevelopersIO
https://dev.classmethod.jp/articles/ecs-exec/

5. FireLensを使用したログ出力

当初、ECSのアプリケーションログはすべて標準出力に出力し、CloudWatch Logsの1つのロググループに出力するよう設定していました。
しかし、アプリケーション開発担当の方から、以下の理由で、調査に必要なログを見つけにくいという意見がありました。

  • 一度のログが複数レコードに分かれてしまう。
  • ヘルスチェックなど、重要ではないものも含め、大量のレコードがある。

このため、以下の記事を参考に、アプリケーション側で一度のログをJSON形式の1レコードにまとめ、FireLens Fluent Bitを利用して、重要度やアプリケーション処理によって、CloudWatch Logsの複数のロググループに振り分けるようにしました。

(参考)
・Rails on ECSな構成でFluent Bitを利用して快適なロギングを実現する – Zenn
https://zenn.dev/machamp/articles/50ed6bc383539c

ヘルスチェックやデバッグログの保存期間は1日、アプリケーションログの保存期間は2年など、ログごとに保存期間を変更することもできました。

FireLens Fluent Bitを利用したログ振り分けで苦労したことは、以下の別記事にまとめています。

・AWS FireLens Fluent BitでAND条件のログ振り分け – 稲葉サーバーデザイン
https://inaba-serverdesign.jp/blog/20230524/firelens-fluent-bit-and-condition.html

6. 外部アクセス時のアクセス元IPアドレスを固定したい

今回、外部サービスとの連携のため、
「アプリケーションからインターネットアクセスする際の、アクセス元IPアドレスを固定する」
という要件が出てきました。
外部サービス側に、アクセス元IPアドレスを登録する必要があるとのこと。

Fargate環境はIPアドレスを固定できないので、アクセス元IPアドレスを固定するには、以下の参考記事のとおり、NATゲートウェイを使用しました。

(参考)
・Fargateから固定IPを使って外部サーバーへ接続する方法を調べてみた – DevelopersIO

  • Publicサブネットで、NATゲートウェイを起動し、ElasticIPを付与する。
  • Fargateが稼働するサブネットのルートテーブルで、デフォルトゲートウェイをNATゲートウェイに設定する。

なお、このような構成にすると、ECSと、VPC外のAWSサービスとの通信もNATゲートウェイ経由となります。

今回は、以下のようなAWSサービスとの通信が該当しました。

  • S3への静的コンテンツファイルアップロード、ダウンロード
  • CloudWatch Logsへのログ出力
  • ECRからのコンテナイメージダウンロード
  • SQSキューへのアクセス

NATゲートウェイは、起動時間の課金のほか、インバウンド、アウトバウンド双方向のデータ処理量(≒転送量)の課金があるため、上記の通信がすべて課金対象となってしまいます。

AWSサービスへの通信を、NATゲートウェイ経由ではなくVPCエンドポイント経由とすれば、NATゲートウェイのデータ処理量課金がなくなりますが、今度はVPCエンドポイントの起動時間課金・データ処理量課金が問題となります。
ECRのように、ひとつのサービスへの通信でも、複数のエンドポイントが必要となるのも痛いところです。

AWSサービスへの転送量がどれぐらいになったら、VPCエンドポイントを使用すべきなのかは、以下の記事が参考になります。

・ECS/Fargate VPCエンドポイントとNAT ゲートウェイの損益分岐点をざっくり計算してみた – スクショはつらいよ

今回のケースでは、コンテナのデプロイ回数は比較的少なく、ログもそれほど多くなく、、転送量は数百GBには達しないだろうと予想しました。
このため、S3との通信についてのみ、無料で使用できるゲートウェイ型VPCエンドポイントを使用し、CloudWatch LogsおよびECRについては、VPCエンドポイントを使用せず、NATゲートウェイを使用することになりました。

  • S3への静的コンテンツファイルアップロード、ダウンロード → VPCエンドポイント経由(ゲートウェイ型)
  • CloudWatch Logsへのログ出力 → NATゲートウェイ経由
  • ECRからのコンテナイメージダウンロード → NATゲートウェイ経由
  • SQSキューへのアクセス。 → NATゲートウェイ経由

今後の運用で、NATゲートウェイのデータ処理量に注意し、多くなってくるようでしたら、VPCエンドポイントの導入を検討します。
CloudWatchダッシュボードでも、、NATゲートウェイのデータ処理量合計を表示するようにしました。

以下の記事を参考に、NATゲートウェイのメトリクス BytesInFromSource, BytesInFromDestination の、期間内の合計値を表示しています。

・[新機能] NAT GatewayがCloudWatchでモニタリング可能になりました – DevelopersIO
https://dev.classmethod.jp/articles/cloudwatch-for-nat-gateway/

・CloudWatch ダッシュボードで指定した期間のデータ量の合計の表示方法と Metric Math を使用した表示方法の紹介 – DevelopersIO
https://dev.classmethod.jp/articles/aggregate-data-for-the-specified-period-in-the-cloudwatch-dashboard/

isdAWS ECS on Fargateを採用してよかったこと

アプリケーション開発担当の方にうかがいました。

  • これを機に、PC開発環境にDockerを導入。コンテナをDBサーバー、バックエンド(API)、フロントエンドに分割して開発(本番環境ではそれぞれ、RDS, ECS, S3に相当)。デプロイ作業はバックエンドのみをECSにデプロイできるので非常に簡単になった。
  • これまではデプロイツールにCapistranoを利用し、Gitに一旦デプロイしてマージするなどの手間がかかった。今はすぐにビルドしてECRにアップロードし、Webコンソールでデプロイまででできて楽になった。ロールバックもサービスのリビジョン変更で終わるので非常に楽。
  • デプロイ時、ヘルスチェックで失敗しても、サーキットブレーカー機能で自動的にロールバックされるので安心感がある。
  • 環境変数を1ファイルにまとめて切り分けられるのが非常に嬉しい。コンテナのビルドと環境変数の設定変更を別々に行えるので、楽になった。環境変数のファイルは専用のS3バケットで管理しているので、扱いが楽になった。
  • (FireLensを利用して)ログを切り分けて出力できるので、エラーの原因究明が素早くできるようになった(これまでは、VMのログファイルをダウンロードしてから検索、などの地味な方法を採っていたので)。ログの検索もコンソールからできるので、ものすごく楽。
  • 開発環境、サーバー環境ともに以前の環境が雑過ぎた感があるが、これを機に刷新できたのが非常に嬉しい。

半分ぐらいは、ECSのメリットというよりは、コンテナ化によるメリットになりますが、今回チャレンジしたことで、ご満足いただけたようで何よりです。

デプロイ、CI/CDまわりは、今回はとりあえず不要とのことで構築していませんが、必要となる場面も出てくるかもしれませんね。

運用についてはまだ始まったばかりですが、心理面を含め、運用管理コストはかなり下げられると期待しています。
Fargateを使用する分、サーバー費用は少しお高いかもしれませんが、サーバー負荷を注視して、コスト最適化を支援できればと思います。

isdおわりに

ECS on Fargateの構築で出てきた課題と解決策、そして、アプリケーション開発担当の方にうかがった ECS on Fargateを採用してよかった点をまとめました。

今回は初めてだったので、少し手間がかかってしまいましたが、ネット上に情報がたくさん公開されていることもあり、ひとつひとつ、解決することができました。
ありがとうございます!

次回以降はスムーズに構築できるのではないかと思います。
 

(関連記事)
・AWS FireLens Fluent BitでAND条件のログ振り分け
https://inaba-serverdesign.jp/blog/20230524/firelens-fluent-bit-and-condition.html
 

Follow me!