ClusterIP — the default
A stable virtual IP, reachable only from inside the cluster, load-balancing across every Pod matching the selector.
apiVersion: v1
kind: Service
metadata:
name: docktionary-app
spec:
type: ClusterIP
selector:
app: docktionary
ports:
- port: 80
targetPort: 3000Any Pod in the cluster can now reach this one with http://docktionary-app (or the fully qualified docktionary-app.default.svc.cluster.local), regardless of which Pods are currently backing it.
NodePort
Opens a fixed port (30000–32767 by default) on every node's own IP, forwarding into the Service. Rarely used directly in production — mostly a building block for LoadBalancer, or a quick way to poke a cluster from outside during development.
spec:
type: NodePort
selector:
app: docktionary
ports:
- port: 80
targetPort: 3000
nodePort: 30080LoadBalancer
On a cloud-managed cluster, this provisions an actual external load balancer (an AWS NLB/ALB, a Google Cloud Load Balancer, an Azure Load Balancer) and points it at the Service.
spec:
type: LoadBalancer
selector:
app: docktionary
ports:
- port: 80
targetPort: 3000Give every public-facing Service its own LoadBalancer and the cloud bill (and the number of IPs you're managing) grows with every service. Ingress exists to put several services behind one.
Ingress
An Ingress resource declares host- and path-based HTTP routing rules. An Ingress controller — nginx-ingress, Traefik, or a cloud-managed one — actually implements them, usually sitting behind a single LoadBalancer Service.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: docktionary
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts: ["docktionary.com"]
secretName: docktionary-tls
rules:
- host: docktionary.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: docktionary-app
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: docktionary-api
port:
number: 80Gateway API: the newer alternative to Ingress
Ingress has a real limitation: it's just host/path rules, so anything more specific — traffic splitting, header-based routing, TCP/UDP instead of just HTTP — ends up as an implementation-specific annotation that only works with one particular controller. Gateway API is the successor, modeling routing as proper typed resources instead of annotations, and splitting the job into a GatewayClass (the controller), a Gateway (a listener — an IP and port), and one or more HTTPRoutes attached to it.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: docktionary
spec:
parentRefs:
- name: shared-gateway
rules:
- matches:
- path:
value: /api
backendRefs:
- name: docktionary-api
port: 80
- backendRefs:
- name: docktionary-app
port: 80Ingress isn't deprecated and still covers most simple cases fine — Gateway API is worth reaching for once you need routing logic Ingress’s annotations can't express portably, or you want one config format that works the same way across different controllers.
Headless Services
Setting clusterIP: None turns off the load-balancing virtual IP entirely. DNS lookups for the Service then return every matching Pod's IP directly instead of one shared address — what a StatefulSet uses so each replica (db-0, db-1, ...) is reachable individually rather than load-balanced as an interchangeable group.
apiVersion: v1
kind: Service
metadata:
name: db
spec:
clusterIP: None
selector:
app: db
ports:
- port: 5432With this in place, db-0.db.default.svc.cluster.local resolves to exactly that one Pod — essential for a clustered database where replicas aren’t interchangeable and a client needs to reach a specific one (the primary, say) rather than whichever one a load balancer happened to pick.
ExternalName: an internal DNS alias for something outside the cluster
An ExternalName Service doesn’t proxy traffic or get a virtual IP at all — it's a DNS CNAME, purely so in-cluster code can use the same short, consistent service-name.namespace pattern for something that actually lives outside the cluster, like a managed database.
apiVersion: v1
kind: Service
metadata:
name: db
spec:
type: ExternalName
externalName: prod-db.abc123.us-east-1.rds.amazonaws.comCode in the cluster connects to db exactly like it would an in-cluster Service; swapping the managed database for an in-cluster one later (or vice versa) is then a one-line change to this Service, not a hunt through every app's configuration.
Checking it from the command line
kubectl get svc
kubectl get endpoints docktionary-app # which Pod IPs are actually backing it
kubectl get ingress
kubectl describe ingress docktionary
# Forward a local port straight to a Pod, bypassing the Service entirely
kubectl port-forward deployment/docktionary-app 3000:3000None of this restricts traffic between Pods inside the cluster -- a Service makes a group of Pods reachable, it doesn’t make anything else unreachable. That's a separate, allow-everything-by- default problem; see NetworkPolicy.