Let a Kubernetes Container create another Container in the cluster
For one of the projects I am working on I have the requirement to let a container within a pod create another container in a new pod in a separate namespace. This to allow new abstraction layers to be created.
Naive Approach
At first it seemed straightforward, just create a piece of code that simply lists the pods running on the cluster (to demonstrate Kubernetes API access) through the Kubernetes
package in Python:
import kubernetes.client as k8s_client
import kubernetes.config as k8s_config
kube_api_v1 = k8s_client.CoreV1Api()
print("Listing pods with their IPs:")
ret = self.kube_api_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))
This will work locally! But this is obviously not being executed on the cluster. When we now run this on the cluster itself from inside a container in a pod, we will get:
urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', port=80): Max retries exceeded with url: /api/v1/pods?watch=False (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f4584536070>: Failed to establish a new connection: [Errno 111] Connection refused'))
Which makes sense as:
- A container is isolated on network level
- A container has specific rights within the cluster, preventing takeovers
Service Accounts
Now luckily for us, we can do this through Service Accounts! Service Accounts allow roles with permissions to be used that can perform certain actions within a namespace.
Now what if we are in namespace A and want to perform actions in namespace B? Well for that we allocate 2 roles within the 2 namespaces and create a rolebinding in namespace B that binds the service account of namespace A.
Creating the namespaces
kubectl create namespace poc-a
kubectl create namespace poc-b
Creating the service accounts
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: poc-a
namespace: poc-a
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: poc-b
namespace: poc-b
EOF
Creating the roles
cat <<EOF | kubectl apply -f -
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: poc-a-role
namespace: poc-a
rules:
- apiGroups: ["", "extensions", "apps"]
resources: ["pods", "services", "deployments", "replicasets"]
verbs: ["get", "watch", "list", "create", "update", "delete"]
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: pob-b-role
namespace: poc-b
rules:
- apiGroups: ["", "extensions", "apps"]
resources: ["pods", "services", "deployments", "replicasets"]
verbs: ["get", "watch", "list", "create", "update", "delete"]
EOF
Create the default role bindings
cat <<EOF | kubectl apply -f -
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: poc-a-rolebinding
namespace: poc-a
subjects:
- kind: ServiceAccount
name: poc-a
roleRef:
kind: Role
name: poc-a-role
apiGroup: rbac.authorization.k8s.io
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: poc-b-rolebinding
namespace: poc-b
subjects:
- kind: ServiceAccount
name: poc-b
roleRef:
kind: Role
name: poc-b-role
apiGroup: rbac.authorization.k8s.io
EOF
Allow SA (ns: poc-a) access to ns: poc-b
cat <<EOF | kubectl apply -f -
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: poc-a-sa-access-to-poc-b-rolebinding
namespace: poc-b
subjects:
- kind: ServiceAccount
name: poc-a
namespace: poc-a
roleRef:
kind: Role
name: poc-b-role
apiGroup: rbac.authorization.k8s.io
EOF
Testing through simple Python Script
To test this now we can create a simple Python script that lists the containers in its current namespace and in the other namespace:
k8s_config.load_incluster_config()
kube_api_v1 = k8s_client.CoreV1Api()
# Get the current namespace (poc-a)
namespace = open(
'/var/run/secrets/kubernetes.io/serviceaccount/namespace'
).read()
print("Current namespace: " + namespace)
print(f"========= Pods in NS: {namespace} =========")
pods = self.kube_api_v1.list_namespaced_pod(namespace=namespace)
for pod in pods.items:
print("- ", pod.metadata.name)
print(f"========= Pods in NS: poc-b =========")
pods = self.kube_api_v1.list_namespaced_pod(namespace="poc-b")
for pod in pods.items:
print("- ", pod.metadata.name)
Which will print out the pods in both namespaces, showing that the container is able to access the service account poc-a
and use the rolebinding to access poc-b
its namespace!
Conclusion
While first it might seem daunting to use Service Accounts, Kubernetes is actually a super sweet platform with a lot of customization options. By this option, we are able to - securely - manage what containers can do in their namespaces and how we can interact between namespaces, isolating our software where needed!