Services are k8s objects that create a one-to-many point of communication for backends. They abstract the underlying (logical) infrastructure, necessary for facilitating that behaviour (port-forwarding, proxying, etc). Most often, they are used in conjunction with a Deployment—the Deployment manages the Pods and the Service tracks the Deployment’s Pods that are able to receive traffic (see EndpointSlices. Also, Services support multiple port definitions which could also be of different protocols (see Supported Protocols).
Architecture
Under the hood, Service functionality is provided through kube-proxy which monitors (by contacting the kube-apiserver) for new Services. When a new Service is created, the proxies on each node1 modify the nodes’ network tables—they add forwarding rules, etc. The proxy operates in different modes which can be modified via its configuration.
Here is a general2 process overview of a Service creation.
- A Service is created
- The kube-controller-manager assigns a virtual IP to it.
- Every kube-proxy instance installs a series of rules for the Service’s virtual IP on its node. These per-Service entries/rules redirect clients to the relevant per-Endpoint3 entries which further redirect to the client’s final destination backends (e.g. pods, etc) by using DNAT (see DNAT).
EndpointSlices
EndpointSlices are lists of network endpoints, representing all Pods that match the Service’s label selector4. The kube-controller-manager (and specifically the EndpointSlices controller) automatically creates EndpointSlices when a Service with a selector is created (see Services without selectors (not a spec type)).
EndpointSlices are dependants5 of Service objects. You can find an ES’s owner Service by looking at its metadata.ownerReference field or kubernetes.io/service-name label. Furthermore, the controller sets the ES’s endpointslice.kubernetes.io/managed-by label to endpointslice-controller.k8s.io to prevent other entities from trying to interfere with the management of the slice.
Important
When using custom ESs it’s important to set the
endpointslice.kubernetes.io/managed-bylabel to some unique value (e.g.,staff,admins, etc) other than the controller’s. This is usually done in the context of Services without selectors (not a spec type).
Note
By default ESs are limited to hold maximum of 100 Endpoints.
Types
Services provide different services (pun intended). Some for internal use, some for external.
Services without selectors (not a spec type)
Although not an official type, Services without selectors offer unique behaviour with its own use cases. When you don’t specify selectors, the ES controller (see EndpointSlices) doesn’t automatically create an ES. Instead, you must create one manually:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: my-service-1 # by convention, use the name of the Service
# as a prefix for the name of the EndpointSlice
labels:
# You should set the "kubernetes.io/service-name" label.
# Set its value to match the name of the Service
kubernetes.io/service-name: my-service
addressType: IPv4
ports:
- name: http # should match with the name of the service port defined above
appProtocol: http
protocol: TCP
port: 9376
endpoints:
- addresses:
- "10.4.5.6"
- addresses:
- "10.1.2.3"Generally, this is used in cases where you want to expose an outside “service” within your k8s cluster, but this is not mandatory (you can specify any valid IP address).
ClusterIP (default)
ClusterIP Services exposes targeted backends through a single listener to the whole cluster (internally).
None (Headless)
Headless Services could be qualified as a sub-type of ClusterIP Services, which by setting their ClusterIP field to None allow clients to discover and connect to all targeted backends directly. This is useful when a client doesn’t need its traffic load balanced to a single random backend Pod. When a client initiates a DNS lookup of a Headless Service, Kubernetes would return multiple A records that point to all backends, instead of a single one like it does in general.
NodePort
This type opens up a port (either specified or from a pool) on each node, accepting inbound requests that will be forwarded to the backends. This way, clients outside the cluster can contact a network socket (<nodeIP>:<nodePort>) which would redirect them to the internal services.
LoadBalancer
Services of type LoadBalancer are intended to handover the load-balancing to an external load balancer. This requires a 3rd-party controller which acts on the LoadBalancer Service’s creation. Most often, it’s the cloud-controller-manager.
ExternalName
ExternalName Services are similar to Services without selectors (not a spec type). They map an external DNS name to their own DNS names:
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.comIf a client looks up my-service.prod.svc.cluster.local, the DNS server would return my.database.example.com as a CNAME.
Note
Although both
ExternalNameServices and Services without selectors provide similar functionality, at its core they rely on completely different strategies.ExternalNameis purely DNS (doesn’t have a ClusterIP), while the other relies on kube-proxy for load balancing and forwarding (has ClusterIP). In that sense ExternalName has more similarities with Headless, instead of Services without selectors.
Discovering Services
There are two ways of discovering a Service:
- Through the internal DNS system, or
- through a Pods env variables.
When a new Pod is created, a kubelet adds the addresses and ports of all active Services as environmental variables, which won’t be updated after creation.
Supported Protocols
Sources
- https://kubernetes.io/docs/concepts/services-networking/service/
- https://kubernetes.io/docs/reference/networking/virtual-ips/
- https://stackoverflow.com/questions/52707840/what-is-a-headless-service-what-does-it-do-accomplish-and-what-are-some-legiti
Footnotes
-
See k8s worker nodes. ↩
-
The process is similar in different modes. ↩
-
When a client initiates a connection to a Service, the backend Endpoint is chosen either randomly or based on session affinity. ↩