Kubernetes Ingress Setup

This guide covers deploying Gordon behind a Kubernetes ingress controller for TLS termination.

Overview

In Kubernetes, an Ingress Controller handles external traffic:

  • Terminates TLS connections
  • Routes requests to services based on hostname/path
  • Sets X-Forwarded-For header with the client's real IP

Common ingress controllers: nginx-ingress, Traefik, Contour, Kong.

Architecture

Client → Ingress Controller (HTTPS/443) → Gordon Service (HTTP) → Gordon Pod
                    ↓
              TLS terminated
              X-Forwarded-For set

Understanding Pod Network CIDR

Kubernetes assigns each pod an IP from the pod network CIDR. When the ingress controller forwards requests to Gordon, it comes from the ingress pod's IP.

To find your pod network CIDR:

# Method 1: Check cluster-info
kubectl cluster-info dump | grep -m 1 cluster-cidr

# Method 2: Check CNI config (varies by CNI plugin)
kubectl get cm -n kube-system kubeadm-config -o yaml | grep podSubnet

# Method 3: Check a pod's IP to infer the range
kubectl get pods -o wide

Common defaults by CNI:

CNI Plugin Default Pod CIDR
Flannel 10.244.0.0/16
Calico 192.168.0.0/16
Weave 10.32.0.0/12
Cilium 10.0.0.0/8
GKE 10.0.0.0/14 or /9
EKS (VPC CNI) VPC CIDR

Gordon Configuration

[api.rate_limit]
enabled = true
# Trust pod network so Gordon reads X-Forwarded-For from ingress
trusted_proxies = ["10.244.0.0/16"]  # Replace with your pod CIDR

Ingress Controller Configuration

The ingress controller must be configured to forward the real client IP.

nginx-ingress

Create or update the ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  # Enable X-Forwarded-For processing
  use-forwarded-headers: "true"
  # Trust these CIDRs to set X-Forwarded-For
  # Include your cloud provider's load balancer IPs
  proxy-real-ip-cidr: "0.0.0.0/0"  # Or specific LB CIDR

If you have a cloud load balancer in front of nginx-ingress (common in EKS, GKE):

data:
  use-forwarded-headers: "true"
  use-proxy-protocol: "false"  # Set to "true" if using NLB with proxy protocol
  proxy-real-ip-cidr: "10.0.0.0/8,192.168.0.0/16"

Traefik

In your Traefik configuration:

# traefik.yaml or Helm values
entryPoints:
  web:
    address: ":80"
    forwardedHeaders:
      trustedIPs:
        - "10.0.0.0/8"
        - "192.168.0.0/16"
  websecure:
    address: ":443"
    forwardedHeaders:
      trustedIPs:
        - "10.0.0.0/8"
        - "192.168.0.0/16"

Complete Example

Gordon Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gordon
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gordon
  template:
    metadata:
      labels:
        app: gordon
    spec:
      containers:
      - name: gordon
        image: your-registry/gordon:latest
        ports:
        - containerPort: 80
        - containerPort: 5000
        volumeMounts:
        - name: config
          mountPath: /etc/gordon
        - name: docker-sock
          mountPath: /var/run/docker.sock
      volumes:
      - name: config
        configMap:
          name: gordon-config
      - name: docker-sock
        hostPath:
          path: /var/run/docker.sock
---
apiVersion: v1
kind: Service
metadata:
  name: gordon
spec:
  selector:
    app: gordon
  ports:
  - name: http
    port: 80
    targetPort: 80
  - name: registry
    port: 5000
    targetPort: 5000

Gordon ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: gordon-config
data:
  gordon.toml: |
    [server]
    port = 80
    registry_port = 5000
    gordon_domain = "gordon.example.com"

    [api.rate_limit]
    enabled = true
    trusted_proxies = ["10.244.0.0/16"]

    [auth]
    enabled = false  # Configure auth as needed

Ingress Resource

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: gordon
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "0"  # Unlimited for image uploads
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - gordon.example.com
    secretName: gordon-tls
  rules:
  - host: gordon.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: gordon
            port:
              number: 80

Verifying the Setup

  1. Deploy Gordon and the ingress
  2. Make a request through the ingress
  3. Check Gordon logs:
kubectl logs -l app=gordon | grep "client_ip"

If you see the ingress pod's IP instead of the client's IP:

  • Verify trusted_proxies includes your pod CIDR
  • Verify ingress controller has use-forwarded-headers: "true"

Cloud-Specific Notes

GKE

GKE uses a VPC-native pod network. Find the pod CIDR in:

  • Console → Kubernetes Engine → Cluster → Networking → Pod address range
trusted_proxies = ["10.0.0.0/14"]  # GKE default

EKS

EKS with VPC CNI assigns pod IPs from your VPC subnets:

trusted_proxies = ["10.0.0.0/16"]  # Your VPC CIDR

AKS

AKS default pod CIDR:

trusted_proxies = ["10.244.0.0/16"]  # Azure CNI default

Troubleshooting

All requests show the same IP

Symptom: Rate limiting ineffective, logs show ingress pod IP.

Cause: trusted_proxies missing pod CIDR or ingress not forwarding headers.

Fix:

  1. Add pod CIDR to trusted_proxies
  2. Enable use-forwarded-headers in ingress controller

X-Forwarded-For contains multiple IPs

Symptom: X-Forwarded-For: 203.0.113.50, 10.0.1.5, 10.244.0.3

This is normal when multiple proxies are involved (cloud LB → ingress → Gordon). Gordon takes the first IP (leftmost), which is the original client.

Client IP shows cloud load balancer

Symptom: Client IP is AWS/GCP/Azure LB IP, not real client.

Cause: Cloud LB not configured to forward client IP.

Fix: Configure cloud LB to preserve or pass client IP (varies by provider).