How to Create an Internal Network Load Balancer (NLB) in EKS Using a Kubernetes Service

Table of Contents

When you run several internal applications inside an Amazon EKS cluster, there often comes a point when you need one stable entry point for all your workloads. Instead of exposing every service individually, you can route traffic through one internal Network Load Balancer (NLB) and manage everything from Kubernetes. This approach keeps things simple, predictable, and aligned with how Kubernetes workloads are usually deployed.

In this guide, we’ll walk through how to create an internal NLB using a Kubernetes Service, how it works behind the scenes, and how you can attach more than one application to the same load balancer. The focus is on clarity rather than length, so you should be able to apply this pattern right after reading.

Why We Don’t Use Ingress for NLBs

A common question beginners ask is: “Can’t I just use an Ingress to create an NLB?”
In EKS, the answer is no.

Here’s why:

  • Ingress is a Layer 7 construct used for HTTP/HTTPS routing.
  • The AWS Load Balancer Controller maps Ingress objects to Application Load Balancers (ALBs) only.
  • Since NLBs operate at Layer 4, they don’t support host/path routing, so Ingress is not the correct resource type.

If your goal is an NLB, the correct method is to use a Service of type LoadBalancer with the right AWS annotations.

How the Internal NLB Is Created Behind the Scenes

Once you apply a Service that requests an NLB:

  1. Kubernetes stores the Service definition.
  2. The AWS Load Balancer Controller notices the Service and reads annotations such as whether the NLB should be internal, what target type to use, and what ports to expose.
  3. The controller talks to AWS APIs and builds:
    • An internal NLB in your VPC
    • A listener for each port you specify
    • A target group for each port
    • TCP health checks for each target group
    • Pod IP registrations so the NLB can send traffic to your workloads

From a user perspective, one Service file results in a complete NLB setup without touching the AWS console.

YAML Example: Internal NLB Backing Multiple Ports

Below is a clear Service definition designed to create one internal NLB that exposes multiple ports. The service names have been replaced with generic application names such as api-service, worker-service, analytics-service, etc.

apiVersion: v1
kind: Service
metadata:
  name: internal-nlb-service
  namespace: app-qa
  labels:
    app: multi-app
    environment: qa
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "external"
    service.beta.kubernetes.io/aws-load-balancer-scheme: "internal"
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
spec:
  type: LoadBalancer
  loadBalancerClass: service.k8s.aws/nlb
  selector:
    nlb-backend: shared-nlb-group
    environment: qa
  ports:
    - name: api-service
      port: 5001
      targetPort: 5001
    - name: worker-service
      port: 5002
      targetPort: 5002
    - name: analytics-service
      port: 5003
      targetPort: 5003
    - name: ml-engine
      port: 5004
      targetPort: 5004
    - name: report-generator
      port: 5005
      targetPort: 5005

What this file accomplishes

  • Creates one internal NLB
  • Adds five ports, one per application
  • Produces five target groups, each mapped to a single port
  • Points all target groups to pods that match the selector

This is the core pattern behind multi-port NLBs inside EKS.

How Multiple Target Groups Are Created

Each entry under the ports: section becomes:

  • A listener on the NLB
  • A target group in AWS
  • A connection to the matching pods based on labels

For example:

Application Name

Port

AWS Target Group

Targets

api-service

5001

TG1

Pod IPs

worker-service

5002

TG2

Pod IPs

analytics-service

5003

TG3

Pod IPs

ml-engine

5004

TG4

Pod IPs

report-generator

5005

TG5

Pod IPs

You maintain one load balancer, one DNS name, and separate traffic paths for every workload.

How Multiple Deployments Attach to the Same NLB

The most important part of this setup is how your different applications connect to the NLB.

The key mechanism is the label selector in the Service:

selector:
  nlb-backend: shared-nlb-group
  environment: qa

Any Deployment that should receive traffic from this NLB must define the same labels:

metadata:
  labels:
    nlb-backend: shared-nlb-group
    environment: qa

This tells Kubernetes:

  • "These pods belong behind the internal NLB."
  • "Register these pods under whichever target group corresponds to their port.”

If a new application is added tomorrow, you only need to:

  1. Add the same labels to the new Deployment
  2. Add a new port entry in the Service YAML

Immediately:

  • A new target group is created
  • Pods from the new Deployment are registered
  • Traffic flows through the same NLB

There is no way to reuse the same NLB across multiple Services, so this is the right and only method.

Final Thoughts

Creating an internal NLB in EKS does not require multiple Services or manual setup in AWS. A single Kubernetes Service with a few annotations is enough to create an internal NLB, listeners, and multiple target groups.

By using shared labels, you can attach several Deployments to the same NLB, allowing you to manage a group of applications under a unified entry point. This approach keeps your configuration simple while giving you full control over how your internal services are exposed within your environment.

If you follow the patterns shown here, you can expand your system with new applications at any time without modifying anything in AWS directly. The Kubernetes Service remains the single source of truth.