Kong Gateway and proxy_protocol on AWS EKS

This post will help you take advantage of proxy_protocol to preserve the client IP address with Kong on Kubernetes.

Kong Gateway and proxy_protocol on AWS EKS
Photo by William Phipps / Unsplash

This post will help you take advantage of proxy_protocol to preserve the client IP address with Kong on Kubernetes. I've been using Terraform to deploy Kong's API Gateway on AWS EKS. You can go look at that deployment code here. The code up to now goes with the Kubernetes-driven way to configure Kong's API Gateway, using the Kong Ingress controller's custom resources. I prefer YAML driven config for this case, especially on Kubernetes when everything else is YAML driven anyway, and it comes with the bonus of not having to manage a separate PostgreSQL database somewhere.

However, quite often the companies I work with want Kong's GUI installed, and the database, and Kong's Admin API available, and the Developer Portal etc. You know, take advantage of all the things they paid for. When deploying Kong on Kubernetes with all the things, I also like to put all of Kong's internal services behind Kong's Gateway. It saves on load balancer cost and allows you to apply Kong plugins easily to Kong's own stuff.

Having said ALL THAT, when deploying to Amazon EKS, Kong recommends an Network Load Balancer in front of Kong's proxy (the gateway). NLBs can do TLS termination now, which is SWEET because it's one less thing to manage. Thanks, Amazon, handle my certs for me, I care not how. Using a Load Balancer in front of Kong means you want to get the client's actual IP address for your client requests instead of the load balancer IP in your logs. Kong has documentation on this but actual code is more fun. What happens is AWS gets your client request, terminates TLS, and then adds headers to the request before passing it to Kong. If you want to get deep into proxy_protocol you can read all about it. Again, I use helm to drive my deployments, so here's the helm values file to use to take advantage of proxy_protocol. To use this you'll need to have some secrets in place and a namespace for Kong (in my case "kong").

image:
  repository: kong/kong-gateway
  tag: "2.6.0.0-alpine"

env:
  prefix: /kong_prefix/
  nginx_worker_processes: "auto"
  anonymous_reports: off
  database: postgres
  pg_user: kong
  pg_password: kong
  plugins: bundled
  admin_api_uri: https://adminapi.domain.com
  admin_gui_url: https://manager.domain.com
  portal_api_url: https://portalapi.domain.com
  portal_gui_host: portal.domain.com
  portal_gui_protocol: https
  portal_session_conf:
    valueFrom:
      secretKeyRef:
        name: kong-session-config
        key: portal_session_conf
  password:
    valueFrom:
      secretKeyRef:
        name: kong-enterprise-superuser-password
        key: password
  trusted_ips: 0.0.0.0/0,::/0
  real_ip_header: proxy_protocol

enterprise:
  enabled: true
  license_secret: kong-enterprise-license
  vitals:
    enabled: true
  portal:
    enabled: true
  rbac:
    enabled: true
    admin_gui_auth: basic-auth
    session_conf_secret: kong-session-config
  smtp:
    enabled: false

admin:
  enabled: true
  type: ClusterIP
  annotations:
    konghq.com/protocol: "http"
  http:
    enabled: true
    servicePort: 8001
    containerPort: 8001
    parameters: []
  tls:
    enabled: false
    servicePort: 8444
    containerPort: 8444
    parameters:
    - http2
  ingress:
    enabled: true
    hostname: adminapi.domain.com
    annotations:
      kubernetes.io/ingress.class: "kong"
    path: /

manager:
  enabled: true
  type: ClusterIP
  annotations:
    konghq.com/protocol: "http"

  http:
    enabled: true
    servicePort: 8002
    containerPort: 8002
    parameters: []
  tls:
    enabled: false
    servicePort: 8445
    containerPort: 8445
    parameters: []
  ingress:
    enabled: true
    hostname: manager.domain.com
    annotations:
      kubernetes.io/ingress.class: "kong"
    path: /

portal:
  enabled: true
  type: ClusterIP
  annotations:
    konghq.com/protocol: "http"
  http:
    enabled: true
    servicePort: 8003
    containerPort: 8003
    parameters: []
  tls:
    enabled: false
    servicePort: 8446
    containerPort: 8446
    parameters:
    - http2
  ingress:
    enabled: true
    hostname: portal.domain.com
    annotations:
      kubernetes.io/ingress.class: "kong"
    path: /

portalapi:
  enabled: true
  type: ClusterIP
  annotations:
    konghq.com/protocol: "http"
  http:
    enabled: true
    servicePort: 8004
    containerPort: 8004
    parameters: []
  tls:
    enabled: false
    servicePort: 8447
    containerPort: 8447
    parameters:
    - http2
  ingress:
    enabled: true
    hostname: portalapi.domain.com
    annotations:
      kubernetes.io/ingress.class: "kong"
    path: /

proxy:
  enabled: true
  type: LoadBalancer
  externalTrafficPolicy: Local
  annotations: {
    "service.beta.kubernetes.io/aws-load-balancer-backend-protocol": "tcp",
    "service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled": "true",
    "service.beta.kubernetes.io/aws-load-balancer-ssl-cert": "arn:aws:acm:us-west-2:XXXXXXXX:certificate/XXXXXX-XXXXXXX-XXXXXXX-XXXXXXXX",
    "service.beta.kubernetes.io/aws-load-balancer-ssl-ports": "kong-proxy-tls",
    "service.beta.kubernetes.io/aws-load-balancer-type": "nlb",
    "service.beta.kubernetes.io/aws-load-balancer-proxy-protocol": "*"
  }

  http:
    enabled: true
    servicePort: 80
    containerPort: 8000
    parameters:
    - proxy_protocol

  tls:
    enabled: true
    servicePort: 443
    overrideServiceTargetPort: 8000
    containerPort: 8443
    parameters:
    - proxy_protocol
  stream: {}
  ingress:
    enabled: false

ingressController:
  enabled: true
  installCRDs: false
  env:
    kong_admin_token:
      valueFrom:
        secretKeyRef:
          name: kong-enterprise-superuser-password
          key: password

# Specify Kong status listener configuration
# This listen is internal-only. It cannot be exposed through a service or ingress.
status:
  enabled: true
  http:
    # Enable plaintext HTTP listen for the status listen
    enabled: true
    containerPort: 8100
    parameters: []

serviceMonitor:
  enabled: true
  # interval: 10s
  # Specifies namespace, where ServiceMonitor should be installed
  namespace: kong

postgresql:
  enabled: true
  postgresqlUsername: kong
  postgresqlDatabase: kong
  postgresqlPassword: kong
  service:
    port: 5432

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 5
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 80
resources:
  limits:
   cpu: 500m
   memory: 512Mi
  requests:
   cpu: 100m
   memory: 256Mi

The downside: without the AWS Load Balancer Controller, you can't use an annotation to actually enable proxy_protocol on your target groups. So you can either also install the AWS Load Balancer Controller to have AWS use your NLB target group annotations OR you can log into the AWS Console or use the API to enable "Proxy Protocol v2" on your new Kong Gateway Proxy NLB's target groups like so:

Disclaimer: none of the above is supported by Kong, its partners, or me AT ALL. Use at your own risk. My views do not reflect those of my employer.