How to Use Sockets from Kubernetes Port Forwarding in Python

Viewed 724

Giving the port forward object

from kubernetes.stream import stream, portforward


    pf = portforward(
         k8s_client_v1.connect_get_namespaced_pod_portforward,
         pod_name,
         name_space,
         ports=port
    )
    
    pf.socket(port)

pf is a socket object from AF_UNIX family.

I need a better understating whether another AF_INET family socket has to be instantiated in oder to achieve the same functionality as kubectl client has, ex: kubectl port-forward $pod_name $port

I appreciate in advance if someone could share any snippets of this type of implementation.

At this moment creating port forward object was inspired from here

2 Answers

I am not an expert on sockets, but I managed to wrap the socket from the port forwarding with a http.client.HTTPConnection. I wanted to patch requests, but I could not make it work.

Anyway, this way the http requests can be implemented more conveniently in a high level fashion. Not sure if you were looking for that, but I was. To query pod names, ports and more you can check the api reference for kubernetes and look up the corresponding method in the python package.

from http.client import HTTPConnection
from kubernetes import client, config
from kubernetes.stream import portforward
from kubernetes.stream.ws_client import PortForward

class ForwardedKubernetesHTTPConnection(HTTPConnection):

    def __init__(self, forwarding: PortForward, port: int):
        super().__init__("127.0.0.1", port)
        self.sock = forwarding.socket(port)

    def connect(self) -> None:
        pass

    def close(self) -> None:
        pass

if __name__ == "__main__":
    config.load_kube_config()
    client_v1 = client.CoreV1Api()
    name_space = "my-namespace"  # or get from api somehow
    pod_name = "my-pod"  # or get from api somehow
    port = 80
    
    pf = portforward(
        client_v1.connect_get_namespaced_pod_portforward,
        pod_name,
        name_space,
        ports=str(port)
    )

    conn = ForwardedKubernetesHTTPConnection(pf, port)
    conn.request("GET", "/my/url")  # will fail for other methods
    resp = conn.getresponse()
    print(resp.status)
    print(resp.headers)
    print(resp.read())

    conn.request("GET", "/my/other/url")
    # ...

Thanks to your code snipped, I could get this to work for me - which means that your code basically works: I have successfully used it to send TCP data on a custom port to a kubernetes services (haven't tried UDP, though). It seems to work quite similar but not exactly the same as kubectl port-forward.

The main difference is that it doesn't actually open an AF_INET socket on your system, but an AF_UNIX socket, which means, that it basically is addressed via a file name instead of a tuple of an IP address and port number. So it really depends on what you try to achieve.

If you just want to write to a socket from a Python script that is connected to a specific port of a service: This is the solution. It doesn't really make a difference for you that the kubernetes API provides you an AF_UNIX socket.

However, if you want to open a local port that connects to the kubernetes service port (e.g. use local tools to connect to a kubernetes service like a CLI based database management tool), this is only half of what you want to achieve. After some time playing around, including trying hacks like socket.fromfd(unix_sock.fileno(), socket.AF_INET, socket.SOCK_STREAM), I came to the conclusion that a straight forward approach to "convert" the unix socket to a internet socket isn't possible.

A solution, I would suggest is to implement a simple port forwarding in Python. That would mean running a thread that opens an internet socket on the local port you'd like to use, receive data on that and simply forward it to the unix socket. It doesn't feel very elegant and probably isn't very efficient, either, but it should do the job.

Another alternative would be using an external tool like socat if available to connect the two sockets.

Related