---
title: サービスディスカバリ
url: https://doc.liz6.com/ja/distributed-systems/05-members-and-discovery/03-service-discovery
locale: ja
area: distributed-systems
tags:
- distributed-systems
- members-and-discovery
date: 2026-06-30
modified: 2026-07-16
description: 動的にスケールアップ・ダウンするクラスタにおいて、呼び出し元はどの IP に接続すべきかどうやって知るのか？サービスディスカバリは、「登録→ヘルスチェック→クエリ」を標準プロセスとして整備する。consul/etcd は登録情報を保持し、DNS-SD とクライアント側ロードバランシングは異なるクエリ経路であり、ヘルスチェックが「このインスタンスがトラフィックを受けられるか」を決定する。
---

# サービスディスカバリ

> 動的にスケールアップ・ダウンするクラスタにおいて、呼び出し元はどの IP に接続すべきかどうやって知るのか？サービスディスカバリは、「登録→ヘルスチェック→クエリ」を標準プロセスとして整備する。consul/etcd は登録情報を保持し、DNS-SD とクライアント側ロードバランシングは異なるクエリ経路であり、ヘルスチェックが「このインスタンスがトラフィックを受けられるか」を決定する。

## 課題

サービスインスタンスの IP:port は動的に変化する（スケールアップ/ダウン、ローリングアップデート、フェイルオーバー）。呼び出し元は「現在利用可能なインスタンスはどれか」をどうやって知るのか？

## DNS-SD (DNS によるサービスディスカバリ)

成熟した DNS インフラストラクチャを活用してサービスディスカバリを行う：

```
サービス: api-service → DNS SRV レコード:
  _api._tcp.example.com SRV 10 60 8080 instance-1.example.com
  _api._tcp.example.com SRV 10 40 8080 instance-2.example.com

クライアント: dig SRV _api._tcp.example.com → [instance-1:8080, instance-2:8080]
```

利点：追加のコンポーネントが不要。制約：DNS TTL が変更の遅延を決定する（通常 30〜60 秒）。ネイティブなヘルスチェックは存在しない。

## Consul/etcd へのサービス登録

サービスインスタンス起動 → Consul/etcd へ登録（ヘルスチェック URL 付き）→ クライアントがクエリ → 正常なインスタンスの一覧を返す：

<svg viewBox="0 0 720 340" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Consul/etcd サービス登録と発見: インスタンス登録、ヘルスチェック、クライアントクエリ">
  <defs><marker id="sdah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="340" fill="#ffffff"/>
  <text x="360" y="30" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Consul/etcd サービス登録と発見: 登録 → ヘルスチェック → クライアントクエリ</text>

  <rect x="40" y="60" width="170" height="44" rx="8" fill="#e2e8f0"/>
  <text x="125" y="87" text-anchor="middle" font-size="13" font-weight="700" fill="#334155">Instance-1</text>
  <rect x="40" y="124" width="170" height="44" rx="8" fill="#e2e8f0"/>
  <text x="125" y="151" text-anchor="middle" font-size="13" font-weight="700" fill="#334155">Instance-2</text>

  <rect x="460" y="60" width="220" height="46" rx="8" fill="#4f46e5"/>
  <text x="570" y="88" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">Consul Server</text>

  <rect x="460" y="136" width="220" height="64" rx="8" fill="#0d9488"/>
  <text x="570" y="162" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">Consul Agent</text>
  <text x="570" y="182" text-anchor="middle" font-size="11" fill="#ccfbf1">ヘルスチェック GET /health</text>

  <rect x="250" y="238" width="220" height="48" rx="8" fill="#e2e8f0"/>
  <text x="360" y="266" text-anchor="middle" font-size="13" font-weight="700" fill="#334155">Client</text>

  <line x1="210" y1="82" x2="458" y2="83" stroke="#475569" stroke-width="1.6" marker-end="url(#sdah)"/>
  <text x="335" y="70" text-anchor="middle" font-size="11" fill="#475569">register(host:8080)</text>
  <line x1="210" y1="146" x2="458" y2="90" stroke="#475569" stroke-width="1.6" marker-end="url(#sdah)"/>
  <text x="300" y="132" text-anchor="middle" font-size="11" fill="#475569">register(host:8080)</text>

  <line x1="570" y1="106" x2="570" y2="134" stroke="#475569" stroke-width="1.6" marker-end="url(#sdah)"/>

  <line x1="360" y1="236" x2="562" y2="202" stroke="#475569" stroke-width="1.6" marker-end="url(#sdah)"/>
  <text x="465" y="212" text-anchor="middle" font-size="11" fill="#475569">GET /v1/health/service/api</text>
  <text x="465" y="227" text-anchor="middle" font-size="10" fill="#64748b">(正常なインスタンス一覧を返す)</text>

  <rect x="40" y="298" width="640" height="32" rx="8" fill="#eef2ff"/>
  <text x="360" y="318" text-anchor="middle" font-size="12" fill="#3730a3">register は登録関係のみを確立する。実際に「発見可能かどうか」を決定するのは、Consul Agent による継続的なヘルスチェックである。</text>
</svg>

サポート機能：HTTP API、DNS インターフェース（DNS-SD と互換）、ロングポーリングウォッチ（変更のプッシュ）。

## クライアント側 vs サーバー側ロードバランシング

```
クライアント側:
  [クライアント] → サービスディスカバリをクエリ → [I1, I2, I3] を取得 → 自ら選択 → 直接接続
  例: gRPC (Consul/etcd リゾルバー使用), Finagle, netflix-eureka

サーバー側:
  [クライアント] → LB (固定アドレス) → LB → [I1, I2, I3]
  例: kube-proxy, nginx upstream, HAProxy
```

比較：クライアント側はホップが1回減る（レイテンシ↓）が、クライアントがサービスディスカバリロジックを維持する必要がある。サーバー側ではクライアントはバックエンドの変化を認識する必要がないが、LB がボトルネックおよび単一障害点となる可能性がある。

## ヘルスチェック

- **パッシブ**: 実際のリクエストの失敗（HTTP 5xx, TCP RST）から判断する。追加のリクエストは不要だが、一時的なエラーによる誤判定を受ける可能性がある
- **アクティブ**: 定期的に GET /health を実行し、200 応答を期待する。信頼性が高いが負荷が増加する
- **TTL ベース (Consul)**: Agent が定期的に Consul へ健康状態を報告する。タイムアウト内にレポートを受信できない場合 → unhealthy とマーク → ディスカバリから削除

## 参考

- **Consul**: consul.io/docs/discovery
- **etcd**: etcd.io/docs
- **gRPC name resolution**: github.com/grpc/grpc/blob/master/doc/naming.md

*キーワード: サービスディスカバリ, DNS-SD, Consul, etcd, ヘルスチェック, クライアント側 LB, サーバー側 LB*
