The ingress-nginx module is used to implement ALB using the Ingress NGINX Controller.

In 2025, Ingress NGINX was placed in maintenance mode, with no plans for active development of new features. Further evolution of inbound traffic load balancing in Kubernetes is focused on the Gateway API.

This does not apply to the module as part of Deckhouse Kubernetes Platform (DKP): the module is maintained by the DKP team, including security updates. Details are in “Module support and security”.

Step-by-step migration to Gateway API is in Migrating from ingress-nginx to alb.

The ingress-nginx module installs the Ingress NGINX Controller and manages it with custom resources. If there is more than one node available for hosting the Ingress controller, it is deployed in the HA mode, taking into account the infrastructure specifics of both cloud and bare-metal environments, as well as various Kubernetes cluster types.

The module supports running and configuring several Ingress NGINX controllers simultaneously (one of the controllers is the primary one. You can create as many additional controllers as you want). This approach allows you to separate extranet and intranet Ingress resources of applications.

Traffic termination options

Traffic to ingress-nginx can be routed in several ways:

  • Directly without the use of an external load balancer.
  • Using an external LoadBalancer. The following variants are supported:
    • Qrator
    • Cloudflare
    • AWS LB
    • GCE LB
    • ACS LB
    • Yandex LB
    • OpenStack LB

HTTPS termination

The module allows you to manage HTTPS security policies for each of the Ingress NGINX controllers, including:

  • HSTS parameters
  • Available SSL/TLS versions and encryption protocols

The module is integrated with the cert-manager module. Thus, it can get SSL certificates automatically and pass them to Ingress NGINX controllers for further use.

Monitoring and statistics

The current ingress-nginx implementation has a Prometheus-based system for collecting statistical data with the following set of metrics:

  • Total response time and backend response time separately
  • Response codes
  • Number of repeated requests (retries)
  • Request and response sizes
  • Request methods
  • content-types
  • Geography of requests, etc.

The data can be grouped by the:

  • namespace
  • vhost
  • ingress resources
  • location (in nginx)

All graphs are grouped by Grafana dashboards. From any graph you can open a more detailed view: for example, from a namespace statistics view, you can click through to the corresponding vhost dashboard for more detail, and continue down the hierarchy.

Statistics

Basic principles of collecting statistics

  1. At the log_by_lua_block stage, the module calculates the necessary metrics for each request and stores them in a buffer (each NGINX worker has its own buffer).
  2. At the init_by_lua_block stage, each NGINX worker starts a process that sends data in protobuf format via TCP socket to the protobuf_exporter every second (developed by Deckhouse Kubernetes Platform).
  3. protobuf_exporter runs as a sidecar container in the Ingress controller pod, receives protobuf messages, parses and aggregates them, and exports metrics for Prometheus.
  4. Prometheus scrapes metrics every 30 seconds from both the Ingress controller and the protobuf_exporter. This scraped data is what statistics is based on.

Metrics structure and representation

All collected metrics include service labels identifying the controller instance: controller, app, instance, and endpoint (visible in /prometheus/targets).

  • All non-geo metrics exported by protobuf_exporter are provided at three detail levels:
    • ingress_nginx_overall_*: Top-level aggregated metrics (non-detailed, all metrics have the following labels: namespace, vhost, content_kind).
    • ingress_nginx_detail_*: In addition to overall metrics, adds ingress, service, service_port, and location.
    • ingress_nginx_detail_backend_*: Backend-level metrics. In addition to detail metrics, adds the pod_ip label.
  • Metrics collected for overall and detail levels:
    • *_requests_total: Total requests (extra labels: scheme, method).
    • *_responses_total: Number of responses (extra label: status).
    • *_request_seconds_{sum,count,bucket}: Response time histogram.
    • *_bytes_received_{sum,count,bucket}: Request size histogram.
    • *_bytes_sent_{sum,count,bucket}: Response size histogram.
    • *_upstream_response_seconds_{sum,count,bucket}: Upstream service response time histogram (total for multiple upstreams).
    • *_lowres_upstream_response_seconds_{sum,count,bucket}: Simplified histogram (for visualization; can’t be used for quantiles).
    • *_upstream_retries_{count,sum}: Number and total of backend retries.
  • Metrics collected for overall level:
    • *_geohash_total: Request counts per geohash (additional labels: geohash, place).
  • Metrics collected for detail_backend level:
    • *_lowres_upstream_response_seconds: Simplified response time histogram for overall and detail.
    • *_responses_total: Number of responses (additional label: status_class, not just status).
    • *_upstream_bytes_received_sum: Total size of data received from backends.

Load balancing configuration examples

Use the IngressNginxController custom resource to configure load balancing.

  • AWS (NLB)
  • GCP, Yandex Cloud, and Azure
  • OpenStack
  • VK Cloud
  • Bare metal (HostWithFailover)
  • Bare metal with external LB
  • MetalLB BGP
  • MetalLB L2

Example for AWS (Network Load Balancer)

When setting up the balancer, all available zones in the cluster are used.

Each zone’s balancer receives its own public IP. If a zone has an Ingress controller instance, its IP is added to the load balancer’s domain name as an A record.

If no instances remain in a zone, that IP is removed from DNS.

If only one Ingress controller instance exists in a zone, its IP is temporarily removed from DNS during pod restarts.

Example IngressNginxController with the LoadBalancer inlet and AWS NLB annotations:

apiVersion: deckhouse.io/v1
kind: IngressNginxController
metadata:
 name: main
spec:
  ingressClass: nginx
  inlet: LoadBalancer
  loadBalancer:
    annotations:
      service.beta.kubernetes.io/aws-load-balancer-type: "nlb"

Example for GCP, Yandex Cloud, and Azure

IngressNginxController with the LoadBalancer inlet:

apiVersion: deckhouse.io/v1
kind: IngressNginxController
metadata:
 name: main
spec:
  ingressClass: nginx
  inlet: LoadBalancer

In GCP, nodes must have an annotation allowing external connections for NodePort services.

Example for OpenStack

IngressNginxController with the LoadBalancerWithProxyProtocol inlet and OpenStack Proxy Protocol annotations:

apiVersion: deckhouse.io/v1
kind: IngressNginxController
metadata:
  name: main-lbwpp
spec:
  inlet: LoadBalancerWithProxyProtocol
  ingressClass: nginx
  loadBalancerWithProxyProtocol:
    annotations:
      loadbalancer.openstack.org/proxy-protocol: "true"
      loadbalancer.openstack.org/timeout-member-connect: "2000"

Example for VK Cloud

Use this configuration for an internal cloud balancer (without a public address).

apiVersion: deckhouse.io/v1
kind: IngressNginxController
metadata:
  name: nginx
spec:
  ingressClass: nginx
  inlet: LoadBalancer
  loadBalancer:
    annotations:
      service.beta.kubernetes.io/openstack-internal-load-balancer: "true"
  nodeSelector:
    node.deckhouse.io/group: worker

Example for bare metal

IngressNginxController with the HostWithFailover inlet on frontend nodes:

apiVersion: deckhouse.io/v1
kind: IngressNginxController
metadata:
  name: main
spec:
  ingressClass: nginx
  inlet: HostWithFailover
  nodeSelector:
    node-role.deckhouse.io/frontend: ""
  tolerations:
    - effect: NoExecute
      key: dedicated.deckhouse.io
      value: frontend

Example for bare metal with external load balancer

Use this configuration with Cloudflare, Qrator, Nginx+, Citrix ADC, Kemp, or other external load balancers.

apiVersion: deckhouse.io/v1
kind: IngressNginxController
metadata:
  name: main
spec:
  ingressClass: nginx
  inlet: HostPort
  hostPort:
    httpPort: 80
    httpsPort: 443
    behindL7Proxy: true

Example for bare metal (MetalLB in BGP LoadBalancer mode)

Available in DKP Enterprise Edition only.

IngressNginxController with the LoadBalancer inlet for use with MetalLB in BGP mode:

apiVersion: deckhouse.io/v1
kind: IngressNginxController
metadata:
  name: main
spec:
  ingressClass: nginx
  inlet: LoadBalancer
  nodeSelector:
    node-role.deckhouse.io/frontend: ""
  tolerations:
    - effect: NoExecute
      key: dedicated.deckhouse.io
      value: frontend

When using MetalLB, its speaker pods (MetalLB components that announce IP addresses) must run on the same nodes as the Ingress controller pods.

To preserve the real client IP addresses, the Ingress controller Service should be created with externalTrafficPolicy: Local to avoid inter-node SNAT. In this configuration, MetalLB speaker will only announce the Service from nodes running target pods.

Example ModuleConfig for the metallb module:

apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: metallb
spec:
  enabled: true
  version: 2
  settings:
    speaker:
      nodeSelector:
        node-role.deckhouse.io/frontend: ""
      tolerations:
        - effect: NoExecute
          key: dedicated.deckhouse.io
          value: frontend

Example for bare metal (MetalLB in L2 LoadBalancer mode)

Available in DKP Enterprise Edition only.

  1. Enable the metallb module:

    apiVersion: deckhouse.io/v1alpha1
    kind: ModuleConfig
    metadata:
      name: metallb
    spec:
      enabled: true
      version: 2
    
  2. Create a MetalLoadBalancerClass resource:

    MetalLB balancers should be placed on the same nodes as Ingress controllers. In typical deployment scenarios, frontend nodes are used for this purpose. To deploy Ingress controllers and MetalLB load balancers on frontend nodes, set the label node-role.deckhouse.io/frontend: "" in nodeSelector.

    apiVersion: network.deckhouse.io/v1alpha1
    kind: MetalLoadBalancerClass
    metadata:
      name: ingress
    spec:
      addressPool:
        - 192.168.2.100-192.168.2.150
      isDefault: false
      nodeSelector:
        node-role.deckhouse.io/frontend: "" # Load balancer node selector.
      type: L2
    
  3. Create an IngressNginxController resource:

    apiVersion: deckhouse.io/v1
    kind: IngressNginxController
    metadata:
      name: main
    spec:
      ingressClass: nginx
      inlet: LoadBalancer
      loadBalancer:
        loadBalancerClass: ingress
        annotations:
          # Number of addresses to allocate from the pool defined in MetalLoadBalancerClass.
          network.deckhouse.io/l2-load-balancer-external-ips-count: "3"
      nodeSelector:
        node-role.deckhouse.io/frontend: ""
      tolerations:
        - effect: NoExecute
          key: dedicated.deckhouse.io
          value: frontend
          operator: Equal
    

    When creating an ingress controller, you can also specify certain IP addresses from the pool that will be assigned to its Service. Use the annotation network.deckhouse.io/load-balancer-ips.

    If you need more than one address, also set network.deckhouse.io/l2-load-balancer-external-ips-count to the number of addresses allocated from the pool. That value must not be less than the number of addresses listed in network.deckhouse.io/load-balancer-ips.

    See “Example of using annotations” to assign specific addresses from the pool to the Service.

DKP will create a LoadBalancer Service with the specified number of IPs:

d8 k -n d8-ingress-nginx get svc

Example output:

NAME                   TYPE           CLUSTER-IP      EXTERNAL-IP                                 PORT(S)                      AGE
main-load-balancer     LoadBalancer   10.222.130.11   192.168.2.100,192.168.2.101,192.168.2.102   80:30689/TCP,443:30668/TCP   11s

Example of segregating access between public and administrative zones

In many applications, the same backend serves both the public part and the administrative interface. For example:

  • https://example.com is the public zone;
  • https://admin.example.com is the administrative zone, access to which must be restricted (ACL, mTLS, IP whitelist, and so on).

For this scenario, offload administrative traffic to a separate Ingress controller (with a dedicated Ingress class if necessary) and restrict access to it by using the spec.acceptRequestsFrom parameter.

  • Single Ingress controller
  • Separate Ingress controllers

Specifics of using a single Ingress controller

The example below shows a single Ingress controller serving requests from both the public zone and the administrative interface.

Example of Ingress resource configuration for this case:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: admin-ingress
  annotations:
    nginx.ingress.kubernetes.io/whitelist-source-range: "1.2.3.4/32"
spec:
  ingressClassName: nginx # The Ingress resource for administrative traffic is associated with the same Ingress controller as the Ingress resource for public traffic.
  rules:
    - host: admin.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: backend
                port:
                  number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: public-ingress
spec:
  ingressClassName: nginx # The Ingress resource for public traffic is associated with the same Ingress controller as the Ingress resource for administrative traffic.
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: backend
                port:
                  number: 80

With processing and forwarding of X-Forwarded-* headers enabled, the backend can rely on the x-forwarded-host header when making authorization decisions. In the example above, public Ingress traffic can reach the administrative zone via x-forwarded-host. Therefore, requests to the Ingress controller must come only from trusted sources.

Using separate Ingress controllers

To avoid that situation, do the following:

  • Configure access rules at the Ingress resource level.
  • Use separate Ingress controllers.
  • Restrict which source addresses are allowed to connect to the Ingress controllers.

Example of Ingress resource configuration for this case:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: admin-ingress
  annotations:
    nginx.ingress.kubernetes.io/whitelist-source-range: "1.2.3.4/32"
spec:
  ingressClassName: admin-nginx # The Ingress resource for administrative traffic is associated with a separate Ingress controller.
  rules:
    - host: admin.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: backend
                port:
                  number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: public-ingress
spec:
  ingressClassName: public-nginx # The Ingress resource for public traffic is associated with a separate Ingress controller.
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: backend
                port:
                  number: 80

Example of an Ingress controller that serves administrative Ingress resources and accepts connections only from specified subnets:

apiVersion: deckhouse.io/v1
kind: IngressNginxController
metadata:
  name: admin
spec:
  ingressClass: admin-nginx
  inlet: HostPort
  acceptRequestsFrom:
    - 1.2.3.4/32
    - 10.0.0.0/16
  hostPort:
    httpPort: 80
    httpsPort: 443
    behindL7Proxy: true

In this example:

  • The Ingress controller is exposed on node ports through the HostPort inlet.
  • The acceptRequestsFrom parameter allows connections to the controller only from the listed subnets.
  • Even if an external load balancer or client can set its own X-Forwarded-* header values, the decision whether to allow the connection to reach the controller is made based on the actual source address, not on headers.
  • Administrative Ingress resources (in this example admin-ingress) are served by this controller according to the configured Ingress class.

Example of an Ingress controller that serves Ingress resources for public traffic:

apiVersion: deckhouse.io/v1
kind: IngressNginxController
metadata:
  name: public
spec:
  ingressClass: public-nginx
  inlet: HostPort
  hostPort:
    httpPort: 8080
    httpsPort: 8443
    behindL7Proxy: true

Module support and security

The ingress-nginx module is covered by DKP maintenance for the entire platform support lifecycle, regardless of the upstream project’s development status. The DKP team tracks CVEs in the controller and its dependencies — NGINX, Lua modules, and base images — and delivers fixes in platform releases.

For compliance with PCI DSS expectations regarding vendor support and vulnerability remediation timelines, Flant is the responsible vendor of the module. DKP certification with FSTEC of Russia also covers vulnerability management processes and the release of security updates.

Additional resources