承接上一篇docker container web terminal,实现Kubernetes的Pod中运行容器terminal连接。所不同的是后端使用的是python kubernetes的package。完整的工程地址请参见:web_terminal_kubernetes。
Python kubernetes package直接可以pip安装:
$ pip install kubernetes
Github 官方地址:https://github.com/kubernetes-client/python
kubernetes client连接服务所用的认证信息区别传统的形式,而是对kubectl的一个封装,需要指定连接集群的主机API访问地址,此外就是比较重要的用户认证。
如果访问集群实接入了keystone,那么在使用kubernetes client连接时提供 username 和 password 即可,keystone会通过此两项信息得知用户身份,但用户的权限范围仍然是kubernetes控制。
对应的python连接方式如下:
from kubernetes import client
kub_conf = client.Configuration()
kub_conf.host = api_host
kub_conf.username = 'your_username'
kub_conf.password = 'your_password'
kub_client = client.ApiClient(configuration=kub_conf)
笔者所访问集群并未接入keystone认证,而是用证书形式,类似于在机器上配置kubectl config 时使用的指定地址和ca的方式进行API访问。
对应的python连接方式如下:
from kubernetes import client
kub_conf = client.Configuration()
kub_conf.host = api_host
kub_conf.ssl_ca_cert = 'your_ssl_ca_cert_path'
kub_conf.cert_file = 'your_cert_file_path'
kub_conf.key_file = 'your_key_file_path'
kub_client = client.ApiClient(configuration=kub_conf)
注意:上述 ssl_ca_cert,cert_file,key_file所需要提供的都是证书在机器上的存放path。
kubernetes更新比较快,能够获取到的信息,以及提供的API都应去Github上实时查阅。kubernetes.client.CoreV1Api提供的API参见:https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/CoreV1Api.md 。
此处调用CoreV1Api 下的 connect_get_namespaced_pod_exec方法用以连接Pod中container。
connect_get_namespaced_pod_exec方法都是一个API形式的服务,并非返回socket对象开启连接发送流数据,所以需要借助 kubernetes.stream ,将执行方法和参数传入,返回websocket。
class K8SClient(KubernetesAPI):
def __init__(self, api_host, ssl_ca_cert, key_file, cert_file):
super(K8SClient, self).__init__(
api_host, ssl_ca_cert, key_file, cert_file)
@staticmethod
def gen_ca():
ssl_ca_cert = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
'_credentials/kubernetes_dev_ca_cert')
key_file = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
'_credentials/kubernetes_dev_key')
cert_file = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
'_credentials/kubernetes_dev_cert')
return ssl_ca_cert, key_file, cert_file
def terminal_start(self, namespace, pod_name, container):
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']
container_stream = stream(
self.client_core_v1.connect_get_namespaced_pod_exec,
name=pod_name,
namespace=namespace,
container=container,
command=command,
stderr=True, stdin=True,
stdout=True, tty=True,
_preload_content=False
)
return container_stream
KubernetesAPI 对 kubernetes client做了简单封装。
注意:
1,connect_get_namespaced_pod_exec 传入的参数标准输入 stdin,标准输出stdout,和标准错误 stderr都开启
2,tty参数一定要开启,此参数标示 以类似 kubectl exec的形式运行命令。
3,_preload_content,if False, the urllib3.HTTPResponse object will be returned without reading/decoding response data. Default is True。原文注释,标识是否需要编解码。
先前提过container的开启的bash如果没有退出,会直接挂起,之前提出的大异常包括范围,只能去关闭与容器的连接,但是bash没有退出。
1,后端服务大异常包裹,并且在检测到前台ws关闭追加 ‘exit\r’ 模拟用户退出终端。
2,在浏览器的退出和刷新时弹出提示,提醒用户退出终端。
但是模拟exit退出解决不了嵌套,诸如用户进入container中,开启python shell,之后并不手动退出,而是直接关闭浏览器,那么此时就会有一个bash挂起
。
以nginx代理此服务,配置正确后,界面加载均正常显示,唯独开启socket时会出现异常,后发现需要加载额外配置。
在nginx的conf文件中,可以参照以下配置:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name localhost your_domain;
root your_root_path
location /terminal/ {
proxy_pass http://localhost:5000;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_ignore_headers X-Accel-Buffering;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-Real-IP $remote_addr;
}
}
注意:
一定要设置代理访问的协议是http1.1
proxy_http_version 1.1
因为之前有提过,ws是需要先发送http1.1的请求进行握手操作,之后才能开启连接。
Flask算是个人用的比较少框架了,但是很轻,很容易使用。
Flask对于app稍大一点是可以进行拆分的,可以用Blueprint进行拆分,示例如下:
独立api文件如下
import json
from flask import Blueprint
a_api = Blueprint('a_api', __name__)
a_ws = Blueprint('a_api', __name__)
@k8s_api.route('/your_route', methods=['GET'])
def inner_terminal():
return json.dumps('api hello')
@a_ws.route('/your_ws', methods=['GET'])
def inner_terminal(ws):
return json.dumps('ws hello')
主文app.py件中加载
from api import a_api, a_ws
app = Flask(__name__, static_folder='static',
static_url_path='/terminal/static')
sockets = Sockets(app)
app.register_blueprint(k8s_api)
sockets.register_blueprint(k8s_ws)
按此思路完成了对Flask大app的拆分。
在上文示例中,new Flask对象中可以设置静态文件所在的文件夹,已经请求文件的url前缀,在前台文件中加载静态文件可以形如:
<script src="{{ url_for('static', filename='xterm/dist/xterm.js') }}"></script>
在实际访问时便是以
http://localhost:5000/terminal/static/xterm/dist/xterm.js 的地址去加载js文件了。
url_for 函数用以生成url访问地址。
通常想获取路径的方法可以引入 os库,但是:
import os
os.getcwd()
os.path.dirname(__file__)
是不一样的。
os.path.dirname(file) 是获取当前执行py文件的路行。
os.getcwd() 是获取外层调用者的路径。即触发os.getcwd()运行的脚本文件地址。倘若直接运行当前文件执行os.getcwd()和os.path.dirname(file) 获取的路径是一样的,但是如果是在内层函数中操作os.getcwd(),则获取到的是主调该函数调用者所在的文件路径。