Medley Developer Blog

株式会社メドレーのエンジニア・デザイナーによるブログです

ECSサービス間の通信をAmazon ECS サービスディスカバリで実現した話

株式会社メドレーのエンジニアの阪本です。

緊急事態宣言も開け、普段の生活を取り戻しつつあるこの時期、 皆さんはいかがお過ごしでしょうか?

私は野球観戦(虎党)を毎日の楽しみとしています。 今年はコロナ渦の影響で開幕予定が遅延したものの、自粛期間を経て6月中旬にめでたくシーズン開幕を迎えることができました。 ここまでの「長い冬」が明け、テレビをつけると野球が見られる。 これで私自身も2020年が開幕したなと実感しています。

今回は、私がインフラ開発時に直面した問題と解決までの事例について紹介させて頂きます。

背景

私はジョブメドレーのサービス開発を行っています。 このシステムは多くの機能で構成された大規模なもので、AWSのElastic Container Service(以下ECS)にて稼働しています。

このシステムに対し既存機能のリプレース案件に携わる機会がありました。 現在のシステムは多くの時間をかけて多くの機能を実装した結果、かなり大きなコードとなっています。 これにより、一つの変更が及ぼす影響が甚大なものになり得る状況だったため、リプレース対象の機能を新システムとして別アプリケーションに切り出して開発することにしました。

f:id:medley_inc:20200629201809p:plain

しかし、別システムとして一部の機能を切り出すものの、この機能は 既存システムとの連携が必要となります。 そのため、この連携をシステム間のAPIリクエストで実現することにしました。

課題

ここで1つの問題が発生しました。 システム間の通信が必要になりましたが、お互いECSサービスで分離した構成となるため このままではアドレスの解決が出来ないことに気づきました。

同一ECSサービス内でかつ、ネットワークモードがawsvpcモードであれば ポート番号を分ける事により相互でアクセスが可能であるものの 異なるサービスであればポート以前にアドレス解決ができません。

f:id:medley_inc:20200629201843p:plain

そのため、何らかの手段を持ってお互いの場所を認識できる状態にする必要があります。 そこで、これを実現できる幾つかの方法を検討しました。

アプローチ

その1 全て1つのECSのサービスにまとめる

f:id:medley_inc:20200629201919p:plain

上記の通り、既存システムが動いているECSサービス/タスクに新システム(のコンテナ)を全て混ぜる方法です。 この場合、全てのポートを個別に割り振ることで127.0.0.1:portによるアクセスが可能となるため 相互のリクエストも実現できることになります。

ただ、インフラ的には2つのシステムが1つの塊として構成される事になるため デプロイの単位やスケールの単位を常に双方共有することになります。

f:id:medley_inc:20200629201942p:plain

こうなると、せっかくアプリケーションを分けたにも関わらず 運用の部分では何もメリットを得られないどころか制約が増えただけのようになりそうなのが問題です。

その2 内部Application Load Balancerを経由する

f:id:medley_inc:20200629202007p:plain

VPC内部にinternalなApplication Load Balancer(以下ALB) を設置し、接続先となる既存システムをTargetGroupに登録します。 この方法であれば、ALBのエンドポイントに向けてリクエストすることで配下の既存システムにアクセスする経路が確保できます。

またECSサービスとTargetGroupが紐づくことにより既存システム側のデプロイやスケールが自動的にALB側にも連動することになるため、新システム側は既存システムのステータスを意識する必要は少なくなります。

これだと不自然な点も無くアプリケーション間の通信経路も確保できると期待しましたが・・・新たに問題が発生しました。

検証時にALB/TargetGroupを新規作成。 ECSサービスについては 後付けでTargetGroup脱着はできない 複数TargetGroupの付与はAWSコンソールでは対応していない といった理由のためにAWS CLIでの作成作業を行ったのですが、下記エラーが発生してしまいました。

An error occurred (InvalidParameterException) when calling the CreateService operation: load balancers can have at most 5 items.

これはAWSによる制限で、1つのECSサービスに関連付けることのできるTargetGroupは最大5つまでという事を表しています。 つまり既存システムは多くの機能やコンテナが同居しているECSサービスとなっていたため、既にTargetGroupが5つ存在しており上限に達していたのです。

サービスで使用するロードバランサーを表すロードバランサーオブジェクト。Application Load Balancer または Network Load Balancer を使用するサービスの場合、サービスにアタッチできる 5 つのターゲットグループの制限があります。

docs.aws.amazon.com

この方法を取るなら不要なTargetGroupを削るかまとめるかの手を打つ必要がありますが、 周辺環境に対する影響があまりにも大きい為に現実的ではありませんでした。 そのため、新たな選択肢を探すことにしました。

その3 Amazon ECS サービスディスカバリを使用する

そんな中、ECSの機能としてサービスディスカバリ(サービス検出)というものを見つけました。

aws.amazon.com

f:id:medley_inc:20200629202146p:plain

これはECSサービスとRoute53 内部ホスト空間を紐付ける機能です。 またECSサービスの起動・停止・スケールといった対象が変動した場合にも、自動的にホスト空間のレコードを登録/解除し最新の状態に追従してくれるものです。

この場合、別々のサービス間でもお互い相手の名称を把握することができる上、 TargetGroupも新規に必要としないため、今回のニーズに適した方法になりそうです。

f:id:medley_inc:20200629202201p:plain

導入してみた結果

試しにECSサービスにサービスディスカバリを導入し、疎通できるか試してみます。 既に存在するサービスに後付でのサービスディスカバリは出来ないため、新規にサービスを作成 することになります。

f:id:medley_inc:20200629202228p:plain

今回は初めてのサービスディスカバリとなるので、名前空間も同時に作成することになります。 ひとまず名前空間を「medley-blog.local」、サービス検出名を「service-discovery」としてみます。

f:id:medley_inc:20200629202502p:plain

このままECSサービスを作成すると、Route 53にて新たな名前空間medley-blog.local」が作成されていることが確認できます。

f:id:medley_inc:20200629202249p:plain

この状態でサービスのタスクを1つ起動ししばらく待つと、サービス検出名.名前空間となる service-discovery.medley-blog.local にAレコードが追加されています。 このレコードに紐づくIPアドレスこそ、ECSタスクに紐付けられているIPアドレスになります。

f:id:medley_inc:20200629202731p:plain

あとはこの名称で別環境から疎通できるか試してみます。 curlコマンドでservice-discovery.medley-blog.localで通信してみると、見事にレスポンスを受け取ることが出来ました。

f:id:medley_inc:20200629202751p:plain

※ここでは検証のため、接続先アプリケーションはNginxコンテナを配置しています

これにより、異なるECSサービス間での通信を実現することができました。

今後の予定

現段階では検証段階のため評価環境への導入のみとなりますが、今の所は大きな問題も発生せず順調に稼働しています。 このまま特に問題無く稼働できれば、本番環境への導入も目指したいと思います。

さいごに

メドレーのエンジニアは大小問わず課題に対して真摯に向き合い、試行錯誤し、突破口を開く取り組みを常に続けています。 そんな我々と一緒に働きたいと思った方、まずは下記リンクからご応募いただきカジュアルにお話しませんか?

www.medley.jp