This is the second of three articles explaining and detailing the Kubernetes Network model with a hands-on approach. In the first article, I spoke about Pod-to-Pod communication. This article will focus on Pod-to-Service communication, and the next one will discuss Ingress / Ingress Controllers. These articles are based on a repository I created which contains more examples and content. I invite you to review the repo at https://github.com/rafaelmnatali/kubernetes.
Pod to Service communication
A Service serves as a central entry point that allows Pods to communicate with one another. For instance, when a group of Pods (referred to as "backends") offers functionality to other Pods (known as "frontends") within your cluster, the backend Service will furnish the frontend with a list of backend Pods that the frontend can establish connections with.
There are four Kubernetes Service types:
ClusterIP
ClusterIP is is the default type of Service. Kubernetes will assign an IP address from a pool of IP addresses that your cluster has reserved for that purpose.
Deploy a NGINX web server, and let's see how the ClusterIP works:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
initContainers:
- name: init
image: busybox
command: ['sh', '-c', 'echo "Welcome to the home page of host $(hostname)!" > /usr/share/nginx/html/index.html']
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
containers:
- name: nginx
image: nginx
ports:
- name: http-web-svc
containerPort: 80
protocol: TCP
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: default
spec:
selector:
app: nginx
ports:
- name: http-web-svc
protocol: TCP
port: 80
targetPort: http-web-svc
List of Services:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 197d
nginx-service ClusterIP 10.110.110.219 <none> 80/TCP 6s
Use the nicolaka/netshoot image to execute network validations.
kubectl run tmp-shell --rm -i --tty --image nicolaka/netshoot -- /bin/bash
tmp-shell:~# nslookup nginx-service
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: nginx-service.default.svc.cluster.local
Address: 10.110.110.219
The DNS returns the IP address as we saw in the previous command.
tmp-shell:~# curl nginx-service
Welcome to the home page of host nginx-5c7575f557-j2pzq!
tmp-shell:~# curl nginx-service
Welcome to the home page of host nginx-5c7575f557-vbglc!
tmp-shell:~# curl nginx-service
Welcome to the home page of host nginx-5c7575f557-82mx4!
The NGINX Service is associated with three Pods (as shown in the ENDPOINTS column). In this particular example, the Service directed the request to a different Pod each time:
$ kubectl get endpointslices.discovery.k8s.io
NAME ADDRESSTYPE PORTS ENDPOINTS
kubernetes IPv4 8443 192.168.49.2
nginx-service-5pf9n IPv4 80 10.244.3.46,10.244.3.47,10.244.3.48 17m
Using Kubeshark, it's possible to have a visual representation of the network traffic.
The tmp-shell Pod does DNS requests to resolve the A/AAAA records for nginx-service.default.svc.cluster.local. After that it sends a GET to the Service in port 80 and then it receives the answer from 10.244.3.48.
Headless Service
Headless Service is created by explicitly setting ClusterIP to None when creating the Service. That does not allocate an IP address or forward traffic. In my opinion, the main use case is for Stateful applications, where the headless is responsible for the network identity of the Pods.
How is the IP discovery different from ClusterIP to Headless?
Deploy a Headless Service for our NGINX:
apiVersion: v1
kind: Service
metadata:
name: nginx-headless-service
namespace: default
spec:
clusterIP: None
selector:
app: nginx
ports:
- name: http-web-svc
protocol: TCP
port: 80
targetPort: http-web-svc
List of Services:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP
nginx-headless-service ClusterIP None <none> 80/TCP
nginx-service ClusterIP 10.110.110.219 <none> 80/TCP
Use the nicolaka/netshoot image to execute network validations.
kubectl run tmp-shell --rm -i --tty --image nicolaka/netshoot -- /bin/bash
Run nslookup to query both services:
tmp-shell:~# nslookup nginx-service
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: nginx-service.default.svc.cluster.local
Address: 10.110.110.219
tmp-shell:~# nslookup nginx-headless-service
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: nginx-headless-service.default.svc.cluster.local
Address: 10.244.3.46
Name: nginx-headless-service.default.svc.cluster.local
Address: 10.244.3.47
Name: nginx-headless-service.default.svc.cluster.local
Address: 10.244.3.48
In the first case, the ClusterIP returns its own IP address. The Headless Service returns the IP address of each Pod.
Summary
In this article, I experimented with two types of Kubernetes Services — ClusterIP, which provides an IP address and load-balancing, and headless Service, which don’t offer load-balancing and returns a list of all Pods associated. The article explores how you can connect to your applications using these two Services.
A Service is the preferred method of exposing a network application running as one or more Pods in your cluster. One of the key advantages of Kubernetes Services is that you don’t need to modify your existing application to use a different service discovery mechanism.
In the third and final part of this series, I will discuss Ingress and Ingress Controllers.
Comments