Kubelet API
13 Jan 2020In this post, we’ll describe how a pod or a user can access the kubelet API available on each node of a kubernetes cluster to get information about pods (and more) on that node. We first discuss which ports are available for this purpose, then list the available endpoints (resources) of the kubelet API. Lastly we discuss how to query the secure-port of and which authentication & authorization mechanisms are used.
Ports
kubelet gets a long list of parameters at startup, most of which are deprecated and will be set by using a kubelet configuration file in upcoming releases. Either way, there are two parameters that relates to ports:
- –port (port in config file)
- –read-only-port (readOnlyPort in config file)
The port parameter specifies on which port the kubelet will listen to all requests securely (i.e. https). We’ll refer to this port as secure-port. It’s default value is 10250.
The read-only-port parameter specifies on which port the kubelet will listen to some of the read-only (i.e. only GET) requests. We’ll refer to this port as read-only-port. Not only the http methods, but also the endpoints exposed on this port are limited on this port (see Endpoints). It’s default value is 10255.
Cloud kubernetes providers (like Azure AKS) generally leave those fields as default, so these ports will mostly be the same.
The default values for all ports can also be found in the source code.
Endpoints
Although kubelet REST API is not documented, there some side documentation that mentions about the endpoints of kubelet:
-
In the Kubelet authentication/authorization documentation, some endpoints are mentioned (/stats/, /metrics/, /logs/, /spec/)
-
The absence of a documented API is also an indication that the API is prone to change. So, the best (and only) place to fully see what resources are available is the code of kubelet’s server, which lists endpoints as:
- /metrics
- /metrics/cadvisor
- /metrics/resource/v1alpha1
- /metrics/probes
- /spec/
- /stats/
- /logs/
The ones in bold are the endpoints available both for read-only-port and secure-port
-
If you further dig into the code, you can see that there are some more endpoints:
- /pods
-
The code also reveals that there are many debugging endpoints (The asterisk indicates there are route parameters to be defined like /portForward/{podNamespace}/{podID}):
- /configz (kubelet’s configuration endpoint)
- /run/*
- /exec/*
- /attach/*
- /portForward/*
- /containerLogs/*
- /runningpods/
- /cri/
Read-Only-Port Endpoints
As mentioned above these endpoints are not only limited to read-only-port and secure-port also have these endpoints.
/metrics
The endpoint for metrics related to kubelet’s own statistics like
-
go_memstats_alloc_bytes
-
http_request_duration_microseconds
-
kubelet_cgroup_manager_latency_microseconds
-
kubelet_container_log_filesystem_used_bytes
-
kubelet_docker_operations_duration_seconds
-
kubelet_runtime_operations_duration_seconds
-
kubelet_volume_stats_used_bytes
-
storage_operation_duration_seconds
As the API is not documented and the kubelet is prone to change at any time; the best place to get current list of all metrics is basically running a GET request against your own kubelets (see Authentication and Querying). But you can find an example here
/metrics/cadvisor
These include the most interesting metrics from a developer point of view. Because it provides the metrics for the pods and containers running at that node. Some of them are:
- container_cpu_load_average_10s
- container_cpu_usage_seconds_total
- container_fs_usage_bytes
- container_fs_write_seconds_total
- container_memory_usage_bytes
- container_memory_max_usage_bytes
- container_spec_cpu_quota
- machine_cpu_cores
- machine_memory_bytes
The current list can be found here
Those metrics are provided with helpful labels, thus allowing easy filtering on pod-name, container-name, namespace, etc.
For example the below metric provides CPU load average for the last 10 seconds for a specific container:
container_cpu_load_average_10s{
container="prometheus-config-reloader",
container_name="prometheus-config-reloader",
id="/kubepods/burstable/pod123456",
image="quay.io/coreos/prometheus-config-reloader@sha256:123456",
name="k8s_prometheus-config-reloader_prometheus-k8s-0_abcde",
namespace="mynamespace",
pod="prometheus-k8s-0",
pod_name="prometheus-k8s-0"}
It’s also interesting that metrics for some of the linux-system services are also served among these metrics:
container_cpu_load_average_10s{
container="",
container_name="",
id="/system.slice/networking.service",
image="", name="",namespace="",pod="",pod_name=""}
Note that, such metrics have only id field set.
/metrics/resource/v1alpha1
The resource endpoint requires a version. The current version is v1alpha1.
This endpoint lists resource (cpu & memory) usage metrics of containers and the node. So they are already included in the cadvisor endpoint. The metrics are:
- container_cpu_usage_seconds_total
- container_memory_working_set_bytes
- node_cpu_usage_seconds_total
- node_memory_working_set_bytes
- scrape_error (1 if there was an error while getting container metrics, 0 otherwise)
/metrics/probes
This endpoint gives the liveness or readiness probe results for the pods in that node. It gives this results only for the pods that expose such an interface in its kubernetes deployment configuration.
The output is similar to this:
# HELP prober_probe_result The result of a liveness or readiness probe for a container.
# TYPE prober_probe_result gauge
prober_probe_result{
container="coredns",
container_name="coredns",
namespace="kube-system",
pod="coredns-1234567890-abcde",
pod_name="coredns-1234567890-abcde",
pod_uid="some uuid",
probe_type="Liveness"} 0
prober_probe_result{
container="prometheus",
container_name="prometheus",
namespace="monitoring",
pod="prometheus-12345",
pod_name="prometheus-12345",
pod_uid="some uuid",
probe_type="Liveness"} 0
/spec/
This gives the specifications of the node; like cpu frequency, cpu core count, memory capacity, filesystems, network devices, etc. The output is in JSON format.
/stats/
This gives some statistical information for the resources in the node in JSON format. The resources include not only cpu & memory but also disks, network interfaces and processes.
/pods
This gives information for the pods deployed to the node. The information is very detailed and includes the metadata, labels, annotations, owner references (for example the DaemonSet that owns the pod), volumes, containers, and status.
You can find the structure of a pod entry here.
Secure-Port Endpoints
Note: For the examples in this section we’ll use the notation discussed in the Secure Port section. The examples are written for being run from inside a pod in that node. If you like to run them without deploying a pod, follow the steps in Accessing from outside section.
Additionally, as many users tend to use a cloud-based kubernetes solution, which generally lacks of Token-based authentication and uses Certificate-based authentication for kubelet
, the examples are using /proxy/xxx
, which supports tokens.
/logs/
The logs endpoint itself doesn’t expose logs directly but rather includes many subresources. If you run
curl -k --header "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/nodes/$NODE_NAME/proxy/logs/
it will return many links to logs on the system:
<pre>
<a href="alternatives.log">alternatives.log</a>
<a href="alternatives.log.1">alternatives.log.1</a>
<a href="apt/">apt/</a>
<a href="auth.log">auth.log</a>
<a href="auth.log.1">auth.log.1</a>
<a href="auth.log.2.gz">auth.log.2.gz</a>
...
which can be queried individually:
curl -k --header "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/nodes/$NODE_NAME/proxy/logs/auth.log
/configz
This basically returns the kubelet’s configuration in JSON format. This endpoint supports only GET, but as it includes some private information, it’s accessible via only secure-port.
/containerLogs/
This is a namespaced-endpoint, i.e. it requires to have container’s namespace defined in the request route. You can query individual container’s logs by querying /containerLogs/{podNamespace}/{podID}/{containerName}
. For example:
curl -k --header "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/nodes/$NODE_NAME/proxy/containerLogs/kube-system/coredns-1234567890-abcde/coredns
/runningpods/
This endpoint returns a PodList json, which includes the metadata and spec definitions for all the pods on that node.
curl -k --header "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/nodes/$NODE_NAME/proxy/runningpods/
gives
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"metadata": {
"name": "kube-proxy-12345",
"namespace": "kube-system",
"uid": "",
"creationTimestamp": null
},
"spec": {
"containers": [
{
"name": "kube-proxy",
"image": "sha256:123",
"resources": {}
}
]
},
"status": {}
},
...
/run/, /exec/, /attach/, /portForward/, /cri/
These endpoints are used by kubernetes to manage the node, pods and containers. These have many options and are out of scope of this documentation.
Authentication and Querying
Read-Only Port
A pod running on a node can query the read-only port (over http) on that node without any further requirement. This gives a convenient way to get metrics on a specific node. If there are no custom settings you’ve made for pod or node network isolation, a pod can also query other nodes on the same cluster.
So, before going into a programmatic approach, you can just run /bin/sh (or /bin/bash) on any suitable pod on the target node, and query the cadvisor metrics using curl or wget:
curl http://<NODE NAME or NODE IP>:10255/metrics/cadvisor
which will return the pre-mentioned cadvisor metrics as the body of the response.
By using this capability, one can create his own DaemonSet (let’s call it metrics-proxy
) and apply filtering on the kubelet metrics for the use of Prometheus. Prometheus can be configured to scrape metrics from metrics-proxy
, and metrics-proxy
can add new metrics as well as filter out some metrics provided by the kubelet. For example, it can filter out metrics from other namespaces other than the requested one.
Secure Port
Accessing the secure port requires authenticating & authorizing against the API Server of the cluster. Thus, you’ll need to use the credentials of a valid user or service account while querying the secure port over https.
Required ClusterRoleBinding
Fortunately, the kubernetes documentation includes a page for the authentication of the kubelet. It lists the kubelet API resources and the requested privileges for that resource. In fact, all kubelet API resources is controlled by the node
resource. For example, if you like to query (http GET) /metrics
endpoint (resource) of kubelet, then the service account of the pod is required to have nodes/metrics
given get
privilege.
This can be done by creating a ClusterRoleBinding
to the service account the pod uses. There’s already a suitable ClusterRole for binding for this purpose, ` system:kubelet-api-admin`. In fact, it has more privileges than needed for just querying metrics:
rules:
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- proxy
- apiGroups:
- ""
resources:
- nodes/log
- nodes/metrics
- nodes/proxy
- nodes/spec
- nodes/stats
verbs:
- '*'
As you can see, the verbs for node
resources are *
. So, it might be better to create our own limited ClusterRole
:
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: mymonitoring-clusterrole
namespace: default
rules:
- apiGroups: [""]
resources:
- nodes/log
- nodes/metrics
- nodes/proxy
- nodes/spec
- nodes/stats
verbs:
- 'get'
Once you have the ClusterRole
, you can bind it to your service account, via a ClusterRoleBinding
definition:
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: mymonitoring-clusterrole-binding
roleRef:
kind: ClusterRole
name: mymonitoring-clusterrole
apiGroup: "rbac.authorization.k8s.io"
subjects:
- kind: ServiceAccount
name: mymonitoring
namespace: default
Using Service Account Credentials
Once you’ve setup the service account and its cluster role binding, you can use it in the Pod for querying against kubelet. Let’s assume we have deployed this Pod to our cluster:
apiVersion: v1
kind: Pod
metadata:
name: mymonitoring-ubuntu
namespace: default
spec:
serviceAccountName: mymonitoring
containers:
- name: mymonitoring-ubuntu
image: ubuntu:18.04
command:
- /bin/bash
- -c
- "sleep 1000000"
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
Here you can see we’ve specified the service account and also supplied the NODE_NAME which will be useful later. Once you deploy this and exec
into it (kubectl exec -it mymonitoring-ubuntu -- bin/bash
) you can get the access token for the mymonitoring
account by reading the contents of the /var/run/secrets/kubernetes.io/serviceaccount/token
file:
TOKEN=cat /var/run/secrets/kubernetes.io/serviceaccount/token
Then you can install curl
and run this command to get metrics from /metrics
endpoint of the kubelet
:
curl -k --header "Authorization: Bearer $TOKEN" https://$NODE_NAME:10250/metrics
At this point you may also get an “Unauthorized” response, which indicates the cluster setup is not (easily) suitable for this method (Details below) . At that point you can access the same metrics using the /proxy
endpoint of the Kubernetes’ API Server:
curl -k --header "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/nodes/$NODE_NAME/proxy/metrics
Please note that, this is possible because we’ve included nodes/proxy
in the allowed resources of our ClusterRole
definition.
Accessing from outside
If you like to access kubelet endpoints without deploying a pod, you can directly talk with Kubernetes API Server, once you get the credentials for the privileged service account.
Firstly, create the service account and give privilege to it by binding the ClusterRole, as mentioned in the Required ClusterRoleBinding section.
Then, get its secret token:
SERVICE_ACCOUNT=<the service account name>
SECRET=$(kubectl get serviceaccount ${SERVICE_ACCOUNT} -o json | jq -Mr '.secrets[].name | select(contains("token"))')
TOKEN=$(kubectl get secret ${SECRET} -o json | jq -Mr '.data.token' | base64 -d)
Please note that, kubectl returns a base64-encoded version of the token, so we decode it before use.
Next step is to get the address of your cluster’s API Server:
APISERVER=$(kubectl config view | grep server | cut -f 2- -d ":" | tr -d " ")
Please note that, if you have several clusters in your ~/.kube/config
file the above command may not work. Then simply run the kubectl config view | grep server | cut -f 2- -d ":" | tr -d " "
command and assign one of the suitable API servers’ address to APISERVER
manually.
Last step is to select the node, which can be done by running a kubectl get nodes
command and assigning the desired node’s name to NODE
variable.
Once you’ve setup those steps you can run requests against the API Server similarly:
curl -k -H "Authorization: Bearer $TOKEN" $APISERVER/api/v1/nodes/$NODE/proxy/metrics/cadvisor
Kubelet Authentication and Authorization Mechanism
For the authentication part, there are three ways to have access to kubelet API:
Anonymous Authentication
This option is available only if the kubelet had been started with --anonymous-auth=false
flag
Certificate Based Authentication
You can use X509 client certificate authentication with kubelet, if:
-
the kubelet had been started with the
--client-ca-file
flag, (ex.--client-ca-file=/etc/kubernetes/certs/ca.crt
) providing a CA bundle to verify client certificates with. -
The kubernetes API Server shad been started with
--kubelet-client-certificate
and--kubelet-client-key
flagsThat means you’ll need to have certificates signed with the given
ca.crt
Certificate Authority file (and it’s counter-partca.key
). If you’re running your own cluster, you’ll create and pass these files easily, but for cloud-managed clusters (like Azure Kubernetes Service (AKS)), the certificate (theca.key
part, indeed) passed to the apiserver is not available to the user. Because, the file is created by AKS and the master node is not accessible. In this case, you might want to use the/proxy
endpoint to redirect the same requests to thekubelet
having API Server in the middle, which generally supports Token based authentication.
Token Based Authentication
For most cases token based authentication is the way to go. To be able to use service account token for the kubelet, there are some pre-conditions to be met for API Server authentication and authorization, which are listed in the Kubelet documentation mentioned above.
It says that:
- ensure the
authentication.k8s.io/v1beta1
API group is enabled in the API server- start the kubelet with the
--authentication-token-webhook
and--kubeconfig
flags
For the cloud-provided cluster solutions (for ex. Azure AKS), first item is always met. You can check whether it’s available or not by running kubectl get apiservice
, which should return a response that includes:
v1beta1.authentication.k8s.io Local True
However, the second requirement may not be met. You can check it by either running a ps aux | grep kubelet
command from a privileged pod or (better) run
curl -k --header "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/nodes/$NODE_NAME/proxy/configz
which will return the kubelet configuration. It includes these parts:
"authentication": {
"x509": {
"clientCAFile": "/etc/kubernetes/certs/ca.crt"
},
"webhook": {
"enabled": false,
"cacheTTL": "2m0s"
},
"anonymous": {
"enabled": false
}
},
"authorization": {
"mode": "Webhook",
"webhook": {
"cacheAuthorizedTTL": "5m0s",
"cacheUnauthorizedTTL": "30s"
}
},
You can see that anonymous access is already disabled and webhook authentication is disabled, as well. The only way to authenticate against the kubelet is using certificates.
But let’s assume that we’re utilizing out own-managed cluster and have the Token based authentication for kubelet enabled. Then, we’ll basically pass the same Authorization bearer token as a header parameter with our request (--header "Authorization: Bearer $TOKEN"
). It’ll be used for both authentication and authorization against the kubelet.
For the authorization part, similar conditions exist. From the Kubelet documentation:
- ensure the
authorization.k8s.io/v1beta1
API group is enabled in the API server- start the kubelet with the
--authorization-mode=Webhook
and the--kubeconfig
flags
Both can be checked similar to the authentication discussed above. The above kubelet configuration shows that the cloud-provided cluster (AKS) is setup correctly to use Webhooks.