ECSのサービスディスカバリーを試す
概要
サービスディスカバリーとは、マイクロサービスなんかを作るときには必須となるあれである。 AWSにおいては、各種リソース間で通信を行う際に、相手先のエンドポイントを解決する仕組みが用意されている。
外向き、つまり対インターネットに対しては、ロードバランサーを用意して、Route53でドメインをくっつけてというのが一般的だと思うが、内部でしか利用しないエンドポイントであれば、VPCのプライベートDNSクエリを用いればよい。
そしてECSサービスの場合は、タスクのオートスケーリングに対して、インスタンスの登録/解除を自動でマネージドしてくれる サービスディスカバリー という機能がある。それの仕組みと構築方法を紹介します。
結論から
- ECSで特定のエンドポイントをもつサービスを作る際、ロードバランサーなしでも実現できる。
- EIPとかで固定する必要もない。
- サービス検出は完全にマネージド。ヘルスチェックはタスク定義のコンテナレベルのものでOK.
ユースケース
- アプリ1をEC2にデプロイ。
- アプリ2をECSにデプロイ。起動タイプはFargate.
- アプリ1からアプリ2にHTTPリクエストを送る。
- ロードバランサー (ALB) は利用しない。
- アプリ1とアプリ2は同じVPCに配置する。
- アプリ1はパブリックサブネットに配置する。
- アプリ2はプライベートサブネットに配置する。
構成図はこんな感じ。 アプリ2は puma+nginxで、nginxのポート80番をエンドポイントとしている。 (ちなみにサンプルはSinatraで作ったが、Railsでも特に大差はない)
解決すべき課題
Fargateは起動するたびにIPアドレスが変わるため、アプリ1からはリクエストする先が分からなくなる。そのため、一般的にはロードバランサーを置いて名前解決と負荷分散を行うことになるのだが、今回取るのは別の方法。
ロードバランサーを作るのが面倒、コストを削減したい、L4やL7などロードバランサー特有の機能を使う予定がない、という場合は、ECSのサービスディスカバリーを使うことができる。
サービスディスカバリーを使った最終的な構成がこれ。
順に解説していく。
登場人物
AWS Cloud Map
AWS上で展開する各種サービスを検出するためのレジストリで、 誤解を恐れずにざっくり言ってしまえば、ただの名前空間。
https://aws.amazon.com/jp/cloud-map/
エンドポイントにカスタム名をつけて管理し、APIコールまたはVPC DNSクエリを通じて適切にサービスにルーティングを行う仕組み。
特にマイクロサービスを作る上では必須となるサービスディスカバリを支援してくれる。
一般にはIPアドレスをもった何かしらのサーバーリソースをイメージすることが多いと思うが、LambdaやSQSのエンドポイントも解決できる。
今回は ECS と VPC Private DNS と組み合わせて使用するので、特に意識する必要はなく、ただの名前空間くらいの認識でよい。
興味があれば以下の記事が詳しい。
詳細解説「AWS Cloud Map」とは #reinvent
Route53 Private DNS
Rout53 にパブリックではなくプライベートな Hosted Zone を作成して、VPC内のインターナルな名前解決を行う仕組み。
特定のVPCに紐づけて、当該VPC内からAWSのプライベートDNSに対してクエリを行う。
複数のVPCやリージョンをまたぐことも可能。
Amazon Route 53 Private DNSを複数VPCに適用する
今回は同一VPC内で起動したFargateタスクに対して、タスクに割り当てられたプライベートIPへの名前解決を行いたいので、要件に合致している。
構築
全てCloudFormationを用いて行う。 疎通確認用に、app1としてEC2インスタンスから作業を行うために、ssh接続のためのキーペアだけ事前に作成しておく。
話の本題の部分だけ解説するので、他のVPCやECSの定義はgithubを参照してほしい。
AWS Cloud Map
PrivateDNSNamespace: Type: AWS::ServiceDiscovery::PrivateDnsNamespace Properties: Name: osushi.service Vpc: !Ref VPC
VPCに紐づけた Private DNS クエリを利用するのでリソースタイプはAWS::ServiceDiscovery::PrivateDnsNamespace
となる。
「Cloud Map」という言葉が出てこないので結構ややこしいのだが、コンソール上では [AWS Cloud Map] のページにリソースが作成される。
名前はドメイン名、つまりはエンドポイントのサフィックスになるので、それっぽい名前をつけることをお勧めする。
ECS
マネジメントコンソールでサービスを作成する際に設定することができる [サービス検出(オプション)] は、CloudFormation上では、ServiceRegistries
というプロパティで設定する。
ECSService: Type: AWS::ECS::Service Properties: Cluster: !Ref ECSCluster DesiredCount: !Ref ServiceDesiredCount LaunchType: FARGATE NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: DISABLED SecurityGroups: - !Ref SecurityGroup Subnets: - !Ref PrivateSubnet ServiceName: !Ref AppName TaskDefinition: !Ref ECSTaskDefinition ServiceRegistries: - ContainerName: nginx ContainerPort: !Ref Port RegistryArn: !GetAtt DiscoveryService.Arn
今回は、nginxがリクエストを捌くので、nginxコンテナの名前とポートを指定する。 ここで指定したコンテナのポートとプライベートIPアドレスが、Cloud Mapからターゲットとして検出され、自動でDNSに登録されることになる。
RegistryArn
に指定するためのCloudMapのサービスを登録する。
DiscoveryService: Type: AWS::ServiceDiscovery::Service Properties: Description: discovery service DnsConfig: RoutingPolicy: MULTIVALUE DnsRecords: - TTL: 60 Type: A - TTL: 60 Type: SRV NamespaceId: !Ref PrivateDNSNamespaceID Name: myapp
AレコードとSRVレコードの両方を登録する。 TTLはとりあえず60秒。
NamespaceId
には VPC の項で作成した名前空間を指定。
Name
は、エンドポイントのプレフィックス(サブドメイン)となる部分。
これで前述の名前空間と合わせて、 myapp.osushi.service
でクエリすると、Rouute53のプライベートDNSを経由して、nginxコンテナの80番ポートにリクエストが届くようになる。
CloudMapの階層を整理しないとちょっと混乱するかもしれないのでざっくりと図解する。 名前空間、サービス、サービスインスタンスの3つのグループがある。
サービスインスタンスの実体は、Fargateのタスクだったり、Lambdaだったりする。 オートスケールすれば、そのタスクの数だけサービスインスタンスとして登録される。 ALBと同じ感じで理解してもらえればいい。
ヘルスチェックはコンテナごとの設定に準拠する。 成功・失敗に応じて、サービスインスタンスの登録と解除をマネージドしてくれる。
HealthCheck: Command: - CMD-SHELL - curl -f http://localhost/ - "|| exit 1" StartPeriod: 30
Route53 Private DNS
この時点で、もうできている。
CloudMapが自動でプライベートなホストゾーンを作成し、 ECSの設定に応じて、検出したレコードを登録している。
値は、ステータスが HEALTHY なタスクのプライベートIPになっている。
疎通確認。 アプリ1として立てているEC2インスタンスにssh接続し、以下のコマンドを実行する。
dig myapp.osushi.service +short
5つタスクを起動しているので、5つのプライベートIPに解決されている。 次にHTTPリクエストを送る。
curl -I myapp.osushi.service
ちゃんと返ってきてる。 ちなみに80番ポートは、セキュリティグループでパブリックサブネットのCIDRのみを許可している。 外部からはもちろん接続できないし、VPC外になるのでプライベートDNSによる名前解決も不可能。
そういえばすでに存在するドメインを指定した場合、先にプライベートに問い合わせるからパブリックな方には疎通できないとかなんとか。
所感
ロードバランサーなしでさくっと作れるのは便利。
あまり構築も運用コストもかけたくない社内用のシステムをサクッと作る場合とか、ECSを選択しやすくなる。
ALBの機能を使い倒さない場合はこれで十分かな 🤔
大規模なマイクロサービスを運用する場合はどうなんだろう。