目次
AWS Cloud Map 完全ガイド v2.0
初心者から実務者向けの包括的解説
AWS Cloud Map は、マイクロサービス・コンテナ・リソースの動的なサービスディスカバリーを提供するフルマネージドサービス です。ECS・EC2・Lambda・DynamoDB 等の複数リージョン・複数アカウント分散リソースを、カスタム名前空間で管理し、DNS クエリまたは AWS SDK API で動的にエンドポイント解決します。2025年8月の重要アップデートでは、AWS Resource Access Manager (RAM) との統合により、クロスアカウント・サービスディスカバリー が実現され、複数アカウント環境での一元管理が可能になりました。本ドキュメントは Cloud Map の概念・アーキテクチャ・実装・最新動向を体系的に解説する包括的ガイドです。
ドキュメントの目的
本ガイドは以下を対象としています。
- 初心者向け: Service Discovery とは何か、AWS Cloud Map が解決する問題を学びたい方
- DevOps/SRE 向け: ECS Service Discovery、マイクロサービス間通信、ヘルスチェック統合の実装
- マルチアカウント担当者向け: クロスアカウント・サービスディスカバリー、AWS RAM 統合
- アーキテクト向け: ECS/Fargate、App Mesh、VPC Lattice との使い分け、設計パターン
- 意思決定者向け: HashiCorp Consul / Kubernetes Service Discovery / Istio 等との比較検討
2025-2026 年の AWS Cloud Map エコシステム
- クロスアカウント・サービスディスカバリー(2025年8月): AWS RAM 統合により、ECS タスク・EC2 インスタンス・DynamoDB テーブル等を複数アカウント間で共有可能に。プラットフォームエンジニアが一元管理する中央レジストリを複数ワークロードアカウントから参照可能
- API ベースディスカバリーの低レイテンシ化(2025年): 5秒以内のリソース更新反映で、より動的なマイクロサービス環境に対応
- CloudWatch Application Map 統合(2025年11月): 非インストルメント化サービスの自動発見により、カスタムアプリケーションの可視化を強化
- IPv6 フルサポート(2023年以降): Dualstack エンドポイントでの IPv6 専用ネットワーク対応
- CloudFormation ネイティブサポート強化: IaC による Cloud Map リソースの完全管理
- AWS Service Connect 連携(ECS Anywhere): オンプレミス環境を含むハイブリッドサービスディスカバリー
目次
- 概要
- Cloud Map が解決する課題
- 主な特徴
- アーキテクチャ
- コアコンポーネント
- Namespace の種類
- Service と Instance の管理
- ヘルスチェック戦略
- 主要ユースケース
- ECS Service Discovery 統合
- クロスアカウント・サービスディスカバリー
- CLI/SDK 実装例
- Terraform/CDK による IaC
- 他のサービスディスカバリーとの比較
- ベストプラクティス
- トラブルシューティング
- セキュリティ・コンプライアンス
- 料金モデル
- 2025-2026 最新動向
- 学習リソース・参考文献
- 実装チェックリスト
- まとめ
概要
AWS Cloud Map は、アプリケーションが依存するバックエンドサービス・リソースの動的なマッピング・登録・検出 を提供する完全マネージドのサービスディスカバリーサービスです。
従来のマイクロサービス環境では、コンテナの起動・停止により IP アドレスが頻繁に変わるため、クライアントが最新のエンドポイント情報を常に把握することが困難でした。Cloud Map は以下を解決します:
- 動的なエンドポイント管理: ECS タスク・EC2 インスタンスの起動・停止に自動連動して登録・削除
- ヘルスベースの自動除外: 不健全なインスタンスを自動的に検出対象から除外
- 複数の検出方式: DNS クエリ、AWS SDK API、HTTP ベースのディスカバリーに対応
- 低遅延検出: API ベースディスカバリーで 5 秒以内のリソース更新反映
Cloud Map が解決する課題
1. マイクロサービス間通信の複雑さ
従来の課題:
Client A → IP: 10.0.1.5 (ハードコード)
│
↓ (Service B が新インスタンスで再起動)
│
Service B: IP が 10.0.2.7 に変更
│
↓ (Client A はハードコード IP に接続失敗)
↓
接続エラー
Cloud Map による解決:
Client A → "payment-service.dev.myapp.local" (名前解決)
│
↓ (Route 53 DNS または Cloud Map API)
│
Cloud Map → 最新の健全な Instance IP を返却
│
↓ (10.0.2.7 に自動切り替え)
│
Service B: 新インスタンスに接続成功
2. 複数リージョン・複数アカウント環境での一元管理
従来、マルチアカウント環境では各アカウントで独立した Service Discovery を運用し、同期を手動で管理していました。AWS RAM との統合により、一つの中央 Cloud Map Namespace を複数アカウント間で共有 でき、プラットフォームエンジニアが一元管理できます。
3. ヘルスチェック統合
ECS タスクがクラッシュ・応答不可になった場合、自動的にディスカバリー対象から除外し、クライアントは常に健全なインスタンスのみを取得できます。
主な特徴
| 特徴 | 説明 |
|---|---|
| フルマネージド | インフラ構築・スケーリング・可用性を AWS が管理 |
| 複数の検出方式 | DNS(Route 53)、SDK API、HTTP ベースで柔軟に対応 |
| ECS ネイティブ統合 | ECS Service Discovery で自動登録・削除 |
| ヘルスチェック統合 | Route 53 Health Check またはカスタムヘルスチェックを統合 |
| クロスアカウント対応(2025年8月) | AWS RAM で複数アカウント間の共有が可能に |
| マルチテナント対応 | 複数の独立した Namespace で複数アプリを管理 |
| 低遅延(5秒以内) | API ベースディスカバリーでリアルタイム更新 |
| IPv6 対応 | IPv4 と IPv6 の両方をサポート |
アーキテクチャ
graph TB
subgraph "AWS Cloud Map"
NS["Namespace: dev.myapp.local<br/>(プライベート DNS)"]
SVC1["Service: payment-service<br/>(DNS CNAME)"]
SVC2["Service: order-service<br/>(DNS SRV)"]
SVC1 --> INS1["Instance 1: 10.0.1.5:8080<br/>(HEALTHY)"]
SVC1 --> INS2["Instance 2: 10.0.2.7:8080<br/>(HEALTHY)"]
SVC1 --> INS3["Instance 3: 10.0.3.4:8080<br/>(UNHEALTHY - 除外)"]
SVC2 --> INS4["Instance 4: 10.0.4.9:8081<br/>(HEALTHY)"]
end
subgraph "ECS/EC2"
TASK1["ECS Task 1"]
TASK2["ECS Task 2"]
EC2["EC2 Instance"]
end
subgraph "クライアント"
CLIENT["Application Client"]
end
NS --> SVC1
NS --> SVC2
TASK1 -.自動登録.-> INS1
TASK2 -.自動登録.-> INS2
EC2 -.手動登録.-> INS4
CLIENT -->|DNS: payment-service.dev.myapp.local| SVC1
CLIENT -->|API: DiscoverInstances| SVC1
style INS3 fill:#ffcccc
style INS1 fill:#ccffcc
style INS2 fill:#ccffcc
style INS4 fill:#ccffcc
データフロー
-
登録フェーズ
- ECS Service Launcher が新タスクを起動
- タスクのメタデータから IP・ポート・カスタム属性を抽出
- Cloud Map RegisterInstance API を呼び出し
- リソース情報が Namespace > Service > Instance に保存
-
検出フェーズ
- クライアントが
DiscoverInstancesAPI または DNS クエリを実行 - Cloud Map がヘルスチェック状態を確認
- 健全なインスタンスのみを返却
- クライアントが
-
除外フェーズ
- ヘルスチェック失敗を検出
- 自動的に検出対象から除外
- 新しい健全なインスタンスを優先
コアコンポーネント
1. Namespace(名前空間)
Namespace は、Services とその Instances をグループ化する論理単位です。
3 つの Namespace タイプ:
| 種類 | 用途 | 検出方式 | 例 |
|---|---|---|---|
| Private DNS Namespace | VPC 内の内部通信 | Route 53 DNS | payment-service.dev.myapp.local |
| Public DNS Namespace | インターネット公開 | Route 53 Public DNS | api.example.com |
| HTTP Namespace | API ベースのみ | AWS SDK API | myapp-services(DNS なし) |
2. Service(サービス)
Service は、同じ種類のリソースのテンプレート・グループです。
Namespace: dev.myapp.local
├── Service: payment-service
│ ├── DNS Records: A, SRV
│ ├── Health Check: Route 53 (TCP:8080)
│ └── Routing Policy: WEIGHTED
│
├── Service: order-service
│ ├── DNS Records: A
│ ├── Health Check: Custom (App-provided)
│ └── Routing Policy: RANDOM
3. Instance(インスタンス)
Instance は、実際のリソース(ECS タスク、EC2 インスタンス等)を表します。
登録情報:
{
"InstanceId": "payment-task-001",
"Attributes": {
"AWS_INSTANCE_IPV4": "10.0.1.5",
"AWS_INSTANCE_PORT": "8080",
"AWS_INSTANCE_IPV6": "2600:1f13:9e2:4f00::1",
"version": "v2.1.0",
"environment": "production",
"region": "ap-northeast-1"
}
}
4. Health Check(ヘルスチェック)
3 つの方式:
| 方式 | 説明 | 遅延 |
|---|---|---|
| Route 53 Health Check | HTTP/HTTPS/TCP エンドポイント監視 | 30 秒 |
| Custom Health Check | アプリケーション提供のヘルスステータス | リアルタイム(UpdateInstanceCustomHealthStatus) |
| 無し | ヘルスチェック非実施(手動管理) | N/A |
Namespace の種類
1. Private DNS Namespace
VPC 内でのみ DNS 解決される内部用 Namespace。
# Private DNS Namespace の作成
aws servicediscovery create-private-dns-namespace \
--name dev.myapp.local \
--vpc vpc-0123456789abcdef0 \
--vpc-region ap-northeast-1 \
--description "Development microservices namespace"
利点:
- VPC 内での安全な解決
- 複数の VPC をサポート(AWS PrivateLink 経由)
- 標準的な DNS ベース検出
2. Public DNS Namespace
Route 53 Public Hosted Zone を使用してインターネット公開。
# Public DNS Namespace の作成(既存ドメイン "example.com" 使用)
aws servicediscovery create-public-dns-namespace \
--name api.example.com \
--description "Public API services"
利点:
- インターネットからのアクセス可能
- 外部パートナー・クライアントへの提供
- Route 53 の All AWS features が使用可能
3. HTTP Namespace
DNS 不要で API ベースの検出のみ。
# HTTP Namespace の作成
aws servicediscovery create-http-namespace \
--name myapp-services \
--description "API-based service discovery without DNS"
利点:
- カスタムスキーム(IP:Port、URL 等)に対応
- DNS インフラ不要
- IoT、カスタムプロトコル環境向け
Service と Instance の管理
Service の作成
# DNS ベースの Service(Private Namespace)
aws servicediscovery create-service \
--name payment-service \
--namespace-id ns-0123456789abcdef0 \
--dns-config '
{
"DnsRecords": [
{"Type": "A", "TTL": 60},
{"Type": "SRV", "TTL": 60}
],
"RoutingPolicy": "WEIGHTED",
"NamespaceId": "ns-0123456789abcdef0"
}
' \
--health-check-custom-config '{"FailureThreshold": 3}' \
--description "Payment Service"
RoutingPolicy オプション:
| ポリシー | 用途 | 動作 |
|---|---|---|
| WEIGHTED | トラフィック分散制御 | 重み付けに基づいて割り当て |
| MULTIVALUE | 複数値レスポンス | 複数 A レコードを返却 |
| RANDOM | ランダム選択 | 均等分散(Weighted の特殊形) |
Instance の登録(ECS 以外)
# EC2 インスタンスの手動登録
aws servicediscovery register-instance \
--service-id srv-0123456789abcdef0 \
--instance-id ec2-payment-001 \
--attributes '{
"AWS_INSTANCE_IPV4": "10.0.1.5",
"AWS_INSTANCE_PORT": "8080",
"version": "v2.1.0",
"environment": "production",
"region": "ap-northeast-1"
}'
ヘルスステータスの更新(カスタムヘルスチェック)
# アプリケーションが自身の健全性を報告
aws servicediscovery update-instance-custom-health-status \
--service-id srv-0123456789abcdef0 \
--instance-id ec2-payment-001 \
--status HEALTHY
# 不健全状態を報告(例: DB 接続失敗)
aws servicediscovery update-instance-custom-health-status \
--service-id srv-0123456789abcdef0 \
--instance-id ec2-payment-001 \
--status UNHEALTHY
ヘルスチェック戦略
Route 53 Health Check(推奨)
Route 53 が定期的にエンドポイントを監視。
# TCP ヘルスチェックの作成
aws route53 create-health-check \
--type TCP \
--ip-address 10.0.1.5 \
--port 8080 \
--failure-threshold 3 \
--health-check-config '
{
"Type": "TCP",
"IPAddress": "10.0.1.5",
"Port": 8080,
"RequestInterval": 30,
"FailureThreshold": 3
}
'
# Service に Health Check をアタッチ
aws servicediscovery create-service \
--name order-service \
--namespace-id ns-xxx \
--dns-config ... \
--health-check-config '{"HealthCheckArn": "arn:aws:route53:::healthcheck/..."}'
カスタムヘルスチェック
アプリケーション側で健全性を報告。
import boto3
import time
servicediscovery = boto3.client('servicediscovery')
def report_health_status(service_id, instance_id, is_healthy):
"""アプリケーション内でヘルスステータスを報告"""
status = 'HEALTHY' if is_healthy else 'UNHEALTHY'
servicediscovery.update_instance_custom_health_status(
ServiceId=service_id,
InstanceId=instance_id,
Status=status
)
print(f"Health status updated: {instance_id} -> {status}")
# 使用例: 定期的にヘルスチェック
def health_check_loop():
service_id = 'srv-xyz'
instance_id = 'task-001'
while True:
# アプリケーション健全性のチェック
db_connected = check_database_connection()
cache_available = check_cache_connection()
is_healthy = db_connected and cache_available
report_health_status(service_id, instance_id, is_healthy)
time.sleep(10) # 10秒ごとに報告
主要ユースケース
1. ECS マイクロサービス間通信
シナリオ: Payment Service が Order Service を呼び出す
import boto3
servicediscovery = boto3.client('servicediscovery')
def discover_and_call_service(namespace_name, service_name):
"""サービスディスカバリーでエンドポイントを取得して呼び出し"""
# 健全なインスタンスを検出
response = servicediscovery.discover_instances(
NamespaceName=namespace_name,
ServiceName=service_name,
MaxResults=10,
HealthStatus='HEALTHY',
QueryParameters={'environment': 'production'}
)
instances = response.get('Instances', [])
if not instances:
raise Exception("No healthy instances found")
# ロードバランシング(ラウンドロビン)
import random
selected = random.choice(instances)
host = selected['Attributes']['AWS_INSTANCE_IPV4']
port = selected['Attributes']['AWS_INSTANCE_PORT']
print(f"Selected instance: {host}:{port}")
# HTTP リクエスト実行
import requests
response = requests.post(
f"http://{host}:{port}/api/orders",
json={"payment_id": "pay-123", "amount": 100}
)
return response.json()
# 使用例
try:
result = discover_and_call_service('dev.myapp.local', 'order-service')
print(f"Order created: {result}")
except Exception as e:
print(f"Error: {e}")
2. Lambda から ECS サービスへのアクセス
import json
import boto3
from urllib.request import urlopen
servicediscovery = boto3.client('servicediscovery')
def lambda_handler(event, context):
"""Lambda が ECS サービスを検出して呼び出し"""
# Cloud Map でサービスエンドポイントを検出
response = servicediscovery.discover_instances(
NamespaceName='prod.services.local',
ServiceName='api-gateway-service',
HealthStatus='HEALTHY'
)
instances = response.get('Instances', [])
if not instances:
return {
'statusCode': 503,
'body': json.dumps('Service unavailable')
}
# 最初の健全なインスタンスを使用
instance = instances[0]
host = instance['Attributes']['AWS_INSTANCE_IPV4']
port = instance['Attributes']['AWS_INSTANCE_PORT']
# サービス呼び出し
endpoint = f"http://{host}:{port}/api/data"
try:
response = urlopen(endpoint)
data = json.loads(response.read())
return {
'statusCode': 200,
'body': json.dumps(data)
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps(str(e))
}
3. API Gateway と ECS の統合
API Gateway の VPC Link を Cloud Map サービスエンドポイントに指定。
# Network Load Balancer を Service に関連付け
aws servicediscovery create-service \
--name api-service \
--namespace-id ns-xxx \
--dns-config '{
"DnsRecords": [{"Type": "A", "TTL": 60}]
}' \
--description "API Service for API Gateway"
# API Gateway VPC Link
aws apigateway create-vpc-link \
--name api-vpc-link \
--target-arns arn:aws:elasticloadbalancing:...:targetgroup/... \
--region ap-northeast-1
4. マルチリージョン・ディザスターリカバリー
複数リージョンの同一サービスを Cloud Map で管理。
# リージョン A の Namespace
aws servicediscovery create-private-dns-namespace \
--name prod.app.regional --vpc vpc-region-a
# リージョン B の Namespace
aws servicediscovery create-private-dns-namespace \
--name prod.app.regional --vpc vpc-region-b
# Route 53 ゲオロケーションルーティングで切り替え
# リージョンA の DNS → Route 53 Geolocation Policy で APAC 向け
# リージョンB の DNS → EMEA 向け
5. Kubernetes 外の ECS Anywhere(ハイブリッド)
オンプレミスサーバーを Cloud Map に登録。
# オンプレミス EC2 Instance Connect でサーバー登録
aws ssm send-command \
--instance-ids i-onpremise-001 \
--document-name AWS-RunShellScript \
--parameters 'commands=[
"aws servicediscovery register-instance --service-id srv-xxx --instance-id onprem-001 --attributes AWS_INSTANCE_IPV4=192.168.1.100 AWS_INSTANCE_PORT=8080"
]'
6. IoT デバイス管理
IoT デバイスを HTTP Namespace で登録・検出。
# HTTP Namespace での IoT デバイス登録
aws servicediscovery register-instance \
--service-id srv-iot-devices \
--instance-id device-sensor-001 \
--attributes '{
"AWS_INSTANCE_IPV4": "192.168.1.50",
"AWS_INSTANCE_PORT": "8883",
"deviceType": "temperature-sensor",
"location": "warehouse-a",
"firmwareVersion": "v1.2.3"
}'
ECS Service Discovery 統合
Cloud Map の最も一般的な使用方法は、ECS サービス作成時に自動統合。
ECS サービス作成時に Cloud Map を有効化
# Service Discovery 対応の ECS Service 作成
aws ecs create-service \
--cluster production-cluster \
--service-name payment-service \
--task-definition payment-task:5 \
--desired-count 3 \
--launch-type FARGATE \
--network-configuration 'awsvpcConfiguration={
subnets=[subnet-aaa, subnet-bbb, subnet-ccc],
securityGroups=[sg-0123456789abcdef0],
assignPublicIp=DISABLED
}' \
--service-registries '[{
"registryArn": "arn:aws:servicediscovery:ap-northeast-1:123456789012:service/srv-payment",
"port": 8080,
"containerName": "payment-app",
"containerPort": 8080
}]' \
--health-check-grace-period-seconds 60
自動化フロー:
- ECS が新タスク起動
- タスク内コンテナが UP 状態に
- タスクのメタデータ(IP、ポート)を抽出
- Cloud Map RegisterInstance 自動実行
- DNS/API で即座に検出可能に
ECS Service Discovery の削除・アップデート
# Service Registry の更新
aws ecs update-service \
--cluster production-cluster \
--service payment-service \
--service-registries '[{
"registryArn": "arn:aws:servicediscovery:ap-northeast-1:123456789012:service/srv-payment",
"port": 8080
}]'
# Service Discovery を無効化
aws ecs update-service \
--cluster production-cluster \
--service payment-service \
--service-registries '[]'
クロスアカウント・サービスディスカバリー
2025年8月の重要アップデート
AWS Cloud Map と AWS RAM の統合により、複数アカウント間でサービスを共有 できるようになりました。
中央アカウント(Platform Team)
├── Namespace: prod.services.shared
├── Service: payment-service
├── Service: order-service
└── [AWS RAM で複数ワークロードアカウントに共有]
│
├─ Workload Account A
│ └── Service ディスカバリーでエンドポイント参照
│
├─ Workload Account B
│ └── Service ディスカバリーでエンドポイント参照
│
└─ Workload Account C
└── Service ディスカバリーでエンドポイント参照
セットアップ手順
ステップ 1: 中央アカウントで Namespace・Service を作成
# 中央アカウントで実行
aws servicediscovery create-private-dns-namespace \
--name prod.services.shared \
--vpc vpc-central-account \
--region ap-northeast-1
# Service 作成
aws servicediscovery create-service \
--name payment-service \
--namespace-id ns-central-shared \
--dns-config '{...}'
ステップ 2: AWS RAM で Service を複数アカウントに共有
# Service ARN を取得
SERVICE_ARN="arn:aws:servicediscovery:ap-northeast-1:123456789012:service/srv-payment"
# AWS RAM Resource Share を作成
aws ram create-resource-share \
--name cloud-map-services-share \
--resource-arns "$SERVICE_ARN" \
--principals "arn:aws:organizations::123456789012:ou/o-xxx/ou-workload" \
--allow-external-principals false \
--region ap-northeast-1
ステップ 3: ワークロードアカウント側で検出
# ワークロードアカウント A で実行
aws servicediscovery discover-instances \
--namespace-name prod.services.shared \
--service-name payment-service \
--health-status HEALTHY
レスポンス例:
{
"Instances": [
{
"InstanceId": "payment-task-001",
"NamespaceName": "prod.services.shared",
"ServiceName": "payment-service",
"Attributes": {
"AWS_INSTANCE_IPV4": "10.0.1.5",
"AWS_INSTANCE_PORT": "8080"
}
}
]
}
CLI/SDK 実装例
Python SDK 実装(高度な例)
import boto3
import time
import random
import requests
from datetime import datetime
class ServiceDiscoveryClient:
def __init__(self, region='ap-northeast-1'):
self.sd = boto3.client('servicediscovery', region_name=region)
self.cache = {}
self.cache_ttl = 30 # 秒
def discover_service(self, namespace_name, service_name,
health_status='HEALTHY', use_cache=True):
"""サービスエンドポイント検出(キャッシュ付き)"""
cache_key = f"{namespace_name}:{service_name}"
# キャッシュ確認
if use_cache and cache_key in self.cache:
cached_time, instances = self.cache[cache_key]
if time.time() - cached_time < self.cache_ttl:
return instances
try:
response = self.sd.discover_instances(
NamespaceName=namespace_name,
ServiceName=service_name,
HealthStatus=health_status,
MaxResults=10
)
instances = response.get('Instances', [])
# キャッシュに保存
self.cache[cache_key] = (time.time(), instances)
return instances
except Exception as e:
print(f"Discovery error: {e}")
return []
def get_service_endpoint(self, namespace_name, service_name):
"""ランダムにサービスエンドポイントを選択"""
instances = self.discover_service(namespace_name, service_name)
if not instances:
raise Exception(f"No instances found for {service_name}")
instance = random.choice(instances)
attrs = instance['Attributes']
host = attrs.get('AWS_INSTANCE_IPV4')
port = attrs.get('AWS_INSTANCE_PORT')
return f"http://{host}:{port}"
def call_service(self, namespace_name, service_name,
endpoint_path, method='GET', **kwargs):
"""サービス検出して API 呼び出し"""
try:
base_url = self.get_service_endpoint(namespace_name, service_name)
url = f"{base_url}{endpoint_path}"
response = requests.request(method, url, **kwargs)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Service call failed: {e}")
raise
# 使用例
if __name__ == "__main__":
sd_client = ServiceDiscoveryClient()
# Payment Service を検出して呼び出し
try:
result = sd_client.call_service(
namespace_name='prod.services.local',
service_name='payment-service',
endpoint_path='/api/payments',
method='POST',
json={'amount': 100, 'currency': 'JPY'}
)
print(f"Payment created: {result}")
except Exception as e:
print(f"Error: {e}")
Node.js SDK 実装
const AWS = require('aws-sdk');
const axios = require('axios');
class ServiceDiscoveryClient {
constructor(region = 'ap-northeast-1') {
this.sd = new AWS.ServiceDiscovery({region});
this.cache = {};
this.cacheTTL = 30000; // 30 秒(ミリ秒)
}
async discoverService(namespaceName, serviceName,
healthStatus = 'HEALTHY') {
const cacheKey = `${namespaceName}:${serviceName}`;
// キャッシュ確認
if (cacheKey in this.cache) {
const {timestamp, instances} = this.cache[cacheKey];
if (Date.now() - timestamp < this.cacheTTL) {
return instances;
}
}
try {
const params = {
NamespaceName: namespaceName,
ServiceName: serviceName,
HealthStatus: healthStatus,
MaxResults: 10
};
const result = await this.sd.discoverInstances(params).promise();
const instances = result.Instances || [];
// キャッシュに保存
this.cache[cacheKey] = {
timestamp: Date.now(),
instances: instances
};
return instances;
} catch (error) {
console.error('Discovery error:', error);
return [];
}
}
async getServiceEndpoint(namespaceName, serviceName) {
const instances = await this.discoverService(
namespaceName,
serviceName
);
if (instances.length === 0) {
throw new Error(`No instances found for ${serviceName}`);
}
// ランダムに選択
const instance = instances[
Math.floor(Math.random() * instances.length)
];
const attrs = instance.Attributes;
const host = attrs.AWS_INSTANCE_IPV4;
const port = attrs.AWS_INSTANCE_PORT;
return `http://${host}:${port}`;
}
async callService(namespaceName, serviceName,
endpointPath, config = {}) {
try {
const baseUrl = await this.getServiceEndpoint(
namespaceName,
serviceName
);
const url = `${baseUrl}${endpointPath}`;
const response = await axios({
url,
...config
});
return response.data;
} catch (error) {
console.error('Service call failed:', error);
throw error;
}
}
}
// 使用例
(async () => {
const sdClient = new ServiceDiscoveryClient();
try {
const result = await sdClient.callService(
'prod.services.local',
'payment-service',
'/api/payments',
{
method: 'POST',
data: {
amount: 100,
currency: 'JPY'
}
}
);
console.log('Payment created:', result);
} catch (error) {
console.error('Error:', error);
}
})();
Terraform/CDK による IaC
Terraform 実装
# variables.tf
variable "app_name" {
default = "myapp"
}
variable "environment" {
default = "production"
}
# main.tf
provider "aws" {
region = "ap-northeast-1"
}
# Namespace の作成
resource "aws_service_discovery_private_dns_namespace" "main" {
name = "${var.environment}.${var.app_name}.local"
vpc = aws_vpc.main.id
tags = {
Name = "${var.app_name}-namespace"
Environment = var.environment
}
}
# Service の作成
resource "aws_service_discovery_service" "payment" {
name = "payment-service"
namespace_id = aws_service_discovery_private_dns_namespace.main.id
dns_config {
namespace_id = aws_service_discovery_private_dns_namespace.main.id
dns_records {
ttl = 60
type = "A"
}
dns_records {
ttl = 60
type = "SRV"
}
routing_policy = "WEIGHTED"
}
health_check_custom_config {
failure_threshold = 3
}
tags = {
Name = "payment-service"
}
}
# ECS Service との統合
resource "aws_ecs_service" "payment" {
name = "payment-service"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.payment.arn
desired_count = 3
launch_type = "FARGATE"
network_configuration {
subnets = aws_subnet.private[*].id
security_groups = [aws_security_group.ecs.id]
assign_public_ip = false
}
service_registries {
registry_arn = aws_service_discovery_service.payment.arn
port = 8080
container_name = "payment-app"
container_port = 8080
}
depends_on = [aws_service_discovery_service.payment]
}
# AWS RAM で複数アカウントに共有
resource "aws_ram_resource_share" "payment_service_share" {
name = "payment-service-share"
allow_external_principals = false
tags = {
Name = "payment-service-share"
}
}
resource "aws_ram_resource_association" "payment_service" {
resource_arn = aws_service_discovery_service.payment.arn
resource_share_arn = aws_ram_resource_share.payment_service_share.arn
}
resource "aws_ram_principal_association" "workload_ou" {
principal = "arn:aws:organizations::ACCOUNT_ID:ou/o-XXX/ou-WORKLOAD"
resource_share_arn = aws_ram_resource_share.payment_service_share.arn
}
AWS CDK 実装(TypeScript)
import * as cdk from 'aws-cdk-lib';
import * as servicediscovery from 'aws-cdk-lib/aws-servicediscovery';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ram from 'aws-cdk-lib/aws-ram';
export class ServiceDiscoveryStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Namespace 作成
const namespace = new servicediscovery.PrivateDnsNamespace(
this,
'MyAppNamespace',
{
name: 'production.myapp.local',
vpc: vpc, // VPC オブジェクト
description: 'Production namespace'
}
);
// Service 作成
const paymentService = new servicediscovery.Service(
this,
'PaymentService',
{
namespace: namespace,
name: 'payment-service',
dnsRecordType: servicediscovery.DnsRecordType.A_AND_SRV,
dnsTtl: cdk.Duration.seconds(60),
healthCheck: servicediscovery.HealthCheckConfig.custom({
failureThreshold: 3
})
}
);
// ECS Service への統合
const ecsService = new ecs.FargateService(
this,
'PaymentFargateService',
{
cluster: cluster,
taskDefinition: taskDef,
desiredCount: 3,
serviceName: 'payment-service',
cloudMapOptions: {
name: 'payment-service',
port: 8080,
dnsRecordType: servicediscovery.DnsRecordType.A_AND_SRV,
containerPort: 8080
}
}
);
// AWS RAM で複数アカウントに共有
const resourceShare = new ram.ResourceShare(
this,
'PaymentServiceShare',
{
allowExternalPrincipals: false,
resources: [
ram.Resource.fromArn(this, 'Service', paymentService.serviceArn)
]
}
);
// OU に対して共有
resourceShare.grantConsumption(
`arn:aws:organizations::${this.account}:ou/o-XXX/ou-WORKLOAD`
);
}
}
// アプリケーション
const app = new cdk.App();
new ServiceDiscoveryStack(app, 'ServiceDiscoveryStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: 'ap-northeast-1'
}
});
他のサービスディスカバリーとの比較
| 観点 | Cloud Map | Kubernetes Service Discovery | HashiCorp Consul | App Mesh | VPC Lattice |
|---|---|---|---|---|---|
| マネージド | 完全マネージド | なし(K8s 管理) | セルフマネージド / Consul Cloud | マネージド | マネージド |
| スケーリング | 自動スケーリング | K8s クラスタに依存 | 手動または自動化 | 自動 | 自動 |
| DNS サポート | 対応 | 対応 | 対応 | なし | なし |
| API ベース検出 | 対応 | 対応 | 対応 | Envoy API | URL ベース |
| マルチクラウド | AWS のみ | すべてのプラットフォーム | マルチクラウド対応 | AWS のみ | AWS のみ |
| トラフィック管理 | なし | なし | なし | 高度な制御 | ロードバランシング |
| 推奨用途 | ECS マイクロサービス | Kubernetes 標準 | マルチクラウド | サービスメッシュ | マルチ VPC |
| 学習曲線 | 低 | 中 | 中~高 | 高 | 中 |
ベストプラクティス
1. 命名規約の統一
[namespace].[environment].[company].local
例:
- services.production.acme.local(本番)
- services.staging.acme.local(ステージング)
- services.development.acme.local(開発)
Service 名:
- [service-name]-[version]
例:
- payment-service-v2
- order-service-v1
- inventory-service-v3
2. ヘルスチェックの適切な設定
# Route 53 Health Check(推奨:本番環境)
# TCP ポート監視 + カスタムヘルスチェック併用
FailureThreshold: 3
RequestInterval: 30 秒
→ 90 秒で不健全判定
# カスタムヘルスチェック(推奨:重要なサービス)
# アプリケーションが依存関係を自己報告
- DB 接続状態
- キャッシュ可用性
- 外部 API アクセス
3. キャッシング戦略
# SDK のディスカバリー結果をキャッシュ
# TTL: 30~60秒(リアルタイム性 vs キャッシュ効率のバランス)
# Route 53 DNS の場合は DNS TTL(60~300秒)
DISCOVERY_CACHE_TTL = 30 # 秒
# Cloud Map API 呼び出し削減
# → API コスト削減
# → レイテンシ改善
4. マルチアカウント環境での設計
中央アカウント(Platform Team)
├── Namespace: prod.services.shared
├── Service: payment-v2
├── Service: order-v1
└── AWS RAM で OU に共有
→ Workload Account 1-N で参照
→ Cross-account + Multi-region での一元管理
5. セキュリティ設定
# VPC 内での Private DNS(推奨)
# → インターネット非公開
# → VPC 間アクセスのみ(AWS PrivateLink)
# IAM ポリシー
{
"Effect": "Allow",
"Action": [
"servicediscovery:DiscoverInstances",
"servicediscovery:GetInstance"
],
"Resource": [
"arn:aws:servicediscovery:*:*:service/srv-*",
"arn:aws:servicediscovery:*:*:namespace/ns-*"
]
}
トラブルシューティング
1. インスタンスが検出されない
症状: DiscoverInstances の結果が空
原因と対策:
| 原因 | 確認方法 | 対策 |
|---|---|---|
| Namespace 名が不正確 | describe-private-dns-namespace |
正確なフルドメイン名を指定 |
| Service が見つからない | list-services |
Service ARN を確認 |
| インスタンスが登録されていない | list-instances |
RegisterInstance の実行確認 |
| ヘルスチェック失敗 | CloudWatch メトリクス | Health Check ログを確認 |
| VPC ルート設定 | VPC DNS 設定 | enableDnsSupport, enableDnsHostnames を有効化 |
# デバッグコマンド
# Namespace の確認
aws servicediscovery list-namespaces
# Service の確認
aws servicediscovery list-services --namespace-id ns-xxx
# Instance の確認
aws servicediscovery list-instances --service-id srv-xxx
# 詳細情報取得
aws servicediscovery get-instance --service-id srv-xxx --instance-id inst-001
# ヘルスチェック状態確認
aws route53 get-health-check-status --health-check-id hc-xxx
2. DNS 解決失敗
症状: nslookup payment-service.prod.myapp.local が失敗
原因と対策:
# VPC DNS 設定確認
aws ec2 describe-vpc-attribute --vpc-id vpc-xxx --attribute enableDnsSupport
aws ec2 describe-vpc-attribute --vpc-id vpc-xxx --attribute enableDnsHostnames
# サブネット DNS ホスト名設定
aws ec2 modify-vpc-attribute --vpc-id vpc-xxx --enable-dns-support
aws ec2 modify-vpc-attribute --vpc-id vpc-xxx --enable-dns-hostnames
# EC2 / ECS Task からのテスト
# ECS exec で Task に入って
aws ecs execute-command \
--cluster my-cluster \
--task <task-id> \
--container my-container \
--command "/bin/bash"
# Task 内で
nslookup payment-service.prod.myapp.local
# または
getent hosts payment-service.prod.myapp.local
3. クロスアカウント検出が機能しない
症状: ワークロードアカウント側で DiscoverInstances がエラー
原因と対策:
# 中央アカウント側:AWS RAM 共有確認
aws ram list-resource-shares --resource-owner OTHER_ACCOUNTS
# ワークロードアカウント側:共有リソース確認
aws ram list-resources --resource-owner OTHER_ACCOUNTS
# IAM ロール・ユーザーのアクセス権限確認
aws iam get-role-policy --role-name ecs-task-execution-role --policy-name service-discovery-policy
# CloudTrail で API 呼び出しエラー確認
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=ResourceName,AttributeValue=DiscoverInstances \
--max-results 10
セキュリティ・コンプライアンス
VPC 分離
┌─ VPC A ─────────────────┐
│ Namespace: app-a.local │
│ ↓ Private DNS │
│ Service Discovery │
└─────────────────────────┘
(VPC 内のみ解決)
┌─ VPC B ─────────────────┐
│ ↓ VPC Peering │
│ Service Discovery │
└─────────────────────────┘
(AWS PrivateLink で共有 VPC の Namespace にアクセス)
IAM ポリシー(最小権限)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowServiceDiscovery",
"Effect": "Allow",
"Action": [
"servicediscovery:DiscoverInstances",
"servicediscovery:GetInstance",
"servicediscovery:GetNamespace",
"servicediscovery:GetService"
],
"Resource": [
"arn:aws:servicediscovery:ap-northeast-1:123456789012:service/srv-payment",
"arn:aws:servicediscovery:ap-northeast-1:123456789012:namespace/ns-production"
]
},
{
"Sid": "AllowRoute53HealthCheck",
"Effect": "Allow",
"Action": [
"route53:GetHealthCheck",
"route53:GetHealthCheckStatus"
],
"Resource": "*"
}
]
}
料金モデル
| 課金項目 | 価格(米ドル) | 備考 |
|---|---|---|
| Cloud Map API クエリ | $0.10 / 100 万クエリ | DiscoverInstances 他 |
| Route 53 DNS クエリ | $0.40 / 100 万クエリ | DNS ベース検出 |
| Route 53 ヘルスチェック | $0.50 / チェック / 月 | TCP/HTTP/HTTPS |
| 登録インスタンス | 無料 | Storage 料金なし |
コスト最適化:
- API キャッシングで DiscoverInstances 呼び出し削減
- DNS TTL を長めに設定(最大 300秒)
- Health Check は本当に必要な Service のみに適用
2025-2026 最新動向
1. クロスアカウント・サービスディスカバリー(2025年8月)
AWS RAM との統合により、一つの Cloud Map Namespace を複数アカウント間で共有可能に。プラットフォームエンジニアが中央レジストリを管理し、複数ワークロードアカウントから参照できます。
利点:
- ネットワーク/セキュリティアカウントが一元管理
- ECS タスク・EC2 インスタンス・DynamoDB テーブルを共有
- Organizations OU 単位での権限制御
2. CloudWatch Application Map との統合(2025年11月)
非インストルメント化サービスの自動検出機能により、カスタムアプリケーション・レガシーシステムの可視化が強化。
影響:
- マイクロサービス依存関係の自動マッピング
- 異常検知の精度向上
- デプロイ前の影響分析
3. リージョン拡大(2025年)
Asia Pacific(Taipei)・Asia Pacific(New Zealand)リージョンでの一般提供開始。
4. API 低遅延化の継続
5秒以内のリソース更新反映で、より動的なスケーリング環境に対応。
学習リソース・参考文献
AWS 公式ドキュメント
ベンダー・オープンソース
-
HashiCorp Consul - Service Discovery & Mesh
- マルチクラウド対応・エンタープライズ機能
- Terraform Native Integration
-
Etcd - Distributed configuration
- Kubernetes ネイティブ
- 簡軽量な KV ストア
-
Eureka - Netflix Service Registry
- Spring Boot 統合
- Java / JVM エコシステム向け
-
Apache ZooKeeper - Centralized coordination
- 分散システム向け
- コンプレックスだが堅牢
参考ブログ・記事
- OneUpTime Blog - AWS Cloud Map Service Discovery Guide (2025-2026)
- AWS What’s New - Cloud Map Cross-Account Discovery (2025)
- AWS re:Invent 2024 - ECS Service Discovery 実装ワークショップ
実装チェックリスト
- [ ] Namespace 設計:Private DNS / Public / HTTP を選択
- [ ] Service テンプレート定義:ポート、DNS レコード、ルーティングポリシー
- [ ] ヘルスチェック設定:Route 53 / カスタム / なし を選択
- [ ] ECS Service Discovery 統合(該当する場合)
- [ ] IAM ロール・ポリシー設定:最小権限の実装
- [ ] キャッシング戦略:API キャッシュ TTL 設定
- [ ] クロスアカウント共有(AWS RAM):OU 単位での権限設定
- [ ] セキュリティ検証:VPC DNS 設定、ネットワークポリシー
- [ ] CloudTrail ロギング:監査ログの有効化
- [ ] CloudWatch モニタリング:カスタムメトリクス設定
- [ ] 災害復旧計画:マルチリージョン / フェイルオーバー検証
- [ ] 本番デプロイ前テスト:全 Namespace / Service / Instance の動作確認
まとめ
AWS Cloud Map は、マイクロサービス環境での動的なサービスエンドポイント管理 を解決するフルマネージドサービスです。
主要なポイント:
- ECS との密結合: Service Discovery は ECS 標準機能として統合され、タスク起動・停止に自動連動
- 複数の検出方式: DNS(Route 53)と API ベースで柔軟性が高い
- クロスアカウント対応(2025年8月新機能): AWS RAM との統合で マルチアカウント環境での一元管理が実現
- ヘルスベース自動除外: 不健全なインスタンスを自動検出対象から除外
- スケーラビリティ: 完全マネージドで無限スケーリング
- 低遅延: API ベースディスカバリーで 5 秒以内の更新反映
推奨用途:
- ECS Fargate マイクロサービス間通信
- Lambda ↔ ECS サービス呼び出し
- マルチアカウント・マルチリージョン環境
- ハイブリッド環境(ECS Anywhere)
今後の注目点:
- CloudWatch Application Map との連携強化
- リージョン拡大(APAC 重視)
- Observability 機能の統合
最終更新:2026-04-26 バージョン:v2.0