8 min read

Anatomy of an API Request to Kube-API Server.

Anatomy of an API Request to Kube-API Server.

The Kube-API Server is a collection of REST endpoints, accepting json as inputs from various clients (like kubectl) and using the inputs to manage the state of the cluster. Examining the HTTP conversation between the Kube-API Server and native K8s objects like Pods can be used to troubleshoot communication errors within the cluster.

A sneak peek into a HTTP request from kubectl to Kube-API Server.

Assume we want a pod hosting an nginx container.

Figure 1: The manifest for the pod being used for our discussion.

We can use kubectl create -f <name of YAML manifest for pod> to generate this pod. However, we will append -v 6 to the end of the create command to get a verbose response printed on the terminal.

Figure 2: The kubectl create command with verbosity level 6.

Lets examine the two lines in the response printed:

  • The blue line shows kubectl loading the kube config file to extract 2 major bits of information: (a) the url of the node where the kube-api server is located and (b) the authentication token that will let kube-api server know that the caller (in this case, the kubectl instance) is trusted.
  • The red line shows a POST command was sent which resulted in a 201 HTTP status code indicating the pod was created.

Get information about the pod.

Figure 3: Using -v 6 to get minimal information about the pod.

Notice the url at which the pod is accesssible:

  • https://k8scp:6443 is the address for the kube-api server.
  • /api/v1/namespaces/default/pod/nginx-pod is the file containing the pods metadata.

Try to create the same pod again.

Run the kubectl create -f <name of pod manifest> again.

Figure 4: Notice the 409 HTTP Conflict Status Code.

Using verbosity level 9 for detailed responses to be printed on console.

Figure 5: Repeat the same command as in Figure 3.

The response sent back from the kube-api server is very detailed and has a significant amount of metadata. A sample of this output is attached below:

Going through the contents of the file above, notice the metadata that has been provided as part of the response. It contains basic information on the nature of the pod as it is known by the kube-api server.

📢
Verbosity levels range from 0 all the way to 9 and each higher level displays additional details.
Figure 6: Verbosity levels and their payloads. Taken from https://www.shellhacks.com/kubectl-debug-increase-verbosity/

Deep dive into traffic exchange between client and kube-api server.

So far, we've been using kubectl commands with high verbosity levels to get a taste of the traffic that is flowing between the client (kubectl) and the kube-api server.

We can also use curl to directly send traffic to the kube-api server. However before doing that, we have to 'authenticate' curl (so that kube-api server feels ok sending back any sort of response) using the kubeconfig file that is stored on our node.

Figure 7: This command will set up the kube-proxy to represent our client to kube-api server.

As soon as the proxy is started, it will be sent to the background (because of the '&') and you will also see the IP address on which it is listening for our commands.

Figure 8: The proxy is now available at 127.0.0.1:8001.

We can send our request to the proxy and it will relay it to the kube-api server on our behalf.

Figure 9: kube proxy is our intermediary and passes our messages to the kube-api server.

We can now issue a curl command to extract information on our nginx-pod pod.

Figure 9: The curl command directly being sent to kube-api server through the proxy.
📢
The command has the following structure:
- curl is the linux module that is used for establishing HTTP communication with some end point.
- http://localhost:8001 is the address of the kube-proxy that is acting like the intermediary for our HTTP communication.
- /api/v1/namespaces/default/pods/nginx-pod is the location on the kube-api server where nginx-pods metadata is saved (refer to Figure 3).

The response, as a result of the curl call in Figure 9 shows the same kind of meta-data for nginx-pod as was seen after the commands executed as per Figure 5.

To turn the proxy off, bring it to the foreground, followed by pressing ctrl + c.

Figure 10: Bring proxy to the foreground and CTRL + C to kill it.

Adding a --watch to our kubectl commands.

The WATCH flag is considered a special API request.

Let's put a watch on all the pods in the cluster.

First, execute kubectl get pods --watch -v 6 & to

  • Send a GET request to get a list of resources (in our case, pods)
  • Put a watch on this list of resources using --watch
  • Set verbosity level to 6, using -v 6 and
  • Send the kubectl process to the background using &
Figure 11: Command to get pods, apply watch on the kubectl process at a verbosity level of 6 and send it to the background.

As a result of the command in Figure 11, the response returned show minimal network chatter:

Figure 12: The response from kube-api (due to steps taken in Figure 11).

The response shows us the following happened:

  • The kube config file was loaded to get the authentication token from it along with the location of the kube-api server.
  • A GET request was sent to https://k8scp:6443/api/v1/namespaces/default/pods. The first 500 pods in the default namespace were returned in 10 milliseconds.
  • a GET request was sent to https://k8scp:6443/api/v1/namespaces/default/pod?resourceVersion=388590 with watch set to true.
📢
Wondering what k8scp:6443 means? Look at my post here.
TLDR; k8scp is the alias used for the Control Plane.

What does resourceVersion=388590 mean?

With each change applied to the pods in the default namespace, resourceVersion changes. It is this changing value that tells watch something has changed and therefore should be tracked and displayed.

Finally, we can see that kubectl has kept the TCP session with kube-api server open and is waiting for data to flow back from there:

Figure 13: The client end is at 192.168.0.214:4462 and the server is at 192.168.0.214:6443.

We can confirm that the kubectl process has indeed established a TCP connection with kube-api server since we do see ESTABLISHED 1332809/kubectl printed on the terminal.

Testing the use of --watch by deleting and recreating a pod.

We start off with deleting nginx-pod first.

Figure 14: The delete pod request shows the various stages kube-api server will go through before deleting the pod.

We could also bring the pod back and also view the stages kube-api goes through to create a pod.

Figure 15: Note the various stages the pod goes through till it is Running.

Accessing logs: Another special API request.

For a start, lets simply access logs for the pod and its underlying container.

Figure 16: The simple kubectl logs nginx-pod returned a set of logs from the nginx container.

Repeat the above with a verbosity level of 6.

Figure 17: Kubectl logs command with -v 6

In addition to the outputs already seen in Figure 16, notice the multiple GET commands:

  • The blue GET command is checking for the existence of nginx-pod and is returning with a 200 OK, confirmint the pod does exist.
  • The red GET command is going a step deeper and also extracting the logs for nginx-pod.

Logs can also be accessed through kubectl proxy.

As we did in previous examples, we can set up a direct proxy-led connection to the kube-api server (as shown in Figure 7 and Figure 8).

Once the proxy is active and listening, use curl to get access to the same logs from the same pod.

Figure 18: Using curl to http://localhost:8001/api/v1/namespaces/default/pods/nginx-pod/log returns the same records as before.

Checking authentication failures.

If we go through the responses generated by our -v 6 levels, one thing is obvious in all of them: the use of /.kube/config file to authenticate with the kube-api server. Once the authentication is successful, the remaining calls are sent and responses are received.

To demo authentication failure, we would need to make sure the /.kube/config information is either missing or incorrect.

  • First make a copy of the actual /.kube/config file:
Figure 19: Make a copy of the /.kube/config file.
  • Now edit the /.kube/config file and introduce an error in it. The easiest thing to change is the user name value:
Figure 20: Change kubernetes-admin to kubernetes-fake-user.

Once the error has been introduced, we can issue another GET request for all pods in the default namespace. A prompt asking for username and password will be issued since kube-api server does not know anything about kuberbetes-fake-user. Enter any random user name and password and press enter. The HTTP response that comes back will show a 403 Forbidden, indicating there is an authorization issue.

Figure 21: Since the username provided does not exist, a 403 is issued.

Revert the user name inside /.kube/config back to kubernetes-admin and attempt the get pods command again:

Figure 21: Things are back to normal.
📢
In summary, the techniques shown here can be applied to any kubernetes object like pods, deployments, replicasets, services etc and makes it easier to troubleshoot the cluster if needed.

I write to remember and if in the process, I can help someone learn about Containers, Orchestration (Docker Compose, Kubernetes), GitOps, DevSecOps, VR/AR, Architecture, and Data Management, that is just icing on the cake.