This article extends my notes from an Udemy course "Kubernetes for the Absolute Beginners - Hands-on". All course content rights belong to course creators.
The previous article in the series was Introduction to Kubernetes Services | My Public Notepad.
NodePort Service
The goal is to make external access to the application running in the pod. This service enables this by mapping a port on the node to a port on the pod. NodePort service is in fact like a virtual server inside the node. Inside the cluster, it has its own IP address, and that IP address is called the cluster IP of the service (e.g. 10.106.1.12 as in our example).
There are three ports involved, from the viewpoint of the service:
- Target port is the port on the pod where the actual web server is running e.g. 80. That is where the service forwards the request to.
- (Service) port is the port on the service itself. It is simply referred to as the port.
- Node port is the port on the node itself, which we use to access the web server externally. In our example it is set to 30008. Node ports can only be in a valid range, which by default is from 30000 to 32767.
Node: port 30008 <-- Node port - external requests come to it
- NodePort Service (10.106.1.12): port 80
- Pod (10.244.0.2): port 80 <-- target port
How to create NodePort Service?
Just like how we create a Deployment, ReplicaSet or Pod - via definition file:
service-definition.yaml:
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: NodePort
ports:
- targetPort: 80
port: 80
nodePort: 30008
selector:
app: myapp
type: front-end
metadata contains the name of the service. It can also have labels.
spec contains type which refers to the type of service we are creating (NodePort, LoadBalancer or ClusterIP which is default value). For NodePort type, we specify ports which is an array of port mappings as we can have multiple port mappings within a single service. Each port mapping is a dictionary and the only mandatory key is port. If targetPort is not specified, it is assumed to be the same as port. If nodePort is not specified, a free port in the valid range between 30000 and 32767 is automatically allocated.
There could be hundreds of pods with web services running on port 80. We need somehow to specify those that service wants to target. We'll use the approach frequently used in Kubernetes, the same one which is used by ReplicaSets to filter out those pods that it will be scaling up: pod labels and selectors.
Pods are created with labels and we'll use those labels in the service definition file, under the selector property, just like in the ReplicaSet and Deployment definition files selector provides a list of labels to identify pods. So to link service to pods, we'll pull the labels from the pod definition file (under metadata >> labels) and place them under the selector section.
To create the service:
$ kubectl create -f service-definition.yaml
service/myapp-service created
To see the created service:
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 23d
myapp-service NodePort 10.104.148.160 <none> 80:30008/TCP 49s
We should be able to use the node IP and port 30008 to access the web content served from the pod with labels as specified in the service definition file above.
How to get the node IP address?
When working with minikube, to get the IP address of the node we can use either of these commands:
$ minikube node list
minikube 192.168.59.100
$ minikube ip
192.168.59.100
$ minikube service list
|----------------------|---------------------------|--------------|-----------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|----------------------|---------------------------|--------------|-----------------------------|
| default | kubernetes | No node port | |
| default | myapp-service | 80 | http://192.168.59.100:30008 |
| kube-system | kube-dns | No node port | |
| kubernetes-dashboard | dashboard-metrics-scraper | No node port | |
| kubernetes-dashboard | kubernetes-dashboard | No node port | |
|----------------------|---------------------------|--------------|-----------------------------|
$ minikube service myapp-service --url
http://192.168.59.100:30008
Let's check what objects we have and whether pods are running and are ready:
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/myapp-deployment-88c4d7667-76nhb 1/1 Running 1 (66m ago) 2d16h
pod/myapp-deployment-88c4d7667-7gktq 1/1 Running 1 (66m ago) 2d17h
pod/myapp-deployment-88c4d7667-hsg4h 1/1 Running 1 (66m ago) 2d17h
pod/myapp-deployment-88c4d7667-j6t7q 1/1 Running 1 (66m ago) 2d17h
pod/myapp-deployment-88c4d7667-qpf7k 1/1 Running 1 (66m ago) 2d17h
pod/myapp-deployment-88c4d7667-vhsrd 1/1 Running 1 (66m ago) 2d17h
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 24d
service/myapp-service NodePort 10.104.148.160 <none> 80:30008/TCP 22h
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/myapp-deployment 6/6 6 6 3d
NAME DESIRED CURRENT READY AGE
replicaset.apps/myapp-deployment-6866d9c964 0 0 0 2d23h
replicaset.apps/myapp-deployment-6bf7c4cbf 0 0 0 2d17h
replicaset.apps/myapp-deployment-759b778ddf 0 0 0 2d17h
replicaset.apps/myapp-deployment-75d76f4c78 0 0 0 2d17h
replicaset.apps/myapp-deployment-7b5bcfbfc6 0 0 0 2d17h
replicaset.apps/myapp-deployment-7b8958bfff 0 0 0 3d
replicaset.apps/myapp-deployment-88c4d7667 6 6 6 2d17h
replicaset.apps/myapp-deployment-b6c557d47 0 0 0 3d
Let's try to use curl:
$ curl http://192.168.59.100:30008
curl: (7) Failed to connect to 192.168.59.100 port 30008 after 0 ms: Connection refused
Let's check whether labels of pods are matching those specified in the service definition.
To list all pods and their labels use:
$ kubectl get pods -A --show-labels
NAMESPACE NAME READY STATUS RESTARTS AGE LABELS
default myapp-deployment-88c4d7667-76nhb 1/1 Running 1 (76m ago) 2d17h app=myapp,pod-template-hash=88c4d7667
default myapp-deployment-88c4d7667-7gktq 1/1 Running 1 (76m ago) 2d17h app=myapp,pod-template-hash=88c4d7667
default myapp-deployment-88c4d7667-hsg4h 1/1 Running 1 (76m ago) 2d17h app=myapp,pod-template-hash=88c4d7667
default myapp-deployment-88c4d7667-j6t7q 1/1 Running 1 (76m ago) 2d17h app=myapp,pod-template-hash=88c4d7667
default myapp-deployment-88c4d7667-qpf7k 1/1 Running 1 (76m ago) 2d17h app=myapp,pod-template-hash=88c4d7667
default myapp-deployment-88c4d7667-vhsrd 1/1 Running 1 (76m ago) 2d17h app=myapp,pod-template-hash=88c4d7667
kube-system coredns-5dd5756b68-zv66l 1/1 Running 4 (76m ago) 24d k8s-app=kube-dns,pod-template-hash=5dd5756b68
kube-system etcd-minikube 1/1 Running 4 (76m ago) 24d component=etcd,tier=control-plane
kube-system kube-apiserver-minikube 1/1 Running 4 (76m ago) 24d component=kube-apiserver,tier=control-plane
kube-system kube-controller-manager-minikube 1/1 Running 4 (76m ago) 24d component=kube-controller-manager,tier=control-plane
kube-system kube-proxy-8cw9s 1/1 Running 4 (76m ago) 24d controller-revision-hash=dffc744c9,k8s-app=kube-proxy,pod-template-generation=1
kube-system kube-scheduler-minikube 1/1 Running 4 (76m ago) 24d component=kube-scheduler,tier=control-plane
kube-system storage-provisioner 1/1 Running 11 (76m ago) 24d addonmanager.kubernetes.io/mode=Reconcile,integration-test=storage-provisioner
kubernetes-dashboard dashboard-metrics-scraper-7fd5cb4ddc-z5p5r 1/1 Running 2 (76m ago) 14d k8s-app=dashboard-metrics-scraper,pod-template-hash=7fd5cb4ddc
kubernetes-dashboard kubernetes-dashboard-8694d4445c-9td6g 1/1 Running 2 (76m ago) 14d gcp-auth-skip-secret=true,k8s-app=kubernetes-dashboard,pod-template-hash=8694d4445c
Let's check the labels in the service again. We can check its definition file or we can get its YAML definition via:
$ kubectl get service myapp-service -o yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2024-05-11T23:39:57Z"
name: myapp-service
namespace: default
resourceVersion: "326186"
uid: 79da7881-9c43-4b5e-8826-b7bf3561f53c
spec:
clusterIP: 10.104.148.160
clusterIPs:
- 10.104.148.160
externalTrafficPolicy: Cluster
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- nodePort: 30008
port: 80
protocol: TCP
targetPort: 80
selector:
app: myapp
type: front-end
sessionAffinity: None
type: NodePort
status:
loadBalancer: {}
We can see that pods actually don't have label type: front-end which is specified in the service definition so let's remove it from the service.
$ kubectl edit service myapp-service
// delete line: type: front-end
Let's now try to access the web service:
$ curl http://192.168.59.100:30008
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
Service can be mapped to a single or multiple pods.
In the production environment, we have multiple instances of our web application running for high availability and load balancing purposes. In this case, we have multiple similar pods running our web application. They all have different internal IP addresses (e.g. 10.244.0.3, 10.244.0.2, 10.244.0.4) but the same labels with a key app and set to a value of myapp. The same label is used as a selector during the creation of the service.
When the service is created, it looks for a matching pod with the specified label. The service then automatically selects all matching pods as endpoints to forward the external request coming from the user. We don't have to do any additional configuration to make this happen.
Service uses a random algorithm to balance the load across these different pods. This way the service acts as a built-in load balancer to distribute load across different parts.
That was an example when we have multiple pods on a single node.
NodePort service in a Multi-node cluster
When pods are distributed across multiple nodes we have the web application on pod on separate nodes in the cluster. Each node has its own IP address e.g. 192.168.1.2, 192.168.1.3, 192.168.1.4. Each of them has isolated, independent internal network so the IP addresses of pods might be 10.244.0.3, 10.244.0.2, 10.244.0.4.
When we create a service, without us having to do any additional configuration, Kubernetes automatically creates a service that spans across all the nodes in the cluster and maps the targetPort to the same nodePort on all the nodes in the cluster. This way we can access our application using the IP of any node in the cluster and using the same port number, which in this case is 30008:
$ curl http://192.168.1.2:30008
$ curl http://192.168.1.3:30008
$ curl http://192.168.1.4:30008
How to delete service?
$ kubectl delete service myapp-service
Why it's not good to use NodePort service in production?
Using a NodePort service in Kubernetes is generally not recommended for production environments for several reasons:
- Limited Port Range
- NodePort services use a limited range of ports (default is 30000-32767). This range may not be sufficient for large-scale applications with many services. Managing and tracking the allocation of these ports can also become cumbersome.
- Lack of Load Balancing
- NodePort services do not provide advanced load balancing features. They expose the service on a specific port on each node, but they do not distribute traffic efficiently across all nodes. This can lead to uneven load distribution and potential bottlenecks.
- Security Concerns
- Exposing services via NodePort means that our services are accessible on all nodes on a specified port, which can create security vulnerabilities. It increases the attack surface of our cluster by making services directly accessible over the network.
- Scalability Issues
- As our cluster grows, managing NodePorts can become complex. With more nodes and services, it's harder to ensure that port conflicts do not occur and that the services remain accessible and properly balanced across the cluster.
- Dependence on Specific Nodes
- NodePort services expose an application on a specific port on each node, which can create dependencies on specific nodes being up and running. This dependency can complicate maintenance and scaling operations.
- Inefficient Traffic Routing
- Traffic routed through NodePort can be less efficient compared to other service types like LoadBalancer or Ingress. NodePort may require additional hops to reach the desired service, leading to increased latency.
Recommended Alternatives
- LoadBalancer
- For cloud environments, the LoadBalancer service type is preferred.
- See Kubernetes LoadBalancer Service | My Public Notepad
- Ingress
- More flexible and powerful solution for managing external access to services within a Kubernetes cluster.
- See
Summary
In any case, whether it be a single pod on a single node, multiple pods on a single node or multiple pods on multiple nodes, the service is created exactly the same without usw having to do any additional steps during the service creation.
When pods are removed or added, the service is automatically updated, making it highly flexible and adaptive. Once created, we won't typically have to make any additional configuration changes.
While NodePort can be useful for development and testing purposes due to its simplicity, it is generally not suitable for production environments due to limitations in scalability, load balancing, and security. Using LoadBalancer or Ingress resources provides more robust, scalable, and secure ways to expose our services to external traffic in a production setting.
No comments:
Post a Comment