Introduction
In Kubernetes, pods
are the basic building blocks that run the containers. Each pod is given an IP address. So if we want to reach the servers running inside the pods we need a pod IP. Lets say we have 4 pod replicas running for a deployment and we can access the service with the pod IP address of any of the 4 running pods. The pods may be killed and replaced to rollout a new release or new pods can be created to scale for the traffic. The point is, the pods in a deployment may be changing once in a while. So how do we keep track of the up-to-date IP address of the pods in the deployment? Services
in kubernetes is there to solve this i.e to keep track of the right set of IP address of the pods at any point of time.
Demo
Create kind cluster
To create a kind cluster, follow this tutorial here
Create a deployment
First create a kubernetes deployment with 3 pod replicas using kubectl apply
command. Note that the pod has labels app: hello
and tier: backend
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
spec:
selector:
matchLabels:
app: hello
tier: backend
replicas: 3
template:
metadata:
labels:
app: hello
tier: backend
spec:
containers:
- name: hello
image: "gcr.io/google-samples/hello-go-gke:1.0"
ports:
- name: http
containerPort: 80
Now run, kubectl get pods -o wide
and see the pods running
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hello-d6b44f4-9dwht 1/1 Running 0 43m 10.244.0.22 test-kind-k8s-control-plane <none> <none>
hello-d6b44f4-gcmbf 1/1 Running 0 43m 10.244.0.21 test-kind-k8s-control-plane <none> <none>
hello-d6b44f4-v24wj 1/1 Running 0 43m 10.244.0.20 test-kind-k8s-control-plane <none> <none>
Create a service for the deployment
Its time to create a service for the deployment to reach the server inside the pod. Note that the pod labels app: hello
and tier: backend
are mentioned under the .spec.selector
field. This tells kubernetes to look for pods with the labels as mentioned in the yaml. Also, the service mentions the port to reach itself via theport
field and the port to containers via the targetPort
field.
apiVersion: v1
kind: Service
metadata:
name: hello
spec:
selector:
app: hello
tier: backend
ports:
- protocol: TCP
port: 80
targetPort: 80
If the targetPort
is not mentioned, by default it is considered the same as that of port
field value.
Run the command, kubectl get service
to get the available kubernetes services.
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello ClusterIP 10.102.56.197 <none> 80/TCP 25m
Endpoints
The endpoints
is a kubernetes resource that maintains the IP addressses of the pods for the services to send the traffic to. As soon as we create the above service, Kubernetes creates a new endpoint hello
. Run the command, kubectl get endpoints
to get the endpoints.
NAME ENDPOINTS AGE
hello 10.244.0.20:80,10.244.0.21:80,10.244.0.22:80 26m
You might notice that the ip addresses(10.244.0.20, 10.244.0.21, 10.244.0.22) in the endpoint output are that of the pods created by the above deployment.
Now lets, kill a pod and see what happens.
> kubectl delete pod hello-d6b44f4-gcmbf
pod "hello-d6b44f4-gcmbf" deleted
> kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hello-d6b44f4-9dwht 1/1 Running 0 47m 10.244.0.22 test-kind-k8s-control-plane <none> <none>
hello-d6b44f4-smzx7 1/1 Running 0 43s 10.244.0.23 test-kind-k8s-control-plane <none> <none>
hello-d6b44f4-v24wj 1/1 Running 0 47m 10.244.0.20 test-kind-k8s-control-plane <none> <none>
Since the deployment yaml specifies the pod replica count as 3, a new pod is created by Kubernetes. That is why the new pod hello-d6b44f4-smzx7
is running to compensate the deleted pod. If you see the endpoint object, it will automatically be updated by adding the new pods IP address 10.244.0.23
and removing the deleted pod IP address 10.244.0.21
.
kubectl get endpoints
NAME ENDPOINTS AGE
hello 10.244.0.20:80,10.244.0.22:80,10.244.0.23:80 51m
Accessing the service
Create a new pod test
to see how we can reach our hello
service.
> kubectl create deployment test-pod --image=k8s.gcr.io/echoserver:1.4
deployment.apps/test-pod created
> kubectl get pods --selector app=test-pod
NAME READY STATUS RESTARTS AGE
test-pod-6f4b5b47d6-dmmh2 1/1 Running 0 2m25s
> kubectl exec -it test-pod-6f4b5b47d6-dmmh2 -- /bin/bash
The above commands create a test-pod
and enters the pod so we can run commands inside it.
There are multiple ways to reach a service from a pod.
1. DNS
With DNS enabled in your cluster, a pod can call a service with its name. For the service hello
in our example, it can be reached via url http://hello
from the same namespace. If the pod runs in a namespace different from that of the service, it is accessed like http://hello.<namespace>
. In our example, we defined the service in the default
namespace. Lets execute the commands within the test-pod
to reach the hello
service.
root@test-pod-6f4b5b47d6-dmmh2:/# curl http://hello
{"message":"Hello"}
root@test-pod-6f4b5b47d6-dmmh2:/# curl http://hello.default
{"message":"Hello"}
2. Service IP address
The kubectl get svc command outputs the service IP address. From within the cluster, we can access with the service IP address. For the service hello
, the IP address is 10.102.56.197
. See that the service is accessible with its IP address when using cURL command.
curl 10.102.56.197
{"message":"Hello"}
3. Environment variables
By default, Kuberenetes exposes the service info with environment variables. Inside the test-pod
, run the printenv
command and see the service details populated.
root@test-pod-6f4b5b47d6-dmmh2:/# printenv | grep HELLO
HELLO_PORT=tcp://10.102.56.197:80
HELLO_PORT_80_TCP=tcp://10.102.56.197:80
HELLO_SERVICE_PORT=80
HELLO_PORT_80_TCP_ADDR=10.102.56.197
HELLO_PORT_80_TCP_PORT=80
HELLO_PORT_80_TCP_PROTO=tcp
HELLO_SERVICE_HOST=10.102.56.197
root@test-pod-6f4b5b47d6-dmmh2:/# curl ${HELLO_SERVICE_HOST}:${HELLO_SERVICE_PORT}
{"message":"Hello"}
Types of service
A Kuberenetes services can be exposed in the following ways. You can specify the service type with the type
field.
1. ClusterIP
If no type is provided, Kubernetes creates a service with cluster IP address by default. The example used above has only clusterIP. As the name suggests the service is accessible only within the cluster. This is mainly used for services that we don’t want to expose outside the cluster or when we are trying to access the service using ingress. When ingress controller is used, it creates a loadbalancer, and the loadbalancer access the service with clusterIP. We can access a ClusterIP service using the above 3 ways i.e DNS, env variable, ClusterIP.
2. NodePort
When the service type is NodePort
, the service is accessible with the cluster node IP addresses in addition to the above 3 methods. Lets convert the service type from ClusterIP
to NodePort
by applying the below yaml
apiVersion: v1
kind: Service
metadata:
name: hello
spec:
type: NodePort
selector:
app: hello
tier: backend
ports:
- protocol: TCP
port: 80
targetPort: http
nodePort: 30001
The yaml, specifies the type of service as NodePort
and mentiones the nodePort as 30001
. If the nodePort
field is not specified, a random port gets alloted.
> kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
test-kind-k8s-control-plane Ready master 2m44s v1.19.1 172.19.0.4 <none> Ubuntu Groovy Gorilla (development branch) 4.19.121-linuxkit containerd://1.4.0
test-kind-k8s-worker Ready <none> 2m17s v1.19.1 172.19.0.2 <none> Ubuntu Groovy Gorilla (development branch) 4.19.121-linuxkit containerd://1.4.0
test-kind-k8s-worker2 Ready <none> 2m17s v1.19.1 172.19.0.3 <none> Ubuntu Groovy Gorilla (development branch) 4.19.121-linuxkit containerd://1.4.0
> kubectl exec -it test-pod-6f4b5b47d6-dmmh2 -- /bin/bash
root@test-pod-6f4b5b47d6-dmmh2:/# curl 172.19.0.2:30001
{"message":"Hello"}
root@test-pod-6f4b5b47d6-dmmh2:/# curl 172.19.0.3:30001
{"message":"Hello"}
root@test-pod-6f4b5b47d6-dmmh2:/# curl 172.19.0.4:30001
{"message":"Hello"}
3. LoadBalancer
If the service type is mentioned as LoadBalancer
, then the cloud provider will create a loadbalancer. This serviceType is not mostly used as a new loadbalancer is created for each service type LoadBalancer
. This will be costly and also difficult to maintain the multiple load balancers, in case many services are of type loadbalancer.
4. ExternalName
This service type is used when we want to access a website outside the kubernetes cluster.
apiVersion: v1
kind: Service
metadata:
name: google-svc
spec:
type: ExternalName
externalName: google.com
As you see, this service is a mapping to google.com website which is accessible within the cluster as follows:
> kubectl exec -it test-pod-6f4b5b47d6-dmmh2 -- /bin/bash
root@test-pod-6f4b5b47d6-dmmh2:/# curl -k google.com http://google-svc
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>
Comments