✦ For everyone, free.

Practical knowledge for real and everyday life

Home

Kubernetes Service Design Guidelines

Kubernetes Service Design Guidelines explain how to structure and manage services in a Kubernetes cluster for scalability, reliability, and efficient operations.

Kubernetes Service Design Guidelines describe the practices for exposing a set of Pods as a stable, addressable network endpoint using the Service resource, so that clients — whether other Pods inside the cluster or external consumers — can reach a workload reliably despite the constant churn of individual Pod IPs as Pods are created, rescheduled, and terminated. A Service decouples "what I want to talk to" from "which specific Pod is currently running it," and these guidelines cover choosing the right Service type, designing selectors correctly, and handling the traffic routing and session behavior that Services control.


Service Types

ClusterIP

ClusterIP, the default type, exposes a Service on a stable virtual IP reachable only from within the cluster. This is the correct choice for internal-only communication between workloads — most Services in a typical cluster should be ClusterIP, with external exposure handled by a dedicated ingress layer rather than by exposing every Service directly.

NodePort

NodePort exposes a Service on a static port on every node's IP, allowing external traffic to reach it via any node. This is rarely the right choice for production external exposure — it lacks the load balancing sophistication of a proper load balancer, hardcodes assumptions about node reachability, and is more commonly used as a low-level building block that other exposure mechanisms are built on top of.

LoadBalancer

LoadBalancer provisions an external load balancer (via the cloud provider's integration) that routes to the Service. This is appropriate for Services that genuinely need a dedicated external entry point, but provisioning a separate cloud load balancer per Service does not scale economically or operationally to a cluster with many externally-facing Services — an Ingress controller in front of a smaller number of Services is usually the better pattern at scale.

ExternalName

ExternalName maps a Service to an external DNS name rather than to Pods at all, useful for giving in-cluster consumers a stable, cluster-local name for a resource that lives outside Kubernetes (an external database, for instance), so that the external dependency can later be migrated in-cluster without changing how consumers address it.

Headless Services

Setting clusterIP: None creates a headless Service, which does not get a virtual IP or load-balance traffic at all — instead, DNS queries for the Service resolve directly to the individual Pod IPs. This is the mechanism StatefulSet relies on for stable per-Pod DNS identity, and is appropriate whenever clients need to address individual backing Pods rather than a load-balanced abstraction over them.


Selector Design

Label Matching as the Binding Mechanism

A Service selects its backing Pods purely by label selector — there is no direct reference to a Deployment or StatefulSet. This means the correctness of a Service depends entirely on Pod labels matching the selector precisely; an overly broad selector can accidentally capture Pods from an unrelated workload, while an overly narrow one can silently exclude Pods that should be receiving traffic.

Consistent Labeling Conventions

Using consistent, well-scoped labels (such as app, component, and a version or track label) prevents selector ambiguity, especially in namespaces running multiple related workloads or multiple versions of the same workload simultaneously during a rollout or canary.


Traffic Routing Behavior

Endpoints and EndpointSlices

A Service's backing Pod IPs are tracked via EndpointSlice objects, updated as Pods become ready or unready. Because Endpoints are driven by Pod readiness, this ties directly back to health check design — traffic only reaches Pods whose readiness probe is currently passing, making readiness probe correctness a Service-level routing concern as well as a rollout safety concern.

Session Affinity

sessionAffinity: ClientIP routes repeated requests from the same client IP to the same backing Pod, useful for stateful client interactions that aren't otherwise externalized. This should be used deliberately and sparingly, since it undermines even load distribution across replicas and reintroduces a form of hidden state dependency into an otherwise stateless routing layer.

Internal Traffic Policy

internalTrafficPolicy: Local restricts a Service to only route to Pods on the same node as the client, reducing cross-node network hops at the cost of requiring at least one backing Pod on every node that might originate traffic — appropriate for latency-sensitive internal calls paired with a DaemonSet-style backing workload, but a poor default for ordinary services.


Multi-Port and Named Port Design

Named Ports

Services exposing multiple ports should use named ports (name: http, name: metrics) rather than bare numbers, since named ports allow the underlying container port definitions to change without requiring the Service definition to be updated in lockstep, and make multi-port Service definitions self-documenting.

Separating Concerns Across Services

Distinct traffic concerns — application traffic versus a metrics-scraping endpoint — are often better exposed as genuinely separate ports on the same Service (or separate Services entirely) rather than conflated, since they typically have different consumers, different security postures, and different expected traffic patterns.


Example Configuration

apiVersion: v1
kind: Service
metadata:
  name: codartium-api
spec:
  type: ClusterIP
  selector:
    app: codartium-api
    track: stable
  ports:
    - name: http
      port: 80
      targetPort: http
    - name: metrics
      port: 9090
      targetPort: metrics
---
apiVersion: v1
kind: Service
metadata:
  name: codartium-db-headless
spec:
  clusterIP: None
  selector:
    app: codartium-db
  ports:
    - name: db
      port: 5432
      targetPort: 5432

Practical Consequences

Well-designed Services provide stable, predictable addressing for workloads regardless of Pod churn, route traffic only to instances that are actually ready, and expose only what genuinely needs external reachability. Poorly designed Services commonly manifest as traffic silently dropped to Pods that were never actually ready, unexpected cross-workload traffic caused by an overly broad selector, or unnecessary cost and operational overhead from provisioning a dedicated external load balancer per Service instead of consolidating external exposure behind a shared ingress layer.