Modern applications are transformed to microservice based, API driven architecture. When the application is containerized, it needs to access other applications’ published APIs and K8s cluster API for its business logic.
In this article, an python application is containerized, its container image is built and pushed into docker register, an kubernetes manifest is created and deployed into K8s cluster and the containerized python application will access K8s cluster APIs to get cluster’s deployed pods’ configuration information. Each application running within containers of pods needs to be authenticated and authorized by K8s API server before each API access. When the application is containerized and deployed into Kubernetes cluster, each pod belongs to a specific namespace with its according service account (SA). Each service account has its own authentication secret, which can be used to authenticate with K8s API server. Role Based Access Control (RBAC) can then be used to control the authorization of associated service accounts. This service account secret is mounted to a pre-configured path, which can be accessed by the application running within the pod. The Kubernetes python package hides all those details from user python application code to make all those complicated processes easier to achieve.
Step One: Build the container image for the python application
Create a Dockerfile with following content :
controlplane $ cat Dockerfile
FROM ubuntu
RUN apt-get update
FROM python:latest
RUN mkdir /src
COPY . /src
WORKDIR /src
RUN pip install -r requirements.txt
controlplane $ cat requirements.txt
Kubernetes
Create following python program in the same directory of above Dockerfile based on this example:
from kubernetes import client, config
def main():
config.load_incluster_config()
v1 = client.CoreV1Api()
print("Listing pods with their IPs:")
ret = v1.list_pod_for_all_namespaces(watch=False)
for i in ret.items:
print("%s\t%s\t%s" %
(i.status.pod_ip, i.metadata.namespace, i.metadata.name))
if __name__ == '__main__':
main()
Then, build the docker image and push it to the docker registry:
controlplane $ docker build -t podapiaccess:v1 .
controlplane $ docker tag podapiaccess:v1 twintiger/podapiaccess:v1
controlplane $ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: <Your Docker Registry User Name>
Password: <Your according password>
Login Succeeded
controlplane $ docker push twintiger/podapiaccess:v1
The push refers to repository [docker.io/twintiger/podapiaccess]
084f4d130a72: Pushed
d32ebda4ee0b: Pushed
db8d0fe6cf95: Mounted from library/python
00901a4c6fc7: Mounted from library/python
7e7decd61f68: Mounted from library/python
aedcb370b058: Mounted from library/python
c3a0d593ed24: Mounted from library/python
26a504e63be4: Mounted from library/python
8bf42db0de72: Mounted from library/python
31892cc314cb: Mounted from library/python
11936051f93b: Mounted from library/python
v1: digest: sha256:795b7425e878f4478147a6c136eba6994fc0113cf8c7537fbe42954fb1257582 size: 2631
controlplane $
Step Two: Test the newly built docker image
Test the above docker image by following steps:
controlplane $ docker run twintiger/podapiaccess:v1
Unable to find image 'twintiger/podapiaccess:v1' locally
v1: Pulling from twintiger/podapiaccess
0e29546d541c: Pull complete
9b829c73b52b: Pull complete
cb5b7ae36172: Pull complete
6494e4811622: Pull complete
6f9f74896dfa: Pull complete
fcb6d5f7c986: Pull complete
290438add9da: Pull complete
ab11df61f44a: Pull complete
de4793a5fa46: Pull complete
b7c69d9f5717: Pull complete
4d8c3388b53a: Pull complete
ceea8c40c3ea: Pull complete
Digest: sha256:5c800bd260a1cea55d71c4454166c49597c8f35d3520eadf7e82e8643f198cbf
Status: Downloaded newer image for twintiger/podapiaccess:v1
Traceback (most recent call last):
File "/src/podapiaccess.py", line 16, in <module>
main()
File "/src/podapiaccess.py", line 5, in main
config.load_incluster_config()
File "/usr/local/lib/python3.10/site-packages/kubernetes/config/incluster_config.py", line 121, in load_incluster_config
try_refresh_token=try_refresh_token).load_and_set(client_configuration)
File "/usr/local/lib/python3.10/site-packages/kubernetes/config/incluster_config.py", line 54, in load_and_set
self._load_config()
File "/usr/local/lib/python3.10/site-packages/kubernetes/config/incluster_config.py", line 62, in _load_config
raise ConfigException("Service host/port is not set.")
kubernetes.config.config_exception.ConfigException: Service host/port is not set.
controlplane $
Based on the above testing, the python code failed. Why ? The authentication with K8s API server failed. There is no corresponding authentication secret which is needed for the python application code to be authenticated with K8s API server.
This piece of python container code must be deployed into Kubernetes cluster, running from pod to make authentication process success.
Step Three: Deploy the containerized python application inside Kubernetes cluster
Create following Kubernetes deployment manifest:
controlplane $ cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-k8s-api
spec:
selector:
matchLabels:
app: hello-k8s-api
replicas: 1
template:
metadata:
labels:
app: hello-k8s-api
spec:
containers:
- name: hello-api
image: twintiger/podapiaccess:v1
command: ["/bin/sleep", "3650d"]
imagePullPolicy: IfNotPresent
restartPolicy: Always
Deploy above manifest and verify its status by following procedures:
controlplane $ kubectl apply -f deployment.yaml
deployment.apps/hello-k8s-api created
controlplane $ kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-k8s-api-5769fb66fb-gtb2f 1/1 Running 0 29s
Test the python Kubernetes API access code by:
controlplane $ kubectl exec -it hello-k8s-api-5d5699b6b-b4v45 -- bash
root@hello-k8s-api-5d5699b6b-b4v45:/src# pwd
/src
root@hello-k8s-api-5d5699b6b-b4v45:/src# ls
Dockerfile requirements.txt
podapiaccess.py
root@hello-k8s-api-5d5699b6b-b4v45:/src# python podapiaccess.py
Listing pods with their IPs:
10.32.0.193 default hello-k8s-api-5d5699b6b-b4v45
10.88.0.2 kube-system coredns-fb8b8dccf-fmrd8
10.88.0.3 kube-system coredns-fb8b8dccf-tzv4p
172.17.0.24 kube-system etcd-controlplane
10.88.0.4 kube-system katacoda-cloud-provider-776f5d7cb-g6r57
172.17.0.24 kube-system kube-apiserver-controlplane
172.17.0.24 kube-system kube-controller-manager-controlplane
172.17.0.26 kube-system kube-keepalived-vip-r5xxf
172.17.0.26 kube-system kube-proxy-5qdzn
172.17.0.24 kube-system kube-proxy-wnzl6
172.17.0.24 kube-system kube-scheduler-controlplane
172.17.0.26 kube-system weave-net-9pjqm
172.17.0.24 kube-system weave-net-wjt9x
The python K8s API access code works and the IP address of all pods are listed.
Now, let’s dig into the deployment and its according pod:
controlplane $ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
hello-k8s-api 1/1 1 1 5m44s
controlplane $ kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-k8s-api-5d5699b6b-b4v45 1/1 Running 0 5m50s
controlplane $ kubectl logs pod/hello-k8s-api-5d5699b6b-b4v45
controlplane $ kubectl describe pod/hello-k8s-api-5d5699b6b-b4v45
Name: hello-k8s-api-5d5699b6b-b4v45
Namespace: default
Priority: 0
PriorityClassName: <none>
Node: node01/172.17.0.26
Start Time: Fri, 31 Dec 2021 19:12:46 +0000
Labels: app=hello-k8s-api
pod-template-hash=5d5699b6b
Annotations: <none>
Status: Running
IP: 10.32.0.193
Controlled By: ReplicaSet/hello-k8s-api-5d5699b6b
Containers:
hello-api:
Container ID: docker://750c20dd890cff333ccbb44de4b1be9ea59f73ffd7a9edf34474b30e6d4a8101
Image: twintiger/podapiaccess:v1
Image ID: docker-pullable://twintiger/podapiaccess@sha256:e809bd1c327941f9de5d4306c9cc9e507b84d0a3020fe8cd0c8dbe01736e058e
Port: <none>
Host Port: <none>
Command:
/bin/sleep
3650d
State: Running
Started: Fri, 31 Dec 2021 19:13:23 +0000
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-64mfb (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
default-token-64mfb:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-64mfb
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 6m17s default-scheduler Successfully assigned default/hello-k8s-api-5d5699b6b-b4v45 to node01
Normal Pulling 6m16s kubelet, node01 Pulling image "twintiger/podapiaccess:v3"
Normal Pulled 5m40s kubelet, node01 Successfully pulled image "twintiger/podapiaccess:v1"
Normal Created 5m40s kubelet, node01 Created container hello-api
Normal Started 5m40s kubelet, node01 Started container hello-api
controlplane $
Pay special attention to the following information of the pod within the deployment:
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-64mfb (ro)
This secret is the service account’s secret within the namespace, which is used by pod to authenticate with K8s API server.