はじめに
WebサーバーのアクセスログがパブリックIPになっていないのが気になったので、クライアントのパブリックIPが表示されるようにしてみました。
本記事では、OCI Load Balancer (LB) と Nginx を使用した環境で、この課題を解決するための設定方法と、その検証過程で得られた知見を共有します。
なぜクライアントのIPが見えなくなるか
これは、OCI LBの仕様(設定)の問題でした。
OCI LBの仕様(設定)
OCIの標準的なLB(Flexible LB)はプロキシ型です。クライアントからLBまでTCPコネクションが張られ、LBで一旦コネクションを終端し、LBからPod(nginx)へ新しいTCPコネクションを張ります。
この仕組み上、Podに届くパケットの「送信元IPアドレス」は、LBのプライベートIPアドレスとなります(クライアントのIPアドレスは見えなくなる)。

解決策
これらの要因から、クライアントIPアドレスをnginxのPodから見えるようにするためにはLBでHTTPヘッダー(X-Forwarded-For)にクライアントIPを埋め込む必要があります。
OCI LBでは、リスナーのプロトコル設定をHTTPにすれば勝手に埋め込んでくれるのでその設定をマニフェストファイル側から行います。以下のようなマニフェストファイルになります。
apiVersion: v1
kind: Service
metadata:
name: lb
annotations:
oci.oraclecloud.com/load-balancer-type: "lb"
service.beta.kubernetes.io/oci-load-balancer-shape: "flexible"
service.beta.kubernetes.io/oci-load-balancer-shape-flex-min: "10"
service.beta.kubernetes.io/oci-load-balancer-shape-flex-max: "100"
service.beta.kubernetes.io/oci-load-balancer-backend-protocol: "HTTP"
spec:
selector:
app: nginx
type: LoadBalancer
ports:
- name: http
port: 80
targetPort: 80
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
nginx.conf: |
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: docker.io/nginx
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: config-volume
configMap:
name: nginx-config
ConfigMapはクライアントIPをログに出すための設定なので、必須ではないです。
検証結果
設定適用後、ログを確認すると以下のように出力されました。
10.0.20.226 - - [03/Dec/2025:23:45:40 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36" "118.158.70.136"
見にくいですが、最後の部分にクライアントのパブリックIPアドレスが表示されています(こちらで確認しました)。
まとめ
OKE + OCI LBでクライアントIPを確認するには、
- LBのプロトコルをHTTPにする(アノテーションで、
service.beta.kubernetes.io/oci-load-balancer-backend-protocol: "HTTP"をつける) - アプリ側でヘッダー(
X-Forwarded-For)のログが見えるようにしておく
の2点が必要なことがわかりました。
関連する話題
検証の過程でexternalTrafficPolicyの挙動についても調べたのでこちらにメモとして置いておきます。
externalTrafficPolicyの設定
KubernetesのService(NodePortとLoadBalancer)には、spec.externalTrafficPolicyという設定項目があります。このexternalTrafficPolicyにはClusterとLocalの二つを指定することができ、 トラフィックの流れを設定できます。
externalTrafficPolicy: Clusterの場合(デフォルト)
externalTrafficPolicy: Cluster の場合、Service に来たトラフィックはクラスタ内の全 Pod に負荷分散されます。 たとえば、あるノード(Node A)で受け取ったパケットが、別のノード(Node B)にある Pod へ転送されることがあります。

この「ノード間転送」が発生した際、パケットの送信元 IP アドレスは 「中継したノード (Node A) の IP」 に書き換えられます(SNAT)。 一方で、転送されずにそのノード内で処理された場合は、送信元 IP は書き換わりません(LB の IP のまま)。
そのため、Nginx のログには 「LB の IP」と「中継ノードの IP」が混在して記録される ことになり、正確なアクセス元の特定が困難になります。
externalTrafficPolicy: Localの場合
externalTrafficPolicy: Local の場合、Service に来たトラフィックは、ロードバランサからパケットを受け取ったノード上の Pod で 必ず 処理されます。(もしそのノードに Pod がなければ、そもそも通信が成立しません)

ノード間転送(横移動)が発生しないため、Kubernetes 内部での IP アドレス書き換え(SNAT)は行われません。
そのため、Nginx のログには、ノードにパケットが届いた時点での送信元 IP アドレス がそのまま記録されます。 今回は OCI Load Balancer (Proxy型) を使用しているため、ここには 「LB のプライベート IP」 が記録されることになります。
(※もし NLB などのパススルー型 LB を使っていた場合は、ここで「本当のクライアント IP」が見えるようになります)
OCI LBと合わせて使う場合の注意点
externalTrafficPolicy: Localの設定でOCI LBを使う場合、ヘルスチェックには注意が必要です。OCI LBのバックエンドセットには全てのワーカーノードが登録されるので、externalTrafficPolicy: Localの場合はPodが存在しないノードに対してはヘルスチェックが通りません(Clusterが指定されている場合は転送されるのでヘルスチェックが通る)。
この設定は今回必要か?
今回は必要ありませんでした。OCI LBがプロキシ型であることからHTTPヘッダーの書き換えを行えば良かったので、TCP/IPレイヤーの設定に関わらずクライアントIPを知ることができます。
ですが、ウェブサーバー以外の通信の場合はHTTPヘッダーという概念がないため、クライアントIPアドレスを知りたい場合にはexternalTrafficPolicy: Localの設定をする必要があります。