What Is a Service in Kubernetes?
A plain-English guide to the object that gives your applications a stable name and address: why Pods need it, how it finds the right containers, how traffic is spread between them, and how to expose your app safely to the world.
Imagine you build an online shop from small programs. One program shows products, one handles the shopping cart, and one takes payments. The cart program needs to call the payment program many times a second. In Kubernetes, each program runs inside a Pod, and here is the awkward truth: Pods are temporary. They crash, they are replaced during updates, they move to other machines, and each new Pod gets a brand-new network address. So how can the cart program ever know where to send a payment request?
The answer is a Kubernetes Service. It is one of the first objects every beginner meets, and one of the most misunderstood. In this guide you will learn what a Service is, why it exists, how it picks the right Pods, how traffic reaches them, what the different Service types do, how names and discovery work, how to expose an app to the internet, and how to secure, watch, and troubleshoot it. Every new word is explained the first time it appears, and no earlier Kubernetes knowledge is needed.
1What Is a Service? The Short Answer
A Service is a stable name and address in front of a changing group of Pods, with built-in load balancing.
The two ideas inside one object
A Kubernetes Service does two jobs at once. First, it gives a group of Pods one steady identity: a fixed internal address and a fixed name that never change while the Service exists. Second, it spreads incoming requests across all the healthy Pods in that group, so no single Pod is overloaded and a failed Pod is skipped automatically. Callers only ever learn the Service’s name. They never need to know how many Pods stand behind it, where those Pods run, or whether they were replaced a minute ago.
Think of a company’s main phone number. When you call it, a receptionist connects you to whichever employee is free. Employees come and go, change desks, and take holidays, yet the main number stays the same for years. A Service is that main number, and the Pods are the employees. Customers dial one number and always reach someone available.
A few words to know first
- Pod: the smallest unit Kubernetes runs, holding one or more containers that share a network address.
- Label: a small tag, like a colored sticker, attached to an object. A Pod might carry the label “app equals cart.”
- Selector: a rule that says “pick every Pod with this label.” A Service uses a selector to find its Pods.
- Load balancing: sharing work between several identical workers so that none is overwhelmed.
- Cluster: the group of machines that Kubernetes manages as one system.
flowchart LR
C["Cart program - the caller"] -->|calls one steady name| S["Service: payments - stable name and address"]
S --> P1["Payment Pod 1"]
S --> P2["Payment Pod 2"]
S --> P3["Payment Pod 3"]
Remember this one sentence: Pods come and go, but the Service stays. Almost every design choice about Services follows from it.
2Why Services Exist: The Problem They Solve
To see why Services matter, look at what life is like without one.
Pods are mortal, and their addresses die with them
Every Pod in a cluster receives its own network address, called an IP address. Two Pods can talk directly using these addresses, and Kubernetes promises that this works even across different machines. That sounds convenient, but there is a catch. Kubernetes treats Pods as disposable. If a Pod crashes, a replacement is created with a new address. If you deploy a new version, the old Pods are removed and new ones appear. If the cluster adds machines or removes them, Pods move. An address that was correct in the morning may point to nothing by the afternoon.
Why not just write the address down?
Suppose the cart program stored the payment Pod’s address in a settings file. The first time that Pod restarted, every payment would fail. Suppose instead the cart program asked the cluster for the current list of payment Pods before each request. That would work, but now every program in your company would need special code to talk to Kubernetes, choose between Pods, and handle failures. It would be repetitive, error-prone, and impossible to share with programs that know nothing about Kubernetes.
The Service as the clean solution
A Service moves this whole problem into the platform. Programs use an ordinary name, the same way a website uses a domain name, and the cluster quietly keeps the list of Pods up to date behind the scenes. Your code needs no Kubernetes knowledge at all. It just calls a name.
Consider a taxi company. You never memorize the mobile numbers of individual drivers, because drivers change shifts, cars, and even jobs. You call the dispatch number, and dispatch sends whichever car is nearby and free. If a car breaks down, dispatch simply sends another, and you may never learn there was a problem. The Service is the dispatch line.
Five gifts a Service gives you
A stable address
The Service’s internal address stays the same for its whole life, no matter how often Pods change.
A friendly name
Every Service automatically gets a name in the cluster’s own phone book, so programs call “payments” instead of a number.
Load balancing
Requests are shared across all matching Pods, so capacity grows simply by adding Pods.
Health awareness
Only Pods that report themselves as ready receive traffic. Sick or starting Pods are skipped.
Controlled exposure
You choose who can reach the app: only other Pods, other machines on the network, or the whole internet.
3Core Concepts: Selectors, Ports, and Endpoints
Five small ideas explain how a Service is described and how it knows where to send traffic.
1. The selector: which Pods belong to me?
A Service does not create Pods and does not own them. It only points at them. The pointing is done with a selector, a label rule. If the Service says “select Pods labeled app equals payments,” then every Pod in the same namespace carrying that label becomes a member, and any Pod losing the label leaves the group. This loose connection is powerful: you can add, remove, or replace Pods freely, and the Service adapts without any edits.
2. Ports: the doors
A machine offers many numbered network doors called ports. A Service has a few port settings, and beginners often mix them up, so here they are in plain words:
| Setting | Meaning | Analogy |
|---|---|---|
| port | The door number the Service itself listens on | The extension number callers dial |
| targetPort | The door number on the Pod where the app actually listens | The desk phone that actually rings |
| nodePort | A door opened on every machine, only for NodePort and LoadBalancer types | A side entrance to the building |
| protocol | The kind of traffic, usually TCP, sometimes UDP | Phone call versus text message |
3. The cluster IP: the virtual address
Most Services receive a cluster IP, an internal address taken from a range reserved for Services. It is called virtual because no machine or network card really owns it. It exists only as a rule inside the cluster’s networking, which redirects any traffic sent to it toward a real Pod. That is why it can stay fixed while the Pods behind it change.
4. Endpoints and EndpointSlices: the current member list
Somebody must keep track of which Pods currently match the selector and are ready. That list is stored in objects called EndpointSlices, which hold the addresses and ports of the healthy Pods. A controller in the cluster creates and updates them automatically. The older Endpoints object did the same job for all Pods at once, and it is being phased out because one huge list was slow to update in big clusters. Slices break the list into smaller pieces, roughly a hundred addresses each by default.
5. Readiness: only healthy Pods get traffic
Each Pod can define a readiness check, a small test that says “I am ready for customers.” While the test fails, for example because the app is still loading data, the Pod stays out of the member list. When it passes, the Pod is added. This is what makes rolling updates and restarts smooth.
Reading a Service definition in plain words
When you write a Service as a configuration file, it usually holds only a handful of facts, and you can read them like a short form. It has a name, which becomes the DNS name. It has a namespace, the room it lives in. It has a type, which decides who can reach it, and if you leave the type out it becomes ClusterIP. It has a selector, the label rule that finds its Pods. And it has a list of ports, each with the number callers use and the number the app listens on. That is nearly everything. A Service definition is one of the shortest and most readable objects in Kubernetes, which is one reason it is a good first object to learn.
A tip for choosing labels: use names that describe the role of the app, not its version. If a Service selects “version two,” it stops matching the moment you release “version three.” Labels for the role stay stable, while a separate label for the version can be added for other purposes, such as gradual rollouts.
A Service works only inside its own namespace when it comes to selecting Pods. A Service in one namespace cannot pick up Pods that live in another. Other namespaces can still call the Service by using its full name.
4Architecture and Components
A Service looks like a single object, but several cluster components cooperate to make it work.
flowchart TB
USER["You create a Service"] --> API["API Server - stores the Service"]
API --> ESC["EndpointSlice controller - watches Pods and Services"]
ESC -->|writes member list| API
API --> KP1["kube-proxy on Node 1 - programs traffic rules"]
API --> KP2["kube-proxy on Node 2 - programs traffic rules"]
API --> DNS["CoreDNS - adds the Service name to cluster DNS"]
KP1 --> POD1["Pod on Node 1"]
KP2 --> POD2["Pod on Node 2"]
The cast of characters
API Server
Receives your Service definition, checks it, saves it in the cluster database, and tells watchers about it. It also allocates the cluster IP.
EndpointSlice controller
Watches Services and Pods. Whenever a matching Pod appears, disappears, or changes readiness, it updates the member list.
kube-proxy
Runs on every worker machine. It watches Services and their member lists and writes the network rules that redirect traffic from the virtual address to real Pods.
CoreDNS
The cluster’s name server. It answers questions like “what is the address of payments?” using the Services stored in the API.
Network plugin
Gives each Pod an address and makes sure Pods on different machines can reach each other. Services rely on it.
Cloud controller
For LoadBalancer Services, it asks the cloud provider to create a real load balancer and reports the resulting address.
Notice that there is no single program called “the Service.” It is a shared agreement stored in the API server, and many independent helpers read it and act. That is why Services keep working even if one helper restarts.
5Internal Working: How Traffic Really Flows
The virtual address is a clever illusion. Here is how the trick is done.
sequenceDiagram
participant C as Cart Pod
participant D as CoreDNS
participant N as Node networking rules
participant P as Payment Pod
C->>D: What is the address of payments?
D-->>C: The Service cluster IP
C->>N: Send request to the cluster IP and port
N->>N: Pick one ready Pod from the member list
N->>P: Forward request to the chosen Pod address
P-->>C: Reply travels back to the cart
The caller uses the name
The cart program asks for “payments.” Cluster DNS answers with the Service’s virtual address. Nothing about individual Pods is revealed.
The packet leaves toward the virtual address
The cart’s request is sent to the virtual address and the Service port, as if a real machine lived there.
The local machine rewrites the destination
Before the packet leaves the machine, rules installed by kube-proxy notice the virtual address. They pick one Pod from the ready list and replace the destination with that Pod’s real address. This trick is called destination network address translation, or DNAT.
The network delivers it
The cluster network carries the packet to the chosen Pod, wherever it runs.
The reply is translated back
The machine remembers the rewrite, so the reply appears to come from the Service address, and the caller never notices the swap.
Why the same connection keeps going to the same Pod
You might wonder what stops the next packet of the same conversation from being sent to a different Pod. The answer is the Linux connection tracking table. The first packet of a new connection triggers the choice of a Pod, and the kernel writes a note saying “this connection belongs to that Pod.” Every later packet in the same connection follows the note, and the replies are translated back using the same note. This is why the balancing decision happens once per connection, not once per request, and why a long-lived connection sticks to one Pod for its whole life. It also explains why a Pod that disappears in the middle of a connection can cause errors: the note still points at it until the connection ends or times out.
kube-proxy modes: three ways to write the rules
| Mode | How it works | Strengths | Watch out for |
|---|---|---|---|
| iptables | Writes a chain of packet-filter rules in the Linux kernel; a rule picks a random backend | Simple, reliable, the long-time default | Rule lists grow long with thousands of Services and can slow updates |
| IPVS | Uses the kernel’s built-in load balancer with hash tables and several balancing methods | Faster lookups at large scale, more balancing choices | Needs extra kernel modules and more setup |
| nftables | Uses the newer Linux packet-filter system to hold rules more efficiently | Better performance than iptables for large rule sets | Newer, so check support in your Kubernetes version and operating system |
Replacing kube-proxy altogether
Some network plugins, most famously Cilium, use a Linux technology called eBPF to perform Service routing directly inside the kernel, without kube-proxy at all. This can be faster and gives better visibility. Google’s GKE Dataplane V2 is built on this idea, and other managed services offer similar options. From your point of view as an application developer, nothing changes: you still create a Service and call its name.
The virtual address does not answer ping requests in the usual way, because no machine truly owns it. Only the ports defined in the Service work. Beginners often think the Service is broken when a ping fails, even though the app is perfectly reachable.
6ClusterIP: The Default Service Type
Every Service has a type that decides who can reach it. The first type, and the default, is ClusterIP.
What it does
A ClusterIP Service gets a virtual address that works only inside the cluster. Other Pods can call it, and so can programs running on the cluster’s own machines, but nobody outside can reach it directly. This is the right choice for most internal parts of an application: databases, payment engines, background workers, and internal APIs. It follows a good security habit: keep things private unless there is a reason to expose them.
ClusterIP is the internal extension number of an office phone system. Colleagues inside the building can dial 214 to reach the accounting team, but someone on the street cannot dial it. It is simple, private, and reliable, which is exactly what you want for internal conversations.
A Service you already use without knowing
Every cluster contains a Service named kubernetes in the default namespace. It is the stable internal address of the API server itself. Programs running in Pods, such as controllers and monitoring tools, use it to find the cluster’s front door without knowing which control plane machine is currently active. This is a perfect example of the idea in action.
Where the virtual address comes from
When the cluster is created, the administrator reserves a block of private addresses just for Services, called the Service address range. The API server hands out one address from this block to every new Service and records it so that no two Services share one. The block is separate from the range used for Pod addresses, which is why Service addresses and Pod addresses never collide. You can ask for a particular address when you create a Service, but it is almost always better to let the cluster choose. If the range is too small, you can run out of addresses in a very large cluster, so platform teams choose it with growth in mind.
When to use ClusterIP
- Any component that is called only by other components in the cluster.
- Databases and caches that should never be public.
- Services placed behind an Ingress or Gateway, which handles the outside world.
- Anything you are unsure about, since private is the safe default.
Strengths
- Simple and stable
- Private by default
- No cost beyond the cluster itself
- Works everywhere Kubernetes runs
Limits
- Not reachable from outside the cluster
- Needs Ingress or a Gateway for public web traffic
- Cluster IP cannot be pinged
7NodePort and LoadBalancer: Opening the Doors
When people outside the cluster must reach your app, two more Service types come into play. They build on each other like layers.
flowchart LR
U["Customer on the internet"] --> LB["Cloud load balancer - public address"]
LB --> NP1["NodePort door on Node 1"]
LB --> NP2["NodePort door on Node 2"]
NP1 --> CIP["ClusterIP - virtual address"]
NP2 --> CIP
CIP --> A["Pod A"]
CIP --> B["Pod B"]
CIP --> C["Pod C"]
NodePort: a side entrance on every machine
A NodePort Service does everything a ClusterIP Service does, and additionally opens the same port number on every worker machine. By default this number comes from the range 30000 to 32767. Anyone who can reach any machine’s address on that port is forwarded to the Service, and from there to a Pod, even if that Pod runs on a different machine. It is a quick way to test an app from outside, and it is the building block that other types use.
In real production use, NodePort has drawbacks. The port numbers are unfamiliar and high. You must know the addresses of your machines, and those may change. Firewalls must be opened for the whole range. And there is no health-checking front layer that stops sending traffic to a machine that has failed. For these reasons, it is mainly used for learning, small tests, or as a hidden layer beneath a load balancer.
LoadBalancer: a proper public front door
A LoadBalancer Service goes one step further. It does all the work of NodePort and then asks the cloud provider, through the cloud controller, to create a real external load balancer with its own public address. The provider’s load balancer checks the health of the machines and spreads traffic across them. When the address is ready, Kubernetes writes it into the Service, and you can point your website name at it.
Picture a shopping mall. The ClusterIP is the internal corridor that only staff use. The NodePort adds a side door at every wing of the mall. The LoadBalancer is the grand main entrance on the street, with a greeter who directs each visitor to a side door that is currently open and quiet, then through the corridor to the right shop.
Internal load balancers
A LoadBalancer Service does not have to face the internet. Most clouds let you ask for an internal load balancer, which receives an address only on your private network. This is useful when a Service must be reachable from other clusters, from virtual machines outside Kubernetes, or from your company network over a private link, but never from the public internet. Requesting it usually means adding a provider-specific annotation to the Service, which is one more reason to review annotations carefully when you move between clouds.
What you need to know about LoadBalancer
- It costs money. Each Service of this type usually creates its own cloud load balancer, and you pay for each one every hour, plus data transfer.
- It depends on the cloud. On AWS, Google Cloud, and Azure, it works out of the box. In a private data center, or on a laptop, there is no cloud to answer, so the address stays “pending” unless you install a helper such as MetalLB.
- Settings live in annotations. Details such as internal versus public, or which kind of load balancer to build, are usually chosen with provider-specific annotations, so they are not fully portable.
- It works at the network level. A LoadBalancer Service moves raw connections and does not understand website addresses or paths. For that, use Ingress or Gateway (Chapter 11).
| Type | Who can reach it | Typical use | Main drawback |
|---|---|---|---|
| ClusterIP | Only things inside the cluster | Internal services and databases | No outside access |
| NodePort | Anyone who can reach a machine on the high port | Testing and building blocks | Awkward and less safe for production |
| LoadBalancer | The internet, or a private network, through a cloud load balancer | Exposing one app or protocol directly | One paid load balancer per Service |
Every LoadBalancer Service you create can quietly add a new bill line. A team that exposes twenty small apps this way can end up with twenty load balancers. Put many web apps behind one shared Ingress or Gateway instead.
8ExternalName, Headless, and Selector-less Services
Not every Service points at ordinary Pods. Three special forms handle unusual needs.
ExternalName: a nickname for something outside
An ExternalName Service does not select Pods at all. Instead, it makes a name inside the cluster point to a name outside it. When a Pod asks for the Service, cluster DNS answers with an alias to the outside name, such as the address of a managed database run by a cloud provider. No proxying or load balancing happens; it is only a nickname. Its advantage is that your applications use a stable in-cluster name, and if you later move the database, you change one Service instead of every application.
Headless Services: the phone book without a receptionist
Sometimes load balancing is exactly what you do not want. A database cluster needs each member to find the others by name, and a client may need to connect to a particular member. A headless Service is created by setting the cluster IP to “None.” It gets no virtual address and no load balancing. Instead, DNS returns the individual addresses of all the ready Pods, and the caller decides which to use.
Headless Services are the partner of StatefulSets, the Kubernetes tool for running stateful programs such as databases. Together they give each Pod a stable, predictable name, like database-0, database-1, and database-2, which survives restarts.
A normal Service is a company switchboard that picks who answers. A headless Service is a staff directory pinned in the lobby: it simply lists every employee’s direct line, and you choose whom to call. A team that must talk to each other by name, like surgeons in an operating room, needs the directory, not the switchboard.
Services without selectors: pointing at anything
If you create a Service without a selector, Kubernetes does not create a member list for you. You supply the addresses yourself. This lets you give a friendly in-cluster name to something that is not a Pod, for example an old database server in your data center, or an application in another cluster. Applications inside the cluster call the Service name, and you control where it points.
ExternalName
Use to give an outside service an in-cluster nickname. Watch for outside names that use their own security certificates, since the name the client sees may differ.
Headless
Use for databases, message brokers, and any program where members need to find each other or clients need a specific member.
No selector
Use to wrap something outside Kubernetes in a Service, with addresses you maintain yourself.
9Service Discovery: Finding Services by Name
A Service is only useful if others can find it. Kubernetes gives you two mechanisms, and one of them is far better.
DNS: the recommended way
The cluster runs its own name server, CoreDNS. Whenever you create a Service, it automatically gains a name of the form service-name.namespace.svc.cluster.local. So a Service called payments in the namespace shop is known as payments.shop.svc.cluster.local. Every Pod is configured to search sensible shortcuts, so the ways you can call it depend on where you are:
| Where the caller lives | Name the caller can use | Why it works |
|---|---|---|
| Same namespace | payments | The Pod’s search list adds its own namespace |
| Different namespace | payments.shop | Adds the namespace but leaves the rest to the search list |
| Anywhere, most explicit | payments.shop.svc.cluster.local | The full, fully qualified name |
DNS also supports named ports, which lets callers look up not only the address but also which port carries a particular protocol. For headless Services, DNS returns the list of Pod addresses instead of a single virtual address.
A hidden cost: the search list
The convenience of short names comes with a small price. Each Pod is configured with a list of suffixes to try, such as its own namespace, the general Service suffix, and the cluster suffix. It also has a setting that treats any name with fewer than five dots as “probably local,” so it tries the suffixes first. When your program looks up a real outside website like a payment provider, the Pod may first ask DNS several wrong questions, such as the outside name with the cluster suffix added, before it gets to the correct one. In a busy application this can multiply the load on CoreDNS and add small delays. Teams that notice this often add a trailing dot to outside names, which marks them as complete, or adjust the setting for their Pods, or run a local DNS cache on every machine.
Environment variables: the older way
When a Pod starts, Kubernetes also places details of the Services that already exist into its environment variables, for example the host and port of a Service named payments. This works, but it has a trap: only Services created before the Pod starts appear. Creating things in the wrong order leads to confusing behavior. For this reason, teams rely on DNS.
DNS is a live phone book that is updated the moment a new listing appears. Environment variables are a printed directory handed to you on your first day, which cannot include anyone who joined after you. Use the live phone book.
In shared code and configuration, use the short name for calls inside a namespace and the name with the namespace for calls between namespaces. This keeps configuration files portable between development, staging, and production namespaces.
10Data Flow and Lifecycle
The most important behavior of a Service happens during change: updates, crashes, and scaling.
Life of a Service
Created
You submit the Service. The API server saves it and allocates a cluster IP. DNS gains a new name.
Populated
The EndpointSlice controller finds Pods matching the selector and records those that are ready.
Programmed
Every kube-proxy sees the new Service and writes the rules that redirect its address to real Pods.
Serving
Traffic flows. As Pods come and go, the member list and rules keep updating automatically.
Deleted
The rules, the DNS name, and any cloud load balancer are removed. The Pods themselves are not touched, because a Service never owned them.
What happens during a rolling update
sequenceDiagram
participant D as Deployment
participant NP as New Pod
participant OP as Old Pod
participant E as EndpointSlice
participant K as kube-proxy
D->>NP: Start a new version Pod
NP->>E: Becomes ready, so its address is added
E->>K: Member list changed
K->>K: Add new Pod to traffic rules
D->>OP: Begin shutting down an old Pod
OP->>E: Marked as terminating, so removed from new traffic
E->>K: Member list changed
K->>K: Remove old Pod from traffic rules
OP->>OP: Finish in-flight requests and exit
Watch the order. A new Pod receives traffic only after its readiness check passes. An old Pod is taken out of the member list when it starts terminating, and only then is it stopped. In an ideal update, no customer request is sent to a Pod that is not ready or that has already gone.
Scaling up and down behind a Service
The Service makes scaling almost invisible. When traffic grows, an autoscaler raises the number of Pods in the Deployment. Each new Pod starts, passes its readiness check, and only then joins the member list. From that moment the Service shares traffic with it, and the caller sees nothing but faster answers. When traffic falls, the autoscaler removes Pods, and each leaving Pod is taken out of the member list before it stops. Because callers only know the Service name, they never have to be told that the number of workers changed. This separation, where callers depend on a name and the platform manages the workers, is the central idea of the Service, and it is why a small team can run an application that grows a hundred times bigger without rewriting how its parts find each other.
One practical point: a new Pod that takes a long time to become ready will not help during a sudden spike, because it receives no traffic until it passes its check. Applications that start quickly, or that keep a few spare Pods warm, cope with spikes better than ones that need a minute to load data.
The race that catches beginners
There is a small timing gap. The signal to shut down a Pod and the update of network rules on every machine happen at the same time, not one after the other. A few machines may still send a request to the old Pod for a fraction of a second after the Pod has started closing. If the app exits instantly, those requests fail. The fix is to give the app a graceful shutdown: keep serving for a short while after the shutdown signal, finish work already in progress, and then exit. Many teams add a short pause in a “pre-stop” step to cover the gap.
Forgetting to define a readiness check. Without one, Kubernetes considers a Pod ready as soon as its container starts, even if the app inside is still loading. Users then get errors during every deployment, and the Service is blamed for a problem it was never told about.
11Service, Ingress, and Gateway: Who Does What
A common beginner puzzle is how these three fit together. They are layers, not competitors.
flowchart LR
U["Customer browser"] --> DNSX["DNS for the website name"]
DNSX --> CLB["Cloud load balancer"]
CLB --> IC["Ingress controller or Gateway - reads host and path"]
IC -->|shop.example.com| S1["Service: storefront"]
IC -->|shop.example.com/api| S2["Service: catalog"]
IC -->|pay.example.com| S3["Service: payments"]
S1 --> P1["Storefront Pods"]
S2 --> P2["Catalog Pods"]
S3 --> P3["Payment Pods"]
Service
Gives a group of Pods a stable address and balances the load. Understands connections and ports, not websites.
Ingress
A set of web routing rules, such as “requests for this website name and path go to that Service.” A separate program, the Ingress controller, carries them out. Also handles security certificates for HTTPS.
Gateway API
The modern, more expressive successor to Ingress. It separates the roles of the platform team, who own the gateway, and app teams, who attach their own routes, and supports more protocols and traffic rules.
How to choose
- For internal calls between programs, use a ClusterIP Service.
- For web applications reached by website names and paths, use Ingress or Gateway in front of ClusterIP Services.
- For non-web protocols such as a database port, a game server, or a message broker that must be public, use a LoadBalancer Service directly.
- For a quick test, use NodePort or a temporary port-forward to one Pod.
Ingress controllers and gateways are products maintained by different projects and vendors, and their support status changes over time. Before you choose one for a long-lived system, check that it is actively maintained, and read its own documentation for supported features.
12Advanced Traffic Control
Once the basics feel comfortable, a few extra settings let you fine-tune how a Service treats traffic.
Session affinity
By default each new connection may go to a different Pod. Setting session affinity to client address sends a given caller to the same Pod for a period, three hours by default. Useful for old apps that keep visitor data in memory, but it makes load less even.
External traffic policy
With the default setting, traffic entering at any machine may hop to a Pod on another machine and lose the original client address. Setting the policy to “Local” keeps traffic on the receiving machine, which preserves the client address and saves a hop, but can unbalance load and only works where Pods exist.
Internal traffic policy
Restricts calls from Pods to use only Pods on the same machine, which cuts network delay for helper programs such as log collectors.
Topology-aware routing
Prefers Pods in the caller’s own availability zone, lowering delay and cross-zone data charges. It needs balanced Pod placement to work well.
Publishing unready addresses
An option for programs like clustered databases whose members must find one another before any is fully ready.
Dual-stack
Lets a Service carry both older and newer kinds of network addresses, IPv4 and IPv6, at the same time.
Session affinity is like asking the bank to always send you to the same teller because she knows your case. Topology-aware routing is asking to be served at the branch nearest your home instead of the one across town. Both save effort for you but can make the bank’s queues less even, which is the trade-off to remember.
Choosing the right balance
Each of these switches trades simplicity for efficiency or control. Start with defaults. Turn on a special setting only when you have a measured reason, such as a legal need to see real client addresses, or a bill showing large cross-zone transfer costs. Then test, because a setting that fits one workload can quietly harm another.
13Advantages, Disadvantages, and Trade-offs
Services solve a big problem elegantly, but they have limits worth knowing.
Advantages
- Stable names and addresses for changing Pods
- Automatic load balancing and health awareness
- Works without any change to application code
- Portable across laptops, data centers, and clouds
- Built-in DNS discovery
- Simple building block for larger designs like Ingress and service meshes
Disadvantages
- Balancing is simple: connection-level, not request-aware
- Long-lived connections may stick to old Pods
- Large numbers of Services can slow older proxy modes
- LoadBalancer Services can multiply cloud costs
- Cloud-specific behavior hides in annotations
- Misconfigured selectors fail silently
When you might not need a Service
Not every Pod needs a Service. A batch job that reads from a queue and writes to a database receives no incoming calls, so no Service is required. A program that only sends requests out never needs an address of its own. Creating Services for things that do not receive traffic adds clutter and, in very large clusters, extra rules for the machines to maintain. A helpful question is “does anyone need to call this by name?” If the answer is no, leave the Service out.
Key trade-offs at a glance
| Choice | You gain | You give up |
|---|---|---|
| One LoadBalancer per app | Simplicity and isolation | Higher cost and many public addresses |
| Shared Ingress or Gateway | One entry point, lower cost, central certificates | A shared component to run and protect |
| Headless Service | Direct Pod access and stable identities | Built-in load balancing |
| Session affinity | Consistent Pod per visitor | Even load and easy scaling |
| Local external traffic policy | Real client address, fewer hops | Uniform balance across machines |
| Service mesh on top | Smart request-level routing, retries, encrypted traffic | More components, more resource use, more learning |
A useful rule of thumb: start with plain ClusterIP Services and one shared entry point. Add complexity, such as a service mesh or special policies, only when a real need appears. Simple designs are easier to understand at two in the morning when something breaks.
14Securing Services
A Service decides who can reach your application, which makes it one of the most security-sensitive objects you create.
Think of each Service as a door in an office building. The question is not only “does the door work?” but “who has a key, and which rooms does it open?” Leaving the back door propped open to the street because it is convenient is how most break-ins happen, and most Kubernetes exposure incidents come from a door opened wider than the team realized.
Layers of protection
Keep it private
Use ClusterIP unless outside access is truly needed. A Service that is not exposed cannot be attacked from the internet.
Network policies
By default, any Pod can call any Service. Network policies add rules such as “only the cart may call payments.” They need a network plugin that enforces them.
Restrict public load balancers
Where the cloud supports it, limit which source address ranges may reach a LoadBalancer Service, or make it internal-only so only your private network can connect.
TLS in transit
Terminate HTTPS at the Ingress or Gateway for outside traffic, and consider encrypting traffic between Services too, using a service mesh or application-level encryption.
Avoid raw NodePorts
Opening a high port on every machine widens your attack surface. Firewall it, or avoid it in production.
Prove who is calling
Do not rely only on network location. Use tokens or mutual TLS so a Service can verify that the caller is really who it claims to be.
Common ways Services are exposed by accident
- A LoadBalancer Service created “temporarily” for a test and never removed, exposing an admin page or database to the internet.
- A database exposed as a public LoadBalancer for convenience during a migration.
- Selectors that are too broad, so a Service accidentally forwards traffic to Pods that were never meant to receive it.
- Cloud firewall rules opened wide to make NodePort work.
- A monitoring or dashboard Service exposed publicly without sign-in.
Assuming that “inside the cluster” means “safe.” If one Pod is compromised, an attacker there can try every Service in the cluster. Network policies and per-Service authentication limit how far one break-in can spread.
Segmenting with namespaces and policies
Use namespaces to group related Services, and combine them with network policies so that traffic between groups must be explicitly allowed. A good starting rule is “deny everything, then allow what each app needs.” It takes an afternoon to set up and pays back the first time something goes wrong.
15Monitoring, Logging, Metrics, and Troubleshooting
Because a Service is a set of rules and lists, not a running program, you watch it indirectly, through the parts that make it work.
What to watch
| Signal | Where it comes from | What it tells you |
|---|---|---|
| Number of ready endpoints | EndpointSlices, cluster state metrics | How many Pods can really serve traffic; zero means an outage |
| Request rate, errors, latency | Application metrics, Ingress or mesh metrics | What customers actually experience |
| DNS query volume and failures | CoreDNS metrics and logs | Whether names resolve quickly and reliably |
| Rule update time | kube-proxy metrics | How fast changes reach the machines; long times mean stale routes |
| Load balancer health | Cloud provider metrics | Whether the outside entrance is healthy and which machines it trusts |
| Pod restarts and readiness flaps | Cluster events and Pod status | Instability that causes traffic to jump on and off |
| Dropped connections | Network plugin or mesh metrics | Network policies or capacity problems |
Logs and events
Services themselves do not write logs, but the components around them do. CoreDNS logs show name lookups. Ingress controllers log every web request, which is often the best record of customer traffic. Cloud load balancers can write access logs. Kubernetes events, for example about a load balancer that could not be created, appear on the Service object and are the first place to look when something is stuck in a pending state.
A calm way to troubleshoot
When “the Service does not work,” walk down the path from the caller to the Pod, one step at a time. Most problems are found in the first three questions.
flowchart TD
START["Requests to the Service fail"] --> Q1{"Does the name resolve in DNS?"}
Q1 -->|No| F1["Check spelling, namespace and CoreDNS health"]
Q1 -->|Yes| Q2{"Does the Service list ready endpoints?"}
Q2 -->|No| F2["Check selector labels, Pod readiness and Pod status"]
Q2 -->|Yes| Q3{"Does targetPort match the app port?"}
Q3 -->|No| F3["Correct the port numbers"]
Q3 -->|Yes| Q4{"Do network policies allow the traffic?"}
Q4 -->|No| F4["Add or fix the allow rule"]
Q4 -->|Yes| F5["Check app logs, kube-proxy and network plugin health"]
The usual suspects
- Selector mismatch: a typo means zero Pods match, so the Service has no endpoints and every call fails.
- Wrong target port: the app listens on one number and the Service forwards to another.
- Pods not ready: the readiness check fails, so Pods are silently left out.
- Wrong namespace: the caller uses a short name that resolves in its own namespace, where no such Service exists.
- Pending load balancer: no cloud controller, no quota, or a permission problem.
- Blocked by policy: a network policy or a cloud firewall quietly drops the traffic.
The single most useful habit: look at whether the Service has endpoints. If the list is empty, the problem is between the Service and the Pods. If it is full, the problem lies elsewhere. This one check splits the search in half.
16Design Patterns and Anti-Patterns
Patterns are habits shared by teams that run Services well. Anti-patterns are shortcuts that look harmless until they break.
Patterns that work
Private by default
Give every workload a ClusterIP Service, and expose only the few that must be public, through a shared entry point.
One Service per role
Give each distinct role its own Service and name, such as storefront, catalog, and payments, so ownership and permissions stay clear.
Shared edge
Route many web apps through one Ingress or Gateway to reduce cost and manage certificates in one place.
Name the dependency
Wrap outside systems, such as a managed database, in an ExternalName or selector-less Service so applications use a stable in-cluster name.
Headless plus StatefulSet
Use this pair for databases and brokers that need stable, individually addressable members.
Graceful shutdown
Make apps keep serving briefly after a stop signal, so rolling updates never drop a request.
Anti-patterns to avoid
What it looks like
Hard-coding Pod addresses in configuration files, or copying a Service’s cluster IP into a program.
Why it hurts
Pod addresses change constantly, and a cluster IP changes if the Service is deleted and recreated. The program breaks at the next restart, often at night.
Do this instead
Always call the Service by its DNS name and let the cluster do the lookup.
What it looks like
Creating a separate public LoadBalancer Service for every web app.
Why it hurts
Costs multiply, addresses sprawl, certificates are scattered, and each exposed load balancer is another door to defend.
Do this instead
Put web apps behind a shared Ingress or Gateway, and keep the Services behind it as ClusterIP.
What it looks like
Running Pods without readiness checks and relying on the Service to “just know” when they are ready.
Why it hurts
Traffic is sent to Pods that are still starting or already broken, producing errors on every deployment and after every crash.
Do this instead
Define meaningful readiness checks that test that the app can really serve requests.
What it looks like
Depending on plain Service load balancing to spread requests evenly over long-lived connections, such as gRPC or database pools.
Why it hurts
A Service balances when a connection is opened, not for each request. One long connection can send all its traffic to one Pod, leaving others idle.
Do this instead
Use a proxy or service mesh that balances each request, or use client-side balancing with a headless Service.
17Best Practices and Common Mistakes
A short checklist that saves a lot of late-night debugging.
Best practices
- Use clear, consistent labels. A shared labeling scheme makes selectors simple and reduces accidental matches.
- Name ports. Named ports let you change a port number in the Pod without editing every Service.
- Always define readiness checks and, for slow starters, startup checks.
- Handle shutdown gracefully so old Pods finish current work while the member list updates.
- Prefer DNS over environment variables for discovery.
- Run more than one Pod behind every important Service, spread across machines and zones.
- Restrict traffic with network policies and start from “deny by default.”
- Limit LoadBalancer Services and review them regularly for forgotten ones.
- Keep Services in the same namespace as their Pods and use clear namespace boundaries.
- Test failure. Delete a Pod under load and confirm that customers notice nothing.
Common mistakes
| Mistake | What happens | Better habit |
|---|---|---|
| Label typo in the selector | Zero endpoints; every request fails | Check that endpoints exist after every change |
| Confusing port and targetPort | Connections refused or timed out | Write both numbers down and match them to the app |
| Testing a ClusterIP from outside the cluster | Nothing responds | Test from a Pod inside, or use a temporary port-forward |
| Expecting ping to work | Looks broken when it is fine | Test the real port with a proper request |
| Ignoring the pending load balancer | The address never appears | Read the events on the Service for the cause |
| Deleting and recreating a Service | New cluster IP and a short outage | Update in place whenever possible |
| Using the same label on unrelated Pods | Requests reach the wrong app | Give each app a unique identifying label |
| Not planning for many Services | Slow updates in very large clusters | Consider IPVS, nftables, or an eBPF network plugin |
18Real-World and Industry Examples
These examples come from what projects and companies have said publicly. Details may have changed since.
The cluster’s own front door is a Service
Inside every Kubernetes cluster, a Service named kubernetes in the default namespace points at the API server. Controllers, monitoring agents, and operators running in Pods use it to reach the control plane without knowing which machine is currently active. The project uses its own building block for its most important connection.
Managed clouds and load balancers
On Amazon EKS, Google GKE, and Azure AKS, creating a LoadBalancer Service makes the platform build a real cloud load balancer and fill in its address. Teams commonly use one shared Ingress or Gateway for their many web apps and reserve direct LoadBalancer Services for special protocols. This split saves money and keeps the number of public doors small.
Replacing kube-proxy with eBPF
Cilium, a network plugin used by many organizations, can handle Service routing inside the Linux kernel using eBPF instead of kube-proxy. Google’s GKE Dataplane V2 is built on Cilium, and other managed services offer similar options. Large clusters adopt this to cut the delay and update cost of very long rule lists, while applications keep using ordinary Services.
MetalLB on bare metal
Companies running Kubernetes in their own data centers have no cloud to build load balancers. Projects like MetalLB fill the gap by announcing addresses from a reserved pool on the local network, so LoadBalancer Services get real, reachable addresses just as they would in a cloud.
Service meshes build on Services
Tools such as Istio and Linkerd take the ordinary Service and add smarter, request-level features: retries, timeouts, gradual traffic shifts, and encrypted connections between programs. They still use Services as the map of what exists, which shows how central the Service is to the whole ecosystem.
A typical online store
A store runs a storefront, a catalog, a cart, and a payment program. Each has a ClusterIP Service with a clear name. A shared Gateway with one cloud load balancer receives customer traffic and routes it by website address to the storefront and catalog Services. The cart calls payments by its short name, and the payment Service passes each request to a ready Pod. During a sale, the cart scales from three to thirty Pods, and the Service adds each new Pod as it becomes ready. No settings anywhere are edited by hand.
19Frequently Asked Questions
20Summary and Key Takeaways
Key Takeaways
- A Service is a stable name and address in front of changing Pods. It solves the problem that Pod addresses die with the Pod, and it shares traffic across all healthy members.
- Labels and selectors do the matching. The Service owns no Pods. It finds them through a selector, and a controller keeps the member list of ready Pods in EndpointSlices.
- The virtual address is an illusion built from rules. kube-proxy, or an eBPF network plugin, rewrites traffic from the Service address to a chosen Pod, and CoreDNS provides the name.
- The type decides who can reach it. ClusterIP is internal, NodePort opens a door on every machine, LoadBalancer adds a cloud entrance, ExternalName is an alias, and a headless Service gives direct access to Pods.
- Use a shared Ingress or Gateway for websites. Keep most Services private, and expose only what must be public through a small number of well-protected doors.
- Readiness, graceful shutdown, and policies make it reliable and safe. Define readiness checks, finish work before exit, restrict traffic with network policies, and check for endpoints first whenever something breaks.