14 Kubernetes Service Current

14 Kubernetes Service Current #

Kubernetes Service is an abstraction layer that groups a set of pods and provides network access to them. It allows applications to be decoupled from the specific pods they run on, making it easier to manage and scale applications in a Kubernetes cluster.

A service in Kubernetes is defined by a YAML file that specifies the desired state of the service. This includes the number of replicas, the type of service (such as ClusterIP, NodePort, or LoadBalancer), and the port mappings.

Once a service is created, Kubernetes automatically assigns a unique IP address to it. This allows other pods or external clients to connect to the service using the provided IP address and port number.

Services in Kubernetes can be used for load balancing, internal communication, and exposing applications to the outside world. It provides a stable endpoint for accessing pods, even if pods are added or removed.

In addition to creating services manually, Kubernetes also supports service discovery and load balancing through DNS. Each service is assigned a DNS name that can be used to access the service. When multiple replicas are available, Kubernetes automatically load balances the traffic among them.

Overall, Kubernetes Service simplifies the management and connectivity of applications in a Kubernetes cluster, making it an essential component for building scalable and reliable applications.

Source of demand #

Why do we need service discovery? #

In a Kubernetes (K8s) cluster, applications are deployed using pods. Unlike traditional application deployment, where applications are deployed on specific machines and we know how to call the IP address of other machines, in K8s, applications are deployed using pods, which have a transient lifecycle. During the lifecycle of a pod, such as its creation or destruction, its IP address can change. This means that the traditional deployment method, where we specify an IP address to access a specific application, cannot be used in K8s.

In addition, in the deployment of K8s applications, although we have learned about the deployment pattern of deployments, we still need to create a group of pods and this group of pods needs to provide a unified access point and a way to control traffic load balancing within the group. For example, in the testing environment, pre-production environment, and production environment, the same deployment template and access method need to be maintained during the deployment process. This way, the same application template can be directly released in different environments.

Service: Service discovery and load balancing in Kubernetes #

Finally, the application services need to be exposed for external access and need to be provided to external users for invocation. In the previous section, we learned that the network of pods is not the same as the network of machines, so how can we expose the pod network for external access? This is where service discovery comes in.

avatar

In K8s, service discovery and load balancing are provided by K8s Service. The figure above shows the architecture of Service in K8s. K8s Service provides access to external networks and the pod network. External networks can access the pod network through the service, and the pod network can also be accessed through K8s Service.

In terms of implementation, K8s connects to another group of pods, which can be load balanced using K8s Service. This solves the redundancy problem mentioned earlier and provides a unified access point for service discovery, while also allowing access from external networks and providing a unified access address for different pods.

Use Case Interpretation #

Let’s interpret an actual use case to understand how to declare and use a Kubernetes service for a pod.

Service Syntax #

avatar

First, let’s take a look at the syntax of a Kubernetes Service. The image above represents the declaration structure of a Kubernetes Service. This structure contains several syntax elements that are similar to the ones mentioned earlier for other standard Kubernetes objects. For example, using labels for selection, using a selector, and declaring label tags.

There is a new concept here, the definition of a protocol and port used for service discovery in Kubernetes Service. Let’s continue examining this template. It declares a Kubernetes Service named “my-service”, which has a label “app:my-service” and selects pods with the label “app:MyApp” as its backend.

Lastly, it defines a protocol and port for service discovery. In this example, we use the TCP protocol, with port 80 as the service’s target port. This means that any request to the service’s port 80 will be load balanced to the backend pods with the “app:MyApp” label on their port 9376.

Creating and Viewing Service #

How do we create the service with the given declaration, and what is the effect after creation? It’s as simple as running the following commands:

  • kubectl apply -f service.yaml

or

  • kubectl created -f service.yaml

These commands will create the service. Once created, you can use the command:

  • kubectl describe service

to view the result of the service creation.

avatar

After the service is created, you can see that its name is “my-service”. The Namespace, Label, and Selector are the same as what we declared earlier. Once the declaration is complete, an IP address will be generated, which becomes the IP address of the service. This IP address can be accessed by other pods within the cluster, serving as a unified access point for the pods and for service discovery. There is another property called Endpoints here. With Endpoints, we can see: which pods are selected by the selector declared earlier? What is the state of these pods? For example, through the selector, we can see that it selects the IP of these pods and the port declared by these pods.

avatar

The actual architecture is shown in the figure above. After the service is created, it creates a virtual IP address and port in the cluster. In the cluster, all pods and nodes can access this service through this IP address and port. This service mounts the selected pods and their IP addresses to the backend. By accessing the service’s IP address, it can balance the load on these backend pods.

When the lifecycle of a pod changes, such as when one pod is destroyed, the service will automatically remove this pod from the backend. This achieves the purpose of maintaining unchanged endpoints even if the lifecycle of the pod changes.

Accessing Services within the Cluster #

How can other pods within the cluster access the service we created? There are three ways:

  • First, we can access it by using the service’s virtual IP. For example, for the newly created “my-service” service, we can see its virtual IP address is 172.29.3.27 and port is 80 by using kubectl get svc or kubectl describe service. Then we can directly access this service’s address in the pod using this virtual IP and port.
  • The second way is to access the service directly through its name using DNS resolution. Pods in the same namespace can directly access the service declared earlier by using the service’s name. In different namespaces, we can access the service by appending the namespace where the service resides to the service name. For example, we can use curl to access it like my-service:80 to reach this service.
  • The third way is to access it through environment variables. When pods in the same namespace are started, Kubernetes will put some IP addresses, ports, and simple configurations of the service into the environment variables of the Kubernetes pod. After the Kubernetes pod container is started, it can read the values of the environment variables and obtain the addresses or port numbers of other services in the namespace. For example, in a pod in the cluster, we can directly use curl to get the value of an environment variable, such as curl $MY_SERVICE_SERVICE_HOST, which is the IP address, MY_SERVICE is the service we declared just now, and SERVICE_PORT is the port number. This way, we can also make requests to the MY_SERVICE service in the cluster.

Headless Service #

A special form of service is the Headless Service. When creating a service, we can specify clusterIP: None, indicating that a virtual IP is not required for this service (i.e., the virtual IP mentioned earlier in the cluster is not needed). In this case, how can load balancing and unified access be achieved?

The operation is as follows: pods can directly resolve the IP addresses of all backend pods by using DNS resolution with the service name. A DNS A record will resolve all IP addresses of backend pods, and clients can select one IP address from the list of A records. This A record list will change with the lifecycle of the pod. Therefore, client applications need to select an appropriate address from the A record list returned by DNS, and the client application will then access the pod using this address.

avatar

You can see from the figure above the difference between this and the template we declared just now. The difference is that a clusterIP: None has been added in the middle, indicating that no virtual IP is needed. When the pod in the cluster accesses “my-service”, it will directly resolve to the IP address of all pods corresponding to the service and return it to the pod. Then the pod itself can select an IP address to access directly.

Exposing Services to the Outside of the Cluster #

Previously, we introduced how nodes or pods in the cluster can access the service. How can the service be exposed to the outside world? How can the application be exposed to the public for access? There are two types of services that can solve this problem: NodePort and LoadBalancer.

  • The NodePort type exposes a port on the nodes in the cluster (i.e., the host of the nodes in the cluster). After accessing this port, it will do another layer of forwarding and forward to the virtual IP address on the node host, which is the virtual IP address mentioned just now.

  • The LoadBalancer type adds another layer of conversion on top of NodePort. The NodePort mentioned earlier is actually a port on each node in the cluster. LoadBalancer attaches a load balancer in front of all nodes. For example, if you attach an SLB on Alibaba Cloud, this load balancer will provide a unified entrance and load balance all traffic it encounters to the node pods of each cluster. Then the node pod will be transformed into a ClusterIP and access the actual pod.

Operation Demonstration #

Next, we will demonstrate the actual operation by experiencing how to use K8s Service on Alibaba Cloud Container Service.

Create Service #

We have already created an Alibaba Cloud container cluster, and configured the connection from the local terminal to the Alibaba Cloud container cluster.

First, you can use kubectl get cs to see that we have successfully connected to the cluster of Alibaba Cloud Container Service.

avatar

Today, we will use these templates to actually experience using K8s Service on Alibaba Cloud Container Service. There are three templates, first is the client, which is used to simulate accessing the K8s service through the service and load balancing to a group of pods declared in our service.

avatar

As for K8s Service, as mentioned earlier, we created a K8s Service template, which includes pods. K8s Service will use the specified frontend port 80 to load balance to the backend port 80 of the pods, and selector selects some pods with the label run: nginx as its backend.

avatar

Then, create a group of pods with this label. How to create pods? It is through the K8s Deployment introduced earlier. With Deployment, we can easily create a group of pods, and declare run: nginx as a label, and it has two replicas, so two pods will be run simultaneously.

avatar

First, create a group of pods by creating this K8s Deployment through kubectl create -f service.yaml. This deployment is also created, let’s see if the pod has been created. As shown in the figure below, we can see that the two pods created by this deployment are already running. You can see the IP addresses through kubectl get pod -o wide. By using -l, that is, label for filtering, run=nginx. As shown in the figure below, you can see that these two pods each have an IP address, which is 10.0.0.135 and 10.0.0.12, and both of them have the run=nginx label.

avatar

Next, let’s create the K8s service, which is to select these two pods through the service we just mentioned. This service has already been created.

avatar

According to what was just mentioned, we can see the actual status of this service through kubectl describe svc. As shown in the figure below, the nginx service created just now, its selector is run=nginx, and it selects the backend pod addresses through the run=nginx selector, which are the addresses of the two pods mentioned earlier: 10.0.0.12 and 10.0.0.135. Here we can see that K8s has generated a virtual IP address in the cluster for it. Through this virtual IP address, it can load balance to the two backend pods.

avatar

Now, let’s create a client pod to actually feel how to access this K8s Service. We create the client pod through client.yaml, and kubectl get pod can see that the client pod has been created and is running.

avatar

kubectl exec into this pod, enter this pod to feel the three access methods mentioned just now. First, you can directly access this ClusterIP generated by K8s, which is the virtual IP address. By using curl to access this IP address, this pod does not have curl installed. Use wget instead to test it. You can see that accessing the actual IP address through this is accessible to the backend nginx. This virtual IP is a unified entry point.

avatar

The second method is to access this service directly by the name of the service. Similarly, using wget, access the service named nginx that we just created, and you can find that the result is the same as what we just saw.

avatar

When in different namespaces, you can also access the service by adding the name of the namespace, such as the namespace here is default. avatar Finally, we introduce another way to access services through environment variables. In this pod, you can directly execute the env command to see the actual injected environment variables. Check whether various configurations of the nginx service have been registered.

avatar

You can also access this environment variable using wget and access our service.

avatar

After introducing these three access methods, let’s take a look at how to access services through external networks. Let’s use vim to directly modify the service we just created.

avatar

Finally, we add a type: LoadBalancer, which is the external access method we mentioned earlier.

avatar

Then, we use kubectl apply to directly apply the modified content to the created service.

avatar

Now, let’s see what changes the service has. By using kubectl get svc -o wide, we can see that the newly created nginx service has an EXTERNAL-IP, which is an IP address for external access. Previously, we accessed the CLUSTER-IP, which is a virtual IP address within the cluster.

avatar

Now, let’s actually access this external IP address 39.98.21.187 and experience how to expose our application service through the service. Just click it in the terminal. Here, we can see that we can directly access this service through the external access endpoint of this application. Isn’t it simple?

avatar

Finally, let’s take a look at how service achieves service discovery in Kubernetes. The access address of the service is independent of the lifecycle of the pod. Let’s now see the addresses of the two pods behind the service.

avatar

Now, let’s delete one of the pods using kubectl delete.

avatar

We know that the deployment will automatically generate a new pod. Now, we can see that the IP address has changed to 137.

avatar

Now, let’s describe the service again. As shown below, we can see that the access endpoints in the front have not changed, and the IP address of the external LoadBalancer has not changed. In all cases where client access is not affected, the IP address of a backend pod has been automatically added to the backend of the service.

avatar

This means that when calling the components of the application, you don’t need to care about changes in the lifecycle of the pod.

That’s all for the demonstrations.

Architecture Design #

** **Finally, there is a simple analysis of the design and implementation principles of K8s.

Kubernetes Service Discovery Architecture #

avatar

As shown in the above figure, the overall architecture of K8s service discovery and K8s service is as follows.

K8s is divided into master nodes and worker nodes:

  • The master node mainly manages the content of K8s.
  • The worker node is where user applications are actually run.

Within the K8s master node, there is APIServer, which is where all K8s objects are managed. All components will register with the APIServer to listen for changes to these objects, such as the lifecycle changes of our component pod.

There are three key components here:

  • One is the Cloud Controller Manager, responsible for configuring the load balancer of the LoadBalancer to allow external access.
  • The other is Coredns, which observes changes in the backend pods of the service in the APIServer, configures the DNS resolution of the service, and enables access to the virtual IP of the service directly by using the name of the service, or resolves the IP list in the Headless type Service.
  • Then, each node will have a kube-proxy component, which listens for changes in services and pods, and then actually configures the access of node pods or virtual IP addresses in the cluster.

What is the actual access chain? For example, from a client pod3 inside the cluster to access the service, it is similar to the demonstration we just showed. Client pod3 first resolves the Service IP through Coredns. Coredns returns the service IP corresponding to the ServiceName to it. Client pod3 will then use this Service IP to make the request. After its request reaches the host network, it will be intercepted and processed by iptables or IPVS configured by kube-proxy. Then, it will be load balanced to each actual backend pod, thus achieving load balancing and service discovery.

For external traffic, such as the request we just accessed through the public network, it listens for changes in the service through the external load balancer Cloud Controller Manager, configures the load balancer, and then forwards it to the NodePort on the node. The NodePort will also go through the iptables configured by kube-proxy, converting the NodePort traffic into ClusterIP, and then converting it into the IP address of the backend pod to achieve load balancing and service discovery. This is the overall structure of Kubernetes service discovery and K8s service.

Follow-up Advancements #

In the advanced section, we will delve deeper into the implementation principles of K8s service, as well as techniques for diagnosing and repairing service network issues.

Summary of this Section #

The main content of this section ends here. Here is a brief summary for everyone:

  1. Why cloud-native scenarios need service discovery and load balancing,
  2. How to use Kubernetes Services for service discovery and load balancing in Kubernetes,
  3. Components and implementation principles related to Services in a Kubernetes cluster.

I believe that after studying this section, everyone will be able to quickly and standardly orchestrate complex enterprise-level applications using Kubernetes Services.