CloudMapを使って異なるVPCのECSタスクに名前解決させてみた
【はじめに】
こんにちは。コンサルティング部の津谷です。
皆さんは、CloudMapというサービスでECSのタスク間通信を実装したことはありますでしょうか。
概要はこちらをご覧ください。
CloudMapの利点はタスクのスケーリングに合わせて、動的にDNSのレコード登録を行ってくれることですね。タスクがスケールイン・アウトすると対応するプライベートIPが変わってしまうので、うれしい機能です。この機能はECSとネイティブに統合しており、コンソール上で簡単に設定可能です。最近ではECS Service Connectというサービスも登場しており、タスク間通信の負荷分散機能やセキュリティ機能も利用することができます。
今回は、VPCが異なるタスク同士の通信は可能なのか気になりましたので検証してみました。
【構成図】
構成は下記になります。
基本構成としては、異なるVPCを用意し、その中に別々のECSクラスターを立てます。
サービスAはサービスBにHTTPアクセスを行うコマンドを定期実行し、その結果をCloudWatchログに転送します。サービスBはシンプルなNginxを立てておきます。
利用するサービスは以下です。
- ECS
検証用のサービスA、サービスBを構築します。 - CloudMap
サービスBの名前空間を作成します(プライベートホストゾーン) - VPCPeering
VPC間の通信を可能にします。今回の構築手順では割愛します。 - CloudWatch
サービスAからサービスBにHTTPアクセスした際の結果をログ出力します。 - VPCEndpoint
タスクの実行ログを出力するために、エンドポイント経由でCloudWatchに接続します。
構築手順は割愛します。 - NatGateway,InternetGateway
タスク実行の際のイメージを取得するために使用します。今回は、イメージを外部公開されているDockerレジストリから取得します。構築手順は割愛します。
【構築手順】
以下の手順はVPCPeeringを張った状態からスタートします。
ネットワーク周りの設定(通信許可設定含め)は事前に済ませておきましょう。
基本的にコンソールで作成していきます。
①CloudMapで名前空間を作成します。
名前空間はホストゾーン名になります。今回は「backend.local」としておきました。
DNSクエリを行うので「API呼び出しとVPCのDNSクエリ」を指定しましょう。
DNSはインターネット公開しないので今回は真ん中でOKです。
この後サービスを関連付けることでFQDN名としてドメインが完成します。
②次にサービスも作成させておきましょう。
サービス名は「api」にしておきます。これでECSタスクが名前解決する際のFQDNが「api.backend.local」になりました。タスクがスケールしている場合、複数値回答するようにルーティングポリシーは「MultiValue」にしておきます。TTLは60秒に設定します。
*TTLの設定は注意が必要です。レコードのTTLを長くするとクライアント側のTTLキャッシュとして残ってしまい、タスクがスケールした場合に古いIPにアクセスしてエラーが発生する懸念があります。
忘れてはいけないのがホストゾーンに関連付ける作業です。
今回はサービスA側のVPCが名前解決するので、サービスAのVPCを作成したプライベートホストゾーンに関連付けるのを忘れないようにしましょう。
③ECSのクラスターを作成します。
サービスA・サービスBで両方作成しましょう。
(画像はサービスA側の方)
名前は以下のようにしました。
サービスA:frontend-cluster
サービスB:backend-cluster
(既にクラスターを作成しているのでエラーが出ているのですがご愛嬌で。)
ここでは、Service Connectは使わないのでオフの状態で大丈夫です!サービス側の設定にある「サービス検出」の項目でCloudMapを関連付けます。今回はFargateで起動させます。
④ECSタスク定義を作成します。(サービスはタスク定義を指定する必要があるので先にタスクです)
【サービスA】
ファミリー名は「frontend-task」にします。
インフラストラクチャはデフォルト設定のままで大丈夫です。
コンテナ設定ですが、名前は「frontend-container」にします。イメージは「alpine:latest」を指定します。コンテナで起動するOSとして軽量なLinuxパッケージを使用します。(サービスBヘHTTPアクセスするコマンドを打つだけなので)
あとでログを見てアクセスできているか確認するのでサービスA側のログ出力は有効化します。
サービスBへのAPI接続を行うコマンドを指定しています。
HTTP接続を30秒間隔で行い、成功したらメッセージを出力するという内容です。
【サービスB】
ファミリー名は「backend-task」にします。インフラストラクチャはデフォルトのままで、サービスAと同様なので割愛します。
コンテナ名は「backend-container」にします。Nginxをインストールします。コンテナの待ち受けポートは80です。
ログは特に出力しないので、無効化にします。
⑤ECSサービスを作成します。
【サービスA】
先ほど作成したタスク定義を指定します。
必要なタスクは1で問題ないです。
ここで、ServiceConnectとCloudMapの設定が可能です。
今回はCloudMapのネイティブ機能のみ利用しますが、サービスA側は名前解決する側なので特に関連付けは必要ないです。
【サービスB】
先ほど作成したタスク定義を指定しましょう。
今回はタスクを複数用意し、CloudMapにレコードが複数登録されるか確認したいので2にしておきます。
サービス検出で、作成したCloudMapの名前空間を関連付けます。
サービスは作成した「api」を利用するので「既存のサービス検出サービスを選択」を選びましょう。
【動作確認(ログ)】
タスクが実行中のステータスになったら、CloudMatchログを見てサービスA⇒サービスBに疎通成功しているか確認します。
問題なくいけてますね。これでHTTPアクセスできていることは証明されたのですが、念のためホストゾーンも見ておきます。
レコードが複数値登録されました。
これで検証完了です。
【最後に】
ECSのサービス間接続を別VPCで試してみました。CloudMapだけではなく、いろんなサービスが出ているので機能の比較検証をしてみると、用途を明確にできるのでいずれやってみたいです。ECSとネイティブ統合しているのでかなりコンソール上でも設定しやすいです。お読みいただきありがとうございました。