• Home
  • LLMs
  • Python
  • Docker
  • Kubernetes
  • Java
  • Maven
  • All
  • About
Kubernetes | Services (services|svc)
  1. Notes
  2. Services (services|svc)
  3. ClusterIP
  4. NodePort
  5. Expose a Service using the "kubectl expose" command
  6. Delete Services
  7. Kubernetes Service Discovery

  1. Notes
    See these pages for more details about Services:
    https://kubernetes.io/docs/concepts/services-networking/service/
    https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
    https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
    https://kubernetes.io/docs/concepts/security/controlling-access/
    https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/
  2. Services (services|svc)
    Pods have their own unique IP addresses but these IP addresses are not reliable (if a Pod get rescheduled it might get a new IP address). For a large deployment, it will be hard to maintain and keep updated a list of all these IP addresses,.

    Kubernetes provides a solution that makes accessing the Pods easier and reliable. The Kubernetes Service is a special object that regroup a set of related Pods. A Kubernetes Service has a unique IP address (ClusterIP), DNS name, and Port that last for the life of the service. The Kubernetes Service load balance requests across its associated Pods.

    Kubernetes Service uses the Kubernetes Endpoints object (endpoints|ep) to manage Pods. For each service, an associated EndPoints object will be created. This object will keep updating the list of all the Pods that their labels match the Service's selector. Pods (coming from new deployment, scaling up an existing deployment) that their labels match the Service's label selector, will dynamically be added to the Endpoints object. If a Pod get updated (labels), become unhealthy (failure), or terminated (an existing deployment get deleted or rolled back), it will be removed from the Endpoints object. The EndPoints object will have the name of the Service.

    Linking Pods to a Service using labels and label selectors allow Pods to be loosely coupled with the Service. To be selected, a pod must define all the labels defined in the Service's label selector. A Pod can define more labels than the one defined in the Service's selector.

    The Service object allow you to specify the type (spec.type) of the Service you want. The possible values are:
    • ClusterIP (default):
      The Service (unique IP address, DNS name, and Port) will be, by default, only available within the cluster.

    • NodePort:
      Superset of ClusterIP. It also exposes a node port (TCP/UDP) that allow the Service to be available from outside the cluster.

    • LoadBalancer:
      Superset of NodePort. If supported, It creates an external load balancer in the cloud provider and assigns an external IP address to the Service.

    • ExternalName:
      Can be used when there's need to direct requests to applications running outside the Kubernetes cluster.

    "Headless" Service (spec.clusterIP set to none) is a special case of the ClusterIP Service. A query to the Service will return the Pods' IP addresses (no load balancing). Statefulset is a known use case where "Headless" Services are used.

    To create/deploy a Service, you need first to create a manifest YAML file. The manifest file describe the Service and its components (name, type, Ports, Selector, ...). You can use "kubectl" (or any rest client tool) to post the manifest file to the API server If applicable, the API server verifies that the request is authenticated, validates it's authorized, and runs the admission controllers on it. The API server verifies the manifest file and, if no issues, it writes a record for that manifest in the cluster store (etcd). The scheduler will then read the record and deploy the Pod of the Service to a healthy worker node with enough available resources.

    The same behaviour, described above, applies if you use the imperative command ("kubectl create") to create the Service.
  3. ClusterIP
    Let's define a simple Service:
    $ vi hello-nginx-service.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: hello-nginx
    spec:
      type: ClusterIP # this's default if the field type is skipped
      ports:
      - port: 80
        targetPort: 80 # this defaults to the Port's value if the field targetPort is skipped
        protocol: TCP # this's the default if the field protocol is skipped
      selector:  # the Service's selector verifies Pods that define all its labels
        app: hello-nginx
    The Service defines a Selector (spec.selector): "app=hello-nginx"
    Pods that have all the labels defined in the Service's Selector will be selected.

    Let's apply now the Service:
    $ kubectl apply -f hello-nginx-service.yaml
    service/hello-nginx created
    Check the Service:
    $ kubectl get service hello-nginx -o wide
    NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE   SELECTOR
    hello-nginx   ClusterIP   10.109.87.124   <none>        80/TCP    11m   app=hello-nginx
    In the bellow Deployment example, the Pods will be selected because they define (spec.template.metadata.labels) the label "app=hello-nginx". The other labels of the Pods ("product=mtitek", "stage=dev") are not considered.

    Let's define a simple deployment:
    $ vi hello-nginx-deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: hello-nginx
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: hello-nginx
      template:
        metadata:
          labels: # a subset of the Pod's labels must match the Service's selector
            app: hello-nginx
            product: mtitek
            stage: dev
        spec:
          containers:
          - name: hello-nginx
            image: nginx:latest
            ports:
            - containerPort: 80
    Let's apply the Deployment:
    $ kubectl apply -f hello-nginx-deployment.yaml
    deployment.apps/hello-nginx created
    Verify that Pods are running and ready (note the IP addresses of each Pod):
    $ kubectl get pods -o wide
    NAME                           READY   STATUS    RESTARTS   AGE   IP           NODE             NOMINATED NODE   READINESS GATES
    hello-nginx-5d77cc88f4-gj6k2   1/1     Running   0          13m   10.1.0.157   docker-desktop   <none>           <none>
    hello-nginx-5d77cc88f4-pz9dj   1/1     Running   0          13m   10.1.0.156   docker-desktop   <none>           <none>
    Let's validate the Service (note that the Endpoints field contains the Pods' IP addresses):
    $ kubectl describe service hello-nginx
    Name:              hello-nginx
    Namespace:         default
    Labels:            <none>
    Annotations:       <none>
    Selector:          app=hello-nginx # list of labels that Pods must define so they can bed added to the Service's Endpoints
    Type:              ClusterIP
    IP:                10.109.87.124 # clusterIP address (internal) of the Service
    Port:              <unset>  80/TCP # the port that the Service listens on inside the cluster
    TargetPort:        80/TCP # the port that the Pods are listening on
    Endpoints:         10.1.0.156:80,10.1.0.157:80 # list of Pods (IP addresses) that are healthy and their labels match the service's label selector
    Session Affinity:  None
    Events:            <none>
    Check the Endpoints object of the Service (note the ENDPOINTS column):
    $ kubectl get endpoints hello-nginx
    NAME          ENDPOINTS                     AGE
    hello-nginx   10.1.0.156:80,10.1.0.157:80   23m
    Describe the Endpoints object of the Service (note the Addresses field):
    $ kubectl describe endpoints hello-nginx
    Name:         hello-nginx
    Namespace:    default
    Labels:       <none>
    Annotations:  endpoints.kubernetes.io/last-change-trigger-time: 2021-06-06T10:31:11Z
    Subsets:
      Addresses:          10.1.0.156,10.1.0.157
      NotReadyAddresses:  <none>
      Ports:
        Name     Port  Protocol
        ----     ----  --------
        <unset>  80    TCP
    
    Events:  <none>
    To access the service from inside the cluster:
    $ kubectl exec -ti hello-nginx-5d77cc88f4-gj6k2 -- bash
    root@hello-nginx-5d77cc88f4-gj6k2:/# curl -I -X GET 10.109.87.124:80
    HTTP/1.1 200 OK
    Server: nginx/1.21.0
    Date: Wed, 06 Jun 2021 02:52:40 GMT
    Content-Type: text/html
    Content-Length: 612
    Last-Modified: Tue, 25 May 2021 12:28:56 GMT
    Connection: keep-alive
    ETag: "60aced88-264"
    Accept-Ranges: bytes
    
    root@hello-nginx-5d77cc88f4-gj6k2:/#
  4. NodePort
    The NodePort Service defines a node port on every node in the cluster. Requests from outside the cluster can target the node port of any cluster node to reach the Service. The NodePort Service will load balance requests to the Pods that are associated to it.

    Requests -> Cluster Node (NodePort) -> Service -> Pods

    Let's use the Deployment created previously (hello-nginx-deployment.yaml).

    Let's define a simple NodePort Service:
    $ vi hello-nginx-service-node-port.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: hello-nginx-node-port
    spec:
      type: NodePort
      ports:
      - port: 80
        targetPort: 80 # this defaults to the Port's value if the field targetPort is skipped
        protocol: TCP # this's the default if the field protocol is skipped
        nodePort: 30080 # if not defined, Kubernetes will assign a port from a range (default: 30000-32767)
      selector:  # the Service's selector verifies Pods that define all its labels
        app: hello-nginx
    Let's apply the Service:
    $ kubectl apply -f hello-nginx-service-node-port.yaml
    service/hello-nginx-node-port created
    Check the Service:
    $ kubectl get service hello-nginx-node-port -o wide
    NAME                    TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE   SELECTOR
    hello-nginx-node-port   NodePort   10.106.138.253   <none>        80:30080/TCP   24s   app=hello-nginx
    Let's validate that Service (note that the Endpoints field contains the Pods' IP addresses):
    $ kubectl describe service hello-nginx-node-port
    Name:                     hello-nginx-node-port
    Namespace:                default
    Labels:                   <none>
    Annotations:              <none>
    Selector:                 app=hello-nginx
    Type:                     NodePort
    IP:                       10.106.138.253
    LoadBalancer Ingress:     localhost
    Port:                     <unset>  80/TCP
    TargetPort:               80/TCP
    NodePort:                 <unset>  30080/TCP # the port to access the Service from outside the cluster
    Endpoints:                10.1.0.156:80,10.1.0.157:80
    Session Affinity:         None
    External Traffic Policy:  Cluster
    Events:                   <none>
    Check the Endpoints object of the Service (note the ENDPOINTS column):
    $ kubectl get endpoints hello-nginx-node-port
    NAME                    ENDPOINTS                     AGE
    hello-nginx-node-port   10.1.0.156:80,10.1.0.157:80   83s
    Describe the Endpoints object of the Service (note the Addresses field):
    $ kubectl describe endpoints hello-nginx-node-port
    Name:         hello-nginx-node-port
    Namespace:    default
    Labels:       <none>
    Annotations:  endpoints.kubernetes.io/last-change-trigger-time: 2021-06-09T02:36:41Z
    Subsets:
      Addresses:          10.1.0.156,10.1.0.157
      NotReadyAddresses:  <none>
      Ports:
        Name     Port  Protocol
        ----     ----  --------
        <unset>  80    TCP
    
    Events:  <none>
    To access the service from inside the cluster, execute the same command we did above for the ClusterIP service (on port 80).

    To access the service from outside the cluster (note the node port 30080 and the IP address of the docker desktop VM):
    $ curl -I -X GET 192.168.2.10:30080
    HTTP/1.1 200 OK
    Server: nginx/1.21.0
    Date: Wed, 06 Jun 2021 02:58:03 GMT
    Content-Type: text/html
    Content-Length: 612
    Last-Modified: Tue, 25 May 2021 12:28:56 GMT
    Connection: keep-alive
    ETag: "60aced88-264"
    Accept-Ranges: bytes
  5. Expose a Service using the "kubectl expose" command
    You can use the "kubectl expose" command to create a Service object that exposes a Deployment.

    Let's use the Deployment created previously (hello-nginx-deployment.yaml).

    To expose the "hello-nginx"" Deployment:
    $ kubectl expose deployment hello-nginx \
      --name=hello-nginx \
      --port=80 \
      --target-port=80 \
      --type=ClusterIP
    service/hello-nginx exposed
    • The --name flag defines the name of the service.

    • The --port flag defines the port that the Service listens on inside the cluster.

    • The --target-port flag defines the port that the Pods are listening on.

    • The --type flag defines the service type.

    To get details about the Service, you can use the same commands explained above (kubectl get services, kubectl describe services).
    $ kubectl get services hello-nginx -o wide
    NAME          TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE   SELECTOR
    hello-nginx   ClusterIP   10.97.96.83   <none>        80/TCP    76s   app=hello-nginx
  6. Delete Services
    To delete a Service: using manifest file (kubectl delete):
    $ kubectl delete -f hello-nginx-service.yaml
    service "hello-nginx" deleted
    To delete a Service using its name:
    $ kubectl delete services hello-nginx
    service "hello-nginx" deleted
  7. Kubernetes Service Discovery
    See more details about Kubernetes DNS Service (and dnsutils utility) in this page: Kubernetes Cluster

    The Kubernetes DNS (controller) watches the API Server for new Services. When a new Service is created (get assigned a virtual IP address: ClusterIP), the Kubernetes DNS will automatically create the DNS records for the Service (the Service's name and IP address get registered with the cluster DNS).

    The fully qualified domain name (FQDN) of a Service will be created by combing the following data:
    • The Service name (metadata.name).
      This's also called the short name or the unqualified name of the Service (i.e. hello-nginx).

    • The namespace where the service is created.

    • The service type name (svc).

    • The cluster domain address (cluster.local).

    This can be represented as following: <SERVICE-NAME>.<NAMESPACE>.svc.cluster.local
    Example (Service: hello-nginx): hello-nginx.default.svc.cluster.local

    • A request to a Service from outside its namespace needs to use the Service's FQDN: hello-nginx.default.svc.cluster.local.

    • A request to a Service from inside its namespace can use the Service's name (short name or unqualified name): hello-nginx.

    It's also possible to use the Service's name concatenated with the namespace: hello-nginx.default.

    The nslookup command can be used to resolve the "hello-nginx" Service (you should get something like the following):
    $ kubectl exec -i -t dnsutils -- nslookup hello-nginx
    Server:  10.96.0.10 <- The IP address of the Kubernetes DNS (kube-dns Service ClusterIP)
    Address: 10.96.0.10#53
    
    Name:    hello-nginx.default.svc.cluster.local <- The FQDN of the hello-nginx Service
    Address: 10.109.87.124 <- The clusterIP of the hello-nginx Service
    When Pods are created, they are configured to know about the Kubernetes DNS (ClusterIP, search domains). This will allow Pods to query the Kubernetes DNS to resolve a service's name to its IP address.

    Kubernetes will configure each Pod's "/etc/resolv.conf" file with the IP address (nameserver) of the Kubernetes DNS Service and the search domains (search) that can be appended to the Services' unqualified names.
    $ kubectl exec hello-nginx-5d77cc88f4-gj6k2 -- cat /etc/resolv.conf
    nameserver 10.96.0.10 <- Kubernetes DNS IP address (kube-dns Service ClusterIP) to which request will be senn
    search default.svc.cluster.local svc.cluster.local cluster.local #<- search domains to be appended to unqualified names
    options ndots:5
© 2025  mtitek