Skip to content
Published on

サービスメッシュ(Service Mesh)実践ガイド: Istio·Envoy·Linkerd基盤mTLS·トラフィック管理·可観測性確保

Authors
  • Name
    Twitter
Service Mesh Architecture

はじめに

マイクロサービスアーキテクチャの普及に伴い、サービス間通信の複雑さが急激に増加している。認証、暗号化、トラフィック管理、可観測性、障害隔離などの横断的関心事(cross-cutting concerns)を各サービスに直接実装すると、コードの重複と運用負担が指数的に増大する。サービスメッシュ(Service Mesh)はこれらのネットワーキング関心事をインフラレイヤーに抽出し、アプリケーションコードの変更なしに一貫したセキュリティ、可観測性、トラフィック制御を提供する。

本記事では、サービスメッシュのコア概念であるデータプレーンとコントロールプレーンを説明し、Istio(Envoyサイドカー)とLinkerdを比較し、mTLS設定、トラフィック分割、サーキットブレーカー、可観測性ツール、そして最新のAmbient Meshまで実践例とともに解説する。本番環境で遭遇し得る代表的な障害シナリオと対応策も併せて整理する。

サービスメッシュコア概念

データプレーンとコントロールプレーン

サービスメッシュは大きく2つのレイヤーで構成される。

レイヤー役割実装
データプレーンサービス間の全ネットワークトラフィックをインターセプトして処理Envoy(Istio)、linkerd2-proxy(Linkerd)
コントロールプレーンプロキシ設定配布、証明書管理、サービスディスカバリIstiod(Istio)、destination/identity(Linkerd)

Istio vs Linkerd比較

項目IstioLinkerd
プロキシEnvoy(C++)linkerd2-proxy(Rust)
コントロールプレーンIstiod(統合)destination、identity、proxy-injector
プロキシメモリ約50MB+ / サイドカー約20-30MB / サイドカー
コントロールプレーンメモリ1-2GB(本番)200-300MB
L7機能非常に豊富(ヘッダールーティング、ミラーリング等)コア機能中心
学習曲線急峻緩やか
CRD数50+約10個
Ambientモードサポート(ztunnel + waypoint)未サポート
適用環境大規模、複雑なトラフィック管理中小規模、迅速な導入

性能オーバーヘッド比較

# ベンチマーク結果(2000 RPS基準)
# P99レイテンシ追加量:
#   No mesh:          基準値
#   Linkerd:          +2.0ms
#   Istio Sidecar:    +5.8ms
#   Istio Ambient:    +2.4ms

# リソース使用量(サイドカー当たり):
#   Envoy:            ~50MB RAM, ~0.5 vCPU
#   linkerd2-proxy:   ~20MB RAM, ~0.2 vCPU

# 大規模環境(12800 RPS)ベンチマークにおいて
# Istio Ambientが最低レイテンシを記録
# Linkerd対比P99で約11msの差

Istioアーキテクチャと設定

Istioインストール

# istioctl インストール
curl -L https://istio.io/downloadIstio | sh -
cd istio-1.24.0
export PATH=$PWD/bin:$PATH

# 本番プロファイルでインストール
istioctl install --set profile=default -y

# ネームスペースにサイドカー自動インジェクション有効化
kubectl label namespace default istio-injection=enabled

# インストール確認
istioctl verify-install
kubectl get pods -n istio-system

VirtualServiceとDestinationRule

# VirtualService: トラフィックルーティングルール定義
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: reviews-route
  namespace: default
spec:
  hosts:
    - reviews
  http:
    - match:
        - headers:
            end-user:
              exact: beta-tester
      route:
        - destination:
            host: reviews
            subset: v2
          weight: 100
    - route:
        - destination:
            host: reviews
            subset: v1
          weight: 90
        - destination:
            host: reviews
            subset: v2
          weight: 10
---
# DestinationRule: サービスサブセットとポリシー定義
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: reviews-destination
  namespace: default
spec:
  host: reviews
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        h2UpgradePolicy: DEFAULT
        http1MaxPendingRequests: 100
        http2MaxRequests: 1000
    loadBalancer:
      simple: ROUND_ROBIN
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2
      trafficPolicy:
        loadBalancer:
          simple: LEAST_REQUEST

トラフィック分割(カナリアデプロイメント)

# カナリアデプロイメント: v2へ段階的にトラフィック増加
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: my-service-canary
spec:
  hosts:
    - my-service
  http:
    - route:
        - destination:
            host: my-service
            subset: stable
          weight: 95
        - destination:
            host: my-service
            subset: canary
          weight: 5
# カナリアトラフィック比率段階的増加スクリプト
# 5% → 10% → 25% → 50% → 100%
for weight in 10 25 50 100; do
  stable_weight=$((100 - weight))
  kubectl patch virtualservice my-service-canary --type=json \
    -p="[
      {\"op\":\"replace\",\"path\":\"/spec/http/0/route/0/weight\",\"value\":${stable_weight}},
      {\"op\":\"replace\",\"path\":\"/spec/http/0/route/1/weight\",\"value\":${weight}}
    ]"
  echo "Canary weight: ${weight}%, Stable weight: ${stable_weight}%"
  echo "Monitoring for 5 minutes..."
  sleep 300
done

サーキットブレーカー設定

# DestinationRuleを利用したサーキットブレーカー
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: payment-service-cb
spec:
  host: payment-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 50
      http:
        http1MaxPendingRequests: 50
        http2MaxRequests: 100
        maxRequestsPerConnection: 10
        maxRetries: 3
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 30s
      maxEjectionPercent: 50
      minHealthPercent: 30
# サーキットブレーカーステータス確認
istioctl proxy-config cluster <pod-name> --fqdn payment-service.default.svc.cluster.local -o json | grep -A 20 "outlierDetection"

# Envoy統計でサーキットブレーカー動作確認
kubectl exec <pod-name> -c istio-proxy -- pilot-agent request GET stats | grep "circuit_breakers"

mTLS設定とセキュリティ

Strict mTLS適用

# ネームスペース全体にStrict mTLS適用
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: default
spec:
  mtls:
    mode: STRICT
---
# メッシュ全体にStrict mTLS適用(istio-systemネームスペースに作成)
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT

特定ポート除外(レガシーサービス連携)

# 特定サービスで一部ポートのみPERMISSIVEモード
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: legacy-integration
  namespace: default
spec:
  selector:
    matchLabels:
      app: legacy-adapter
  mtls:
    mode: STRICT
  portLevelMtls:
    8080:
      mode: PERMISSIVE

証明書管理とSPIFFE

# 現在のmTLSステータス確認
istioctl authn tls-check <pod-name>

# 証明書情報確認
istioctl proxy-config secret <pod-name> -o json

# SPIFFE ID形式: spiffe://cluster.local/ns/NAMESPACE/sa/SERVICE_ACCOUNT
# IstioはKubernetesサービスアカウントに基づいてSPIFFE IDを自動割り当て

# 証明書有効期限確認(デフォルト24時間、自動更新)
kubectl exec <pod-name> -c istio-proxy -- \
  openssl x509 -noout -dates -in /var/run/secrets/istio/tls/cert-chain.pem

# 証明書ローテーション強制実行(デバッグ用)
kubectl delete secret istio-ca-root-cert -n default
# Istiodが自動的に新しい証明書を発行

Authorization Policy(アクセス制御)

# 特定サービスからのアクセスのみ許可
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: payment-access
  namespace: default
spec:
  selector:
    matchLabels:
      app: payment-service
  rules:
    - from:
        - source:
            principals:
              - cluster.local/ns/default/sa/order-service
              - cluster.local/ns/default/sa/checkout-service
      to:
        - operation:
            methods: ['POST', 'GET']
            paths: ['/api/v1/payments/*']
---
# 全アクセス拒否(デフォルト拒否ポリシー)
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: default
spec: {}

Ambient Mesh(サイドカーレスモード)

Ambient Meshアーキテクチャ

Ambient Meshはサイドカーを排除し、2つのレイヤーでメッシュ機能を提供する。

レイヤーコンポーネント機能
L4(Secure Overlay)ztunnel(ノード毎DaemonSet)mTLS、L4認可、L4テレメトリ
L7(Waypoint)waypointプロキシ(ネームスペース毎)HTTPルーティング、L7認可、L7テレメトリ
# AmbientモードでIstioインストール
istioctl install --set profile=ambient -y

# ネームスペースをAmbientメッシュに追加
kubectl label namespace default istio.io/dataplane-mode=ambient

# ztunnel DaemonSet確認
kubectl get pods -n istio-system -l app=ztunnel

# L7機能が必要な場合Waypointプロキシをデプロイ
istioctl waypoint apply --namespace default --name default-waypoint

# Waypointプロキシ確認
kubectl get pods -n default -l istio.io/gateway-name=default-waypoint

Ambient vs Sidecar比較

# Sidecarモード:
#   長所: 完全なL7制御、成熟したエコシステム
#   短所: Pod毎のプロキシオーバーヘッド、再起動が必要
#   リソース: ~50MB RAM + ~0.5 vCPU / Pod

# Ambientモード:
#   長所: Pod再起動不要、低リソースオーバーヘッド
#   短所: L7はwaypoint必要、比較的新しい技術
#   リソース: ztunnel ~30MB RAM / ノード + waypoint共有

# 選択基準:
#   既存ワークロード移行 → Ambientを優先検討
#   細かなL7制御が必要 → Sidecar維持
#   リソース節約優先 → Ambient
#   安定性優先 → Sidecar(より成熟)

可観測性(Observability)確保

Kiali ダッシュボード

# Kialiインストール(Istioアドオン)
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/kiali.yaml

# Kialiダッシュボードアクセス
istioctl dashboard kiali

# Kialiが提供する情報:
# - サービス間トラフィックフローグラフ
# - リクエスト成功率 / エラー率
# - P50/P90/P99レイテンシ
# - mTLSステータス(ロックアイコン)
# - Istio設定検証(エラーハイライト)

分散トレーシング(Jaeger/Zipkin)

# Jaegerインストール
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/jaeger.yaml

# Jaegerダッシュボードアクセス
istioctl dashboard jaeger

# アプリケーションでトレースヘッダーの伝播が必須
# 以下のヘッダーをupstreamに転送する必要がある:
# x-request-id
# x-b3-traceid
# x-b3-spanid
# x-b3-parentspanid
# x-b3-sampled
# x-b3-flags
# traceparent
# tracestate
# Python Flaskでのトレースヘッダー伝播例
import requests
from flask import Flask, request

app = Flask(__name__)

TRACE_HEADERS = [
    'x-request-id',
    'x-b3-traceid',
    'x-b3-spanid',
    'x-b3-parentspanid',
    'x-b3-sampled',
    'x-b3-flags',
    'traceparent',
    'tracestate',
]

def propagate_headers():
    headers = {}
    for header in TRACE_HEADERS:
        value = request.headers.get(header)
        if value:
            headers[header] = value
    return headers

@app.route('/api/orders')
def get_orders():
    # ダウンストリームサービス呼び出し時にトレースヘッダーを伝播
    headers = propagate_headers()
    response = requests.get(
        'http://payment-service:8080/api/payments',
        headers=headers
    )
    return response.json()

Prometheus + Grafanaメトリクス

# PrometheusとGrafanaインストール
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/prometheus.yaml
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/grafana.yaml

# Grafanaダッシュボードアクセス
istioctl dashboard grafana

# Istioが自動収集する主要メトリクス:
# istio_requests_total          - 総リクエスト数
# istio_request_duration_milliseconds - リクエストレイテンシ
# istio_request_bytes           - リクエストサイズ
# istio_response_bytes          - レスポンスサイズ
# istio_tcp_connections_opened_total - TCP接続数
# Prometheusでの主要クエリ例
# サービス別エラー率(5xx)
# rate(istio_requests_total{response_code=~"5.."}[5m])
#   /
# rate(istio_requests_total[5m])

# P99レイテンシ
# histogram_quantile(0.99,
#   sum(rate(istio_request_duration_milliseconds_bucket[5m]))
#   by (le, destination_service_name))

障害シナリオと対応

シナリオ1: サイドカーインジェクション失敗

# 症状: PodはRunningだがサイドカー(istio-proxy)がない

# 1. ネームスペースラベル確認
kubectl get namespace default --show-labels
# istio-injection=enabledラベルがあるか確認

# 2. Podのコンテナリスト確認
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].name}'
# istio-proxyがリストにない場合インジェクション失敗

# 3. 原因診断
# a. ネームスペースラベル欠如
kubectl label namespace default istio-injection=enabled

# b. Podにインジェクション無効化アノテーションがある場合
kubectl get pod <pod-name> -o jsonpath='{.metadata.annotations.sidecar\.istio\.io/inject}'
# "false"の場合インジェクションが無効化された状態

# c. Webhook設定確認
kubectl get mutatingwebhookconfiguration istio-sidecar-injector -o yaml

# 4. 手動インジェクション(緊急時)
istioctl kube-inject -f deployment.yaml | kubectl apply -f -

# 5. Pod再起動(インジェクション適用のため)
kubectl rollout restart deployment <deployment-name>

シナリオ2: 証明書ローテーション失敗

# 症状: サービス間通信失敗、TLSハンドシェイクエラー

# 1. 証明書ステータス確認
istioctl proxy-config secret <pod-name>
# VALIDステータスと有効期限確認

# 2. Istiodログで証明書関連エラー確認
kubectl logs -n istio-system deployment/istiod | grep -i "certificate\|cert\|error"

# 3. CA証明書確認
kubectl get secret istio-ca-secret -n istio-system -o jsonpath='{.data.ca-cert\.pem}' | base64 -d | openssl x509 -noout -dates

# 4. 証明書強制更新
# Podのistio-proxyを再起動
kubectl delete pod <pod-name>

# 5. Istiod再起動(CA問題の場合)
kubectl rollout restart deployment istiod -n istio-system

# 6. Root CAローテーション(計画された作業)
# 新しいRoot CA作成後、中間CAを通じた段階的切替
# 公式ドキュメントのCA rotation guideを参照

シナリオ3: 過剰メモリ使用(Envoy OOM)

# 症状: istio-proxyコンテナがOOMKilledで再起動

# 1. 現在のリソース使用量確認
kubectl top pod <pod-name> --containers

# 2. Envoy統計確認
kubectl exec <pod-name> -c istio-proxy -- pilot-agent request GET stats/memory

# 3. リソース制限調整
kubectl patch deployment <deployment-name> --type=json \
  -p='[{"op":"replace","path":"/spec/template/metadata/annotations/sidecar.istio.io~1proxyMemoryLimit","value":"512Mi"}]'

# 4. グローバルプロキシリソース設定(IstioOperator)
# istio-operator.yamlで以下のように設定
# spec:
#   meshConfig:
#     defaultConfig:
#       proxyMetadata: {}
#   values:
#     global:
#       proxy:
#         resources:
#           requests:
#             cpu: 100m
#             memory: 128Mi
#           limits:
#             cpu: 500m
#             memory: 512Mi

運用時の注意事項

  1. 段階的導入: サービスメッシュを一度にクラスター全体に適用せず、非クリティカルなワークロードから段階的に拡張する。PERMISSIVE mTLSモードから始めてSTRICTに移行する。

  2. リソース予算確保: Envoyサイドカー基準でPod毎に約50MB RAM + 0.5 vCPUを追加確保する。大規模クラスターではこのオーバーヘッドが相当な量になる可能性がある。

  3. トレースヘッダー伝播: 分散トレーシングが正しく機能するには、アプリケーションでトレースヘッダー(x-b3-traceidなど)を必ず伝播する必要がある。サービスメッシュが自動的に行わない部分である。

  4. CRD管理: Istioは50以上のCRDを使用する。アップグレード時にCRDの互換性を必ず確認し、カナリアアップグレードを推奨する。

  5. Ambient Meshの検討: 新規導入であればAmbient Meshを積極的に検討する。サイドカーオーバーヘッドなしにL4セキュリティを即座に確保でき、L7機能は必要なサービスにのみwaypointをデプロイすればよい。

  6. Istiod高可用性: 本番環境ではIstiodを最低2つ以上のレプリカで運用し、Pod Disruption Budgetを設定する。

# Istiodレプリカ拡張
kubectl scale deployment istiod -n istio-system --replicas=3

# PDB設定
kubectl apply -f - <<ENDF
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: istiod-pdb
  namespace: istio-system
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: istiod
ENDF

まとめ

サービスメッシュは、マイクロサービス環境においてセキュリティ、可観測性、トラフィック管理という3つのコア課題をインフラレベルで解決する。Istioは豊富な機能と細かな制御を、Linkerdは軽量化と迅速な導入を強みとしている。最新のAmbient Meshはサイドカーオーバーヘッドを排除しつつコアセキュリティ機能を提供することで、サービスメッシュ導入の障壁を大幅に下げている。

本番環境で最も重要なのは段階的導入である。PERMISSIVE mTLSから始めて可観測性を確保し、安定性を確認した後STRICTモードに移行する段階的アプローチが成功の鍵である。サービスメッシュが提供する一貫した可観測性とセキュリティは、マイクロサービス運用の複雑さを大幅に軽減してくれるだろう。

参考資料