ECS サービス間の通信を Amazon ECS サービスディスカバリで実現した話
株式会社メドレーのエンジニアの阪本です。
緊急事態宣言も開け、普段の生活を取り戻しつつあるこの時期、 皆さんはいかがお過ごしでしょうか?
私は野球観戦(虎党)を毎日の楽しみとしています。 今年はコロナ渦の影響で開幕予定が遅延したものの、自粛期間を経て 6 月中旬にめでたくシーズン開幕を迎えることができました。 ここまでの「長い冬」が明け、テレビをつけると野球が見られる。 これで私自身も 2020 年が開幕したなと実感しています。
今回は、私がインフラ開発時に直面した問題と解決までの事例について紹介させて頂きます。
背景
私はジョブメドレーのサービス開発を行っています。 このシステムは多くの機能で構成された大規模なもので、AWS の Elastic Container Service(以下 ECS)にて稼働しています。
このシステムに対し既存機能のリプレース案件に携わる機会がありました。 現在のシステムは多くの時間をかけて多くの機能を実装した結果、かなり大きなコードとなっています。 これにより、一つの変更が及ぼす影響が甚大なものになり得る状況だったため、リプレース対象の機能を新システムとして別アプリケーションに切り出して開発することにしました。
しかし、別システムとして一部の機能を切り出すものの、この機能は 既存システムとの連携が必要となります。 そのため、この連携をシステム間の API リクエストで実現することにしました。
課題
ここで1つの問題が発生しました。 システム間の通信が必要になりましたが、お互い ECS サービスで分離した構成となるため このままではアドレスの解決が出来ないことに気づきました。
同一 ECS サービス内でかつ、ネットワークモードが awsvpc モードであれば ポート番号を分ける事により相互でアクセスが可能であるものの 異なるサービスであればポート以前にアドレス解決ができません。
そのため、何らかの手段を持ってお互いの場所を認識できる状態にする必要があります。 そこで、これを実現できる幾つかの方法を検討しました。
アプローチ
その1 全て1つの ECS のサービスにまとめる
上記の通り、既存システムが動いている ECS サービス/タスクに新システム(のコンテナ)を全て混ぜる方法です。 この場合、全てのポートを個別に割り振ることで 127.0.0.1:port によるアクセスが可能となるため 相互のリクエストも実現できることになります。
ただ、インフラ的には2つのシステムが1つの塊として構成される事になるため デプロイの単位やスケールの単位を常に双方共有することになります。
こうなると、せっかくアプリケーションを分けたにも関わらず 運用の部分では何もメリットを得られないどころか制約が増えただけのようになりそうなのが問題です。
その2 内部 Application Load Balancer を経由する
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 つのターゲットグループの制限があります。
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service_definition_parameters.html
この方法を取るなら不要な TargetGroup を削るかまとめるかの手を打つ必要がありますが、 周辺環境に対する影響があまりにも大きい為に現実的ではありませんでした。 そのため、新たな選択肢を探すことにしました。
その3 Amazon ECS サービスディスカバリを使用する
そんな中、ECS の機能としてサービスディスカバリ(サービス検出)というものを見つけました。
https://aws.amazon.com/jp/blogs/news/amazon-ecs-service-discovery/
これは ECS サービスと Route53 内部ホスト空間を紐付ける機能です。 また ECS サービスの起動・停止・スケールといった対象が変動した場合にも、自動的にホスト空間のレコードを登録/解除し最新の状態に追従してくれるものです。
この場合、別々のサービス間でもお互い相手の名称を把握することができる上、 TargetGroup も新規に必要としないため、今回のニーズに適した方法になりそうです。
導入してみた結果
試しに ECS サービスにサービスディスカバリを導入し、疎通できるか試してみます。 既に存在するサービスに後付でのサービスディスカバリは出来ないため、新規にサービスを作成 することになります。
今回は初めてのサービスディスカバリとなるので、名前空間も同時に作成することになります。
ひとまず名前空間を「medley-blog.local
」、サービス検出名を「service-discovery
」としてみます。
このまま ECS サービスを作成すると、Route 53 にて新たな名前空間「medley-blog.local
」が作成されていることが確認できます。
この状態でサービスのタスクを 1 つ起動ししばらく待つと、サービス検出名.名前空間
となる
service-discovery.medley-blog.local
に A レコードが追加されています。
このレコードに紐づく IP アドレスこそ、ECS タスクに紐付けられている IP アドレスになります。
あとはこの名称で別環境から疎通できるか試してみます。
curl
コマンドでservice-discovery.medley-blog.local
で通信してみると、見事にレスポンスを受け取ることが出来ました。
※ここでは検証のため、接続先アプリケーションは Nginx コンテナを配置しています
これにより、異なる ECS サービス間での通信を実現することができました。
今後の予定
現段階では検証段階のため評価環境への導入のみとなりますが、今の所は大きな問題も発生せず順調に稼働しています。 このまま特に問題無く稼働できれば、本番環境への導入も目指したいと思います。
さいごに
メドレーのエンジニアは大小問わず課題に対して真摯に向き合い、試行錯誤し、突破口を開く取り組みを常に続けています。 そんな我々と一緒に働きたいと思った方、まずは下記リンクからご応募いただきカジュアルにお話しませんか?
https://www.medley.jp/jobs/engineer1.html