Excellent feedback systems are based on the gradual improvement out
The article describes an issue we have to deal with security and multi-branch frequent testing and developed a Alodi system, Alodi can quickly build a test environment via a button to generate a temporary address to access detailed information you can see this article : Alodi: to maintain secrecy I developed a system
After the system on-line, SSH login become a pressing need console, Kubernetes the Dashboard console Although WebSSH features, but no way to combine with Alodi system, integrated decision WebSSH functions Alodi in, take a look at the last realize the effect it
Involving technology
- Kubernetes Stream: receiving data execution, provides real-time data stream returned
- Django Channels: longer maintain the connection, receiving data is transferred to the distal end Kubernetes, while transmitting the data returned to the front end Kubernetes
- xterm.js: a front-end terminal assembly, an interface for an analog display Terminal
The basic data flow are: User -> xterm.js -> django channels -> kubernetes stream, then look at the specific code implementation
Kubernetes Stream
Kubernetes stream itself provides a way to implement exec function, a data stream is returned WebSocket may be used, also very easy to use, as follows:
from kubernetes import client, config
from kubernetes.stream import stream
class KubeApi:
def __init__(self, namespace='alodi'):
config.load_kube_config("/ops/coffee/kubeconfig.yaml")
self.namespace = namespace
def pod_exec(self, pod, container=""):
api_instance = client.CoreV1Api()
exec_command = [
"/bin/sh",
"-c",
'TERM=xterm-256color; export TERM; [ -x /bin/bash ] '
'&& ([ -x /usr/bin/script ] '
'&& /usr/bin/script -q -c "/bin/bash" /dev/null || exec /bin/bash) '
'|| exec /bin/sh']
cont_stream = stream(api_instance.connect_get_namespaced_pod_exec,
name=pod,
namespace=self.namespace,
container=container,
command=exec_command,
stderr=True, stdin=True,
stdout=True, tty=True,
_preload_content=False
)
return cont_stream
Here pod name can be list_namespaced_pod
acquired as the following code:
def get_deployment_pod(self, RAND):
api_instance = client.CoreV1Api()
try:
r = api_instance.list_namespaced_pod(
namespace=self.namespace,
label_selector="app=%s" % RAND
)
return True, r
except Exception as e:
return False, 'Get Deployment: ' + str(e)
state, data = self.get_deployment_pod(RAND)
pod_name = data.items[0].metadata.name
list_namespaced_pod
Will all the details pod under the namespace listed here passed two parameters, the first one namespace
is a must, we have to list the namespace represents the pod, the second label_selector
non-essential, that can filter by label set under the namespace pod, because we created at the time of deployment are added to each unique app=RAND
label, so here we can filter out items corresponding to the pod
A deployment may correspond to multiple pod, acquired data.items
contains all the information pod, a list file, the name may correspond to pod needed to get
Django Channels
There are two articles in detail before introduced Django Channels, do not understand can first view: Django use Channels to achieve WebSocket-- Part I and Django use Channels to achieve WebSocket-- next , the two most important parts of the code are as follows
routing codes:
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path, re_path
from medivh.consumers import SSHConsumer
application = ProtocolTypeRouter({
'websocket': AuthMiddlewareStack(
URLRouter([
re_path(r'^pod/(?P<name>\w+)', SSHConsumer),
])
),
})
Matches all regular pod
beginning websocket connected are referred called SSHConsumer
the Consumer processing, Consumer code is as follows:
from channels.generic.websocket import WebsocketConsumer
from medivh.backends.kube import KubeApi
from threading import Thread
class K8SStreamThread(Thread):
def __init__(self, websocket, container_stream):
Thread.__init__(self)
self.websocket = websocket
self.stream = container_stream
def run(self):
while self.stream.is_open():
if self.stream.peek_stdout():
stdout = self.stream.read_stdout()
self.websocket.send(stdout)
if self.stream.peek_stderr():
stderr = self.stream.read_stderr()
self.websocket.send(stderr)
else:
self.websocket.close()
class SSHConsumer(WebsocketConsumer):
def connect(self):
self.name = self.scope["url_route"]["kwargs"]["name"]
# kube exec
self.stream = KubeApi().pod_exec(self.name)
kub_stream = K8SStreamThread(self, self.stream)
kub_stream.start()
self.accept()
def disconnect(self, close_code):
self.stream.write_stdin('exit\r')
def receive(self, text_data):
self.stream.write_stdin(text_data)
WebSSH can be seen as a simple connection websocket long after each connection is established it is independent and does not share data with other connection, so there is no need to use Group
When a connection is established through the self.scope
acquisition to the url name, passed Kubernetes API, it will also play a new thread if a continuous cycle of new data is generated, if it is sent to websocket
When the data is received directly websocket write Kubernetes API, when closed websocket is sent a exit
command to the Kubernetes
Front page
The front end of the main uses xterm.js, the overall code is relatively simple
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Alodi | Pod Web SSH</title>
<link rel="Shortcut Icon" href="/static/img/favicon.ico">
<link href="/static/plugins/xterm/xterm.css" rel="stylesheet" type="text/css"/>
<link href="/static/plugins/xterm/addons/fullscreen/fullscreen.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div id="terminal"></div>
</body>
<script src="/static/plugins/xterm/xterm.js"></script>
<script src="/static/plugins/xterm/addons/fullscreen/fullscreen.js"></script>
<script>
var term = new Terminal({cursorBlink: true});
term.open(document.getElementById('terminal'));
// xterm fullscreen config
Terminal.applyAddon(fullscreen);
term.toggleFullScreen(true);
var socket = new WebSocket(
'ws://' + window.location.host + '/pod/{{ name }}');
socket.onopen = function () {
term.on('data', function (data) {
socket.send(data);
});
socket.onerror = function (event) {
console.log('error:' + e);
};
socket.onmessage = function (event) {
term.write(event.data);
};
socket.onclose = function (event) {
term.write('\n\r\x1B[1;3;31msocket is already closed.\x1B[0m');
// term.destroy();
};
};
</script>
</html>
term.open
Initializing a Terminal
term.on
Content will enter all the real-time transfer to the back-end
xterm.js have a fullscreen plugin, after the introduction can be configured fullscreen, or it may be part of the terminal window page
Currently still experiencing a problem can not be resized window is not resolved, the preliminary judgment is the back-end data returned Kubernetes decision, query relevant information, find the kubectl
command by adding COLUMNS
and LINES
setting the env
#!/bin/sh
if [ "$1" = "" ]; then
echo "Usage: kshell <pod>"
exit 1
fi
COLUMNS=`tput cols`
LINES=`tput lines`
TERM=xterm
kubectl exec -i -t $1 env COLUMNS=$COLUMNS LINES=$LINES TERM=$TERM bash
But Stream Kubernetes Python API is not configured to find a place, if you know, tell me trouble
Related Articles Recommended reading: