Merge pull request #10 from RimuruChan/feature/docker-more-options

Feature/docker more options
This commit is contained in:
Suwings 2022-02-10 12:51:57 +08:00 committed by GitHub
commit 32188c8e96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 72 additions and 21 deletions

3
.gitignore vendored
View File

@ -94,3 +94,6 @@ typings/
.env
Note.txt
# IntelliJ IDEA project files
/.idea/*

View File

@ -20,6 +20,7 @@
*/
import { getVersion, initVersionManager } from "./service/version";
initVersionManager();
const VERSION = getVersion();
@ -44,6 +45,7 @@ import http from "http";
import { Server, Socket } from "socket.io";
import logger from "./service/log";
logger.info(`欢迎使用 MCSManager 守护进程`);
import { globalConfiguration } from "./entity/config";
@ -104,7 +106,7 @@ io.on("connection", (socket: Socket) => {
});
// Error report monitoring
process.on("uncaughtException", function (err) {
process.on("uncaughtException", function(err) {
logger.error(`错误报告 (uncaughtException):`, err);
});
@ -125,16 +127,18 @@ console.log("");
// 装载 终端界面UI
import "./service/ui";
process.on("SIGINT", function () {
try {
console.log("\n\n\n\n");
logger.warn("SIGINT close process signal detected.");
InstanceSubsystem.exit();
logger.info("The data is saved, thanks for using, goodbye!");
logger.info("Closed.");
} catch (err) {
logger.error("ERROR:", err);
} finally {
process.exit(0);
}
['SIGINT', 'SIGQUIT'].forEach(function (sig) {
process.on(sig, function () {
try {
console.log("\n\n\n\n");
logger.warn(`${sig} close process signal detected.`);
InstanceSubsystem.exit();
logger.info("The data is saved, thanks for using, goodbye!");
logger.info("Closed.");
} catch (err) {
logger.error("ERROR:", err);
} finally {
process.exit(0);
}
});
});

View File

@ -52,7 +52,12 @@ class DockerProcessAdapter extends EventEmitter implements IInstanceProcess {
public async start() {
await this.container.start();
this.pid = this.container.id;
const stream = (this.stream = await this.container.attach({ stream: true, stdout: true, stderr: true, stdin: true }));
const stream = (this.stream = await this.container.attach({
stream: true,
stdout: true,
stderr: true,
stdin: true
}));
stream.on("data", (data) => this.emit("data", data));
stream.on("error", (data) => this.emit("data", data));
this.wait();
@ -70,12 +75,13 @@ class DockerProcessAdapter extends EventEmitter implements IInstanceProcess {
public async destroy() {
try {
await this.container.remove();
} catch (error) {}
} catch (error) {
}
}
private wait() {
this.container.wait(async (v) => {
this.destroy();
await this.destroy();
this.emit("exit", v);
});
}
@ -87,8 +93,10 @@ export default class DockerStartCommand extends InstanceCommand {
}
async exec(instance: Instance, source = "Unknown") {
if (!instance.config.startCommand || !instance.config.cwd || !instance.config.ie || !instance.config.oe) return instance.failure(new StartupDockerProcessError("启动命令,输入输出编码或工作目录为空值"));
if (!fs.existsSync(instance.absoluteCwdPath())) return instance.failure(new StartupDockerProcessError("工作目录并不存在"));
if (!instance.config.startCommand || !instance.config.cwd || !instance.config.ie || !instance.config.oe)
return instance.failure(new StartupDockerProcessError("启动命令,输入输出编码或工作目录为空值"));
if (!fs.existsSync(instance.absoluteCwdPath()))
return instance.failure(new StartupDockerProcessError("工作目录并不存在"));
try {
// 锁死实例
@ -151,13 +159,22 @@ export default class DockerStartCommand extends InstanceCommand {
// Note: 检验
}
// 容器名校验
let containerName = instance.config.docker.containerName;
if (containerName && (containerName.length > 64 || containerName.length < 2)) {
throw new Error(`非法的容器名: ${containerName}`);
}
// 输出启动日志
logger.info("----------------");
logger.info(`会话 ${source}: 请求开启实例`);
logger.info(`实例标识符: [${instance.instanceUuid}]`);
logger.info(`容器名称: [${containerName}]`);
logger.info(`启动命令: ${commandList.join(" ")}`);
logger.info(`工作目录: ${cwd}`);
logger.info(`端口: ${JSON.stringify(publicPortArray)}`);
logger.info(`网络模式: ${instance.config.docker.networkMode}`);
logger.info(`端口映射: ${JSON.stringify(publicPortArray)}`);
logger.info(`网络别名: ${JSON.stringify(instance.config.docker.networkAliases)}`);
if (maxMemory) logger.info(`内存限制: ${maxMemory} MB`);
logger.info(`类型: Docker 容器`);
logger.info("----------------");
@ -165,6 +182,7 @@ export default class DockerStartCommand extends InstanceCommand {
// 开始 Docker 容器创建并运行
const docker = new Docker();
const container = await docker.createContainer({
name: containerName,
Image: instance.config.docker.image,
AttachStdin: true,
AttachStdout: true,
@ -183,7 +201,15 @@ export default class DockerStartCommand extends InstanceCommand {
CpusetCpus: cpusetCpus,
CpuPeriod: cpuPeriod,
CpuQuota: cpuQuota,
PortBindings: publicPortArray
PortBindings: publicPortArray,
NetworkMode: instance.config.docker.networkMode
},
NetworkingConfig: {
EndpointsConfig: {
[instance.config.docker.networkMode]: {
"Aliases": instance.config.docker.networkAliases
}
}
}
});

View File

@ -52,10 +52,12 @@ export default class InstanceConfig {
// Extend
public docker: IDockerConfig = {
containerName: "",
image: "",
ports: [],
memory: null,
networkMode: "bridge",
networkAliases: [],
cpusetCpus: "",
cpuUsage: null,
maxSpace: null,

View File

@ -125,6 +125,7 @@ export default class Instance extends EventEmitter {
configureEntityParams(this.config, cfg, "endTime", String);
configureEntityParams(this.config, cfg, "fileCode", String);
if (cfg.docker) {
configureEntityParams(this.config.docker, cfg.docker, "containerName", String);
configureEntityParams(this.config.docker, cfg.docker, "image", String);
configureEntityParams(this.config.docker, cfg.docker, "memory", Number);
configureEntityParams(this.config.docker, cfg.docker, "ports");
@ -132,6 +133,7 @@ export default class Instance extends EventEmitter {
configureEntityParams(this.config.docker, cfg.docker, "io", Number);
configureEntityParams(this.config.docker, cfg.docker, "network", Number);
configureEntityParams(this.config.docker, cfg.docker, "networkMode", String);
configureEntityParams(this.config.docker, cfg.docker, "networkAliases");
configureEntityParams(this.config.docker, cfg.docker, "cpusetCpus", String);
configureEntityParams(this.config.docker, cfg.docker, "cpuUsage", Number);
}

View File

@ -23,6 +23,7 @@ import { EventEmitter } from "events";
// interface of docker config
export interface IDockerConfig {
containerName: string;
image: string;
memory: number; //以字节为单位的内存限制。
ports: string[];
@ -30,6 +31,7 @@ export interface IDockerConfig {
network: number;
io: number;
networkMode: string;
networkAliases: string[];
cpusetCpus: string; //允许执行的 CPU例如0-30,1
cpuUsage: number;
}

View File

@ -30,8 +30,8 @@ import os from "os";
// 获取本系统镜像列表
routerApp.on("environment/images", async (ctx, data) => {
if (os.platform() === "win32") return protocol.responseError(ctx, "[Unsupported] Windows 系统暂不支持此功能");
try {
if (os.platform() === "win32") return protocol.responseError(ctx, "[Unsupported] Windows 系统暂不支持此功能");
const docker = new DockerManager().getDocker();
const result = await docker.listImages();
protocol.response(ctx, result);
@ -51,6 +51,18 @@ routerApp.on("environment/containers", async (ctx, data) => {
}
});
// 获取本系统网络列表
routerApp.on("environment/networkModes", async (ctx, data) => {
if (os.platform() === "win32") return protocol.responseError(ctx, "[Unsupported] Windows 系统暂不支持此功能");
try {
const docker = new DockerManager().getDocker();
const result = await docker.listNetworks();
protocol.response(ctx, result);
} catch (error) {
protocol.responseError(ctx, error);
}
});
// 创建镜像
routerApp.on("environment/new_image", async (ctx, data) => {
if (os.platform() === "win32") return protocol.responseError(ctx, "[Unsupported] Windows 系统暂不支持此功能");