このトピックでは、Service Mesh 環境でサービスにアクセスする際に、サーバーアプリケーションがクライアントの送信元 IP アドレスを取得して保持する方法について説明します。
前提条件
バージョン 1.15 以降の ASM Enterprise Edition または Ultimate Edition インスタンスが必要です。詳細については、「ASM インスタンスの作成」および「ASM インスタンスのアップグレード」をご参照ください。
ACK マネージドクラスターが作成されていること。詳細については、「ACK マネージドクラスターの作成」をご参照ください。
イングレスゲートウェイがデプロイされていること。詳細については、「イングレスゲートウェイの作成」をご参照ください。
kubectl を介してクラスターに接続されていること。詳細については、「クラスターの KubeConfig ファイルを取得し、kubectl を使用してクラスターに接続する」をご参照ください。
背景情報
送信元 IP アドレスは、次の典型的なユースケースを含む多くのシナリオで広く使用されています。
アプリケーションのアクセス制御: たとえば、多くのアプリケーションは、ユーザーが異なるリージョンからログインしたことを検出すると、追加の認証を強制します。これは、送信元 IP アドレスを取得することで実現できます。
シンプルなセッション維持: クライアントアドレスに応じて、送信元 IP アドレスに基づいてロードバランシングを実行できます。これにより、同じクライアントからのリクエストが同じサービスインスタンスに転送されます。
アクセスログとモニタリング統計: 送元 IP アドレスを含むアクセスログとメトリックは、開発者が統計を分析するのに役立ちます。
クラウドベースの Server Load Balancer も、クライアントの送信元 IP をバックエンドサービスに渡すことをサポートしています。Istio は、アプリケーションが送信元 IP アドレスを取得する機能を提供する必要があります。しかし、Istio を使用する場合、Sidecar プロキシが Pod に挿入された後、すべてのインバウンドトラフィックは Envoy からリダイレクトされます。現在、Envoy はローカルアドレス (127.0.0.1) にバインドされているアプリケーションにトラフィックを送信するため、アプリケーションは実際の送信元 IP アドレスを確認できません。
サンプルアプリケーションのデプロイ
sleep アプリケーションをデプロイします。
次の内容を使用して sleep.yaml を作成します。
apiVersion: v1 kind: ServiceAccount metadata: name: sleep --- apiVersion: v1 kind: Service metadata: name: sleep labels: app: sleep service: sleep spec: ports: - port: 80 name: http selector: app: sleep --- apiVersion: apps/v1 kind: Deployment metadata: name: sleep spec: replicas: 1 selector: matchLabels: app: sleep template: metadata: labels: app: sleep spec: terminationGracePeriodSeconds: 0 serviceAccountName: sleep containers: - name: sleep image: curlimages/curl command: ["/bin/sleep", "3650d"] imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /etc/sleep/tls name: secret-volume volumes: - name: secret-volume secret: secretName: sleep-secret optional: true次のコマンドを実行して sleep アプリケーションをデプロイします。
kubectl -n default apply -f sleep.yaml
httpbin アプリケーションをデプロイします。
次の内容を使用して httpbin.yaml を作成します。
apiVersion: v1 kind: Service metadata: name: httpbin labels: app: httpbin spec: ports: - name: http port: 8000 selector: app: httpbin --- apiVersion: apps/v1 kind: Deployment metadata: name: httpbin spec: replicas: 1 selector: matchLabels: app: httpbin version: v1 template: metadata: labels: app: httpbin version: v1 spec: containers: - image: docker.io/citizenstig/httpbin imagePullPolicy: IfNotPresent name: httpbin ports: - containerPort: 8000次のコマンドを実行して httpbin アプリケーションをデプロイします。
kubectl -n default apply -f httpbin.yaml
シナリオ 1: 東西トラフィック
ステップ 1: デフォルト構成でサーバーに表示されるクライアントの送信元 IP アドレスの確認
Istio では、東西サービスにアクセスすると、Sidecar の挿入により、サービスに出入りするすべてのトラフィックが Envoy によってインターセプトされ、プロキシされます。その後、Envoy はリクエストをアプリケーションに転送します。したがって、アプリケーションが受信するリクエストの送信元アドレスは、Envoy のアクセスアドレスである 127.0.0.6 になります。
次のコマンドを実行して Pod のステータスを確認します。
kubectl -n default get pods -o wide期待される結果:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES httpbin-c85bdb469-4ll2m 2/2 Running 0 3m22s 172.17.X.XXX cn-hongkong.10.0.0.XX <none> <none> sleep-8f764df66-q7dr2 2/2 Running 0 3m9s 172.17.X.XXX cn-hongkong.10.0.0.XX <none> <none>期待される結果から、sleep アプリケーションのアドレスが
172.17.X.XXXであることがわかります。次のコマンドを実行して sleep コンテナーからリクエストを送信します。
kubectl -n default exec -it deploy/sleep -c sleep -- curl http://httpbin:8000/ip期待される結果:
{ "origin": "127.0.0.6" }期待される結果から、httpbin アプリケーションが受信したリクエストの送信元アドレスは、sleep アプリケーションのアドレスではなく、Envoy のアクセスアドレスである
127.0.0.6であることがわかります。ソケット情報から送信元 IP アドレスが 127.0.0.6 であることを確認します。
httpbin コンテナーにログインし、次のコマンドを実行して netstat をインストールします。
apt update & apt install net-toolshttpbin コンテナーを終了し、次のコマンドを実行してポート 80 に基づく実行情報を表示します。
kubectl -n default exec -it deploy/httpbin -c httpbin -- netstat -ntp | grep 80期待される結果:
tcp 0 0 172.17.X.XXX:80 127.0.0.6:42691 TIME_WAIT -期待される結果から、送信元 IP アドレスが
127.0.0.6であることがわかります。
httpbin Pod のプロキシログの内容を表示します。
フォーマットされたログの例は次のとおりです。
{ "trace_id":null, "bytes_received":0, "upstream_host":"172.17.X.XXX:80", "authority":"httpbin:8000", "downstream_remote_address":"172.17.X.XXX:56160", "upstream_service_time":"1", "upstream_transport_failure_reason":null, "istio_policy_status":null, "path":"/ip", "bytes_sent":28, "request_id":"4501a50a-dab0-44c9-b52c-2a4f425a****", "protocol":"HTTP/1.1", "method":"GET", "duration":1, "start_time":"2022-11-22T16:09:30.394Z", "user_agent":"curl/7.86.0-DEV", "upstream_local_address":"127.0.0.6:42169", "response_flags":"-", "route_name":"default", "response_code":200, "upstream_cluster":"inbound|80||", "x_forwarded_for":null, "downstream_local_address":"172.17.X.XXX:80", "requested_server_name":"outbound_.8000_._.httpbin.default.svc.cluster.local" }ログから、次の情報を取得できます。
"downstream_remote_address":"172.17.X.XXX:56160": sleep のアドレス。"downstream_local_address":"172.17.X.XXX:80": sleep がアクセスした宛先アドレス。"upstream_local_address":"127.0.0.6:42169": httpbin に接続する httpbin Envoy のローカルアドレス (この時点で取得される送信元 IP アドレスは127.0.0.6です)。"upstream_host":"172.17.X.XXX:80": httpbin Envoy がアクセスした宛先アドレス。
ステップ 2: サーバーが正しいクライアントの送信元 IP アドレスを取得できるようにサービスメッシュを構成する
方法 1: TPROXY 透明インターセプトモードを介してクライアントの送信元 IP アドレスを保持する (CentOS 以外のオペレーティングシステムに適用可能)
httpbin アプリケーションのデプロイメントを変更して、インバウンドトラフィックのインターセプトモードとして TPROXY を使用します。
次のコマンドを実行して、httpbin アプリケーションのデプロイメントを変更します。
kubectl patch deployment -n default httpbin -p '{"spec":{"template":{"metadata":{"annotations":{"sidecar.istio.io/interceptionMode":"TPROXY"}}}}}'次のコマンドを実行して sleep コンテナーからリクエストを送信します。
kubectl -n default exec -it deploy/sleep -c sleep -- curl http://httpbin:8000/ip期待される結果:
{ "origin": "172.17.X.XXX" }期待される結果から、httpbin が sleep の実際の IP アドレスを取得できることがわかります。
次のコマンドを実行して、ポート 80 に基づく実行情報を表示します。
説明Pod が再起動された後、netstat を再インストールする必要があります。
kubectl -n default exec -it deploy/httpbin -c httpbin -- netstat -ntp | grep 80期待される結果:
tcp 0 0 172.17.X.XXX:80 172.17.X.XXX:36728 ESTABLISHED -期待される結果から、送信元 IP アドレスが
172.17.X.XXXであることがわかります。httpbin Pod のプロキシログの内容を表示します。
フォーマットされたログの例は次のとおりです。
{ "route_name":"default", "bytes_received":0, "trace_id":null, "request_id":"1ccabe60-63cf-469b-8565-99cac546****", "upstream_cluster":"inbound|80||", "response_flags":"-", "protocol":"HTTP/1.1", "upstream_transport_failure_reason":null, "requested_server_name":"outbound_.8000_._.httpbin.default.svc.cluster.local", "response_code":200, "user_agent":"curl/7.86.0-DEV", "start_time":"2022-11-22T16:03:32.803Z", "path":"/ip", "authority":"httpbin:8000", "bytes_sent":31, "downstream_remote_address":"172.17.X.XXX:39058", "upstream_service_time":"1", "method":"GET", "downstream_local_address":"172.17.X.XXX:80", "duration":1, "upstream_host":"172.17.X.XXX:80", "istio_policy_status":null, "upstream_local_address":"172.17.X.XXX:46129", "x_forwarded_for":null }ログから、次の情報を取得できます。
"downstream_remote_address":"172.17.X.XXX:39058": sleep のアドレス。"downstream_local_address":"172.17.X.XXX:80": sleep がアクセスした宛先アドレス。"upstream_local_address":"172.17.X.XXX:46129": httpbin に接続する httpbin Envoy のローカルアドレス (sleep の IP アドレス)。"upstream_host":"172.17.X.XXX:80": httpbin Envoy がアクセスした宛先アドレス。
方法 2: XFF リクエストヘッダーを介してクライアントの送信元 IP アドレスを取得する
次の構成を使用して、サーバーアプリケーションのメッシュプロキシ Sidecar が、インバウンドリクエストをアプリケーションに転送する前に、リクエストに X-Forwarded-For リクエストヘッダーを追加するようにできます。リクエストヘッダーの値は、実際のクライアント IP アドレスに設定されます。この方法にはノードのオペレーティングシステムに関する制限はありませんが、アプリケーションが XFF リクエストヘッダーによって運ばれるクライアントの送信元 IP アドレスを識別して取得できる必要があります。
「Envoy フィルターテンプレートを使用して Envoy フィルターを作成する」を参照して、EnvoyFilter テンプレートを介して次の EnvoyFilter を ASM インスタンスに適用します。
apiVersion: networking.istio.io/v1alpha3 kind: EnvoyFilter metadata: name: enable-xff-for-sidecar-inbound namespace: istio-system # この名前空間をゲートウェイが配置されている名前空間に変更します labels: asm-system: "true" provider: "asm" spec: configPatches: - applyTo: NETWORK_FILTER match: proxy: proxyVersion: "^1.*" context: SIDECAR_INBOUND listener: name: "virtualInbound" filterChain: filter: name: "envoy.filters.network.http_connection_manager" patch: operation: MERGE value: typed_config: "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" use_remote_address: true次のコマンドを実行して sleep コンテナーからリクエストを送信します。
kubectl -n default exec -it deploy/sleep -c sleep -- curl http://httpbin:8000/ip期待される結果:
{ "origin": "172.17.X.XXX" }期待される結果から、httpbin が sleep の実際の IP アドレスを取得できることがわかります。クライアントの送信元 IP アドレスは、Sidecar によって X-Forwarded-For リクエストヘッダーを介して httpbin アプリケーションに渡されることに注意してください。httpbin アプリケーションは X-Forwarded-For リクエストヘッダーからクライアントの送信元 IP アドレスを取得することをサポートしているため、クライアントの送信元 IP アドレスを正しく読み取ることができます。アプリケーションに X-Forwarded-For リクエストヘッダーから送信元 IP アドレスを取得する機能がない場合、この方法は適用できません。
シナリオ 2: 南北のトラフィック
南北のトラフィックの場合、クライアントはまず Server Load Balancer にリクエストを送信し、次にリクエストは Istio イングレスゲートウェイに転送され、その後バックエンドサービスに転送されます。中間にイングレスゲートウェイが追加されるため、クライアントの送信元 IP アドレスの取得はより複雑になります。以下のセクションでは、HTTP および HTTPS プロトコルリクエストの送信元 IP アドレスの保持を構成および検証する方法について説明します。
HTTP プロトコルリクエスト
送信元 IP の保持なし
次の内容を使用して http-demo.yaml を作成し、HTTP プロトコルを使用して httpbin にアクセスします。
apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: httpbin-gw-httpprotocol namespace: default spec: selector: istio: ingressgateway servers: - hosts: - '*' port: name: http number: 80 protocol: HTTP --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: httpbin namespace: default spec: gateways: - httpbin-gw-httpprotocol hosts: - '*' http: - route: - destination: host: httpbin port: number: 8000次のコマンドを実行して、ゲートウェイと仮想サービスをデプロイします。
kubectl -n default apply -f http-demo.yaml次のコマンドを実行して、イングレスゲートウェイを介して httpbin にアクセスします。
export GATEWAY_URL=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}') curl http://$GATEWAY_URL:80/ip期待される結果:
{ "origin": "10.0.0.93" }期待される結果から、返された IP アドレスが Kubernetes クラスターのノードアドレスであることがわかります。
イングレスゲートウェイのアクセスログを表示します。
サンプルログ:
{ "upstream_service_time":"1", "response_code":200, "protocol":"HTTP/1.1", "bytes_sent":28, "upstream_cluster":"outbound|8000||httpbin.default.svc.cluster.local", "start_time":"2022-11-23T03:29:20.017Z", "istio_policy_status":null, "upstream_transport_failure_reason":null, "trace_id":null, "route_name":null, "request_id":"292903be-a889-4d5d-83a0-ab1f5d1a****", "method":"GET", "upstream_host":"172.17.X.XXX:80", "duration":1, "path":"/ip", "downstream_local_address":"172.17.X.XXX:80", "authority":"47.242.XXX.XX", "user_agent":"curl/7.79.1", "downstream_remote_address":"10.0.0.93:5899", "upstream_local_address":"172.17.X.XXX:54322", "requested_server_name":null, "x_forwarded_for":"10.0.0.93", "response_flags":"-", "bytes_received":0 }ログから、次の情報を取得できます。
"downstream_remote_address":"10.0.0.93:5899": 実際のクライアントの送信元アドレスではありません。"downstream_local_address":"172.17.X.XXX:80": イングレスゲートウェイ Pod のアドレス。"upstream_local_address":"172.17.X.XXX:54322": イングレスゲートウェイ Pod のアドレスは保持されますが、ポートの値は変更されます。"upstream_host":"172.17.X.XXX:80": httpbin Pod のアドレス。
送信元 IP の保持あり
外部トラフィックポリシーを Local に設定します。(Terway ネットワークモードのクラスターでは、このステップをスキップしてください。)
ASM コンソールにログインします。左側のナビゲーションウィンドウで、 を選択します。
[メッシュ管理] ページで、ASM インスタンスの名前をクリックします。左側のナビゲーションウィンドウで、 を選択します。
[イングレスゲートウェイ] ページで、対象のゲートウェイの右側にある [YAML の表示] をクリックします。
[編集] ダイアログボックスで、spec フィールドの下にある externalTrafficPolicy フィールドを Local に設定し、[OK] をクリックします。

次のコマンドを実行して、イングレスゲートウェイを介して httpbin にアクセスします。
curl http://$GATEWAY_URL:80/ip期待される結果:
{ "origin": "120.244.xxx.xxx" }期待される結果から、返された IP アドレスが実際のクライアントの送信元 IP アドレスであることがわかります。
イングレスゲートウェイのアクセスログを表示します。
サンプルログ:
{ "istio_policy_status":null, "upstream_transport_failure_reason":null, "path":"/ip", "x_forwarded_for":"120.244.XXX.XXX", "route_name":null, "method":"GET", "duration":2, "downstream_remote_address":"120.244.XXX.XXX:28504", "bytes_received":0, "upstream_cluster":"outbound|8000||httpbin.default.svc.cluster.local", "bytes_sent":34, "protocol":"HTTP/1.1", "response_flags":"-", "upstream_local_address":"172.17.X.XXX:57498", "upstream_service_time":"2", "request_id":"9c0295d4-e77f-4a3a-b292-e5c58d92****", "start_time":"2022-11-23T03:24:04.413Z", "response_code":200, "trace_id":null, "authority":"47.242.XXX.XX", "user_agent":"curl/7.79.1", "downstream_local_address":"172.17.X.XXX:80", "upstream_host":"172.17.X.XXX:80", "requested_server_name":null }ログから、次の情報を取得できます。
"downstream_remote_address":"120.244.XXX.XXX:28504": 期待どおりのクライアントの送信元アドレス。"downstream_local_address":"172.17.X.XXX:80": イングレスゲートウェイ Pod のアドレス。"upstream_local_address":"172.17.X.XXX:57498": イングレスゲートウェイ Pod のアドレスは保持されますが、ポートの値は変更されます。"upstream_host":"172.17.X.XXX:80": httpbin Pod のアドレス。
HTTPS プロトコルリクエスト
前のセクションでは、HTTP プロトコルリクエストの送信元 IP 保持を設定する前後の比較について詳しく説明しました。したがって、このセクションでは、HTTPS プロトコルリクエストの送信元 IP 保持を構成および検証する方法についてのみ説明します。
次の内容を使用して https-demo を作成し、HTTPS プロトコルを使用して httpbin にアクセスします。
apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: httpbin-gw-https namespace: default spec: selector: istio: ingressgateway servers: - hosts: - '*' port: name: https number: 443 protocol: HTTPS tls: credentialName: myexample-credential mode: SIMPLE --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: httpbin-https namespace: default spec: gateways: - httpbin-gw-https hosts: - '*' http: - route: - destination: host: httpbin port: number: 8000次のコマンドを実行して、ゲートウェイと仮想サービスをデプロイします。
kubectl -n default apply -f https-demo.yaml次のコマンドを実行して、イングレスゲートウェイを介して httpbin にアクセスします。
export GATEWAY_URL=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}') curl -k https://$GATEWAY_URL:443/ip期待される結果:
{ "origin": "120.244.XXX.XXX" }期待される結果から、返された IP アドレスが実際のクライアントの送信元 IP アドレスであることがわかります。