fix(reverse-proxy): preserve real client IP in nginx logs
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 16m7s

Nginx logs showed internal cluster IPs instead of public client IPs.
Root cause was three stacked issues in the traffic path from MetalLB
LoadBalancer VIP to the reverse-proxy pods:

1. Service was using externalTrafficPolicy: Cluster, allowing
   kube-proxy to forward traffic cross-node and SNAT the source IP
   in the process. Changed to externalTrafficPolicy: Local so traffic
   is only handled by pods on the node that received it.

2. reverse-proxy DaemonSet had a pinned  in
   the pod template, forcing all replicas onto a single node instead
   of scheduling one per node as expected for a DaemonSet. Removed
   nodeName so the DaemonSet scheduler places one pod per eligible
   node (both control-plane nodes, tolerations already in place).

3. Even with the above fixed, flannel's CNI masquerade rule was
   still SNAT'ing traffic to the pod network address (10.244.x.x)
   before it reached nginx, since traffic into the pod overlay
   network gets masqueraded regardless of kube-proxy's policy.
   Switched the DaemonSet to hostNetwork: true (with
   dnsPolicy: ClusterFirstWithHostNet) so nginx binds directly to
   the node's network namespace and bypasses the overlay masquerade
   entirely.

Result: nginx access logs now show the real public client IP for
every request instead of node/pod-internal addresses.
This commit is contained in:
2026-07-23 10:06:43 +01:00
parent f5a786386f
commit 93f8d41aac
+16 -4
View File
@@ -4,14 +4,15 @@ metadata:
name: reverse-proxy
---
apiVersion: apps/v1
kind: Deployment
#kind: Deployment
kind: DaemonSet
metadata:
name: reverse-proxy
namespace: reverse-proxy
annotations:
reloader.stakater.com/search: "true"
spec:
replicas: 1
#replicas: 1
selector:
matchLabels:
app: reverse-proxy
@@ -20,7 +21,9 @@ spec:
labels:
app: reverse-proxy
spec:
nodeName: kube-master
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet # required when hostNetwork: true
#nodeName: kube-master
# Allows the sidecar to see and signal the Nginx process
shareProcessNamespace: true
containers:
@@ -63,6 +66,7 @@ spec:
limits:
cpu: 250m
memory: 250Mi
- name: watcher
image: alpine:latest
command: ["/bin/sh", "-c"]
@@ -88,7 +92,15 @@ spec:
volumeMounts:
- name: blacklisted-memory-volume
mountPath: /etc/nginx/blacklisted.d/
tolerations:
# these tolerations are to have the daemonset runnable on control plane nodes
# remove them if your control plane nodes should not run pods
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
volumes:
- name: reverse-proxy-log