App Mesh + Cloud Mapを1年本番運用して見えてきた「使いどころ」と「限界」【EKS実録2026】
「サービスメッシュ入れれば全部解決」と思ってませんか?EKS上でApp Mesh + Cloud Mapを1年回して見えた本音——何が機能して、どこで詰まったか率直に書きます。
先日チームの振り返りで「App Mesh、本当に必要だったんだっけ?」という話になって、導入から1年以上経ってようやく正直に答えられるようになってきた。結論から言うと「使いどころは確実にある。ただし思ってたよりコストが高い」というのが本音だ。
うちのチームは2025年初頭にEKS上のマイクロサービス基盤を全面刷新するタイミングで、App Mesh + Cloud Mapのスタックを採用した。当時は「サービスメッシュを入れれば可観測性と通信制御が一気に解決する」という甘い期待があって、正直なところ設計よりもノリで決めた部分が大きかった。その後の1年でどんな痛みがあって、何が本当に機能したかを書いておきたい。
余談だが、EKSのコスト最適化に興味があればEKS コスト最適化2026|Spot/On-Demand戦略とKarpenter活用も読んでみてほしい。App Meshのオーバーヘッドとコストは密接に関係する話なので。
App Mesh + Cloud Mapの構成と実際に動かした基盤
実際に本番で動かしていた構成はこんな感じだ。
graph TB
subgraph VPC["VPC (10.0.0.0/16)"]
subgraph AZ1["AZ-a"]
subgraph EKS_A["EKS Node Group (AZ-a)"]
POD_API_A["api-service Pod\n+ Envoy Sidecar"]
POD_ORDER_A["order-service Pod\n+ Envoy Sidecar"]
end
end
subgraph AZ2["AZ-c"]
subgraph EKS_B["EKS Node Group (AZ-c)"]
POD_API_B["api-service Pod\n+ Envoy Sidecar"]
POD_ORDER_B["order-service Pod\n+ Envoy Sidecar"]
end
end
subgraph MESH["App Mesh Control Plane"]
VIRTUAL_SVC_API["VirtualService\napi.internal"]
VIRTUAL_SVC_ORDER["VirtualService\norder.internal"]
VIRTUAL_NODE_API["VirtualNode\napi-node"]
VIRTUAL_NODE_ORDER["VirtualNode\norder-node"]
VIRTUAL_ROUTER["VirtualRouter\n(重み付きルーティング)"]
end
subgraph CLOUDMAP["Cloud Map"]
NS["Private DNS Namespace\ninternal.example.com"]
SVC_REG_API["Service Registry\napi-service"]
SVC_REG_ORDER["Service Registry\norder-service"]
end
subgraph OBSERVABILITY["可観測性スタック"]
XRAY["X-Ray"]
CW["CloudWatch Container Insights"]
end
end
subgraph EXTERNAL["External"]
ALB["ALB (Ingress)"]
CLIENT["クライアント"]
end
CLIENT --> ALB
ALB --> POD_API_A
ALB --> POD_API_B
POD_API_A -->|Envoy経由| VIRTUAL_SVC_ORDER
POD_API_B -->|Envoy経由| VIRTUAL_SVC_ORDER
VIRTUAL_SVC_ORDER --> VIRTUAL_ROUTER
VIRTUAL_ROUTER --> VIRTUAL_NODE_ORDER
VIRTUAL_NODE_ORDER --> NS
NS --> SVC_REG_ORDER
SVC_REG_ORDER --> POD_ORDER_A
SVC_REG_ORDER --> POD_ORDER_B
POD_API_A -.->|トレース| XRAY
POD_ORDER_A -.->|トレース| XRAY
XRAY --> CW
Cloud MapのPrivate DNS Namespaceを使って、order-service.internal.example.com みたいな名前でサービスディスカバリするのが基本設計だ。App MeshのVirtualServiceがこの名前解決をラップして、Envoyサイドカーが実際のトラフィック制御を担う。
実際のCloud Map + App Mesh連携のマニフェストはこんな感じで書いていた。
# Cloud Map namespace (Terraform)
resource "aws_service_discovery_private_dns_namespace" "internal" {
name = "internal.example.com"
description = "Private namespace for service discovery"
vpc = aws_vpc.main.id
tags = {
Environment = "production"
ManagedBy = "terraform"
}
}
resource "aws_service_discovery_service" "order_service" {
name = "order-service"
dns_config {
namespace_id = aws_service_discovery_private_dns_namespace.internal.id
dns_records {
ttl = 10
type = "A"
}
routing_policy = "MULTIVALUE"
}
health_check_custom_config {
failure_threshold = 1
}
}
# App Mesh VirtualNode (Kubernetes CRD)
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualNode
metadata:
name: order-service
namespace: production
spec:
meshRef:
name: main-mesh
serviceDiscovery:
awsCloudMap:
namespaceName: internal.example.com
serviceName: order-service
attributes:
- key: ECS_TASK_DEFINITION_FAMILY
value: order-service
listeners:
- portMapping:
port: 8080
protocol: http
healthCheck:
protocol: http
path: /health
healthyThreshold: 2
unhealthyThreshold: 3
timeoutMillis: 2000
intervalMillis: 5000
logging:
accessLog:
file:
path: /dev/stdout
ここまではドキュメント通りに動いた。問題はここからだった。
1年間で踏んだ地雷とEnvoyのやっかいな現実
最初の1ヶ月で気づいたのがEnvoyサイドカーのリソース消費だ。うちの環境ではサービス数が15を超えたあたりから、Envoyが各Podで常時100〜150MBのメモリを使うようになった。小さいように見えるが、マイクロサービスが細かく分割されていると相当なオーバーヘッドになる。数字で見ると一目瞭然で、導入前後でPodのメモリ使用量はどのサービスも軒並み1.5倍以上に膨らんだ。
xychart-beta
title "Envoyサイドカー導入前後のメモリ使用量(Pod平均)"
x-axis ["api-svc", "order-svc", "user-svc", "payment-svc", "notify-svc"]
y-axis "メモリ使用量 (MB)" 0 --> 600
bar [180, 210, 160, 240, 150]
bar [310, 355, 285, 390, 275]
これはKarpenterのNodePoolを調整して何とか乗り越えたが、当時は原因特定に数日かかった。Karpenterノードプロビジョニング最適化|2026年EKS完全ガイドでも触れているが、Envoyが入ると前提のリソース計算がかなり変わる。インスタンスタイプの選定からやり直しになったのはさすがに想定外だった。
もう一つ地味にしんどかったのが、Cloud MapとKubernetes DNSの二重管理だ。KubernetesにはCoreDNSが既にあるのに、Cloud MapのDNSと両方管理することになる。TTLの設定ミスや、ヘルスチェック設定のズレで、デプロイ後しばらくトラフィックが古いインスタンスに流れ続けることが2回あった。
ハマった設定を晒しておく。
# Cloud Mapのヘルスチェック状態確認
aws servicediscovery list-instances \
--service-id srv-xxxxxxxxxx \
--query 'Instances[*].{Id:Id,Health:Attributes.AWS_INIT_HEALTH_STATUS}'
# 出力例(トラブル時)
# [
# { "Id": "10.0.1.45", "Health": "HEALTHY" },
# { "Id": "10.0.1.46", "Health": "HEALTHY" },
# { "Id": "10.0.2.12", "Health": "UNHEALTHY" } # ← Podは削除済みなのに残ってた
# ]
# 手動で古いインスタンスを削除するときの対処
aws servicediscovery deregister-instance \
--service-id srv-xxxxxxxxxx \
--instance-id 10.0.2.12
Cloud MapはKubernetesのEndpointの変化を自動追跡してくれるが、Pod終了時の登録解除が遅延することがある。TTLを短くするのが第一の対策だが、短すぎるとDNSクエリが増えてコストが上がる。うちでは10秒に落ち着いたが、正直まだ最適解かどうか確信はない。このあたりの「どちらに転んでもコストがかかる」感じが、二重管理のしんどさを象徴していると思う。
一方でVirtualRouterを使った重み付きルーティングは、カナリアリリースで実際に助かったケースがあった。
apiVersion: appmesh.k8s.aws/v1beta2
kind: VirtualRouter
metadata:
name: order-service-router
namespace: production
spec:
meshRef:
name: main-mesh
listeners:
- portMapping:
port: 8080
protocol: http
routes:
- name: canary-route
httpRoute:
match:
prefix: /
action:
weightedTargets:
- virtualNodeRef:
name: order-service-v2 # 新バージョン 10%
weight: 10
- virtualNodeRef:
name: order-service-v1 # 旧バージョン 90%
weight: 90
retryPolicy:
httpRetryEvents:
- server-error
- gateway-error
maxRetries: 3
perRetryTimeout:
unit: ms
value: 2000
Kubernetes標準のCanary実装よりずっとシンプルで、X-RayとのトレースIDの連携も自動で入るのが地味に便利だった。ただしVirtualRouterの設定ミスをすると無音で全トラフィックが落ちることがあって、一度ヒヤリとした経験がある。設定変更後は必ずX-Rayでトレースを確認する習慣がついた。
Cloud Map単体での使い方とECS混在環境での真価
App Meshを外してCloud Mapだけで使うケースも実は半年くらい並行して試していた。うちはECSサービスとEKSサービスが混在していた時期があって、そこではCloud Mapだけで統一のサービスディスカバリを提供するのが正解だった。App Meshの複雑さを抜きにすると、Cloud Map自体はかなりシンプルで扱いやすい。
sequenceDiagram
participant EKS_POD as EKS Pod
participant CLOUDMAP as Cloud Map
participant ECS_SVC as ECS Service
EKS_POD->>CLOUDMAP: DNS解決 legacy-service.internal.example.com
CLOUDMAP-->>EKS_POD: 10.0.3.45:8080
EKS_POD->>ECS_SVC: HTTP リクエスト
ECS_SVC-->>EKS_POD: レスポンス
Note over CLOUDMAP: ECSはサービス定義で自動登録<br/>EKSはAWS Cloud Map Controller for K8sで登録
EKS側でCloud Map Controllerを使うと、KubernetesのServiceリソースの変化が自動でCloud Mapに反映される。これは思ったより安定していて、ECS側からEKSのサービスをDNSで引けるようになったのはかなり便利だった。ECS/EKS混在という一見面倒な状況で、逆にCloud Mapの価値がいちばん光ったと思っている。
# aws-cloud-map-controller-for-k8s の設定例
apiVersion: v1
kind: ConfigMap
metadata:
name: cloud-map-controller-config
namespace: aws-cloud-map-controller-system
data:
controller-config.yaml: |
clusterName: production-eks-cluster
region: ap-northeast-1
namespaceConfig:
- namespaceName: internal.example.com
namespaceId: ns-xxxxxxxxxxxx
serviceFilter:
labelSelector:
cloudmap-register: "true" # このラベルがついたServiceだけ登録
機能面とコストを各観点で整理するとこうなる。
| 観点 | App Mesh + Cloud Map | Cloud Map単体 | VPC Lattice |
|---|---|---|---|
| サービスディスカバリ | ◎ | ◎ | ◎ |
| トラフィック制御 | ◎ | △(L4のみ) | ◯(L7あり) |
| 可観測性(トレース) | ◎(X-Ray自動) | △(別途実装) | ◯(CloudWatch) |
| マルチクラスター対応 | △ | ◯ | ◎ |
| ECS/EKS混在 | ◯ | ◎ | ◎ |
| 運用コスト(人的) | 高い | 低い | 中程度 |
| インフラコスト | 高い(Envoy分) | 低い | 中程度 |
| 学習コスト | 高い | 低い | 中程度 |
| 2026年時点のAWS推奨度 | △(ECSはService Connectへ) | ◯ | ◎ |
ECSについてはECS Service Connectに移行して6ヶ月、App Meshの複雑さから解放された話でも書いたが、正直ECS + App Meshの組み合わせはもう新規採用しなくていいと思っている。EKS専用として使うかどうかという判断になってきた。
2026年時点の現実的な判断基準
1年本番運用して出てきた問いは「App Meshを今から入れるべきか?」だ。2026年時点では、AWSの方向性的にもVPC LatticeやECS Service Connectに重心が移っていて、App Meshの新機能開発は正直足踏み感がある。個人的にはすでに「枯れたサービスとして使い続けるか、移行コストを払うか」の二択フェーズに入っていると感じている。
意思決定のフローを整理するとこうなる。
flowchart TD
START(["新規サービスメッシュ検討"]) --> Q1{"EKS専用?<br/>ECS混在あり?"}
Q1 -->|"EKS専用"| Q2{"サービス数は?"}
Q1 -->|"ECS混在あり"| Q3{"ECSはService Connect<br/>移行可能?"}
Q2 -->|"5サービス以下"| REC1["Kubernetes標準機能<br/>(Service/CoreDNS)で十分"]
Q2 -->|"6〜20サービス"| Q4{"L7トラフィック制御<br/>必要?"}
Q2 -->|"20サービス以上"| Q5{"Istio/Cilium<br/>検討可能?"}
Q4 -->|"不要"| REC2["Cloud Map Controller<br/>+ Prometheus/Grafana"]
Q4 -->|"必要"| Q6{"マルチクラスター<br/>要件あり?"}
Q6 -->|"なし"| REC3["App Mesh<br/>(既存踏襲なら許容)"]
Q6 -->|"あり"| REC4["VPC Lattice<br/>または Cilium Mesh"]
Q5 -->|"可能"| REC5["Cilium Service Mesh<br/>(eBPF、Envoy不要)"]
Q5 -->|"難しい"| REC3
Q3 -->|"可能"| REC6["ECS: Service Connect<br/>EKS: Cloud Map Controller"]
Q3 -->|"難しい"| REC7["Cloud Map統一<br/>App Meshは最終手段"]
style REC1 fill:#d4edda
style REC2 fill:#d4edda
style REC4 fill:#d4edda
style REC5 fill:#d4edda
style REC6 fill:#d4edda
style REC3 fill:#fff3cd
style REC7 fill:#f8d7da
個人的に一番注目しているのはCilium Service Meshで、Envoyサイドカーを使わずeBPFでメッシュ機能を実現できる点が魅力だ。コンテナセキュリティ完全ガイド2026|eBPF・SBOM・サプライチェーン対策でeBPFの可能性について触れているが、サイドカーレスのアーキテクチャはリソース効率の観点から見逃せない選択肢になってきている。
実際のところ、うちのチームは今年後半にApp Meshをどうするか再検討フェーズに入っている。候補はCilium Mesh移行かVPC Lattice部分採用のどちらかで、まだ結論は出ていない。
パフォーマンス面でのデータも1年分取れたので貼っておく。
xychart-beta
title "サービス間レイテンシ p99 (ms):App Mesh有無の比較(測定期間6ヶ月)"
x-axis ["1月", "2月", "3月", "4月", "5月", "6月"]
y-axis "レイテンシ p99 (ms)" 0 --> 80
line [18, 19, 20, 21, 19, 20]
line [28, 30, 29, 31, 28, 30]
上が素のKubernetes Service、下がApp Mesh有りだ。Envoyのオーバーヘッドは常時10〜12ms程度あった。これをどう評価するかは完全に要件次第で、トレーサビリティや細かいトラフィック制御が必要なら許容できる数字だし、純粋なスループット重視なら痛い数字だ。個人的には、このレイテンシ増加よりも運用上の複雑さのほうがチームへのインパクトは大きかったと感じている。
まとめ
1年以上App Mesh + Cloud Mapを本番で動かしてきて、正直に言えることをまとめる。
- Cloud MapはシンプルなサービスディスカバリとしてECS/EKS混在環境で今でも価値がある。 DNSベースの設計は理解しやすく、ECS Service Connectとも共存できる
- App MeshのEnvoyサイドカーは確実にリソースを食う。 Pod数 × 100〜150MBのメモリオーバーヘッドは事前に計算して予算に入れておくべきだった
- 重み付きルーティングとX-Rayの自動連携は本当に便利。 カナリアリリースとトレースが統合されているのは、標準機能だけでは再現が難しい価値だった
- 2026年時点では新規のECS + App Meshはおすすめしない。 ECSはService Connect、EKSは要件次第でVPC LatticeかCilium Meshを先に検討したほうがいい
- 段階的なアーキテクチャ変更に備えて、Cloud MapのPrivate Namespaceだけでも早めに整備しておく価値はある。 移行先が何であれ、サービスディスカバリの基盤として使い回せる
次のアクションとしては、既存App Meshユーザーなら現行の移行コストを試算しつつVPC Latticeのプレビューを触ってみることをおすすめする。完全移行は大変でも、新規サービスだけVPC Latticeで立てて比較するくらいはすぐできる。皆さんのチームはサービスメッシュどうしてます?EKSで別のアプローチ取ってる方いれば話聞いてみたい。