Files

473 lines
19 KiB
Python
Raw Permalink Normal View History

2026-04-07 02:04:22 +05:30
#coding: utf-8
#-------------------------------------------------------------------
# YakPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: zouhw <zhw@yakpanel.com>
#-------------------------------------------------------------------
#------------------------------
# Docker模型
#------------------------------
import public
import docker.errors
import projectModel.bt_docker.dk_public as dp
class main:
def __init__(self):
self.alter_table()
def alter_table(self):
if not dp.sql('sqlite_master').where('type=? AND name=? AND sql LIKE ?',
('table', 'container', '%sid%')).count():
dp.sql('container').execute("alter TABLE container add container_name VARCHAR DEFAULT ''", ())
def docker_client(self,url):
return dp.docker_client(url)
# 添加容器
def run(self,args):
"""
:param name:容器名
:param image: 镜像
:param publish_all_ports 暴露所有端口 1/0
:param ports 暴露某些端口 {'1111/tcp': ('127.0.0.1', 1111)}
:param command 命令
:param entrypoint 配置容器启动后执行的命令
:param environment 环境变量 xxx=xxx 一行一条
:param auto_remove 当容器进程退出时在守护进程端启用自动移除容器 0/1
:param args:
:return:
"""
if not hasattr(args,'ports'):
args.ports = False
if not hasattr(args,'volumes'):
args.volumes = False
#检测端口是否已经在使用
if args.ports:
for i in args.ports:
if dp.check_socket(args.ports[i]):
return public.returnMsg(False,"The server port [{}] has been used, please replace it with another port!".format(args.ports[i]))
if not args.image:
return public.returnMsg(False, "If there is no image selected, please go to the image tab to pull the image you need!")
if args.restart_policy['Name'] == "always":
args.restart_policy = {"Name":"always"}
# return args.restart_policy
# if
args.cpu_quota = float(args.cpuset_cpus) * 100000
# if not args.volumes:
# args.volumes = {"/sys/fs/cgroup":{"bind":"/sys/fs/cgroup","mode":"rw"}}
# else:
# if not "/sys/fs/cgroup" in args.volumes:
# args.volumes['/sys/fs/cgroup'] = {"bind":"/sys/fs/cgroup","mode":"rw"}
try:
if not args.name:
args.name = "{}-{}".format(args.image,public.GetRandomString(8))
if int(args.cpu_quota) / 100000 > dp.get_cpu_count():
return public.returnMsg(False,"The CPU quota has exceeded the number of cores available!")
mem_limit_byte = dp.byte_conversion(args.mem_limit)
if mem_limit_byte > dp.get_mem_info():
return public.returnMsg(False, "The memory quota has exceeded the available number!")
res = self.docker_client(args.url).containers.run(
name=args.name,
image=args.image,
detach=True,
publish_all_ports=True if args.publish_all_ports == "1" else False,
ports=args.ports if args.ports else None,
command=args.command,
auto_remove=True if str(args.auto_remove) == "1" else False,
environment=dp.set_kv(args.environment), #"HOME=/value\nHOME11=value1"
volumes=args.volumes, #一个字典对象 {'服务器路径/home/user1/': {'bind': '容器路径/mnt/vol2', 'mode': 'rw'},'/var/www': {'bind': '/mnt/vol1', 'mode': 'ro'}}
# cpuset_cpus=args.cpuset_cpus ,#指定容器使用的cpu个数
cpu_quota=int(args.cpu_quota),
mem_limit=args.mem_limit, #b,k,m,g
restart_policy=args.restart_policy,
labels=dp.set_kv(args.labels), #"key=value\nkey1=value1"
tty=True,
stdin_open=True,
privileged=True
)
if res:
pdata = {
"cpu_limit": str(args.cpu_quota),
"container_name": args.name
}
dp.sql('container').insert(pdata)
public.set_module_logs('docker', 'run_container', 1)
dp.write_log("Create container [{}] successful!".format(args.name))
return public.returnMsg(True,"The container was created successfully!")
return public.returnMsg(False, 'Create failed!')
except docker.errors.APIError as e:
if "container to be able to reuse that name." in str(e):
return public.returnMsg(False, "The container name already exists!")
if "Invalid container name" in str(e):
return public.returnMsg(False, "The container name is illegal, please do not use Chinese container name!")
if "bind: address already in use" in str(e):
port = ""
for i in args.ports:
if ":{}:".format(args.ports[i]) in str(e):
port = args.ports[i]
args.id = args.name
self.del_container(args)
return public.returnMsg(False, "Server port {} is in use! Please change other ports".format(port))
return public.returnMsg(False, 'Create failed! {}'.format(public.get_error_info()))
# 保存为镜像
def commit(self,args):
"""
:param repository 推送到的仓库
:param tag 镜像标签 jose:v1
:param message 提交的信息
:param author 镜像作者
:param changes
:param conf dict
:param path 导出路径
:param name 导出文件名
:param args:
:return:
"""
if not hasattr(args,'conf') or not args.conf:
args.conf = None
if args.repository == "docker.io":
args.repository = ""
container = self.docker_client(args.url).containers.get(args.id)
container.commit(
repository=args.repository if args.repository else None,
tag=args.tag if args.tag else None,
message=args.message if args.message else None,
author=args.author if args.author else None,
# changes=args.changes if args.changes else None,
conf=args.conf
)
if hasattr(args,"path") and args.path:
args.id = "{}:{}".format(args.name,args.tag)
import projectModel.bt_docker.dk_image as dk
return dk.main().save(args)
dp.write_log("Submitting container [{}] as image [{}] succeeded!".format(container.attrs['Name'],args.tag))
return public.returnMsg(True,"提交成功!")
# 容器执行命令
def docker_shell(self, args):
"""
:param container_id
:param args:
:return:
"""
try:
self.docker_client(args.url).containers.get(args.container_id)
cmd = 'docker container exec -it {} /bin/bash'.format(args.container_id)
return public.returnMsg(True, cmd)
except docker.errors.APIError as ex:
return public.returnMsg(False, 'Failed to get container')
# 导出容器为tar 没有导入方法,目前弃用
def export(self,args):
"""
:param path 保存路径
:param name 包名
:param args:
:return:
"""
from os import path as ospath
from os import makedirs as makedirs
try:
if "tar" in args.name:
file_name = '{}/{}'.format(args.path,args.name)
else:
file_name = '{}/{}.tar'.format(args.path, args.name)
if not ospath.exists(args.path):
makedirs(args.path)
public.writeFile(file_name,'')
f = open(file_name, 'wb')
container = self.docker_client(args.url).containers.get(args.id)
data = container.export()
for i in data:
f.write(i)
f.close()
return public.returnMsg(True, "Successfully exported to: {}".format(file_name))
except:
return public.returnMsg(False, 'Operation failed:' + str(public.get_error_info()))
# 删除容器
def del_container(self,args):
"""
:return:
"""
import projectModel.bt_docker.dk_public as dp
container = self.docker_client(args.url).containers.get(args.id)
container.remove(force=True)
dp.sql("cpu_stats").where("container_id=?", (args.id,)).delete()
dp.sql("io_stats").where("container_id=?", (args.id,)).delete()
dp.sql("mem_stats").where("container_id=?", (args.id,)).delete()
dp.sql("net_stats").where("container_id=?", (args.id,)).delete()
dp.sql("container").where("container_nam=?", (container.attrs['Name'])).delete()
dp.write_log("Delete container [{}] succeeded!".format(container.attrs['Name']))
return public.returnMsg(True,"Successfully deleted!")
# 设置容器状态
def set_container_status(self,args):
import time
container = self.docker_client(args.url).containers.get(args.id)
if args.act == "start":
container.start()
elif args.act == "stop":
container.stop()
elif args.act == "pause":
container.pause()
elif args.act == "unpause":
container.unpause()
elif args.act == "reload":
container.reload()
else:
container.restart()
time.sleep(1)
tmp = self.docker_client(args.url).containers.get(args.id)
return {"name":container.attrs['Name'].replace('/',''),"status":tmp.attrs['State']['Status']} #返回设置后的状态
# 停止容器
def stop(self,args):
"""
:param url
:param id
:param args:
:return:
"""
try:
args.act = "stop"
data = self.set_container_status(args)
if data['status'] != "exited":
return public.returnMsg(False, "Stop failing!")
dp.write_log("Stop container [{}] succeeded!".format(data['name']))
return public.returnMsg(True, "Stop success!")
except docker.errors.APIError as e:
if "is already paused" in str(e):
return public.returnMsg(False,"The container has been suspended!")
if "No such container" in str(e):
return public.returnMsg(True, "The container has been stopped and deleted because the container has the option to automatically delete after stopping!")
return public.returnMsg(False,"Stop failing!{}".format(e))
def start(self,args):
"""
:param url
:param id
:param args:
:return:
"""
try:
args.act = "start"
data = self.set_container_status(args)
if data['status'] != "running":
return public.returnMsg(False, "Startup failed!")
dp.write_log("Start the container [{}] successfully!".format(data['name']))
return public.returnMsg(True, "Started successfully!")
except docker.errors.APIError as e:
if "cannot start a paused container, try unpause instead" in str(e):
return self.unpause(args)
def pause(self,args):
"""
Pauses all processes within this container.
:param url
:param id
:param args:
:return:
"""
try:
args.act = "pause"
data = self.set_container_status(args)
if data['status'] != "paused":
return public.returnMsg(False, "Container pause failed!")
dp.write_log("Suspended container [{}] succeeded!".format(data['name']))
return public.returnMsg(True, "Container paused successfully!")
except docker.errors.APIError as e:
if "is already paused" in str(e):
return public.returnMsg(False,"The container has been suspended!")
if "is not running" in str(e):
return public.returnMsg(False, "The container is not started and cannot be paused!")
if "is not paused" in str(e):
return public.returnMsg(False, "The container has not been suspended!")
return str(e)
def unpause(self,args):
"""
unPauses all processes within this container.
:param url
:param id
:param args:
:return:
"""
try:
args.act = "unpause"
data = self.set_container_status(args)
if data['status'] != "running":
return public.returnMsg(False, "Startup failed!")
dp.write_log("Unpausing the container [{}] succeeded!".format(data['name']))
return public.returnMsg(True, "Container unpause succeeded!")
except docker.errors.APIError as e:
if "is already paused" in str(e):
return public.returnMsg(False,"The container has been suspended!")
if "is not running" in str(e):
return public.returnMsg(False, "The container is not started and cannot be paused!")
if "is not paused" in str(e):
return public.returnMsg(False, "The container has not been suspended!")
return str(e)
def reload(self,args):
"""
Load this object from the server again and update attrs with the new data.
:param url
:param id
:param args:
:return:
"""
args.act = "reload"
data = self.set_container_status(args)
if data['status'] != "running":
return public.returnMsg(False, "Startup failed!")
dp.write_log("Reloading container [{}] succeeded!".format(data['name']))
return public.returnMsg(True, "Container reload succeeded!")
def restart(self,args):
"""
Restart this container. Similar to the docker restart command.
:param url
:param id
:param args:
:return:
"""
args.act = "restart"
data = self.set_container_status(args)
if data['status'] != "running":
return public.returnMsg(False, "Startup failed!")
dp.write_log("Restarting the container [{}] succeeded!".format(data['name']))
return public.returnMsg(True, "The container restarted successfully!")
def get_container_ip(self,container_networks):
data = list()
for network in container_networks:
data.append(container_networks[network]['IPAddress'])
return data
def get_container_path(self,detail):
import os
if not "GraphDriver" in detail:
return False
if "Data" not in detail["GraphDriver"]:
return False
if "MergedDir" not in detail["GraphDriver"]["Data"]:
return False
path = detail["GraphDriver"]["Data"]["MergedDir"]
if not os.path.exists(path):
return ""
return path
# 获取容器列表所需的外部数据
def get_other_data_for_container_list(self,args):
import projectModel.bt_docker.dk_image as di
import projectModel.bt_docker.dk_volume as dv
import projectModel.bt_docker.dk_compose as dc
import projectModel.bt_docker.dk_setup as ds
# 获取镜像列表
images = di.main().image_list(args)
if images['status']:
images = images['msg']['images_list']
else:
images = list()
# 获取卷列表
volumes = dv.main().get_volume_list(args)
if volumes['status']:
volumes = volumes['msg']['volume']
else:
volumes = list()
# 获取模板列表
template = dc.main().template_list(args)
if template['status']:
template = template['msg']['template']
else:
template = list()
online_cpus = dp.get_cpu_count()
mem_total = dp.get_mem_info()
docker_setup = ds.main()
return {
"images":images,
"volumes":volumes,
"template":template,
"online_cpus":online_cpus,
"mem_total":mem_total,
"installed":docker_setup.check_docker_program(),
"service_status":docker_setup.get_service_status()
}
# 获取容器列表
def get_list(self,args):
"""
:param url
:return:
"""
# 判断docker是否安装
import projectModel.bt_docker.dk_setup as ds
data = self.get_other_data_for_container_list(args)
if not ds.main().check_docker_program():
data['container_list'] = list()
return public.returnMsg(True,data)
client = self.docker_client(args.url)
if not client:
return public.returnMsg(True,data)
containers = client.containers
attr_list = self.get_container_attr(containers)
# data = self.get_other_data_for_container_list(args)
container_detail = list()
for attr in attr_list:
cpu_usage = dp.sql("cpu_stats").where("container_id=?",(attr["Id"],)).select()
if cpu_usage and isinstance(cpu_usage,list):
cpu_usage = cpu_usage[-1]['cpu_usage']
else:
cpu_usage = "0.0"
tmp = {
"id": attr["Id"],
"name": attr['Name'].replace("/",""),
"status": attr["State"]["Status"],
"image": attr["Config"]["Image"],
"time": attr["Created"],
"merged": self.get_container_path(attr),
"ip": self.get_container_ip(attr["NetworkSettings"]['Networks']),
"ports": attr["NetworkSettings"]["Ports"],
"detail": attr,
"cpu_usage":cpu_usage if attr["State"]["Status"] == "running" else ""
}
container_detail.append(tmp)
data['container_list'] = container_detail
return public.returnMsg(True,data)
# 获取容器的attr
def get_container_attr(self,containers):
c_list = containers.list(all=True)
return [container_info.attrs for container_info in c_list]
# 获取容器日志
def get_logs(self,args):
"""
:param url
:param id
:param args:
:return:
"""
try:
container = self.docker_client(args.url).containers.get(args.id)
res = container.logs().decode()
return public.returnMsg(True,res)
except docker.errors.APIError as e:
if "configured logging driver does not support reading" in str(e):
return public.returnMsg(False,"The container has no log files!")
# 登录容器
# 获取容器配置文件