kube API server and kubernetes python client
Although I briefly covered this in the previous post, I would like to think again about how to access the kube API server and Kubernetes, and briefly record how to interact with the Kubernetes cluster using python.
1. kube API server
As described in the k8s official documentation, the core of the various components of the control plane that makes up Kubernetes is the kube API server. It is a component that allows access to all k8s resources stored in etcd in API form and allows all actions such as inquiry/creation/delete.
2. kubectl
There is probably no one who uses k8s who does not know the kubectl command. If you are new to K8s, whether you are creating a self-managed K8s cluster or a managed K8s cluster from a Cloud Provider (AWS, GCP, Azure, etc.), the first thing you do after creating a cluster is to run the kubectl command to run the cluster. You will learn to interact with.
The kubectl command is a command line tool that allows API communication with the kube API server. Let’s take a look at the example below.
➜ [~] kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 34m
As anyone who reads this post will know, the above command is a command that queries the list of pods in the current namespace and its output. If you run the same command with the "-v=7" option to set the log level, you can see the output as below.
➜ [~] kubectl get pod -v=7
I0813 14:53:24.482339 46420 loader.go:372] Config loaded from file: /Users/my-test-user/.kube/config
I0813 14:53:24.486938 46420 round_trippers.go:463] GET https://kubernetes.docker.internal:6443/api/v1/namespaces/default/pods?limit=500
I0813 14:53:24.486952 46420 round_trippers.go:469] Request Headers:
I0813 14:53:24.486960 46420 round_trippers.go:473] Accept: application/json;as=Table;v=v1;g=meta.k8s.io,application/json;as=Table;v=v1beta1;g=meta.k8s.io,application/json
I0813 14:53:24.486968 46420 round_trippers.go:473] User-Agent: kubectl/v1.24.2 (darwin/arm64) kubernetes/f66044f
I0813 14:53:24.504753 46420 round_trippers.go:574] Response Status: 200 OK in 17 milliseconds
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 37m
As above, you can see that the "kubectl get pod" command is the same as calling the "/api/v1/namespaces/default/pods" path of the kube API server of the current k8s cluster using the GET method.
This time, let’s check the response value received from the kube API server. Below is the command and its output that search the pod list in the current namespace and output it in json format.
➜ [~] kubectl get pod -o json
{
"apiVersion": "v1",
"items": [
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"creationTimestamp": "2023-08-13T05:15:57Z",
"labels": {
"run": "nginx"
},
"name": "nginx",
"namespace": "default",
"resourceVersion": "61398",
"uid": "8144c120-b73c-4786-9137-df0b938f034c"
},
"spec": {
"containers": [
{
"image": "nginx",
"imagePullPolicy": "Always",
"name": "nginx",
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
"name": "kube-api-access-lh6rs",
"readOnly": true
}
]
}
],
"dnsPolicy": "ClusterFirst",
"enableServiceLinks": true,
"nodeName": "docker-desktop",
"preemptionPolicy": "PreemptLowerPriority",
"priority": 0,
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"serviceAccount": "default",
"serviceAccountName": "default",
"terminationGracePeriodSeconds": 30,
"tolerations": [
{
"effect": "NoExecute",
"key": "node.kubernetes.io/not-ready",
"operator": "Exists",
"tolerationSeconds": 300
},
{
"effect": "NoExecute",
"key": "node.kubernetes.io/unreachable",
"operator": "Exists",
"tolerationSeconds": 300
}
],
"volumes": [
{
"name": "kube-api-access-lh6rs",
"projected": {
"defaultMode": 420,
"sources": [
{
"serviceAccountToken": {
"expirationSeconds": 3607,
"path": "token"
}
},
{
"configMap": {
"items": [
{
"key": "ca.crt",
"path": "ca.crt"
}
],
"name": "kube-root-ca.crt"
}
},
{
"downwardAPI": {
"items": [
{
"fieldRef": {
"apiVersion": "v1",
"fieldPath": "metadata.namespace"
},
"path": "namespace"
}
]
}
}
]
}
}
]
},
"status": {
"conditions": [
{
"lastProbeTime": null,
"lastTransitionTime": "2023-08-13T05:15:57Z",
"status": "True",
"type": "Initialized"
},
{
"lastProbeTime": null,
"lastTransitionTime": "2023-08-13T05:16:05Z",
"status": "True",
"type": "Ready"
},
{
"lastProbeTime": null,
"lastTransitionTime": "2023-08-13T05:16:05Z",
"status": "True",
"type": "ContainersReady"
},
{
"lastProbeTime": null,
"lastTransitionTime": "2023-08-13T05:15:57Z",
"status": "True",
"type": "PodScheduled"
}
],
"containerStatuses": [
{
"containerID": "docker://61b89cc6c5899a16b490686a2c86469aaddb1288240ed95b4d4eb64a939fdc78",
"image": "nginx:latest",
"imageID": "docker-pullable://nginx@sha256:67f9a4f10d147a6e04629340e6493c9703300ca23a2f7f3aa56fe615d75d31ca",
"lastState": {},
"name": "nginx",
"ready": true,
"restartCount": 0,
"started": true,
"state": {
"running": {
"startedAt": "2023-08-13T05:16:04Z"
}
}
}
],
"hostIP": "192.168.65.4",
"phase": "Running",
"podIP": "10.1.0.100",
"podIPs": [
{
"ip": "10.1.0.100"
}
],
"qosClass": "BestEffort",
"startTime": "2023-08-13T05:15:57Z"
}
}
],
"kind": "List",
"metadata": {
"resourceVersion": ""
}
}
And let's call the "/api/v1/namespaces/default/pods" path the same way by adding the "--raw" option to call the raw URI of the kube API server. Below are the commands and output.
➜ [~] kubectl get --raw /api/v1/namespaces/default/pods | jq
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "64656"
},
"items": [
{
"metadata": {
"name": "nginx",
"namespace": "default",
"uid": "8144c120-b73c-4786-9137-df0b938f034c",
"resourceVersion": "61398",
"creationTimestamp": "2023-08-13T05:15:57Z",
"labels": {
"run": "nginx"
},
"managedFields": [
{
"manager": "kubectl-run",
"operation": "Update",
"apiVersion": "v1",
"time": "2023-08-13T05:15:57Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:labels": {
".": {},
"f:run": {}
}
},
"f:spec": {
"f:containers": {
"k:{\"name\":\"nginx\"}": {
".": {},
"f:image": {},
"f:imagePullPolicy": {},
"f:name": {},
"f:resources": {},
"f:terminationMessagePath": {},
"f:terminationMessagePolicy": {}
}
},
"f:dnsPolicy": {},
"f:enableServiceLinks": {},
"f:restartPolicy": {},
"f:schedulerName": {},
"f:securityContext": {},
"f:terminationGracePeriodSeconds": {}
}
}
},
{
"manager": "kubelet",
"operation": "Update",
"apiVersion": "v1",
"time": "2023-08-13T05:16:05Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:status": {
"f:conditions": {
"k:{\"type\":\"ContainersReady\"}": {
".": {},
"f:lastProbeTime": {},
"f:lastTransitionTime": {},
"f:status": {},
"f:type": {}
},
"k:{\"type\":\"Initialized\"}": {
".": {},
"f:lastProbeTime": {},
"f:lastTransitionTime": {},
"f:status": {},
"f:type": {}
},
"k:{\"type\":\"Ready\"}": {
".": {},
"f:lastProbeTime": {},
"f:lastTransitionTime": {},
"f:status": {},
"f:type": {}
}
},
"f:containerStatuses": {},
"f:hostIP": {},
"f:phase": {},
"f:podIP": {},
"f:podIPs": {
".": {},
"k:{\"ip\":\"10.1.0.100\"}": {
".": {},
"f:ip": {}
}
},
"f:startTime": {}
}
},
"subresource": "status"
}
]
},
"spec": {
"volumes": [
{
"name": "kube-api-access-lh6rs",
"projected": {
"sources": [
{
"serviceAccountToken": {
"expirationSeconds": 3607,
"path": "token"
}
},
{
"configMap": {
"name": "kube-root-ca.crt",
"items": [
{
"key": "ca.crt",
"path": "ca.crt"
}
]
}
},
{
"downwardAPI": {
"items": [
{
"path": "namespace",
"fieldRef": {
"apiVersion": "v1",
"fieldPath": "metadata.namespace"
}
}
]
}
}
],
"defaultMode": 420
}
}
],
"containers": [
{
"name": "nginx",
"image": "nginx",
"resources": {},
"volumeMounts": [
{
"name": "kube-api-access-lh6rs",
"readOnly": true,
"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
}
],
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "Always"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"serviceAccountName": "default",
"serviceAccount": "default",
"nodeName": "docker-desktop",
"securityContext": {},
"schedulerName": "default-scheduler",
"tolerations": [
{
"key": "node.kubernetes.io/not-ready",
"operator": "Exists",
"effect": "NoExecute",
"tolerationSeconds": 300
},
{
"key": "node.kubernetes.io/unreachable",
"operator": "Exists",
"effect": "NoExecute",
"tolerationSeconds": 300
}
],
"priority": 0,
"enableServiceLinks": true,
"preemptionPolicy": "PreemptLowerPriority"
},
"status": {
"phase": "Running",
"conditions": [
{
"type": "Initialized",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2023-08-13T05:15:57Z"
},
{
"type": "Ready",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2023-08-13T05:16:05Z"
},
{
"type": "ContainersReady",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2023-08-13T05:16:05Z"
},
{
"type": "PodScheduled",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2023-08-13T05:15:57Z"
}
],
"hostIP": "192.168.65.4",
"podIP": "10.1.0.100",
"podIPs": [
{
"ip": "10.1.0.100"
}
],
"startTime": "2023-08-13T05:15:57Z",
"containerStatuses": [
{
"name": "nginx",
"state": {
"running": {
"startedAt": "2023-08-13T05:16:04Z"
}
},
"lastState": {},
"ready": true,
"restartCount": 0,
"image": "nginx:latest",
"imageID": "docker-pullable://nginx@sha256:67f9a4f10d147a6e04629340e6493c9703300ca23a2f7f3aa56fe615d75d31ca",
"containerID": "docker://61b89cc6c5899a16b490686a2c86469aaddb1288240ed95b4d4eb64a939fdc78",
"started": true
}
],
"qosClass": "BestEffort"
}
}
]
}
As shown above, there is a slight difference between the two commands (whether the kind is List or PodList), but you can see that they are ultimately commands with the same content.
3. Access kube API server with curl command
This time, let’s make the same request to the kube API server using the curl command rather than the kubectl command. Check the address of the kube API server to be called using the "kubectl config view" command to view the settings of the kubeconfig file. In the test environment, you can see that the address of the kube API server is "https://kubernetes.docker.internal:6443".
➜ [~] kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://kubernetes.docker.internal:6443
name: docker-desktop
contexts:
- context:
cluster: docker-desktop
user: docker-desktop
name: docker-desktop
current-context: docker-desktop
kind: Config
preferences: {}
users:
- name: docker-desktop
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
Let's call the API using the curl command using the address of the confirmed kube API server. Below is the command and its output.
➜ [~] curl -k -X GET https://kubernetes.docker.internal:6443/api/v1/namespaces/default/pods (docker-desktop/default)
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "pods is forbidden: User \"system:anonymous\" cannot list resource \"pods\" in API group \"\" in the namespace \"default\"",
"reason": "Forbidden",
"details": {
"kind": "pods"
},
"code": 403
}%
A 403 authorization error occurs because there is no "Authorization" header for authentication in the request header. This time, include the authentication token in the request and call it as follows. Below is the command and its output.
➜ [~] TOKEN=$(kubectl create token default)
curl -k --header "Authorization: Bearer ${TOKEN}" -X GET https://kubernetes.docker.internal:6443/api/v1/namespaces/default/pods
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "pods is forbidden: User \"system:serviceaccount:default:default\" cannot list resource \"pods\" in API group \"\" in the namespace \"default\"",
"reason": "Forbidden",
"details": {
"kind": "pods"
},
"code": 403
}%
The message is a little different this time, but a 403 permission error still occurs. This is because the call was made with the token of the default service account in the default namespace, but the service account does not have permission to view the pod list.
This time, grant cluster-admin privileges to the default service account in the default namespace. (The command below grants administrator privileges for the K8s cluster to the default service account, so after testing, be sure to use the "kubectl delete clusterrolebinding testrolebinding" command. It is recommended to delete the binding.)
kubectl create clusterrolebinding testrolebinding --clusterrole cluster-admin --serviceaccount=default:default
Now, try calling the kube API server again with the token of the default service account. Below is the curl command and its output.
➜ [~] TOKEN=$(kubectl create token default)
curl -k --header "Authorization: Bearer ${TOKEN}" -X GET https://kubernetes.docker.internal:6443/api/v1/namespaces/default/pods
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "65454"
},
"items": [
{
"metadata": {
"name": "nginx",
"namespace": "default",
"uid": "8144c120-b73c-4786-9137-df0b938f034c",
"resourceVersion": "61398",
"creationTimestamp": "2023-08-13T05:15:57Z",
"labels": {
"run": "nginx"
},
"managedFields": [
{
"manager": "kubectl-run",
"operation": "Update",
"apiVersion": "v1",
"time": "2023-08-13T05:15:57Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:labels": {
".": {},
"f:run": {}
}
},
"f:spec": {
"f:containers": {
"k:{\"name\":\"nginx\"}": {
".": {},
"f:image": {},
"f:imagePullPolicy": {},
"f:name": {},
"f:resources": {},
"f:terminationMessagePath": {},
"f:terminationMessagePolicy": {}
}
},
"f:dnsPolicy": {},
"f:enableServiceLinks": {},
"f:restartPolicy": {},
"f:schedulerName": {},
"f:securityContext": {},
"f:terminationGracePeriodSeconds": {}
}
}
},
{
"manager": "kubelet",
"operation": "Update",
"apiVersion": "v1",
"time": "2023-08-13T05:16:05Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:status": {
"f:conditions": {
"k:{\"type\":\"ContainersReady\"}": {
".": {},
"f:lastProbeTime": {},
"f:lastTransitionTime": {},
"f:status": {},
"f:type": {}
},
"k:{\"type\":\"Initialized\"}": {
".": {},
"f:lastProbeTime": {},
"f:lastTransitionTime": {},
"f:status": {},
"f:type": {}
},
"k:{\"type\":\"Ready\"}": {
".": {},
"f:lastProbeTime": {},
"f:lastTransitionTime": {},
"f:status": {},
"f:type": {}
}
},
"f:containerStatuses": {},
"f:hostIP": {},
"f:phase": {},
"f:podIP": {},
"f:podIPs": {
".": {},
"k:{\"ip\":\"10.1.0.100\"}": {
".": {},
"f:ip": {}
}
},
"f:startTime": {}
}
},
"subresource": "status"
}
]
},
"spec": {
"volumes": [
{
"name": "kube-api-access-lh6rs",
"projected": {
"sources": [
{
"serviceAccountToken": {
"expirationSeconds": 3607,
"path": "token"
}
},
{
"configMap": {
"name": "kube-root-ca.crt",
"items": [
{
"key": "ca.crt",
"path": "ca.crt"
}
]
}
},
{
"downwardAPI": {
"items": [
{
"path": "namespace",
"fieldRef": {
"apiVersion": "v1",
"fieldPath": "metadata.namespace"
}
}
]
}
}
],
"defaultMode": 420
}
}
],
"containers": [
{
"name": "nginx",
"image": "nginx",
"resources": {},
"volumeMounts": [
{
"name": "kube-api-access-lh6rs",
"readOnly": true,
"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
}
],
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "Always"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"serviceAccountName": "default",
"serviceAccount": "default",
"nodeName": "docker-desktop",
"securityContext": {},
"schedulerName": "default-scheduler",
"tolerations": [
{
"key": "node.kubernetes.io/not-ready",
"operator": "Exists",
"effect": "NoExecute",
"tolerationSeconds": 300
},
{
"key": "node.kubernetes.io/unreachable",
"operator": "Exists",
"effect": "NoExecute",
"tolerationSeconds": 300
}
],
"priority": 0,
"enableServiceLinks": true,
"preemptionPolicy": "PreemptLowerPriority"
},
"status": {
"phase": "Running",
"conditions": [
{
"type": "Initialized",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2023-08-13T05:15:57Z"
},
{
"type": "Ready",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2023-08-13T05:16:05Z"
},
{
"type": "ContainersReady",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2023-08-13T05:16:05Z"
},
{
"type": "PodScheduled",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2023-08-13T05:15:57Z"
}
],
"hostIP": "192.168.65.4",
"podIP": "10.1.0.100",
"podIPs": [
{
"ip": "10.1.0.100"
}
],
"startTime": "2023-08-13T05:15:57Z",
"containerStatuses": [
{
"name": "nginx",
"state": {
"running": {
"startedAt": "2023-08-13T05:16:04Z"
}
},
"lastState": {},
"ready": true,
"restartCount": 0,
"image": "nginx:latest",
"imageID": "docker-pullable://nginx@sha256:67f9a4f10d147a6e04629340e6493c9703300ca23a2f7f3aa56fe615d75d31ca",
"containerID": "docker://61b89cc6c5899a16b490686a2c86469aaddb1288240ed95b4d4eb64a939fdc78",
"started": true
}
],
"qosClass": "BestEffort"
}
}
]
}%
This time the response came normally. You can see that the contents of the response body are the same as the output of the "kubectl get --raw /api/v1/namespaces/default/pods" command above.
Although it has been described at length up to this point, the conclusion is that although there is a difference in the method whether you use the kubectl command, curl command, or any other method, ultimately interacting with k8s is through API communication with the kube API server.
4. kubernetes python client
In the same context as above, it is also possible to access k8s through python and control resources inside the cluster. To access k8s with python, of course, you can also call it through the requests library in the same way as interacting with the curl command above, but you can interact with k8s more easily by using the kubernetes python client library (hereinafter referred to as k8s python client).
First, install the k8s python client library using the pip command.
pip install kubernetes
Then, write a sample code that searches the list of pods in the default namespace as shown below. (In the sample code, the load_kube_config() function is used to read the system's default kubeconfig file (generally ~/.kube/config path) to obtain context information. Bring it.)
from kubernetes import client, config, dynamic
from kubernetes.client import api_client
import traceback
try:
client = dynamic.DynamicClient(
api_client.ApiClient(configuration=config.load_kube_config())
)
api_version = "v1"
kind = "Pod"
namespace = "default"
api = client.resources.get(api_version=api_version, kind=kind)
print(api.get(resource_version=0, namespace=namespace).items)
except:
print("Error during list pod", flush=True)
traceback.print_exc()
When running the sample script above, the output is displayed as follows.
➜ [~] python k8s_list_pod.py
[{'apiVersion': 'v1',
'kind': 'Pod',
'metadata': {'creationTimestamp': '2023-08-13T05:15:57Z',
'labels': {'run': 'nginx'},
'managedFields': [{'apiVersion': 'v1',
'fieldsType': 'FieldsV1',
'fieldsV1': {'f:metadata': {'f:labels': {'.': {}, 'f:run': {}}},
'f:spec': {'f:containers': {'k:{"name":"nginx"}': {'.': {},
'f:image': {},
'f:imagePullPolicy': {},
'f:name': {},
'f:resources': {},
'f:terminationMessagePath': {},
'f:terminationMessagePolicy': {}}},
'f:dnsPolicy': {},
'f:enableServiceLinks': {},
'f:restartPolicy': {},
'f:schedulerName': {},
'f:securityContext': {},
'f:terminationGracePeriodSeconds': {}}},
'manager': 'kubectl-run',
'operation': 'Update',
'time': '2023-08-13T05:15:57Z'},
{'apiVersion': 'v1',
'fieldsType': 'FieldsV1',
'fieldsV1': {'f:status': {'f:conditions': {'k:{"type":"ContainersReady"}': {'.': {},
'f:lastProbeTime': {},
'f:lastTransitionTime': {},
'f:status': {},
'f:type': {}},
'k:{"type":"Initialized"}': {'.': {},
'f:lastProbeTime': {},
'f:lastTransitionTime': {},
'f:status': {},
'f:type': {}},
'k:{"type":"Ready"}': {'.': {},
'f:lastProbeTime': {},
'f:lastTransitionTime': {},
'f:status': {},
'f:type': {}}},
'f:containerStatuses': {},
'f:hostIP': {},
'f:phase': {},
'f:podIP': {},
'f:podIPs': {'.': {}, 'k:{"ip":"10.1.0.100"}': {'.': {}, 'f:ip': {}}},
'f:startTime': {}}},
'manager': 'kubelet',
'operation': 'Update',
'subresource': 'status',
'time': '2023-08-13T05:16:05Z'}],
'name': 'nginx',
'namespace': 'default',
'resourceVersion': '61398',
'uid': '8144c120-b73c-4786-9137-df0b938f034c'},
'spec': {'containers': [{'image': 'nginx',
'imagePullPolicy': 'Always',
'name': 'nginx',
'resources': {},
'terminationMessagePath': '/dev/termination-log',
'terminationMessagePolicy': 'File',
'volumeMounts': [{'mountPath': '/var/run/secrets/kubernetes.io/serviceaccount',
'name': 'kube-api-access-lh6rs',
'readOnly': True}]}],
'dnsPolicy': 'ClusterFirst',
'enableServiceLinks': True,
'nodeName': 'docker-desktop',
'preemptionPolicy': 'PreemptLowerPriority',
'priority': 0,
'restartPolicy': 'Always',
'schedulerName': 'default-scheduler',
'securityContext': {},
'serviceAccount': 'default',
'serviceAccountName': 'default',
'terminationGracePeriodSeconds': 30,
'tolerations': [{'effect': 'NoExecute',
'key': 'node.kubernetes.io/not-ready',
'operator': 'Exists',
'tolerationSeconds': 300},
{'effect': 'NoExecute',
'key': 'node.kubernetes.io/unreachable',
'operator': 'Exists',
'tolerationSeconds': 300}],
'volumes': [{'name': 'kube-api-access-lh6rs',
'projected': {'defaultMode': 420,
'sources': [{'serviceAccountToken': {'expirationSeconds': 3607, 'path': 'token'}},
{'configMap': {'items': [{'key': 'ca.crt', 'path': 'ca.crt'}], 'name': 'kube-root-ca.crt'}},
{'downwardAPI': {'items': [{'fieldRef': {'apiVersion': 'v1', 'fieldPath': 'metadata.namespace'},
'path': 'namespace'}]}}]}}]},
'status': {'conditions': [{'lastProbeTime': None,
'lastTransitionTime': '2023-08-13T05:15:57Z',
'status': 'True',
'type': 'Initialized'},
{'lastProbeTime': None,
'lastTransitionTime': '2023-08-13T05:16:05Z',
'status': 'True',
'type': 'Ready'},
{'lastProbeTime': None,
'lastTransitionTime': '2023-08-13T05:16:05Z',
'status': 'True',
'type': 'ContainersReady'},
{'lastProbeTime': None,
'lastTransitionTime': '2023-08-13T05:15:57Z',
'status': 'True',
'type': 'PodScheduled'}],
'containerStatuses': [{'containerID': 'docker://61b89cc6c5899a16b490686a2c86469aaddb1288240ed95b4d4eb64a939fdc78',
'image': 'nginx:latest',
'imageID': 'docker-pullable://nginx@sha256:67f9a4f10d147a6e04629340e6493c9703300ca23a2f7f3aa56fe615d75d31ca',
'lastState': {},
'name': 'nginx',
'ready': True,
'restartCount': 0,
'started': True,
'state': {'running': {'startedAt': '2023-08-13T05:16:04Z'}}}],
'hostIP': '192.168.65.4',
'phase': 'Running',
'podIP': '10.1.0.100',
'podIPs': [{'ip': '10.1.0.100'}],
'qosClass': 'BestEffort',
'startTime': '2023-08-13T05:15:57Z'}}]
This time, let's create a pod named nginx-test that runs the nginx:latest image in the default namespace.
from kubernetes import client, config, dynamic
from kubernetes.client import api_client
import traceback
import json
try:
client = dynamic.DynamicClient(
api_client.ApiClient(configuration=config.load_kube_config())
)
manifest = '{"kind":"Pod","apiVersion":"v1","metadata":{"name":"nginx-test","creationTimestamp":null,"labels":{"run":"nginx"}},"spec":{"containers":[{"name":"nginx-test","image":"nginx:latest","resources":{}}],"restartPolicy":"Always","dnsPolicy":"ClusterFirst"},"status":{}}'
api_version = "v1"
kind = "Pod"
namespace = "default"
resource_manifest_dict = json.loads(manifest)
api = client.resources.get(api_version=api_version, kind=kind)
print(api.create(body=resource_manifest_dict, namespace=namespace))
except:
print("Error during create pod", flush=True)
traceback.print_exc()
When running the sample script above, the output is displayed as follows. And check the created pod.
➜ [~] python k8s_create_pod.py
ResourceInstance[Pod]:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: '2023-08-13T07:30:38Z'
labels:
run: nginx
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:labels:
.: {}
f:run: {}
f:spec:
f:containers:
k:{"name":"nginx-test"}:
.: {}
f:image: {}
f:imagePullPolicy: {}
f:name: {}
f:resources: {}
f:terminationMessagePath: {}
f:terminationMessagePolicy: {}
f:dnsPolicy: {}
f:enableServiceLinks: {}
f:restartPolicy: {}
f:schedulerName: {}
f:securityContext: {}
f:terminationGracePeriodSeconds: {}
manager: OpenAPI-Generator
operation: Update
time: '2023-08-13T07:30:38Z'
name: nginx-test
namespace: default
resourceVersion: '71336'
uid: d42a3181-6191-476a-bb94-0cacaab67f65
spec:
containers:
- image: nginx:latest
imagePullPolicy: Always
name: nginx-test
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-xsp2s
readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
preemptionPolicy: PreemptLowerPriority
priority: 0
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
volumes:
- name: kube-api-access-xsp2s
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
status:
phase: Pending
qosClass: BestEffort
➜ [~] kubectl get pod nginx-test
NAME READY STATUS RESTARTS AGE
nginx-test 1/1 Running 0 17s
Similar to the above, operations such as get, create, delete, and patch can be performed, so refer to the k8s python client github for more details.
5. In conclusion
Through simple samples, we took time to reflect and understand how users interact with the kube API server. Additionally, if you apply the k8s python client library well, you can create your own application that manages the k8s cluster according to user-specific usage patterns using functions that are not included in the management functions provided by existing k8s and various tools.