Exposing Apps with Services

KubernetesBeginner
Practice Now

Introduction

In previous labs, you learned that Pods come and go. If a Pod dies and a new one replaces it, the new Pod gets a completely different IP address. Imagine trying to call a friend, but their phone number changes every time they hang up!

To solve this, Kubernetes uses a Service.

A service gives a workload a stable network identity even though the backing pods may change. This separation between pod lifecycle and service endpoint is a fundamental Kubernetes networking idea.

This is one of the most important mental models in Kubernetes networking: Pods are meant to be replaceable, but clients still need a dependable address. A Service solves that mismatch by sitting in front of changing Pods and presenting a stable name and virtual IP.

In this lab, you will:

  • Create a Service to give your application a stable address.
  • Learn how the Service knows which Pods to call (using Selectors).
  • Access your application from outside the cluster using a specific type of Service called NodePort.

Create Service YAML

In this step, you will create a configuration file for a Service. We will use a specific type called NodePort.

A NodePort Service opens a specific port on every Node (server) in the cluster. It's like opening a specific door on your house so the outside world can come in.

NodePort is easy to understand because it maps directly to a port on each node, but it is usually not the final production design for public traffic. In larger environments, NodePort is often placed behind a cloud load balancer or ingress layer. Here it is useful because it makes the network path visible to beginners.

Create a file named service.yaml in your project directory:

nano service.yaml

Paste the following content. Notice the selector part—we are intentionally leaving it as app: unknown for now to show you how it works.

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: NodePort
  ports:
    - port: 80 ## The port the Service exposes internally
      targetPort: 80 ## The port the container is listening on
      nodePort: 30008 ## The static door we are opening to the outside
  selector:
    app: unknown ## This needs to match your Pod's label

Save and exit (Ctrl+X, Y, Enter).

This file defines a Service named my-service. It listens on port 30008 (the nodePort). Any traffic hitting this port will be forwarded to pods labeled app: unknown (which currently don't exist!).

Fix the Selector

A Service uses a Selector to find the Pods it should route traffic to. It acts like a mailing label. If the address on the envelope (Service Selector) doesn't match the address on the house (Pod Label), the mail never arrives.

Labels and selectors appear throughout Kubernetes, not just in Services. They are one of the platform's core grouping mechanisms. Instead of hard-coding exact Pod names, Kubernetes resources usually find related objects by matching labels, which keeps the system flexible when Pods are recreated.

In the Setup step, we automatically started a web application for you. Let's find its "address label".

Run this command to see the labels of your running Pods:

kubectl get pods --show-labels

You should see something like: web-app-xxxxx ... app=web-app

The label is app=web-app. However, our Service is looking for app: unknown. Let's fix that.

Edit your service.yaml file:

nano service.yaml

Change the selector section to match your Pod's label:

selector:
  app: web-app

Save and exit. Now the Service knows exactly where to send the traffic.

Apply and Verify

Now that the address is correct, let's turn on the Service.

When you apply this manifest, Kubernetes creates a Service object and programs internal networking rules so that requests reaching the Service can be forwarded to matching Pod endpoints. The important idea is that the Service itself does not run your application. It is a networking abstraction in front of the application Pods.

Apply the configuration to the cluster:

kubectl apply -f service.yaml

You should see: service/my-service created.

To confirm the Service is up and running, check the list of services:

kubectl get svc my-service

You will see:

  • NAME: my-service
  • TYPE: NodePort
  • PORT(S): 80:30008/TCP

This confirms your "Phone Number" is active and waiting for calls on port 30008.

Access the App

Now for the moment of truth. Can we access the application from "outside" (your terminal)?

The application is exposed on port 30008 of the Kubernetes Node.

There are three ports in play here, and beginners often mix them up. The nodePort is the externally opened port on the node, the port is the port exposed by the Service abstraction, and the targetPort is the port inside the container that actually serves the application. Keeping those roles distinct will prevent confusion later.

First, let's get the IP address of the Minikube node:

minikube ip

Let's say it is 192.168.49.2. The full address of your app is http://192.168.49.2:30008.

We can use curl to visit this address and verify the app is responding:

curl http://$(minikube ip):30008

You should see the HTML code for the "Welcome to nginx!" page.

This confirms the full journey of the request:

  1. You called the Node's external port (30008).
  2. The Service picked up the call.
  3. It checked its Selector (app: web-app).
  4. It forwarded the call to one of the Pods matching that label.
  5. The Pod responded with the web page.

Summary

In this lab, you solved the problem of disappearing Pod IP addresses using a Service.

You successfully:

  1. Created a NodePort Service (service.yaml) to open a gateway to your app.
  2. Configured a Selector to tell the Service which Pods to manage.
  3. Accessed your running application from the outside world using its stable address.

Services are the glue that holds Kubernetes applications together, allowing different parts of your system to talk to each other reliably.