見出し画像

Funds システムを ECS に移行しました

この記事は「Funds Advent Calendar 2023」22 日目の記事です。

21 日目の記事を担当されたのは村山さんでした!

ちょっとした距離なら電車乗らないで走った方が速いかなと思うようになりましたww

https://note.com/kaori_funds/n/n47cd8d233ee8

なんだかもうよくわからないスピード感です


ファンズ株式会社でサーバサイド テックリードをしております田邊(@emaggame)です。
今年は少々遅めの新婚旅行に行ってまいりました。昨年危ない目にもあいましたが懲りもせずダイビングをし大きな海中生物に遭遇、大興奮です。ただ、マンタに睨まれれば思わず目をそらすようなわたしをよそに、ジンベエザメを見るやいなや ちょっと考えられないスピードで突撃していく妻の後ろ姿を見てこれはかなわない、と思いましたよね。。


業務面では今年はインフラ整備に多くの時間を割いた年であり、特に Amazon EKS(以降 EKS) から Amazon ECS(以降 ECS) への全面的な移行に注力しました。本記事では移行に関して中心的に触れていこうかと思います。


Funds システム アーキテクチャ

以下が Funds システムを取り巻くアプリケーション群です。

Funds システム アプリケーション群

アプリケーションの種類自体は昨年の以下記事記載のものから変わっていないのですが、EKS 上にデプロイされていたものを全て ECS に移行しています。 

なお管理画面についてはサーバ不要の SPA であるため CloudFront + S3 構成となっています。また、Bastion については 背番号8さん の以下記事をぜひご覧ください!


EKS から ECS への移行の背景

Funds では 2 年ほど EKS をアプリケーション実行基盤とした運用をしていましたが、以下のような運用課題の影響が大きくなっていました。

  • 利用者が責任をもってメンテナンスする範囲が広いこと(例: クラスタ・ノード・別途インストールしたカスタムリソース)

    • DaemonSet を利用したいため、Funds では on EC2 で運用していた

    • EKS 本体やカスタムリソースのアップデート追従コストが無視できない

EKS 運用で得られていることと上記の課題を天秤にかけると、運用コストを抑えられる見込みのある ECS での運用のほうが所属組織として持続可能であり望ましいと判断しました。

ECS 移行の手順

ここからは ECS 移行にあたって実施したことを記載します。

ECS 移行、ほんとにできるの?

まず、今 EKS で行っていることが ECS でもできるのか検証が必要です。また、一部アプリケーションをすでに ECS 上で運用していたものの、ECS の運用ノウハウは社内にそれほどは蓄積されていない状態でした。

そこで、Laguna Edge 合同会社の石橋さん(@bashi0501)にご助力いただきました。

石橋さんにはプロトタイプや ECS 用 Terrafrom モジュールの作成、各種検証の実施、運用ノウハウの共有、困り事の相談など多岐に渡ってお世話になりました。ご経験に裏打ちされたアドバイスであることはもちろん、EKS から無理なく移行できるよう、きめ細やかなご対応をいただき大変感謝しております。
この場を借りて御礼申し上げます。ありがとうございました!

以降は各種検証した内容について記載します。

インフラ検証

アプリケーション実行基盤として EKS で実現していることを ECS でも可能かを検証しました。なお、運用容易性を重視したことから Fargate を選択しています。 

結論としては以下の通り、問題なく移行できると判断しました。

  • モニタリング

  • ロギング

    • もともと各アプリケーションは fluentbit 経由で s3 に転送しており、踏襲するだけで問題ない

  • Datadog

    • 利用状況としては実質的に JVM アプリケーションの JVM メトリクス閲覧用だったので、エージェントを必要なアプリケーションのタスク定義に設定するのみで可能

      • AWS インテグレーションを進めて Datadog でメトリクス全般を見るという考えもあったが、現状はそこまではしていない

    • 新規で APM も導入した。トレーシング用途として今後は JVM アプリケーション以外への導入も検討したい

  • デプロイ

    • もともとローリングアップデートだった。aws ecs コマンドを用いて CI/CD 中に以下を実行することで対応可能

      1. 現在の最新タスク定義を取得する

      2. 最新タスク定義をもとに、イメージの URL や環境変数定義を書き換え、新しい定義として登録する

      3. 新しいタスク定義を利用するようサービスに登録する

      4. 新旧タスクの入れ替えを待つ

    • ecspresso のようなツールも検討すべきかと考えたが、まずはシンプルにスタートした

  • スケーリング

    • いずれも Application Auto Scaling の設定で対応可能

      • 動的: CPU・メモリ状況に応じて増減するように設定した

      • 事前スケジュール: Funds ではファンド募集開始直後にリクエストがスパイクする特性があるため、その日に募集があればタスクを増やすスケジュール設定をする Lambda を作成した

  • 環境変数の受け渡し

  • コンテナにログインできるか

機能検証

EKS から ECS への移行ということで、アプリケーションの機能面の変更はありません。が、考慮漏れがある場合に備え、念には念をと全機能に対する試験を実施しました。
QA チームには相当のご負担をおかけしてしまいましたが、テストが手薄だった機能のテストケースを拡充できたこともあり総点検としてはよかったと思っています。

性能検証

以前より性能検証のための専用環境が必要であるとは思っていたものの、環境準備にもそれなりに時間がかかることから後回しにしていました。が、これを機に性能検証環境を新規に構築しました。
EKS と ECS とで性能に大きく差が出るとは直感的には思わないものの、ネットワーク構成が若干変わることもあり、各種性能が要求されるケースや長期安定試験のシナリオを作成・実施しています。
結果としては EKS と同等の性能が得られることを確認できました。

また、負荷クライアントとして Gatling Enterprise を導入しています。

世の中には多くのロードランナーがありますが、負荷クライアントをどこに・どのようにデプロイするかは悩みの種かなと思います。負荷を上げるためにはクラスタリングを組む必要があるなど、構築にもそれなりにコストがかかります。
Gatling Enterprise の Cloud 版では Gatling DSL で記述したシナリオはそのままに、スケールする負荷クライアントをデプロイできるためこういった悩みを解決してくれる良いソリューションであると思います。

実際の移行

移行はアプリケーションごとに 1 つずつ様子を見ながら実施しました。一時的に EKS と ECS 環境が混ざることにはなってしまうものの、機能検証・性能検証合わせて移行に関して大きな問題はないことがわかっているためこの方式としています。
また、ダウンタイムなしで行えるよう、ALB は ECS 各アプリケーション用に用意しており、それぞれ Route 53 の A レコードや、CloudFront であればオリジンの切替で実施しています。
より安全側に倒し Route 53 で重み付けしながら徐々に移行するという手もありましたが、機能試験・性能試験で動作担保できていること、万が一問題があれば EKS 側に切り戻せばよいということもあり一気に切り替えています。

ECS 移行時に大変だったこと

EKS で運用していたためコンテナ化についてはすでに完了しており、下回りを履き替えるだけに集中できたのは今回の移行でのアドバンテージでした。また、CronJob など k8s 独自の方法でアプリケーションを動かしている、といったこともありませんでした。
よって、大変だったといっても小粒なものやちょっとしたハマりポイント、そもそものリソース管理の問題でしかないのですが、いくつかご紹介いたします。

環境変数の値として空文字が使えない

ECS では環境変数の受け渡しに S3 上の KEY=VALUE 形式の設定が羅列されたファイルを指定することができるのですが、以下の報告の通り KEY=<空白文字> のような空白値とした場合、環境変数として読み込まれません。

この問題はアプリケーションをデフォルト(= 未指定)ではその値を空白文字として読み込むようにすることで対処したのですが、ちょっとした落とし穴というか、この挙動を知らずしばらく頭を悩ませました。。

Terraform のリソース定義が技術ドリブンになっている

EKS でも ECS の問題でもなく Terraform 管理手法の問題なのですが、構成把握に関するものです。

仮に以下のようなリソースを用いる構成の Web アプリケーション Foo があったとします。

  • ALB

  • ECS

  • Aurora RDS

それまでは以下のような、リソース種別を単位とした tf ファイルで管理していました。

alb.tf
├── aws_lb.foo
└── aws_lb.bar
ecs.tf
├── aws_ecs_cluster.foo
└── aws_ecs_cluster.bar
rds.tf
├── ...
...

上記の各ファイルには Foo アプリケーションのリソースはもちろん、別のアプリケーションのリソースも含まれていました。一覧性としては優れているものの、あるアプリケーションがどのようなインフラ構成を取っているかを把握するには参照をたどり集約を整理する必要があります。また、リソースによっては参照するリソースの ARN を直接文字列で指定している場合もあり、関連がわかりづらいこともあります。結果として、以下のような調査に時間がかかることになります。

  • 現在 EKS 上に構築しているアプリケーションを ECS  に移行するためには何を変更すればよいか? 変更してはいけないものはあるか?

  • 不要となった EKS リソースの削除漏れはないか?

    • どこからも参照されていな いリソースが放置されていると、時間が経過すればするほど削除してよいか調査コストが肥大する

そこで、今回の移行にあたり以下のような構成としています。

foo.tf
├── aws_ecs_cluster.foo
├── aws_lb.foo
└── aws_rds_cluster.foo
bar.tf
├── aws_ecs_cluster.bar
├── ...
...

つまりは(実際にモジュールとするかはさておき) Terraform module とできるくらいのモジュラリティを意識するようにし、少しでも構成把握しやすい形としました。かわりに一覧性が乏しくなってしまいますが、それはマネジメントコンソールや CLI の一覧表示で代替可能です。

これはちょうど、アプリケーションにおけるドメイン層を以下のような技術ドリブンなパッケージングにするのではなく、

domain
├── model
│   ├── Customer
│   └── OrderSheet
├── repository
│   ├── CustomerRepository
│   └── OrderSheetRepository
└── service
    ├── AddCustomerService
    └── ApplyOrderService

以下のように業務(目的)ごとに根ざしたパッケージングとすることに近いかなと思います。

domain
├── customer
│   ├── Customer
│   ├── CustomerRepository
│   └── AddCustomerService
└── order
    ├── OrderSheet
    ├── OrderSheetRepository
    └── ApplyOrderService

インフラ構成図を書くときにそのリソースの集約を意識して書くのと同様、その宣言も集約を意識した形となっているべきと思います。

ECS 環境で工夫したところ

ECS での環境構築をする際にいろいろと流儀があるかなと思いますが、以下のような方針にしています。

1 アプリケーション 1 クラスタ

システム全体で ECS クラスタは 1 つ、ECS サービスをアプリケーションごとにぶら下げるといった運用もあるかと思いますが、Funds システムでは 1 アプリケーション 1 クラスタにしています。
これは以下のような効果を期待してのものです。

  • いろいろな構成パターンを考える必要がなくなり ECS に関する Terraform module を作りやすい

  • 原則 Fargate を用いるが、何らかの理由で Fargate Spot を使いたいアプリケーションが出た場合にクラスタ単位で分かれている方がよい

タスク定義は Terraform 管理しない

タスク定義は Terraform 管理対象外としています。
というのも、タスク定義の更新は実際上は各アプリケーションの CI 中で行われるためです。この更新を見越したうえで Terraform 管理することも可能ではありますが、Terraform 側でも更新が発生するとなると意図しないタスク定義の変更が発生するリスクがあるためこの形としました(例えば Terraform 側のコンテナ定義に古い属性があった場合など)。
とはいえ、タスク定義を Terraform 管理していないということはマネジメントコンソールや CLI で意図せず変更してしまったときに気づくのが遅れるということでもあり不安な面もあります。このトピックはいろいろとメリット・デメリットがあると思うので工夫といいながらまだ悩んでいるところではあります。

ECS に移行してよかったこと

いろいろあるのですが、当初の目的通り運用面で日々の考慮事項が減ったことが大きいところです。例えば以下です。

アップデートの手間がない

ECS はそのメンテナンスをよしなに実施してくれ、安全にタスクを再起動する点はやはり素晴らしいです。
EKS やカスタムリソースの更新情報を取得し、順次アップデートする定常業務も嫌いではなかったのですが、片手間にやるのはやはり負担でした。

ECS 自体がサポートしているため追加の管理対象が減った

  • 機密情報の AWS Systems Manager Parameter Store からの受け渡しはEKS では External Secrets Operator  を利用していましたが ECS では標準サポートしています

  • EKS では fluentbit 自体の管理をクラスタ側の設定としていましたが、ECS ではタスク定義にログ出力対象コンテナの FireLens 設定を行うことと、fluentbit 自体をサイドカーとするコンテナ定義の追加となります。アプリケーションと近いところに設定するため認識の負荷が下がりました(サイドカーとしての fluentbit コンテナ定義を個別で行う手間は増えましたが。。)

  • 我々の環境での EKS ではネットワーク関連のアドオンのアップデートも利用者が行う必要がありましたが ECS 移行により不要になりました

細かい点でいえば以下もあります。

リリース時のデプロイ確認作業が簡単になった

EKS 利用時は CI 中で `$kubectl apply …` とし、apply さえ通れば CI ジョブ自体はそこで終了していました。しかしこれは新しいマニフェストファイルを適用しただけであり、実際に新しいバージョンがデプロイされたかどうかは Pod の状況を別途確認する必要がありました(CI 中でも同様のことをすればこの問題はなかったのですが、その作り込む手間を惜しんで開発者に手間をかけさせてしまっていました。。)。

ECS においては `$ aws ecs wait services-stable …`とすることでローリングアップデートの完了まで待つ挙動となるので、CI 中でこのコマンドを実行すると CI ジョブの完了 = デプロイの完了とみなすことができます。

ECS 運用の今後

今年 2023 年 10 月ごろに ECS に移行してから 2 ヶ月ほどになりますが、今のところ大きな問題は出ておらずひとまず安心しています。
運用面においては継続的な性能試験の実施やオブザーバビリティに関するドキュメントを整備中であり、関係者が素早くキャッチアップできる・対外的にも説明しやすいポリシー作りに努めています。この取り組みについても将来的にお話できればと思います。

最後に

いろいろと EKS については言ってしまいましたが、将来的には k8s の柔軟性が必要になるときが来るかもしれません。常に最適な技術を選択し続けられる組織でありたいなと思っています。


ファンズはまだまだスタートアップ、やりたい! と思ったことはインフラやアプリケーションに限らずなんでも挑戦できる環境です。もしファンズで働くことにご興味ございましたら以下募集一覧をご覧いただければ幸いです。

採用に関する特設ページもあり、なかなかつまびらかに書かれています。

ちょっと話だけでも、という場合は X(Twitter) で DM いただくのも大歓迎ですのでお待ちしております!

この記事が気に入ったらサポートをしてみませんか?