16
16
Shows the functionality of portforward streaming using an nginx container.
17
17
"""
18
18
19
+ import select
19
20
import socket
20
21
import time
21
22
import urllib .request
26
27
from kubernetes .client .rest import ApiException
27
28
from kubernetes .stream import portforward
28
29
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
+
29
59
30
60
def portforward_commands (api_instance ):
31
61
name = 'portforward-example'
@@ -53,8 +83,8 @@ def portforward_commands(api_instance):
53
83
}]
54
84
}
55
85
}
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' )
58
88
while True :
59
89
resp = api_instance .read_namespaced_pod (name = name ,
60
90
namespace = 'default' )
@@ -63,46 +93,87 @@ def portforward_commands(api_instance):
63
93
time .sleep (1 )
64
94
print ("Done." )
65
95
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 )
84
122
85
123
# Monkey patch socket.create_connection which is used by http.client and
86
124
# urllib.request. The same can be done with urllib3.util.connection.create_connection
87
125
# if the "requests" package is used.
126
+ socket_create_connection = socket .create_connection
88
127
def kubernetes_create_connection (address , * args , ** kwargs ):
89
128
dns_name = address [0 ]
90
129
if isinstance (dns_name , bytes ):
91
130
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.
94
131
dns_name = dns_name .split ("." )
95
- if len ( dns_name ) != 3 or dns_name [ 2 ] != " kubernetes" :
132
+ if dns_name [ - 1 ] != ' kubernetes' :
96
133
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 ])
97
170
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 )
102
173
socket .create_connection = kubernetes_create_connection
103
174
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 )
106
177
html = response .read ().decode ('utf-8' )
107
178
response .close ()
108
179
print ('Status:' , response .status )
@@ -111,9 +182,9 @@ def kubernetes_create_connection(address, *args, **kwargs):
111
182
112
183
def main ():
113
184
config .load_kube_config ()
114
- c = Configuration ()
185
+ c = Configuration . get_default_copy ()
115
186
c .assert_hostname = False
116
- # Configuration.set_default(c)
187
+ Configuration .set_default (c )
117
188
core_v1 = core_v1_api .CoreV1Api ()
118
189
119
190
portforward_commands (core_v1 )
0 commit comments