DEV Community

Aisalkyn Aidarova
Aisalkyn Aidarova

Posted on

LAB — EKS + Node Group + Deployment + Service + Ingress + ALB

Browser
   ↓
DNS name of ALB
   ↓
AWS Application Load Balancer
   ↓
Ingress routing rule
   ↓
Kubernetes Service
   ↓
Pod
   ↓
Container
   ↓
EKS Worker Node
   ↓
EC2 in Node Group
Enter fullscreen mode Exit fullscreen mode

One important correction before the lab: the Ingress resource does not process live requests. The AWS Load Balancer Controller watches the Ingress and configures AWS ALB resources. The ALB then handles the HTTP/HTTPS traffic.

Goal

By the end, students should be able to explain:

EKS Cluster
    |
    +--- Node Group
    |       |
    |       +--- EC2 Worker Node
    |               |
    |               +--- Pod
    |                    |
    |                    +--- Container
    |
    +--- Service
    |       |
    |       +--- selects Pods
    |
    +--- Ingress
            |
            +--- routing rules

AWS Load Balancer Controller
            |
            +--- watches Ingress
            |
            +--- configures AWS ALB
Enter fullscreen mode Exit fullscreen mode

Part 1 — Check the EKS cluster

First:

aws eks list-clusters --region us-east-1
Enter fullscreen mode Exit fullscreen mode

Connect kubectl:

aws eks update-kubeconfig \
  --region us-east-1 \
  --name YOUR_CLUSTER_NAME
Enter fullscreen mode Exit fullscreen mode

Test:

kubectl get nodes
Enter fullscreen mode Exit fullscreen mode

If you see:

NAME                          STATUS   ROLES
ip-172-31-x-x.ec2.internal    Ready    <none>
Enter fullscreen mode Exit fullscreen mode

you are connected.


Part 2 — Understand the Node Group

This is important before Deployment.

Run:

aws eks list-nodegroups \
  --cluster-name YOUR_CLUSTER_NAME \
  --region us-east-1
Enter fullscreen mode Exit fullscreen mode

Then:

kubectl get nodes -o wide
Enter fullscreen mode Exit fullscreen mode

Explain:

EKS CONTROL PLANE
AWS manages
      |
      |
      ↓
NODE GROUP
      |
      +------------------+
      |                  |
      ↓                  ↓
EC2 Worker 1        EC2 Worker 2
      |                  |
    Pods                Pods
Enter fullscreen mode Exit fullscreen mode

Node Group = group of worker EC2 instances managed together.

The Node Group does not contain the Deployment in the sense of ownership.

Instead:

Deployment
    ↓
ReplicaSet
    ↓
Pod
    ↓ scheduled by Kubernetes
Worker Node
Enter fullscreen mode Exit fullscreen mode

Check where each Pod is running later with:

kubectl get pods -o wide
Enter fullscreen mode Exit fullscreen mode

Part 3 — Create namespace

Let's keep our lab isolated:

kubectl create namespace jumptotech
Enter fullscreen mode Exit fullscreen mode

Set it as default for this context if desired:

kubectl config set-context --current --namespace=jumptotech
Enter fullscreen mode Exit fullscreen mode

Check:

kubectl get pods
Enter fullscreen mode Exit fullscreen mode

Part 4 — Deployment

Create:

touch deployment.yaml
Enter fullscreen mode Exit fullscreen mode

Use your ECR image. For example:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: rose-deployment
spec:
  replicas: 3

  selector:
    matchLabels:
      app: rose

  template:
    metadata:
      labels:
        app: rose

    spec:
      containers:
        - name: rose-container
          image: ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/jumptotech-lab:latest
          ports:
            - containerPort: 80
Enter fullscreen mode Exit fullscreen mode

Replace ACCOUNT_ID with yours.

Apply:

kubectl apply -f deployment.yaml
Enter fullscreen mode Exit fullscreen mode

Check:

kubectl get deployments
Enter fullscreen mode Exit fullscreen mode

Then:

kubectl get rs
Enter fullscreen mode Exit fullscreen mode

Then:

kubectl get pods -o wide
Enter fullscreen mode Exit fullscreen mode

Stop here and explain:

rose-deployment
       ↓
ReplicaSet
       ↓
+------+------+------+
|             |      |
↓             ↓      ↓
Pod 1       Pod 2   Pod 3
              |
              ↓
          Worker Node
Enter fullscreen mode Exit fullscreen mode

Students should notice the NODE column from:

kubectl get pods -o wide
Enter fullscreen mode Exit fullscreen mode

Part 5 — Prove Pod ownership

Pick a Pod:

kubectl describe pod POD_NAME
Enter fullscreen mode Exit fullscreen mode

Look for:

Controlled By: ReplicaSet/rose-deployment-xxxxxxxx
Enter fullscreen mode Exit fullscreen mode

Then:

kubectl describe rs REPLICASET_NAME
Enter fullscreen mode Exit fullscreen mode

Look for:

Controlled By: Deployment/rose-deployment
Enter fullscreen mode Exit fullscreen mode

Now students have proved:

Deployment
    ↓ owns
ReplicaSet
    ↓ owns
Pod
Enter fullscreen mode Exit fullscreen mode

Part 6 — Create Service

Create:

touch service.yaml
Enter fullscreen mode Exit fullscreen mode

Add:

apiVersion: v1
kind: Service
metadata:
  name: rose-service
spec:
  type: ClusterIP

  selector:
    app: rose

  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
Enter fullscreen mode Exit fullscreen mode

Apply:

kubectl apply -f service.yaml
Enter fullscreen mode Exit fullscreen mode

Check:

kubectl get svc
Enter fullscreen mode Exit fullscreen mode

Then:

kubectl describe svc rose-service
Enter fullscreen mode Exit fullscreen mode

And:

kubectl get endpoints rose-service
Enter fullscreen mode Exit fullscreen mode

This is an excellent teaching moment.

You should see Pod backend IPs.

Explain:

rose-service
selector:
app=rose
      |
      |
      +----------------+
      |        |       |
      ↓        ↓       ↓
    Pod 1    Pod 2   Pod 3
 app=rose app=rose app=rose
Enter fullscreen mode Exit fullscreen mode

The Service doesn't own them.

It selects them.


Part 7 — Test Service before adding ALB

This is important troubleshooting practice.

Run:

kubectl port-forward service/rose-service 8080:80
Enter fullscreen mode Exit fullscreen mode

Then open:

http://localhost:8080
Enter fullscreen mode Exit fullscreen mode

If your application appears, you have proved:

Deployment ✓
Pods       ✓
Container  ✓
Service    ✓
Enter fullscreen mode Exit fullscreen mode

Therefore, if ALB later doesn't work, you already know the backend application works.

Stop port-forward with:

Ctrl+C
Enter fullscreen mode Exit fullscreen mode

Part 8 — Why can't we create Ingress yet?

Here's the important question.

If we simply create:

kind: Ingress
Enter fullscreen mode Exit fullscreen mode

who is going to implement it?

Ingress is essentially configuration/routing intent.

We need an Ingress implementation/controller.

For AWS ALB, that's normally:

AWS Load Balancer Controller.

Architecture:

             Kubernetes API
                   |
             Ingress object
                   |
                   ↓
       AWS Load Balancer Controller
                   |
                   | AWS APIs
                   ↓
                  AWS
                   |
                   ↓
                  ALB
Enter fullscreen mode Exit fullscreen mode

Part 9 — Check whether AWS Load Balancer Controller exists

Run:

kubectl get deployment \
  -n kube-system \
  aws-load-balancer-controller
Enter fullscreen mode Exit fullscreen mode

If installed, you should see something like:

NAME                           READY
aws-load-balancer-controller   2/2
Enter fullscreen mode Exit fullscreen mode

Also:

kubectl get pods -n kube-system
Enter fullscreen mode Exit fullscreen mode

Look for:

aws-load-balancer-controller-xxxxx
aws-load-balancer-controller-yyyyy
Enter fullscreen mode Exit fullscreen mode

If it's already installed, do not reinstall it.


Part 10 — If Controller is NOT installed

For class, explain the prerequisites before blindly installing it.

The controller needs permission to call AWS APIs:

AWS Load Balancer Controller
          |
          | AWS API
          ↓
ELBv2 / EC2 APIs
          |
          ↓
Create/configure:
ALB
Target Groups
Listeners
Rules
Security Groups
Enter fullscreen mode Exit fullscreen mode

In a standard EKS setup this means configuring the controller's AWS permissions, commonly through an IAM role associated with its Kubernetes service account (IRSA), or another supported EKS pod identity mechanism.

Installation details can change, so for the actual installation commands use the current AWS/EKS documentation rather than having students copy an old IAM policy blindly.

The key thing they need to understand is:

Controller
   +
AWS permissions
   =
ability to manage ALB resources
Enter fullscreen mode Exit fullscreen mode

Part 11 — Verify IngressClass

After the controller is installed:

kubectl get ingressclass
Enter fullscreen mode Exit fullscreen mode

You want to see an ALB class available, typically:

NAME   CONTROLLER
alb    ingress.k8s.aws/alb
Enter fullscreen mode Exit fullscreen mode

Part 12 — Create Ingress

Create:

touch ingress.yaml
Enter fullscreen mode Exit fullscreen mode

Add:

apiVersion: networking.k8s.io/v1
kind: Ingress

metadata:
  name: rose-ingress

  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip

spec:
  ingressClassName: alb

  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix

            backend:
              service:
                name: rose-service
                port:
                  number: 80
Enter fullscreen mode Exit fullscreen mode

Apply:

kubectl apply -f ingress.yaml
Enter fullscreen mode Exit fullscreen mode

Part 13 — Watch the magic happen

Run:

kubectl get ingress
Enter fullscreen mode Exit fullscreen mode

Initially you may see:

NAME           CLASS   HOSTS   ADDRESS
rose-ingress   alb     *       
Enter fullscreen mode Exit fullscreen mode

Wait a little and run again:

kubectl get ingress
Enter fullscreen mode Exit fullscreen mode

Eventually ADDRESS should contain the ALB DNS name if provisioning succeeds.

You can watch:

kubectl get ingress -w
Enter fullscreen mode Exit fullscreen mode

Part 14 — What actually happened?

This is where I would stop typing commands and draw the architecture.

When you executed:

kubectl apply -f ingress.yaml
Enter fullscreen mode Exit fullscreen mode

you did not directly create an ALB.

You created:

Ingress object
Enter fullscreen mode Exit fullscreen mode

in Kubernetes.

Then:

ingress.yaml
      ↓
API Server
      ↓
Ingress object
      ↓
AWS Load Balancer Controller
      ↓
sees desired configuration
      ↓
calls AWS APIs
      ↓
ALB + Target Group + Listener/rules
Enter fullscreen mode Exit fullscreen mode

That's what a controller does.

It continually tries to make reality match desired state.


Part 15 — Check the Controller logs

This is a great demonstration:

kubectl logs \
  -n kube-system \
  deployment/aws-load-balancer-controller
Enter fullscreen mode Exit fullscreen mode

If there is an ALB problem, this is one of the places to investigate.

You can also:

kubectl describe ingress rose-ingress
Enter fullscreen mode Exit fullscreen mode

Look at:

Rules
Annotations
Backend
Events
Enter fullscreen mode Exit fullscreen mode

Part 16 — Open the ALB

Get:

kubectl get ingress
Enter fullscreen mode Exit fullscreen mode

You'll see an ADDRESS.

Open that ALB DNS name in the browser.

Now the complete request flow is:

                         USER
                           |
                           | HTTP request
                           ↓
                     ALB DNS NAME
                           |
                           ↓
                  APPLICATION LOAD
                      BALANCER
                           |
                    routing rule
                           |
                           ↓
                     rose-service
                           |
                    backend endpoints
                           |
             +-------------+-------------+
             |             |             |
             ↓             ↓             ↓
           Pod 1         Pod 2         Pod 3
             |             |             |
             ↓             ↓             ↓
         Container     Container     Container
Enter fullscreen mode Exit fullscreen mode

With alb.ingress.kubernetes.io/target-type: ip, the AWS target group can target Pod IPs directly, so don't teach students that traffic must always traverse NodePort.


Part 17 — Where is Ingress in the live request?

This is the subtle concept I want your students to get right.

Don't draw:

User → ALB → Ingress object → Controller → Service
Enter fullscreen mode Exit fullscreen mode

as though every HTTP request physically passes through the YAML object/controller.

Instead:

Configuration/control flow

Ingress
   ↓
AWS Load Balancer Controller
   ↓
ALB configuration
Enter fullscreen mode Exit fullscreen mode

Runtime traffic

Conceptually:

User
 ↓
ALB
 ↓
configured backend
 ↓
Service/Pod backend model
 ↓
Pod
Enter fullscreen mode Exit fullscreen mode

The controller is not sitting in the middle forwarding every customer request.


Part 18 — Path-based routing

Now make the lab more interesting.

Imagine:

/
      → frontend-service

/api
      → api-service

/admin
      → admin-service
Enter fullscreen mode Exit fullscreen mode

Ingress could contain:

rules:
  - http:
      paths:

        - path: /api
          pathType: Prefix
          backend:
            service:
              name: api-service
              port:
                number: 80

        - path: /admin
          pathType: Prefix
          backend:
            service:
              name: admin-service
              port:
                number: 80

        - path: /
          pathType: Prefix
          backend:
            service:
              name: frontend-service
              port:
                number: 80
Enter fullscreen mode Exit fullscreen mode

Then:

                    ALB
                     |
        +------------+------------+
        |            |            |
        ↓            ↓            ↓
       /           /api        /admin
        ↓            ↓            ↓
    frontend       API          admin
    service       service       service
       ↓             ↓             ↓
     Pods          Pods          Pods
Enter fullscreen mode Exit fullscreen mode

That's where your earlier GitHub-style /actions analogy becomes useful.


Part 19 — Break it intentionally 🔥

Now teach troubleshooting.

Change Service:

selector:
  app: WRONG
Enter fullscreen mode Exit fullscreen mode

Apply:

kubectl apply -f service.yaml
Enter fullscreen mode Exit fullscreen mode

Check:

kubectl get endpoints rose-service
Enter fullscreen mode Exit fullscreen mode

You may now have no endpoints.

Ask students:

ALB exists. Ingress exists. Service exists. Why doesn't the website work?

Because:

Service
 selector app=WRONG
       ↓
No matching Pods
       ↓
No healthy application backend
Enter fullscreen mode Exit fullscreen mode

Fix:

selector:
  app: rose
Enter fullscreen mode Exit fullscreen mode

Then:

kubectl apply -f service.yaml
Enter fullscreen mode Exit fullscreen mode

Check again:

kubectl get endpoints rose-service
Enter fullscreen mode Exit fullscreen mode

This is a much better lesson than only deploying a working configuration.


Part 20 — Full troubleshooting workflow

Tell students:

Follow the request. Don't randomly type commands.

Start:

kubectl get ingress
Enter fullscreen mode Exit fullscreen mode

Then:

kubectl describe ingress rose-ingress
Enter fullscreen mode Exit fullscreen mode

Then:

kubectl get svc
Enter fullscreen mode Exit fullscreen mode

Then:

kubectl describe svc rose-service
Enter fullscreen mode Exit fullscreen mode

Then:

kubectl get endpoints rose-service
Enter fullscreen mode Exit fullscreen mode

Then:

kubectl get pods -o wide
Enter fullscreen mode Exit fullscreen mode

Then:

kubectl describe pod POD_NAME
Enter fullscreen mode Exit fullscreen mode

Finally:

kubectl logs POD_NAME
Enter fullscreen mode Exit fullscreen mode

Think:

Browser doesn't work
        ↓
Does ALB exist?
        ↓
Does Ingress show ADDRESS/events?
        ↓
Is Controller healthy?
        ↓
Does Service exist?
        ↓
Does Service have endpoints?
        ↓
Are Pods READY?
        ↓
Is targetPort correct?
        ↓
Is container listening?
        ↓
Check application logs
Enter fullscreen mode Exit fullscreen mode

Part 21 — Node Group vs Service vs Ingress

Students often mix these up.

Make them memorize the responsibilities:

Component Job
Node Group Provides/manages worker EC2 capacity
Node Machine where Pods run
Deployment Declares/manages desired application replicas and rollout
ReplicaSet Maintains the requested Pod replicas
Pod Runs application container(s)
Service Stable Kubernetes abstraction/selects backend Pods
Ingress Declares HTTP/HTTPS routing rules
AWS Load Balancer Controller Reconciles Kubernetes resources with AWS load-balancer resources
ALB Handles actual Layer-7 HTTP/HTTPS traffic

Final architecture to draw on the board

I would draw two diagrams, not one.

Kubernetes/AWS resources

                       EKS CLUSTER

              ┌───────────────────────────┐
              │                           │
              │  Deployment               │
              │      ↓                    │
              │  ReplicaSet               │
              │      ↓                    │
              │   Pods                    │
              │    ↓ ↓ ↓                  │
              │  Worker Nodes             │
              │      ↑                    │
              │   Node Group              │
              │                           │
              │  Service → Pods           │
              │                           │
              │  Ingress                  │
              │      ↓ watched by         │
              │ AWS LB Controller         │
              │                           │
              └────────────┬──────────────┘
                           |
                           | AWS API
                           ↓
                      AWS ALB
Enter fullscreen mode Exit fullscreen mode

Customer request

Customer
   |
   | GET /
   ↓
DNS
   ↓
AWS ALB
   |
   | Layer 7 routing
   ↓
Backend for rose-service
   ↓
Pod
   ↓
Container
   ↓
Application
Enter fullscreen mode Exit fullscreen mode

And finish by asking students:

Who creates Pods?
ReplicaSet, under the Deployment's control.

Who chooses which Node runs a Pod?
Kubernetes scheduler.

What provides the worker machines?
Node Group.

How does Service know its Pods?
Labels + selector, which Kubernetes turns into backend endpoint information.

What is Ingress?
Desired HTTP/HTTPS routing configuration.

What does AWS Load Balancer Controller do?
Watches/reconciles Kubernetes resources and configures AWS load-balancing infrastructure.

Who handles the actual customer's HTTP request?
ALB and the configured data path to the application backend.

Top comments (0)