このドキュメントでは、サービスへのアクセス時にクライアントソース IP を保持するように Service Mesh を設定する方法について説明します。
前提条件
-
バージョン 1.15 以降の ASM Enterprise または Ultimate Edition インスタンスが必要です。詳細については、「ASM インスタンスの作成」および「ASM インスタンスのアップグレード」をご参照ください。
ACK マネージドクラスターが作成されていること。詳細については、「ACK マネージドクラスターの作成」をご参照ください。
イングレスゲートウェイがデプロイされていること。詳細については、「イングレスゲートウェイの作成」をご参照ください。
-
kubectl を使用してクラスターに接続していること。詳細については、「クラスターの kubeconfig ファイルを取得し、kubectl を使用してクラスターに接続する」をご参照ください。
背景情報
クライアントソース IP は、多くのシナリオで使用されます。次に例を示します。
-
アプリケーションのアクセス制御:例えば、多くのアプリケーションでは、ユーザーが異なるリージョンからログインする際に追加の認証を要求します。これにはクライアントの元の IP を取得する必要があります。
-
シンプルなセッション保持:ソース IP ベースの負荷分散を実行して、同じクライアントからのリクエストを同じサービスインスタンスに転送できます。
-
アクセスログとモニタリング:実際のソースアドレスを含むアクセスログと監視メトリクスは、開発者がトラフィックを分析し、統計を収集するのに役立ちます。
Server Load Balancer (SLB) のようなクラウドロードバランサーは、クライアントソース IP をバックエンドサービスに渡すことができます。Istio もこの機能を提供する必要があります。しかし、Istio を使用する場合、各 Pod にサイドカープロキシが挿入されます。このプロキシはすべてのインバウンドトラフィックをインターセプトし、ローカル接続 (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 では、各サービスのサイドカープロキシがすべての東西トラフィックをインターセプトします。このプロキシはリクエストをアプリケーションコンテナに転送するため、アプリケーションが確認できるソースアドレスはプロキシのループバックアドレスである 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-tools -
httpbin コンテナを終了し、次のコマンドを実行してポート 8000 の情報を表示します。
kubectl -n default exec -it deploy/httpbin -c httpbin -- netstat -ntp | grep 8000期待される出力:
tcp 0 0 172.17.X.XXX:8000 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:8000", "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:8000", "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:8000":sleep アプリケーションがアクセスする宛先アドレス。 -
"upstream_local_address":"127.0.0.6:42169":httpbin Envoy プロキシが httpbin アプリケーションに接続するために使用するローカルアドレス。この時点で、アプリケーションが確認できるソース IP は127.0.0.6です。 -
"upstream_host":"172.17.X.XXX:8000":httpbin Envoy プロキシがアクセスする宛先アドレス。
-
ステップ 2:ソース IP 保持の設定
方法 1:TPROXY モードの使用
httpbin の Deployment が TPROXY 透過的インターセプトモードを使用するように設定します。
-
次のコマンドを実行して、httpbin アプリケーションの Deployment を変更します。
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 アドレスを取得できることを示しています。
-
次のコマンドを実行して、ポート 8000 の情報を表示します。
説明Pod が再起動した後、netstat を再インストールする必要があります。
kubectl -n default exec -it deploy/httpbin -c httpbin -- netstat -ntp | grep 8000期待される出力:
tcp 0 0 172.17.X.XXX:8000 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:8000", "duration":1, "upstream_host":"172.17.X.XXX:8000", "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:8000":sleep アプリケーションがアクセスする宛先アドレス。 -
"upstream_local_address":"172.17.X.XXX:46129":httpbin Envoy プロキシが httpbin アプリケーションに接続するために使用するローカルアドレス。これは sleep アプリケーションの IP アドレスです。 -
"upstream_host":"172.17.X.XXX:8000":httpbin Envoy プロキシがアクセスする宛先アドレス。
-
方法 2:XFF ヘッダーの使用
次の設定により、サーバー側のサイドカープロキシはインバウンドリクエストに X-Forwarded-For (XFF) ヘッダーを追加します。プロキシは、リクエストをアプリケーションに転送する前に、このヘッダーの値をクライアントの実際の IP アドレスに設定します。この方法には OS の制限はありませんが、アプリケーションが X-Forwarded-For (XFF) ヘッダーからクライアントのソース IP を読み取る必要があります。
-
Envoy フィルターテンプレートを使用して、次の EnvoyFilter を ASM インスタンスに適用します。詳細については、「Envoy フィルターテンプレートを使用して Envoy フィルターを作成する」をご参照ください。
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 アドレスを受信したことを示しています。httpbin アプリケーションは、ソース IP を X-Forwarded-For (XFF) ヘッダーから読み取るように設計されているため、これが可能です。サイドカープロキシがこのヘッダーを追加するようになりました。ご利用のアプリケーションが XFF ヘッダーを読み取れない場合、この方法は機能しません。
シナリオ 2:南北トラフィック
南北トラフィックの場合、リクエストはクライアントからロードバランサーと Istio イングレスゲートウェイを経由してバックエンドサービスに流れます。イングレスゲートウェイを介したこの余分なホップにより、クライアントソース IP の保持がより複雑になります。以下のセクションでは、HTTP および HTTPS リクエストのソース IP 保持を設定および検証する方法について説明します。
HTTP リクエスト
ソース IP 保持なし
-
HTTP 経由で httpbin アプリケーションにアクセスするために、次の内容で http-demo.yaml という名前のファイルを作成します。
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 -
次のコマンドを実行して、Gateway と VirtualService をデプロイします。
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:8000", "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":クライアントの実際のソース IP ではありません。 -
"downstream_local_address":"172.17.X.XXX:80":イングレスゲートウェイ Pod のアドレス。 -
"upstream_local_address":"172.17.X.XXX:54322":イングレスゲートウェイ Pod の IP アドレスは保持されますが、ポート番号は変更されます。 -
"upstream_host":"172.17.X.XXX:8000":httpbin Pod のアドレス。
-
ソース IP 保持あり
-
外部トラフィックポリシーを Local に設定します。(Terway ネットワークモードを使用するクラスターでは、このステップをスキップできます。)
-
ASM コンソールにログインします。左側のナビゲーションウィンドウで、を選択します。
-
[メッシュ管理] ページで、対象の ASM インスタンスの名前をクリックします。左側のナビゲーションウィンドウで、を選択します。
-
Ingress Gateway ページで、対象のゲートウェイの右側にある View YAML をクリックします。
-
Edit ダイアログボックスで、spec セクションを見つけ、externalTrafficPolicy フィールドを Local に設定し、OK をクリックします。
spec: affinity: {} autoCreateGatewayYaml: false clusterIds: - cf0243f2c3009406xxx compression: {} cpu: {} dnsPolicy: ClusterFirst externalTrafficPolicy: Local gatewayType: ingress
-
-
次のコマンドを実行して、イングレスゲートウェイ経由で 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 リクエストについて詳しく説明したため、このセクションでは HTTPS リクエストの設定と検証のみに焦点を当てます。
-
HTTPS 経由で httpbin アプリケーションにアクセスするために、次の内容で https-demo.yaml という名前のファイルを作成します。
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 -
次のコマンドを実行して、Gateway と VirtualService をデプロイします。
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 であることを示しています。