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:
- Kubernetes stores the Service definition.
- 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.
- 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:
- Add the same labels to the new Deployment
- 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.