1616Shows the functionality of portforward streaming using an nginx container.
1717"""
1818
19+ import select
1920import socket
2021import time
2122import urllib .request
2627from kubernetes .client .rest import ApiException
2728from kubernetes .stream import portforward
2829
30+ ##############################################################################
31+ # Kubernetes pod port forwarding works by directly providing a socket which
32+ # the python application uses to send and receive data on. This is in contrast
33+ # to the go client, which opens a local port that the go application then has
34+ # to open to get a socket to transmit data.
35+ #
36+ # This simplifies the python application, there is not local port to worry
37+ # about if that port number is available. Nor does the python application have
38+ # to then deal with opening this local port. The socket used to transmit data
39+ # is immediately provided to the python application.
40+ #
41+ # Below also is an example of monkey patching the socket.create_connection
42+ # function so that DNS names of the following formats will access kubernetes
43+ # ports:
44+ #
45+ # <pod-name>.<namespace>.kubernetes
46+ # <pod-name>.pod.<namespace>.kubernetes
47+ # <service-name>.svc.<namespace>.kubernetes
48+ # <service-name>.service.<namespace>.kubernetes
49+ #
50+ # These DNS name can be used to interact with pod ports using python libraries,
51+ # such as urllib.request and http.client. For example:
52+ #
53+ # response = urllib.request.urlopen(
54+ # 'https://metrics-server.service.kube-system.kubernetes/'
55+ # )
56+ #
57+ ##############################################################################
58+
2959
3060def portforward_commands (api_instance ):
3161 name = 'portforward-example'
@@ -53,8 +83,8 @@ def portforward_commands(api_instance):
5383 }]
5484 }
5585 }
56- resp = api_instance .create_namespaced_pod (body = pod_manifest ,
57- namespace = 'default' )
86+ api_instance .create_namespaced_pod (body = pod_manifest ,
87+ namespace = 'default' )
5888 while True :
5989 resp = api_instance .read_namespaced_pod (name = name ,
6090 namespace = 'default' )
@@ -63,46 +93,87 @@ def portforward_commands(api_instance):
6393 time .sleep (1 )
6494 print ("Done." )
6595
66- pf = portforward (api_instance .connect_get_namespaced_pod_portforward ,
67- name , 'default' ,
68- ports = '80,8080:80' )
69- for port in (80 , 8080 ):
70- http = pf .socket (port )
71- http .settimeout (1 )
72- http .sendall (b'GET / HTTP/1.1\r \n ' )
73- http .sendall (b'Host: 127.0.0.1\r \n ' )
74- http .sendall (b'Accept: */*\r \n ' )
75- http .sendall (b'\r \n ' )
76- response = b''
77- while True :
78- try :
79- response += http .recv (1024 )
80- except socket .timeout :
81- break
82- print (response .decode ('utf-8' ))
83- http .close ()
96+ pf = portforward (
97+ api_instance .connect_get_namespaced_pod_portforward ,
98+ name , 'default' ,
99+ ports = '80' ,
100+ )
101+ http = pf .socket (80 )
102+ http .setblocking (True )
103+ http .sendall (b'GET / HTTP/1.1\r \n ' )
104+ http .sendall (b'Host: 127.0.0.1\r \n ' )
105+ http .sendall (b'Connection: close\r \n ' )
106+ http .sendall (b'Accept: */*\r \n ' )
107+ http .sendall (b'\r \n ' )
108+ response = b''
109+ while True :
110+ select .select ([http ], [], [])
111+ data = http .recv (1024 )
112+ if not data :
113+ break
114+ response += data
115+ http .close ()
116+ print (response .decode ('utf-8' ))
117+ error = pf .error (80 )
118+ if error is None :
119+ print ("No port forward errors on port 80." )
120+ else :
121+ print ("Port 80 has the following error: %s" % error )
84122
85123 # Monkey patch socket.create_connection which is used by http.client and
86124 # urllib.request. The same can be done with urllib3.util.connection.create_connection
87125 # if the "requests" package is used.
126+ socket_create_connection = socket .create_connection
88127 def kubernetes_create_connection (address , * args , ** kwargs ):
89128 dns_name = address [0 ]
90129 if isinstance (dns_name , bytes ):
91130 dns_name = dns_name .decode ()
92- # Look for "<pod-name>.<namspace>.kubernetes" dns names and if found
93- # provide a socket that is port forwarded to the kuberntest pod.
94131 dns_name = dns_name .split ("." )
95- if len ( dns_name ) != 3 or dns_name [ 2 ] != " kubernetes" :
132+ if dns_name [ - 1 ] != ' kubernetes' :
96133 return socket_create_connection (address , * args , ** kwargs )
134+ if len (dns_name ) not in (3 , 4 ):
135+ raise RuntimeError ("Unexpected kubernetes DNS name." )
136+ namespace = dns_name [- 2 ]
137+ name = dns_name [0 ]
138+ port = address [1 ]
139+ if len (dns_name ) == 4 :
140+ if dns_name [1 ] in ('svc' , 'service' ):
141+ service = api_instance .read_namespaced_service (name , namespace )
142+ for service_port in service .spec .ports :
143+ if service_port .port == port :
144+ port = service_port .target_port
145+ break
146+ else :
147+ raise RuntimeError ("Unable to find service port: %s" % port )
148+ label_selector = []
149+ for key , value in service .spec .selector .items ():
150+ label_selector .append ("%s=%s" % (key , value ))
151+ pods = api_instance .list_namespaced_pod (
152+ namespace , label_selector = "," .join (label_selector )
153+ )
154+ if not pods .items :
155+ raise RuntimeError ("Unable to find service pods." )
156+ name = pods .items [0 ].metadata .name
157+ if isinstance (port , str ):
158+ for container in pods .items [0 ].spec .containers :
159+ for container_port in container .ports :
160+ if container_port .name == port :
161+ port = container_port .container_port
162+ break
163+ else :
164+ continue
165+ break
166+ else :
167+ raise RuntimeError ("Unable to find service port name: %s" % port )
168+ elif dns_name [1 ] != 'pod' :
169+ raise RuntimeError ("Unsupported resource type: %s" % dns_name [1 ])
97170 pf = portforward (api_instance .connect_get_namespaced_pod_portforward ,
98- dns_name [0 ], dns_name [1 ], ports = str (address [1 ]))
99- return pf .socket (address [1 ])
100-
101- socket_create_connection = socket .create_connection
171+ name , namespace , ports = str (port ))
172+ return pf .socket (port )
102173 socket .create_connection = kubernetes_create_connection
103174
104- # Access the nginx http server using the "<pod-name>.<namespace>.kubernetes" dns name.
105- response = urllib .request .urlopen ('http://%s.default.kubernetes' % name )
175+ # Access the nginx http server using the "<pod-name>.pod. <namespace>.kubernetes" dns name.
176+ response = urllib .request .urlopen ('http://%s.pod. default.kubernetes' % name )
106177 html = response .read ().decode ('utf-8' )
107178 response .close ()
108179 print ('Status:' , response .status )
@@ -111,9 +182,9 @@ def kubernetes_create_connection(address, *args, **kwargs):
111182
112183def main ():
113184 config .load_kube_config ()
114- c = Configuration ()
185+ c = Configuration . get_default_copy ()
115186 c .assert_hostname = False
116- # Configuration.set_default(c)
187+ Configuration .set_default (c )
117188 core_v1 = core_v1_api .CoreV1Api ()
118189
119190 portforward_commands (core_v1 )
0 commit comments