はじめに
サービスディスカバリ設定をbridgeモードで行いたいなと思った時に世の中にはawsvpcモードのFargate記事ばかりかつ、bridgeモードはawsvpcモードと異なりAレコードを使えず、SRVレコードという初耳レコードのみ利用可能で、名前解決もよしなにやってくれないということで、名前解決方法などもわからず困っていました。
しかし、普通に実装できたので一応残しておこうと思います。
実装はPythonで行っています。
ENI制限なければ全てよしなにやってくれるawsvpcモード使えば良いですし、今回はECSインスタンスなのでプライベートIPでも通信できるため、需要少なそうですが。。。
探した感じこちらのRedisを取り上げたGoでのものしか見つかりませんでした。
Terraform構築も初なので、容赦お願いします。。。
構成
本当はもう少し複雑ですが、簡単な流れを構成として記載しました。
ECSインスタンスAサービスのコンテナ処理から、ECSインスタンスBサービスで動くコンテナのAPIにサービスディスカバリでアクセスしたいというものになります。
PythonでSRVレコードを処理して、PythonのAPIに接続しにいきます。
Route 53とCloud Mapへの登録などは、サービスディスカバリ設定で自動的に作成されます。
SRVレコードとは
詳細説明は割愛しますが、簡単にいうとサービスの場所の情報であるホスト名やポート、プロトコルなどを持っているレコードになります。
Terraform構築
アクセスしたいサービス側にサービスディスカバリ設定を行います。
今回ですと、ECSインスタンスBサービスになります。
ecs_cluster.tf
// プライベートDNSネームスペースの作成 resource "aws_service_discovery_private_dns_namespace" "ecs_namespace" { name = "ecs.internal" description = "Private DNS namespace for ECS services" vpc = data.terraform_remote_state.common.outputs.vpc_id } // サービスディスカバリ設定 resource "aws_service_discovery_service" "service_b" { name = "service_b" dns_config { namespace_id = aws_service_discovery_private_dns_namespace.ecs_namespace.id dns_records { ttl = 10 type = "SRV" // bridgeモードはAレコード使用不可のためSRVレコード指定 } routing_policy = "MULTIVALUE" } health_check_custom_config { failure_threshold = 1 } }
ecs_service_b.tf
resource "aws_ecs_service" "service_b_containers" { name = "${var.prefix}-containers" cluster = aws_ecs_cluster.ecs-cluster.id task_definition = aws_ecs_task_definition.service_b_containers.arn desired_count = 1 launch_type = "EC2" deployment_minimum_healthy_percent = 0 deployment_maximum_percent = 100 placement_constraints { type = "memberOf" expression = "attribute:TaskType == service_b" } # ... (省略) // サービスの登録追加 service_registries { registry_arn = aws_service_discovery_service.service_b.arn container_name = "task_b_container" // タスク内のコンテナ名 container_port = 80 // 設定しているコンテナポート } # ... (省略)
SRVレコードを利用してサービス検出実装
こちらは、ECSインスタンスAサービスのタスクAのコンテナの実装内容となります。
aws_service_discovery_serviceで指定したnameとaws_service_discovery_private_dns_namespaceのnameを「.」繋ぎでアクセスできます。
今回の場合ですと、service_b.ecs.internal
とすればSRVレコードへアクセスでき、
レコード情報が返却されます。
そこからポートを取得しIPアドレスを割り出して、取得します。
これを利用して、ECSインスタンスBサービスのタスクBのコンテナへアクセスできます。
import socket import dns.resolver def resolve_ookami_api_url(): try: answers = dns.resolver.resolve('service_b.ecs.internal', 'SRV') for rdata in answers: hostname = rdata.target.to_text(omit_final_dot=True) // ホスト名取得 ip_address = socket.gethostbyname(hostname) // ホスト名をIPアドレスに変換 return f"http://{ip_address}:{rdata.port}" // URLとしてアプリ側で利用する except (dns.exception.DNSException, socket.gaierror): return http://xxxxxx:xxxx
最後に
今回はコンテナ数がかなり多くENIの上限引き上げしたとしても、引っかかってしまうためawsvpcモードではなくbridgeモードを利用しました。
基本的にはawsvpcモードでFargate使いたいなとは思いましたが、フルマネージド型に甘えない良い勉強の機会になりました。
参考文献
公式ドキュメント:サービス検出を使用して Amazon ECS サービスを DNS 名で接続する
ECSでService Discoveryを使ってみよう