マイクロサービスアーキテクチャは、サービス間の信頼性の高い通信、外部APIリクエストの保護、負荷分散を必要とします。プロキシサーバーは、サービス、外部API、およびクライアント間の仲介者としてこれらの課題を解決します。このガイドでは、プロキシをマイクロサービスインフラストラクチャに正しく統合する方法、さまざまなシナリオに使用するプロキシの種類、および安全な通信の設定方法について説明します。
マイクロサービスアーキテクチャにおけるプロキシの役割
マイクロサービスアーキテクチャにおいて、プロキシサーバーは、匿名化やブロック回避のための従来のプロキシの使用とは異なる、いくつかの重要な機能を果たします。ここでは、プロキシはインフラストラクチャの不可欠な部分となり、システムのコンポーネント間の信頼性の高い安全な通信を確保します。
マイクロサービスにおけるプロキシの主な役割:
- APIゲートウェイ — すべてのクライアントリクエストのための単一のエントリーポイントで、リクエストを適切なマイクロサービスにルーティングし、システムの内部アーキテクチャを隠します。
- サイドカープロキシ — 各サービスの隣で動作するプロキシコンテナ(Service Meshパターン)で、すべての着信および発信トラフィックをキャッチします。
- リバースプロキシ — 同一サービスの複数のインスタンス間での負荷分散、耐障害性の確保。
- フォワードプロキシ — 外部APIへの発信リクエストの制御と保護、インフラストラクチャの内部IPアドレスを隠します。
- セキュリティプロキシ — SSL/TLSの終了、認証、承認、DDoSやその他の攻撃からの保護。
プロキシは、重要なアーキテクチャパターンを実現することを可能にします: サーキットブレーカー(動作していないサービスの自動的な切断)、リトライロジック(障害時の再試行)、レート制限(リクエストの頻度制限)、リクエスト/レスポンス変換(データフォーマットの変換)。これにより、システムは障害に対してより耐性があり、複雑な分散インフラストラクチャの管理が簡素化されます。
重要: マイクロサービスアーキテクチャでは、プロキシは二つのレベルで機能します — クライアントのための外部ゲートウェイ(APIゲートウェイ)として、サービス間の内部プロキシ(Service Mesh)として。両方のレベルがシステムのセキュリティと信頼性にとって重要です。
さまざまな使用シナリオのためのプロキシの種類
プロキシの種類の選択は、マイクロサービスアーキテクチャにおける具体的なタスクに依存します。さまざまなシナリオは、速度、信頼性、匿名性、または地理的分散などの異なる特性を必要とします。
| シナリオ | プロキシの種類 | 理由 |
|---|---|---|
| サービス間の内部通信 | HTTP/HTTPSプロキシ(Envoy、NGINX) | 最大速度、低遅延、HTTP/2のサポート |
| 制限のある外部APIへのリクエスト | レジデンシャルプロキシ | レート制限の回避、実際のユーザーのIP、ブロックのリスクが低い |
| 分析のためのデータパース | データセンタープロキシ | 高速度、低コスト、大量リクエストに適している |
| モバイルAPIとの作業 | モバイルプロキシ | 実際のモバイルユーザーの模倣、モバイル専用APIへのアクセス |
| 負荷分散 | リバースプロキシ(HAProxy、NGINX) | トラフィックの分配、ヘルスチェック、障害時の自動切り替え |
| 地理的に分散したシステム | ジオターゲティング付きのレジデンシャルプロキシ | 地域APIへのアクセス、データローカリゼーションの要件への準拠 |
マイクロサービス間の内部通信には、通常、低遅延と高スループットに最適化されたEnvoy ProxyやNGINXのような専門のプロキシソリューションが使用されます。これらは最新のプロトコル(HTTP/2、gRPC)をサポートし、Service Meshシステムと統合されます。
外部APIとの作業では、選択は特定のサービスの要件に依存します。APIが厳しいレート制限を持っているか、データセンターのIPからのリクエストをブロックする場合は、レジデンシャルプロキシが必要です。速度が匿名性よりも重要な大量データ収集には、データセンタープロキシが適しています。モバイルIPアドレスを必要とするデバイスの種類を確認するAPIとの作業には、モバイルプロキシが必要です。
APIゲートウェイとしてのプロキシ: 保護とルーティング
APIゲートウェイは、すべてのクライアントリクエストのための単一のエントリーポイントとして機能する専門のプロキシサーバーです。クライアントが数十の異なるサービスに直接アクセスするのではなく、すべてのリクエストをAPIゲートウェイの1つのアドレスに送信し、必要なサービスにルーティングします。
APIゲートウェイの主な機能:
- リクエストのルーティング — URL、ヘッダー、またはその他のパラメータに基づいて、どのマイクロサービスがリクエストを処理するかを決定します。
- 認証と承認 — トークン(JWT、OAuth)の検証、さまざまなサービスへのアクセス管理。
- レート制限 — 過負荷やDDoSからの保護のために、1つのクライアントからのリクエスト数を制限します。
- レスポンスの集約 — 複数のサービスからのデータを1つのレスポンスに統合します。
- プロトコルの変換 — RESTからgRPC、HTTP/1.1からHTTP/2への変換。
- キャッシング — サービスへの負荷を減らすために、よくリクエストされるデータを保存します。
- ログ記録と監視 — すべてのリクエストのメトリックとログを集中管理します。
人気のあるAPIゲートウェイソリューションには、Kong、Tyk、AWS API Gateway、Azure API Management、NGINX Plus、Traefikがあります。選択は、システムの規模、パフォーマンス要件、および使用するクラウドプラットフォームによって異なります。
// APIゲートウェイとしてのNGINXの設定例
upstream auth_service {
server auth:8001;
}
upstream user_service {
server user:8002;
}
upstream order_service {
server order:8003;
}
server {
listen 80;
server_name api.example.com;
# リクエストの頻度制限
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
location /api/auth/ {
limit_req zone=api_limit burst=20;
proxy_pass http://auth_service/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /api/users/ {
# プロキシ前のトークン検証
auth_request /auth/verify;
proxy_pass http://user_service/;
}
location /api/orders/ {
auth_request /auth/verify;
proxy_pass http://order_service/;
}
# トークン検証のための内部エンドポイント
location = /auth/verify {
internal;
proxy_pass http://auth_service/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
}
APIゲートウェイは、システムの内部アーキテクチャを外部クライアントから隠します。クライアントは、どれだけのマイクロサービスが存在し、どのように相互作用しているかを知ることはなく、単一のAPIのみを視認します。これにより、バージョン管理が簡素化され、内部構造を変更してもクライアントに影響を与えず、内部サービスがインターネットから直接アクセスできないため、セキュリティが向上します。
Service Mesh (Istio, Linkerd)との統合
Service Meshは、各サービスの隣にデプロイされたプロキシサーバーを使用して、マイクロサービス間の通信を管理するインフラストラクチャ層です(サイドカーパターン)。外部トラフィックのみを処理するAPIゲートウェイとは異なり、Service Meshはサービス間のすべての内部トラフィックを制御します。
最も人気のあるService Meshソリューションには、Istio(Envoy Proxyをサイドカーとして使用)とLinkerd(独自の軽量プロキシを使用)があります。これらは、Kubernetes内の各ポッドの隣にプロキシコンテナを自動的に挿入し、すべての着信および発信トラフィックをキャッチします。
プロキシを介したService Meshの機能:
- 相互TLS (mTLS) — サービス間のすべてのトラフィックを相互認証付きで自動的に暗号化します。
- トラフィック管理 — ルーティングの管理、カナリアデプロイ、A/Bテスト。
- 可観測性 — コードを変更せずにメトリック、トレース、ログを自動的に収集します。
- 耐障害性 — サーキットブレイキング、リトライロジック、タイムアウト管理、テスト用の障害注入。
- サービスディスカバリー — サービスの自動検出と負荷分散。
# ルーティングのためのIstio VirtualServiceの設定例
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- match:
- headers:
version:
exact: "v2"
route:
- destination:
host: user-service
subset: v2
- route:
- destination:
host: user-service
subset: v1
weight: 90
- destination:
host: user-service
subset: v2
weight: 10 # カナリアデプロイ: v2に10%のトラフィック
---
# カスケード障害からの保護のためのサーキットブレーカー
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: user-service
spec:
host: user-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50
Service Meshは「分散モノリス」の問題を解決します — サービス間の相互作用のロジック(リトライ、タイムアウト、サーキットブレイキング)が各サービスのコードに重複することを防ぎます。代わりに、すべてのロジックがプロキシ層に移行され、サービスのコードが簡素化され、システム全体の一貫した動作が保証されます。
重要な利点は、トラフィックの完全な透明性です。サービス間の各リクエストはプロキシを通過し、メトリック(応答時間、エラーコード、ペイロードのサイズ)をログします。これらのデータは自動的に監視システム(Prometheus、Grafana)やトレーシングシステム(Jaeger、Zipkin)に送信され、分散システムの動作の完全なイメージを作成します。各サービスのコードに計測を追加する必要はありません。
プロキシを介した外部APIへのリクエストの保護
マイクロサービスはしばしば外部APIと相互作用します: 支払いシステム、ジオロケーションサービス、ソーシャルメディアAPI、データプロバイダー。外部APIへの直接リクエストは、内部IPアドレスの露出、レート制限を超えた場合のブロックリスク、発信トラフィックの制御の欠如など、いくつかの問題を引き起こします。
発信リクエストにプロキシを使用することで、これらの問題を解決し、追加の機能を提供します:
- インフラストラクチャの隠蔽 — 外部APIはプロキシのIPアドレスを見て、あなたのサーバーのIPアドレスを見ません。
- レート制限の回避 — リクエストを分散させるためのIPアドレスのローテーション。
- 地理的分散 — 必要な国のプロキシを介して地域APIにアクセス。
- 中央管理 — すべての発信リクエストの制御のための単一のポイント。
- レスポンスのキャッシング — 高価なAPIへのリクエスト数を減らします。
- 監視とログ記録 — 外部サービスへのすべてのリクエストを追跡します。
// Python: 外部APIへのリクエストのためのプロキシ設定
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class ExternalAPIClient:
def __init__(self, proxy_url, proxy_rotation=False):
self.session = requests.Session()
# プロキシの設定
self.proxies = {
'http': proxy_url,
'https': proxy_url
}
# 耐障害性のためのリトライロジック
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
def call_payment_api(self, data):
"""プロキシを介して支払いAPIにリクエストを送信します"""
try:
response = self.session.post(
'https://api.payment-provider.com/charge',
json=data,
proxies=self.proxies,
timeout=10,
headers={'User-Agent': 'MyService/1.0'}
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
# エラーのログ記録
print(f"Payment API error: {e}")
raise
# ローテーション用のプロキシプールを使用
class ProxyPool:
def __init__(self, proxy_list):
self.proxies = proxy_list
self.current = 0
def get_next(self):
proxy = self.proxies[self.current]
self.current = (self.current + 1) % len(self.proxies)
return proxy
# 初期化
proxy_pool = ProxyPool([
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080',
'http://user:pass@proxy3.example.com:8080'
])
# 各リクエストで次のプロキシを使用
client = ExternalAPIClient(proxy_pool.get_next())
厳しい制限やデータセンターのIPからのリクエストをブロックする外部APIとの作業には、レジデンシャルプロキシが必要です。これにより、実際の家庭ユーザーのIPアドレスが提供され、ブロックのリスクが低下し、地理的制限を回避できます。
// Node.js: 外部API用のプロキシと自動ローテーション
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');
class ExternalAPIService {
constructor(proxyList) {
this.proxyList = proxyList;
this.currentProxyIndex = 0;
this.requestCounts = new Map(); // レート制限用のリクエストカウンター
}
getNextProxy() {
const proxy = this.proxyList[this.currentProxyIndex];
this.currentProxyIndex = (this.currentProxyIndex + 1) % this.proxyList.length;
return proxy;
}
async callAPI(endpoint, data, options = {}) {
const proxyUrl = this.getNextProxy();
const agent = new HttpsProxyAgent(proxyUrl);
// レート制限: プロキシあたり1分間に100リクエストまで
const proxyKey = proxyUrl;
const now = Date.now();
const count = this.requestCounts.get(proxyKey) || { count: 0, resetTime: now + 60000 };
if (count.count >= 100 && now < count.resetTime) {
// 次のプロキシに切り替え
return this.callAPI(endpoint, data, options);
}
try {
const response = await axios({
method: options.method || 'POST',
url: endpoint,
data: data,
httpsAgent: agent,
timeout: options.timeout || 10000,
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; MyService/1.0)',
...options.headers
}
});
// カウンターを更新
if (now >= count.resetTime) {
this.requestCounts.set(proxyKey, { count: 1, resetTime: now + 60000 });
} else {
count.count++;
}
return response.data;
} catch (error) {
if (error.response?.status === 429) {
// レート制限を超えた - 別のプロキシに切り替え
console.log(`Rate limit on ${proxyUrl}, switching proxy`);
return this.callAPI(endpoint, data, options);
}
throw error;
}
}
}
// 使用
const apiService = new ExternalAPIService([
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080'
]);
module.exports = apiService;
負荷分散と耐障害性
プロキシサーバーは、負荷分散と障害時の自動切り替えを通じて、マイクロサービスシステムの高可用性を確保する上で重要な役割を果たします。同じサービスの複数のインスタンスが起動されている場合(水平スケーリングのため)、プロキシはリクエストをそれらの間で分配し、均等な負荷を確保します。
主な負荷分散アルゴリズム:
- ラウンドロビン — リスト内の各サーバーにリクエストを順番に送信し、均一なサーバーに対して簡単かつ効果的です。
- 最小接続数 — アクティブな接続数が最も少ないサーバーにリクエストを送信し、長いリクエストに適しています。
- IPハッシュ — クライアントをそのIPに基づいて特定のサーバーにバインドし、スティッキーセッションを提供します。
- 重み付きラウンドロビン — サーバーの能力に応じてリクエストを分配します(より強力なサーバーにはより多くのリクエストが送信されます)。
- ランダム — サーバーをランダムに選択し、ステートレスサービスに適しています。
# ヘルスチェックを使用した負荷分散のためのHAProxy設定
global
maxconn 4096
log stdout format raw local0
defaults
mode http
timeout connect 5s
timeout client 50s
timeout server 50s
option httplog
frontend api_frontend
bind *:80
default_backend api_servers
backend api_servers
balance roundrobin
# ヘルスチェック: 2秒ごとに/healthをチェック
option httpchk GET /health
http-check expect status 200
# リトライロジック
retries 3
option redispatch
# 重み付きサーバー(server3は2倍の能力)
server server1 10.0.1.10:8080 check weight 1 maxconn 500
server server2 10.0.1.11:8080 check weight 1 maxconn 500
server server3 10.0.1.12:8080 check weight 2 maxconn 1000
# バックアップサーバー(主要なサーバーが利用できない場合のみ使用)
server backup1 10.0.2.10:8080 check backup
ヘルスチェックは、耐障害性のための重要な機能です。プロキシは、各サーバーの可用性を定期的にチェックし(通常はHTTPエンドポイント/healthまたは/readyを介して)、動作していないサーバーを自動的に負荷分散プールから除外します。サーバーが復旧し、ヘルスチェックに応答を返すと、自動的にプールに戻ります。
プロキシを介した耐障害性の戦略:
- アクティブヘルスチェック — プロキシがサーバーの可用性を確認するために積極的にポーリングします。
- パッシブヘルスチェック — プロキシが実際のリクエストを監視し、エラーが蓄積されるとサーバーを除外します。
- サーキットブレーカー — カスケード障害を防ぐために問題のあるサービスを一時的にオフにします。
- グレースフルデグラデーション — 障害時に簡素化された動作モードやキャッシュデータに切り替えます。
- バックアップへのフェイルオーバー — 自動的にバックアップサーバーやリージョンに切り替えます。
// Python: 外部サービスへのプロキシ用のサーキットブレーカーの実装
from datetime import datetime, timedelta
from enum import Enum
class CircuitState(Enum):
CLOSED = "closed" # 通常の動作
OPEN = "open" # サービスが利用できない、リクエストがブロックされる
HALF_OPEN = "half_open" # 復旧後のテストモード
class CircuitBreaker:
def __init__(self, failure_threshold=5, timeout=60, success_threshold=2):
self.failure_threshold = failure_threshold # 開放までのエラー数
self.timeout = timeout # 復旧までの秒数
self.success_threshold = success_threshold # 閉じるための成功数
self.state = CircuitState.CLOSED
self.failures = 0
self.successes = 0
self.last_failure_time = None
def call(self, func, *args, **kwargs):
if self.state == CircuitState.OPEN:
if datetime.now() - self.last_failure_time > timedelta(seconds=self.timeout):
self.state = CircuitState.HALF_OPEN
print("サーキットブレーカー: HALF_OPENに移行")
else:
raise Exception("サーキットブレーカーがOPEN: サービスが利用できません")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise e
def _on_success(self):
self.failures = 0
if self.state == CircuitState.HALF_OPEN:
self.successes += 1
if self.successes >= self.success_threshold:
self.state = CircuitState.CLOSED
self.successes = 0
print("サーキットブレーカー: 復旧、CLOSEDに移行")
def _on_failure(self):
self.failures += 1
self.last_failure_time = datetime.now()
if self.failures >= self.failure_threshold:
self.state = CircuitState.OPEN
print(f"サーキットブレーカーがOPEN: {self.failures}回のエラーが続いています")
# 使用
breaker = CircuitBreaker(failure_threshold=3, timeout=30)
def call_external_service():
# プロキシを介して外部APIへのリクエストのコード
pass
try:
result = breaker.call(call_external_service)
except Exception as e:
# フォールバックロジック: キャッシュ、デフォルト値など
print(f"サービスが利用できません: {e}")
サービス間通信のセキュリティ
マイクロサービスアーキテクチャにおいて、プロキシサーバーは、トラフィックの暗号化、サービスの認証、攻撃からの保護、ネットワークセグメントの隔離など、複数のセキュリティレベルを提供します。セキュリティが適切に設定されていない場合、サービス間の内部トラフィックは傍受されたり、偽造されたりする可能性があります。
プロキシを介したセキュリティの重要な側面:
- 相互TLS (mTLS) — クライアントとサーバーの両方が互いの証明書を検証する双方向認証。Service Meshは、すべてのサービス間で自動的にmTLSを設定します。
- TLS終了 — プロキシがHTTPSトラフィックを境界で復号し、検証してサービスに安全なチャネルで転送します。
- JWT検証 — リクエストがサービスに到達する前に、プロキシレベルでアクセストークンを検証します。
- IPホワイトリスト — 許可されたIPアドレスからのみサービスへのアクセスを制限します。
- DDoS保護 — レート制限、接続制限、プロキシレベルでのSYN floodからの保護。
- WAF (Web Application Firewall) — 悪意のあるリクエストのフィルタリング、SQLインジェクション、XSSからの保護。
# SSL/TLSとセキュリティを使用したNGINXの設定
server {
listen 443 ssl http2;
server_name api.internal.example.com;
# SSL証明書
ssl_certificate /etc/nginx/certs/api.crt;
ssl_certificate_key /etc/nginx/certs/api.key;
# 最新のプロトコルと暗号
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# mTLS用のクライアント証明書
ssl_client_certificate /etc/nginx/certs/ca.crt;
ssl_verify_client on;
# セキュアヘッダー
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
# レート制限
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
limit_req zone=api burst=200 nodelay;
# 接続制限
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn addr 10;
# IPホワイトリスト
allow 10.0.0.0/8; # 内部ネットワーク
allow 172.16.0.0/12; # VPC
deny all;
location / {
# JWTトークンの検証
auth_jwt "Restricted API";
auth_jwt_key_file /etc/nginx/jwt_key.json;
proxy_pass http://backend_service;
# クライアント証明書の情報を転送
proxy_set_header X-Client-DN $ssl_client_s_dn;
proxy_set_header X-Client-Verify $ssl_client_verify;
}
}
Service Meshは、mTLS用の証明書を自動的に生成およびローテーションし、アクセスポリシーを適用し、サービス間のすべてのトラフィックを暗号化することで、セキュリティの設定を大幅に簡素化します。たとえば、Istioでは、「payment」サービスは「order」サービスからのリクエストのみを受け入れるというポリシーを設定でき、これはサービスのコードを変更することなくプロキシレベルで自動的に適用されます。
本番環境での重要な注意点: サービス間の内部通信には常にmTLSを使用してください。たとえ同じプライベートネットワーク内にあっても、これはman-in-the-middle攻撃から保護し、サービスレベルでの認証を確保します。
プロキシトラフィックの監視とログ記録
プロキシサーバーは、マイクロサービスシステム全体のトラフィックを集中監視するためのユニークな機会を提供します。すべてのトラフィックがプロキシを通過するため(APIゲートウェイを介した外部トラフィックとService Meshを介した内部トラフィックの両方)、各サービスを計測する必要なく、システムの動作を完全に可視化できます。
プロキシレベルで監視するための重要なメトリック:
- レイテンシ(遅延) — 各ステージでのリクエスト処理時間: プロキシ、サービス、外部API。
- スループット(処理能力) — 秒あたりのリクエスト数、転送されたデータ量。
- エラーレート — エラーの割合(4xx、5xx)、エラーの種類、問題のあるエンドポイント。
- 接続メトリック — アクティブな接続数、接続プールの使用状況。
- サーキットブレーカーの状態 — 各サービスのサーキットブレーカーの状態。
- SSL/TLSメトリック — 証明書のステータス、プロトコルのバージョン、ハンドシェイクのエラー。
# Prometheusへのメトリックエクスポート用のNGINX設定
server {
listen 9113;
location /metrics {
stub_status;
access_log off;
allow 10.0.0.0/8; # Prometheusサーバーのみ
deny all;
}
}
# 構造化ログのためのJSON形式でのログ記録
log_format json_combined escape=json
'{'
'"time_local":"$time_local",'
'"remote_addr":"$remote_addr",'
'"request":"$request",'
'"status": "$status",'
'"body_bytes_sent":"$body_bytes_sent",'
'"request_time":"$request_time",'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_addr":"$upstream_addr",'
'"http_referrer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for"'
'}';
access_log /var/log/nginx/access.log json_combined;
分散トレーシングは、プロキシを介した監視の最も強力な機能の1つです。各リクエストには一意のトレースIDが付与され、プロキシがヘッダーに追加してサービスのチェーンに渡します。トレーシングシステム(Jaeger、Zipkin)は、すべてのプロキシから情報を収集し、システムを通じてリクエストの完全な経路を構築し、各サービスでどれだけの時間を費やしたかを示します。
// Node.js: プロキシミドルウェアにトレーシングを追加
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const axios = require('axios');
const app = express();
// トレースIDを追加するためのミドルウェア
app.use((req, res, next) => {
// ヘッダーからトレースIDを取得するか、新しいものを作成します
const traceId = req.headers['x-trace-id'] || uuidv4();
const spanId = uuidv4();
// 次に渡すためにヘッダーに追加します
req.traceId = traceId;
req.spanId = spanId;
res.setHeader('x-trace-id', traceId);
// 処理開始のログを記録します
const startTime = Date.now();
res.on('finish', () => {
const duration = Date.now() - startTime;
// 分析用の構造化ログ
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
traceId: traceId,
spanId: spanId,
method: req.method,
path: req.path,
status: res.statusCode,
duration: duration,
userAgent: req.headers['user-agent'],
ip: req.ip
}));
});
next();
});
// トレーシングヘッダーを渡すプロキシエンドポイント
app.all('/api/*', async (req, res) => {
const targetService = determineTargetService(req.path);
try {
const response = await axios({
method: req.method,
url: `http://${targetService}${req.path}`,
data: req.body,
headers: {
...req.headers,
'x-trace-id': req.traceId,
'x-parent-span-id': req.spanId,
'x-span-id': uuidv4() // 下流リクエストのための新しいspan
}
});
res.status(response.status).json(response.data);
} catch (error) {
console.error(JSON.stringify({
traceId: req.traceId,
error: error.message,
service: targetService
}));
res.status(500).json({ error: 'サービスが利用できません' });
}
});
function determineTargetService(path) {
if (path.startsWith('/api/users')) return 'user-service:8080';
if (path.startsWith('/api/orders')) return 'order-service:8080';
return 'default-service:8080';
}
app.listen(3000);
プロキシのメトリックに基づくアラートは、問題を迅速に検出することを可能にします。たとえば、次のようなアラートを設定できます: 突然のレイテンシの増加(おそらく、サービスの1つが劣化している)、エラーレートがしきい値を超える(コードや依存関係の問題)、トラフィックパターンの変化(DDoS攻撃やウイルス負荷の可能性)。
PythonとNode.jsの実装例
マイクロサービスにおけるプロキシの統合の実践的な例を、PythonとNode.jsで見ていきます。内部通信、外部APIとの作業、負荷分散のシナリオに焦点を当てます。
Python: 外部API用のプロキシを持つサービス
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import httpx
import asyncio
from typing import List, Optional
import logging
app = FastAPI()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ProxyConfig(BaseModel):
url: str
max_requests_per_minute: int = 60
class ProxyPool:
def __init__(self, proxies: List[ProxyConfig]):
self.proxies = proxies
self.current_index = 0
self.request_counts = {p.url: 0 for p in proxies}
self.reset_time = asyncio.get_event_loop().time() + 60
async def get_next_proxy(self) -> str:
# 毎分カウンターをリセット
current_time = asyncio.get_event_loop().time()
if current_time >= self.reset_time:
self.request_counts = {p.url: 0 for p in self.proxies}
self.reset_time = current_time + 60
# 利用可能なリクエストを持つプロキシを探す
for _ in range(len(self.proxies)):
proxy = self.proxies[self.current_index]
self.current_index = (self.current_index + 1) % len(self.proxies)
if self.request_counts[proxy.url] < proxy.max_requests_per_minute:
self.request_counts[proxy.url] += 1
return proxy.url
# すべてのプロキシが制限に達しました
raise HTTPException(status_code=429, detail="すべてのプロキシがレート制限されています")
# プロキシプールの初期化
proxy_pool = ProxyPool([
ProxyConfig(url="http://user:pass@proxy1.example.com:8080", max_requests_per_minute=100),
ProxyConfig(url="http://user:pass@proxy2.example.com:8080", max_requests_per_minute=100),
ProxyConfig(url="http://user:pass@proxy3.example.com:8080", max_requests_per_minute=100)
])
class ExternalAPIClient:
def __init__(self, proxy_pool: ProxyPool):
self.proxy_pool = proxy_pool
async def fetch_data(self, endpoint: str, params: dict = None) -> dict:
proxy_url = await self.proxy_pool.get_next_proxy()
async with httpx.AsyncClient(proxies={"http://": proxy_url, "https://": proxy_url}) as client:
try:
response = await client.get(
endpoint,
params=params,
timeout=10.0,
headers={"User-Agent": "MyMicroservice/1.0"}
)
response.raise_for_status()
logger.info(f"{proxy_url}を介して{endpoint}からデータを正常に取得しました")
return response.json()
except httpx.HTTPStatusError as e:
logger.error(f"{endpoint}からのHTTPエラー {e.response.status_code}")
raise HTTPException(status_code=e.response.status_code, detail=str(e))
except httpx.RequestError as e:
logger.error(f"{endpoint}へのリクエストエラー: {e}")
raise HTTPException(status_code=503, detail="外部APIが利用できません")
api_client = ExternalAPIClient(proxy_pool)
@app.get("/data/{resource_id}")
async def get_external_data(resource_id: str):
"""プロキシを介して外部APIからデータを取得するエンドポイント"""
external_endpoint = f"https://api.external-service.com/v1/resources/{resource_id}"
try:
data = await api_client.fetch_data(external_endpoint)
return {"status": "success", "data": data}
except HTTPException:
raise
except Exception as e:
logger.error(f"予期しないエラー: {e}")
raise HTTPException(status_code=500, detail="内部サーバーエラー")
@app.get("/health")
async def health_check():
return {"status": "healthy", "service": "external-api-proxy"}
# 実行: uvicorn main:app --host 0.0.0.0 --port 8000
Node.js: 負荷分散を持つAPIゲートウェイ
const express = require('express');
const axios = require('axios');
const rateLimit = require('express-rate-limit');
const app = express();
app.use(express.json());
// マイクロサービスの設定
const services = {
users: [
{ url: 'http://user-service-1:8001', healthy: true, activeConnections: 0 },
{ url: 'http://user-service-2:8001', healthy: true, activeConnections: 0 },
// ... その他のサービス
],
orders: [
{ url: 'http://order-service-1:8002', healthy: true, activeConnections: 0 },
{ url: 'http://order-service-2:8002', healthy: true, activeConnections: 0 },
// ... その他のサービス
],
};
// 負荷分散のためのミドルウェア
function loadBalancer(service) {
return (req, res, next) => {
const healthyServices = services[service].filter(s => s.healthy);
const targetService = healthyServices[Math.floor(Math.random() * healthyServices.length)];
if (!targetService) {
return res.status(503).json({ error: 'サービスが利用できません' });
}
req.targetService = targetService;
next();
};
}
// ユーザーサービスのルート
app.use('/api/users', loadBalancer('users'), async (req, res) => {
try {
const response = await axios({
method: req.method,
url: `${req.targetService.url}${req.path}`,
data: req.body,
headers: req.headers,
});
res.status(response.status).json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({ error: error.message });
}
});
// 注文サービスのルート
app.use('/api/orders', loadBalancer('orders'), async (req, res) => {
try {
const response = await axios({
method: req.method,
url: `${req.targetService.url}${req.path}`,
data: req.body,
headers: req.headers,
});
res.status(response.status).json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({ error: error.message });
}
});
// サーバーの起動
app.listen(3000, () => {
console.log('API Gatewayがポート3000で起動しました');
});