HAProxy can act as a Kubernetes Ingress controller, but it’s not a built-in solution and requires a bit of manual setup.

Let’s see HAProxy in action managing traffic for a simple web application deployed in Kubernetes.

Here’s a basic deployment and service for a hypothetical myapp:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp-container
        image: nginxdemos/hello:plain-text
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

Now, to expose this service using HAProxy as an Ingress controller, we need to:

  1. Install HAProxy: This is typically done by deploying HAProxy itself as a Pod within your Kubernetes cluster. You’d usually use a Docker image for HAProxy.
  2. Configure HAProxy: This is the core part. HAProxy needs to be configured to listen for incoming traffic and route it to your myapp-service. This configuration is usually mounted into the HAProxy pod as a ConfigMap.
  3. Expose HAProxy: The HAProxy pod needs to be accessible from outside the cluster. This is often done using a NodePort or LoadBalancer service pointing to the HAProxy pod.

Here’s a simplified example of a HAProxy configuration (haproxy.cfg) that routes traffic for myapp.example.com to our myapp-service:

global
    log /dev/log    local0
    log /dev/log    local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
    stats timeout 30s
    user haproxy
    group haproxy
    daemon

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    timeout connect 5000
    timeout client  50000
    timeout server  50000

frontend http_frontend
    bind *:80
    acl host_myapp hdr(host) -i myapp.example.com
    use_backend myapp_backend if host_myapp

backend myapp_backend
    balance roundrobin
    # This needs to resolve to the Kubernetes service IP and port
    # In a real scenario, you'd use a tool to dynamically update this
    # or configure it to point to the Kubernetes service DNS name.
    # For demonstration, let's assume the service IP is 10.43.0.10 and port 80
    server myapp_server 10.43.0.10:80 check

To make this dynamic and Kubernetes-native, you’d typically use a controller that watches Kubernetes Ingress resources and generates and updates this haproxy.cfg file on the fly. This controller would also programmatically fetch the IP addresses of the myapp-service’s pods.

Here’s how you’d deploy HAProxy and its configuration as Kubernetes resources:

1. HAProxy ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: haproxy-config
data:
  haproxy.cfg: |
    global
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
        stats timeout 30s
        user haproxy
        group haproxy
        daemon

    defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000

    frontend http_frontend
        bind *:80
        acl host_myapp hdr(host) -i myapp.example.com
        use_backend myapp_backend if host_myapp

    backend myapp_backend
        balance roundrobin
        # This will be dynamically updated by the controller
        server myapp_server 10.43.0.10:80 check

2. HAProxy Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: haproxy-ingress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: haproxy-ingress
  template:
    metadata:
      labels:
        app: haproxy-ingress
    spec:
      containers:
      - name: haproxy
        image: haproxy:2.4 # Use an appropriate HAProxy image
        ports:
        - containerPort: 80
          name: http
        - containerPort: 1936 # For stats
          name: stats
        volumeMounts:
        - name: haproxy-config-volume
          mountPath: /usr/local/etc/haproxy/haproxy.cfg
          subPath: haproxy.cfg
      volumes:
      - name: haproxy-config-volume
        configMap:
          name: haproxy-config

3. HAProxy Service (to expose it):

apiVersion: v1
kind: Service
metadata:
  name: haproxy-ingress-service
spec:
  selector:
    app: haproxy-ingress
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    name: http
  - protocol: TCP
    port: 1936
    targetPort: 1936
    name: stats
  type: LoadBalancer # Or NodePort

Once HAProxy is running and exposed, you’d create an Ingress resource to tell HAProxy how to route traffic:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
spec:
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp-service
            port:
              number: 80

The real magic of a Kubernetes Ingress controller is its ability to watch for these Ingress objects and programmatically update the HAProxy configuration and backend server lists based on them. You’re not manually editing haproxy.cfg each time you create an Ingress. Instead, a separate controller component (often running alongside HAProxy or as a separate deployment) monitors the Kubernetes API for Ingress resources and translates them into HAProxy configurations.

The most surprising true thing about using HAProxy as an Ingress controller in Kubernetes is that there isn’t a single, official "Kubernetes HAProxy Ingress Controller" provided by the Kubernetes project itself. Instead, the community has developed various solutions, with the most popular one being ingress-nginx (which uses Nginx, not HAProxy) and a dedicated HAProxy Ingress controller project. These controllers are essentially custom applications that run within your cluster, watch Kubernetes API events, and dynamically reconfigure HAProxy.

The primary challenge is ensuring the HAProxy configuration is kept up-to-date with the state of your Kubernetes services and pods. This dynamic re-configuration is handled by a controller process that monitors the Kubernetes API for changes to Ingress resources, Endpoints (which represent the IPs and ports of your service’s pods), and Services. When changes are detected, the controller regenerates the haproxy.cfg file and signals HAProxy to reload its configuration without dropping active connections.

The next concept you’ll likely encounter is how to handle TLS termination and advanced HAProxy features like sticky sessions or different load balancing algorithms within the Kubernetes Ingress framework.

Want structured learning?

Take the full Haproxy course →