mirror of
https://github.com/hequan2017/seal.git
synced 2026-05-04 18:53:56 +08:00
v0.4.0 k8s 基本功能
This commit is contained in:
@@ -34,7 +34,7 @@
|
||||
|
||||
* 一期: 基础模板 (已完成)
|
||||
* 二期: k8s管理平台 (开发中)
|
||||
* pod 列表 (已完成)
|
||||
* node/service/pod 列表 (已完成)
|
||||
* pod webssh (已完成)
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class MyThread(threading.Thread):
|
||||
self.number += 1
|
||||
|
||||
if "kubectl exec -it" in str_data:
|
||||
#不返回内容
|
||||
# 不返回内容
|
||||
pass
|
||||
else:
|
||||
if "rpc error" in str_data:
|
||||
@@ -68,6 +68,7 @@ class EchoConsumer(WebsocketConsumer):
|
||||
|
||||
def connect(self):
|
||||
# 创建channels group, 命名为:用户名 (最好不要中文名字),并使用channel_layer写入到redis
|
||||
|
||||
async_to_sync(self.channel_layer.group_add)(self.scope['user'].username, self.channel_name)
|
||||
|
||||
self.sshclient = paramiko.SSHClient()
|
||||
@@ -80,6 +81,7 @@ class EchoConsumer(WebsocketConsumer):
|
||||
t1 = MyThread(self)
|
||||
t1.setDaemon(True)
|
||||
t1.start()
|
||||
# 可以在这里根据 用户 要访问的pod 进行 权限控制
|
||||
path = self.scope['path'].split('/')
|
||||
cmd = f"kubectl exec -it {path[2]} -n {path[3]} sh \r"
|
||||
self.chan.send(cmd)
|
||||
|
||||
@@ -20,35 +20,50 @@ class K8sApi(object):
|
||||
v1 = client.CoreV1Api(aApiClient)
|
||||
return v1
|
||||
|
||||
def get_podlist(self):
|
||||
def get_node_list(self):
|
||||
client_v1 = self.get_client()
|
||||
ret = client_v1.list_node()
|
||||
return ret
|
||||
|
||||
def get_service_list(self):
|
||||
client_v1 = self.get_client()
|
||||
ret = client_v1.list_service_for_all_namespaces(watch=False)
|
||||
return ret
|
||||
|
||||
def get_pod_list(self):
|
||||
client_v1 = self.get_client()
|
||||
ret_pod = client_v1.list_pod_for_all_namespaces(watch=False)
|
||||
return ret_pod
|
||||
|
||||
def get_namespacelist(self):
|
||||
def get_pod_detail(self, name, namespace):
|
||||
client_v1 = self.get_client()
|
||||
ret_pod = client_v1.read_namespaced_pod(name, namespace)
|
||||
return ret_pod
|
||||
|
||||
def get_namespace_list(self):
|
||||
client_v1 = self.get_client()
|
||||
ret_namespace = client_v1.list_namespace()
|
||||
return ret_namespace
|
||||
|
||||
def test_pods_connect(self, podname, namespace, command, container=None):
|
||||
client_v1 = self.get_client()
|
||||
if stream(client_v1.connect_get_namespaced_pod_exec, podname, namespace, command=command,
|
||||
container=container,
|
||||
stderr=True, stdin=False,
|
||||
stdout=True, tty=False):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_pods_exec(self, podname, namespace, command, container=None):
|
||||
client_v1 = self.get_client()
|
||||
if container:
|
||||
rest = stream(client_v1.connect_get_namespaced_pod_exec, podname, namespace, command=command,
|
||||
container=container,
|
||||
stderr=True, stdin=False,
|
||||
stdout=True, tty=False)
|
||||
else:
|
||||
rest = stream(client_v1.connect_get_namespaced_pod_exec, podname, namespace, command=command,
|
||||
stderr=True, stdin=False,
|
||||
stdout=True, tty=False)
|
||||
return rest
|
||||
# def test_pod_connect(self, podname, namespace, command, container=None):
|
||||
# client_v1 = self.get_client()
|
||||
# if stream(client_v1.connect_get_namespaced_pod_exec, podname, namespace, command=command,
|
||||
# container=container,
|
||||
# stderr=True, stdin=False,
|
||||
# stdout=True, tty=False):
|
||||
# return True
|
||||
# else:
|
||||
# return False
|
||||
#
|
||||
# def get_pod_exec(self, podname, namespace, command, container=None):
|
||||
# client_v1 = self.get_client()
|
||||
# if container:
|
||||
# rest = stream(client_v1.connect_get_namespaced_pod_exec, podname, namespace, command=command,
|
||||
# container=container,
|
||||
# stderr=True, stdin=False,
|
||||
# stdout=True, tty=False)
|
||||
# else:
|
||||
# rest = stream(client_v1.connect_get_namespaced_pod_exec, podname, namespace, command=command,
|
||||
# stderr=True, stdin=False,
|
||||
# stdout=True, tty=False)
|
||||
# return rest
|
||||
|
||||
@@ -12,9 +12,12 @@ def main():
|
||||
client.Configuration.set_default(configuration)
|
||||
v1 = client.CoreV1Api()
|
||||
ret = v1.list_pod_for_all_namespaces(watch=False)
|
||||
for i in ret.items:
|
||||
print("%s\t%s\t%s" %
|
||||
(i.status.pod_ip, i.metadata.namespace, i.metadata.name))
|
||||
|
||||
ret1 = v1.read_namespaced_pod("nginx-58bdcbcd-z8v2s","default")
|
||||
print(ret1)
|
||||
# for i in ret.items:
|
||||
# print("%s\t%s\t%s" %
|
||||
# (i.status.pod_ip, i.metadata.namespace, i.metadata.name))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
from django.urls import path
|
||||
from k8s import views
|
||||
|
||||
app_name = "k8s"
|
||||
|
||||
urlpatterns = [
|
||||
path('k8s-node-list', views.K8sNodeListView.as_view(), name='k8s-node-list'),
|
||||
|
||||
path('k8s-service-list', views.K8sServiceListView.as_view(), name='k8s-service-list'),
|
||||
|
||||
path('k8s-pod-list', views.K8sPodListView.as_view(), name='k8s-pod-list'),
|
||||
path('k8s-pod-webssh', views.K8sPodWebssh.as_view(), name='k8s-pod-webssh')
|
||||
path('k8s-pod-webssh', views.K8sPodWebssh.as_view(), name='k8s-pod-webssh'),
|
||||
path('k8s-pod-detail', views.K8sPodDetail.as_view(), name='k8s-pod-detail')
|
||||
]
|
||||
|
||||
50
k8s/views.py
50
k8s/views.py
@@ -16,12 +16,48 @@ from k8s.k8sApi.core import K8sApi
|
||||
logger = logging.getLogger('k8s')
|
||||
|
||||
|
||||
class K8sNodeListView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
||||
permission_required = ('k8s.view_ecs',)
|
||||
|
||||
def get(self, request):
|
||||
obj = K8sApi()
|
||||
ret = obj.get_node_list()
|
||||
data = {}
|
||||
for i in ret.items:
|
||||
print(i)
|
||||
data[i.metadata.name] = {"name": i.metadata.name,
|
||||
"status": i.status.conditions[-1].type if i.status.conditions[ -1].status == "True" else "NotReady",
|
||||
"ip": i.status.addresses[0].address,
|
||||
"kubelet_version": i.status.node_info.kubelet_version,
|
||||
"os_image": i.status.node_info.os_image,
|
||||
}
|
||||
return render(request, "k8s/k8s-node-list.html", {"data": data})
|
||||
|
||||
|
||||
class K8sServiceListView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
||||
permission_required = ('k8s.view_ecs',)
|
||||
|
||||
def get(self, request):
|
||||
obj = K8sApi()
|
||||
ret = obj.get_service_list()
|
||||
data = {}
|
||||
for i in ret.items:
|
||||
print(i)
|
||||
ports = []
|
||||
for j in i.spec.ports:
|
||||
ports.append(f"{j.target_port}/{j.port}/{j.node_port}")
|
||||
data[i.metadata.name] = {"name": i.metadata.name, "cluster_ip": i.spec.cluster_ip, "type": i.spec.type,
|
||||
"external_i_ps": i.spec.external_i_ps,
|
||||
"port": ports}
|
||||
return render(request, "k8s/k8s-service-list.html", {"data": data})
|
||||
|
||||
|
||||
class K8sPodListView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
||||
permission_required = ('k8s.view_ecs',)
|
||||
|
||||
def get(self, request):
|
||||
obj = K8sApi()
|
||||
ret = obj.get_podlist()
|
||||
ret = obj.get_pod_list()
|
||||
data = {}
|
||||
for i in ret.items:
|
||||
data[i.metadata.name] = {"ip": i.status.pod_ip, "namespace": i.metadata.namespace}
|
||||
@@ -34,5 +70,15 @@ class K8sPodWebssh(LoginRequiredMixin, PermissionRequiredMixin, View):
|
||||
def get(self, request):
|
||||
name = self.request.GET.get("name")
|
||||
namespace = self.request.GET.get("namespace")
|
||||
return render(request, "k8s/k8s-pod-webssh.html",{"name":name,"namespace":namespace})
|
||||
return render(request, "k8s/k8s-pod-webssh.html", {"name": name, "namespace": namespace})
|
||||
|
||||
|
||||
class K8sPodDetail(LoginRequiredMixin, PermissionRequiredMixin, View):
|
||||
permission_required = ('k8s.view_ecs',)
|
||||
|
||||
def get(self, request):
|
||||
name = self.request.GET.get("name")
|
||||
namespace = self.request.GET.get("namespace")
|
||||
obj = K8sApi()
|
||||
data = obj.get_pod_detail(name, namespace)
|
||||
return render(request, "k8s/k8s-pod-detail.html", {"name": name, "namespace": namespace, "data": data})
|
||||
|
||||
@@ -55,19 +55,28 @@
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
{% if perms.k8s.view_ecs %}
|
||||
<li class="k8s">
|
||||
<a href="#"> <i class="fa fa-gitlab"></i> <span class="nav-label">k8s管理</span></a>
|
||||
<a href="#"> <i class="fa fa-keyboard-o"></i> <span class="nav-label">k8s管理</span></a>
|
||||
|
||||
|
||||
<ul class="nav nav-second-level">
|
||||
{% if perms.k8s.view_ecs %}
|
||||
|
||||
<li class="k8s-node-list">
|
||||
<a href="{% url "k8s:k8s-node-list" %}">Node列表</a>
|
||||
</li>
|
||||
|
||||
<li class="k8s-service-list">
|
||||
<a href="{% url "k8s:k8s-service-list" %}">Service列表</a>
|
||||
</li>
|
||||
|
||||
<li class="k8s-pod-list">
|
||||
<a href="{% url "k8s:k8s-pod-list" %}">Pod列表</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% ifequal user.is_superuser 1 %}
|
||||
|
||||
148
templates/k8s/k8s-node-list.html
Normal file
148
templates/k8s/k8s-node-list.html
Normal file
@@ -0,0 +1,148 @@
|
||||
{% extends "base/base.html" %}
|
||||
{% load asset_filter %}
|
||||
{% load bootstrap4 %}
|
||||
{% load static %}
|
||||
{% block header-css %}
|
||||
|
||||
<style type="text/css">
|
||||
.sort_both_png {
|
||||
background: url('/static/img/sort_both.png') no-repeat right 1px center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sort_asc_png {
|
||||
background: url('/static/img/sort_asc.png') no-repeat right 1px center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sort_desc_png {
|
||||
background: url('/static/img/sort_desc.png') no-repeat right 1px center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}K8S{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block page-content %}
|
||||
|
||||
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-lg-12" id="split-right">
|
||||
|
||||
<div class="ibox float-e-margins">
|
||||
|
||||
<div class="ibox-title">
|
||||
<h5>Node列表</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
|
||||
|
||||
|
||||
<div class="table-responsive">
|
||||
|
||||
|
||||
<form class="form-horizontal "
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
<table class="table table-striped table-bordered table-hover dataTables-example">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
|
||||
<th>服务名</th>
|
||||
<th>状态</th>
|
||||
<th>IP</th>
|
||||
<th>版本</th>
|
||||
<th>镜像</th>
|
||||
<th>IP</th>
|
||||
<th>IP</th>
|
||||
|
||||
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for k,v in data.items %}
|
||||
|
||||
<tr class="gradeA" id="{{ row.id }}" name="{{ k }}">
|
||||
|
||||
|
||||
<td class="center">
|
||||
<div class="">{{ k }}</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
{% if v.status == "Ready" %}
|
||||
<div class="btn btn-success btn-xs"> {{ v.status }}</div>
|
||||
{% else %}
|
||||
<div class="btn btn-danger btn-xs"> {{ v.status }}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="center">
|
||||
<div class="">{{ v.ip}}</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<div class="">{{ v.kubelet_version}}</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<div class="">{{ v.os_image}}</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<div class="">{{ v.ip}}</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<div class="">{{ v.ip}}</div>
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{% block footer-js %}
|
||||
|
||||
<script>
|
||||
window.onload = function () { // 固定左边导航栏
|
||||
$(".k8s").addClass("active");
|
||||
$(".k8s-node-list").addClass("active");
|
||||
};
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
75
templates/k8s/k8s-pod-detail.html
Normal file
75
templates/k8s/k8s-pod-detail.html
Normal file
@@ -0,0 +1,75 @@
|
||||
{% extends "base/base.html" %}
|
||||
{% block title %}资产详情{% endblock %}
|
||||
{% block header-css %}
|
||||
{% load bootstrap4 %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block page-content %}
|
||||
|
||||
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a class="text-center"><i class="fa fa-laptop"></i>{{ name }}</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left: 0">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
|
||||
<pre>{{ data }}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% block footer-js %}
|
||||
<script>
|
||||
window.onload = function () { // 固定左边导航栏
|
||||
$(".k8s").addClass("active");
|
||||
$(".k8s-pod-list").addClass("active");
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
<th>主机名</th>
|
||||
<th>ip</th>
|
||||
<th>命名空间</th>
|
||||
<th>详情</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -87,23 +88,15 @@
|
||||
<td class="center">
|
||||
<div class="">{{ v.namespace}}</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<a class="btn btn-success btn-xs "
|
||||
href="{% url "k8s:k8s-pod-webssh" %}?namespace={{ v.namespace }}&name={{ k }}">webssh</a>
|
||||
<td class="center">
|
||||
<a class="btn btn-warning btn-xs " target="_blank"
|
||||
href="{% url "k8s:k8s-pod-detail" %}?namespace={{ v.namespace }}&name={{ k }}">详情</a>
|
||||
</td>
|
||||
|
||||
{# {% if perms.assets.view_ecs %}#}
|
||||
{# <a class="btn btn-success btn-xs "#}
|
||||
{# href="{% url "assets:ecs-detail" pk=row.id %}">详情</a>#}
|
||||
{# {% endif %}#}
|
||||
{# {% if perms.assets.change_ecs %}#}
|
||||
{# <a class="btn btn-primary btn-xs "#}
|
||||
{# href="{% url "assets:ecs-update" pk=row.id %}">编辑</a>#}
|
||||
{# {% endif %}#}
|
||||
{# {% if perms.assets.delete_ecs %}#}
|
||||
{# <a class="btn btn-danger btn-xs ecs-delete"#}
|
||||
{# href="#">删除</a>#}
|
||||
{# {% endif %}#}
|
||||
<td class="center">
|
||||
<a class="btn btn-success btn-xs " target="_blank"
|
||||
href="{% url "k8s:k8s-pod-webssh" %}?namespace={{ v.namespace }}&name={{ k }}">webssh</a>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
136
templates/k8s/k8s-service-list.html
Normal file
136
templates/k8s/k8s-service-list.html
Normal file
@@ -0,0 +1,136 @@
|
||||
{% extends "base/base.html" %}
|
||||
{% load asset_filter %}
|
||||
{% load bootstrap4 %}
|
||||
{% load static %}
|
||||
{% block header-css %}
|
||||
|
||||
<style type="text/css">
|
||||
.sort_both_png {
|
||||
background: url('/static/img/sort_both.png') no-repeat right 1px center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sort_asc_png {
|
||||
background: url('/static/img/sort_asc.png') no-repeat right 1px center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sort_desc_png {
|
||||
background: url('/static/img/sort_desc.png') no-repeat right 1px center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}K8S{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block page-content %}
|
||||
|
||||
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-lg-12" id="split-right">
|
||||
|
||||
<div class="ibox float-e-margins">
|
||||
|
||||
<div class="ibox-title">
|
||||
<h5>Service列表</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
|
||||
|
||||
|
||||
<div class="table-responsive">
|
||||
|
||||
|
||||
<form class="form-horizontal "
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
<table class="table table-striped table-bordered table-hover dataTables-example">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
|
||||
<th>服务名</th>
|
||||
<th>cluster_ip</th>
|
||||
<th>类型</th>
|
||||
<th>external_i_ps</th>
|
||||
|
||||
<th>target_port/port/node_port</th>
|
||||
|
||||
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for k,v in data.items %}
|
||||
|
||||
<tr class="gradeA" id="{{ row.id }}" name="{{ k }}">
|
||||
|
||||
|
||||
<td class="center">
|
||||
<div class="">{{ k }}</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<div class="">{{ v.cluster_ip }}</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<div class="">{{ v.type}}</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<div class="">{{ v.external_i_ps}}</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<div class="">{% for i in v.port %}{{ i }} <br />{% endfor %}</div>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{% block footer-js %}
|
||||
|
||||
<script>
|
||||
window.onload = function () { // 固定左边导航栏
|
||||
$(".k8s").addClass("active");
|
||||
$(".k8s-service-list").addClass("active");
|
||||
};
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user