目次

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): オンプレミス環境を含むハイブリッドサービスディスカバリー

目次

  1. 概要
  2. Cloud Map が解決する課題
  3. 主な特徴
  4. アーキテクチャ
  5. コアコンポーネント
  6. Namespace の種類
  7. Service と Instance の管理
  8. ヘルスチェック戦略
  9. 主要ユースケース
  10. ECS Service Discovery 統合
  11. クロスアカウント・サービスディスカバリー
  12. CLI/SDK 実装例
  13. Terraform/CDK による IaC
  14. 他のサービスディスカバリーとの比較
  15. ベストプラクティス
  16. トラブルシューティング
  17. セキュリティ・コンプライアンス
  18. 料金モデル
  19. 2025-2026 最新動向
  20. 学習リソース・参考文献
  21. 実装チェックリスト
  22. まとめ

概要

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

データフロー

  1. 登録フェーズ

    • ECS Service Launcher が新タスクを起動
    • タスクのメタデータから IP・ポート・カスタム属性を抽出
    • Cloud Map RegisterInstance API を呼び出し
    • リソース情報が Namespace > Service > Instance に保存
  2. 検出フェーズ

    • クライアントが DiscoverInstances API または DNS クエリを実行
    • Cloud Map がヘルスチェック状態を確認
    • 健全なインスタンスのみを返却
  3. 除外フェーズ

    • ヘルスチェック失敗を検出
    • 自動的に検出対象から除外
    • 新しい健全なインスタンスを優先

コアコンポーネント

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

自動化フロー:

  1. ECS が新タスク起動
  2. タスク内コンテナが UP 状態に
  3. タスクのメタデータ(IP、ポート)を抽出
  4. Cloud Map RegisterInstance 自動実行
  5. 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 公式ドキュメント

ベンダー・オープンソース

参考ブログ・記事


実装チェックリスト

  • [ ] 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 は、マイクロサービス環境での動的なサービスエンドポイント管理 を解決するフルマネージドサービスです。

主要なポイント:

  1. ECS との密結合: Service Discovery は ECS 標準機能として統合され、タスク起動・停止に自動連動
  2. 複数の検出方式: DNS(Route 53)と API ベースで柔軟性が高い
  3. クロスアカウント対応(2025年8月新機能): AWS RAM との統合で マルチアカウント環境での一元管理が実現
  4. ヘルスベース自動除外: 不健全なインスタンスを自動検出対象から除外
  5. スケーラビリティ: 完全マネージドで無限スケーリング
  6. 低遅延: API ベースディスカバリーで 5 秒以内の更新反映

推奨用途:

  • ECS Fargate マイクロサービス間通信
  • Lambda ↔ ECS サービス呼び出し
  • マルチアカウント・マルチリージョン環境
  • ハイブリッド環境(ECS Anywhere)

今後の注目点:

  • CloudWatch Application Map との連携強化
  • リージョン拡大(APAC 重視)
  • Observability 機能の統合

最終更新:2026-04-26 バージョン:v2.0