0825
This commit is contained in:
47
auto-check/Dockerfile
Normal file
47
auto-check/Dockerfile
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# stress-tools Dockerfile
|
||||||
|
# 用于 auto-check 压力测试的容器镜像
|
||||||
|
# 预装所有压测工具,避免目标机在线安装依赖
|
||||||
|
#
|
||||||
|
# 构建: docker build -t stress-tools:latest .
|
||||||
|
# 推送: docker push registry.example.com/stress-tools:latest
|
||||||
|
# 离线导入: docker save stress-tools:latest | gzip > stress-tools.tar.gz
|
||||||
|
# docker load < stress-tools.tar.gz
|
||||||
|
|
||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
|
LABEL maintainer="auto-check"
|
||||||
|
LABEL description="压力测试工具集(stress-ng, fio, stressapptest, lm-sensors)"
|
||||||
|
LABEL version="1.0.0"
|
||||||
|
|
||||||
|
# 避免交互式提示
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# 安装压测核心工具
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
# 压力测试
|
||||||
|
stress-ng \
|
||||||
|
stressapptest \
|
||||||
|
# 磁盘测试
|
||||||
|
fio \
|
||||||
|
# 温度监控
|
||||||
|
lm-sensors \
|
||||||
|
# 系统信息
|
||||||
|
sysstat \
|
||||||
|
procps \
|
||||||
|
coreutils \
|
||||||
|
util-linux \
|
||||||
|
# 脚本工具
|
||||||
|
bash \
|
||||||
|
awk \
|
||||||
|
sed \
|
||||||
|
grep \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 初始化 sensors(加载内核模块)
|
||||||
|
RUN sensors --detect 2>/dev/null || true
|
||||||
|
|
||||||
|
WORKDIR /workspace
|
||||||
|
|
||||||
|
# 默认使用 bash(脚本通过 stdin 传入)
|
||||||
|
ENTRYPOINT ["/bin/bash"]
|
||||||
@@ -30,6 +30,11 @@ stress:
|
|||||||
auto_install: true # 缺工具时在线安装(apt/dnf/yum 等),需要目标机可联网
|
auto_install: true # 缺工具时在线安装(apt/dnf/yum 等),需要目标机可联网
|
||||||
use_mirror: false # 在线安装是否切换国内镜像源(清华TUNA)。国内网络卡顿/超时再改 true
|
use_mirror: false # 在线安装是否切换国内镜像源(清华TUNA)。国内网络卡顿/超时再改 true
|
||||||
|
|
||||||
|
# --- Docker 容器化压测 ---
|
||||||
|
docker:
|
||||||
|
enabled: true # 启用 Docker 模式(目标机需已安装 Docker,如飞牛系统)
|
||||||
|
image: "stress-tools:latest" # 镜像名,支持 registry/xxx:tag
|
||||||
|
|
||||||
# --- 工作流 ---
|
# --- 工作流 ---
|
||||||
workflow:
|
workflow:
|
||||||
interval: 180s
|
interval: 180s
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ type Runner struct {
|
|||||||
tools ToolSet
|
tools ToolSet
|
||||||
metrics []Metric
|
metrics []Metric
|
||||||
installFailed []string // 自动安装仍失败的工具
|
installFailed []string // 自动安装仍失败的工具
|
||||||
|
useDocker bool // 是否使用 Docker 模式执行
|
||||||
|
dockerImage string // Docker 镜像名(如 registry/stress-tools:latest)
|
||||||
|
sudoPwd string // sudo 密码(用于 docker 命令)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRunner 创建执行器(检查/在线安装 → 构建指标 → 生成脚本 → 执行)
|
// NewRunner 创建执行器(检查/在线安装 → 构建指标 → 生成脚本 → 执行)
|
||||||
@@ -41,6 +44,31 @@ func NewRunner(cfg Config, types []TestType, client *ssh.Client, autoInstall boo
|
|||||||
return &Runner{cfg: cfg, types: types, client: client, tools: tools, metrics: metrics, installFailed: failed}
|
return &Runner{cfg: cfg, types: types, client: client, tools: tools, metrics: metrics, installFailed: failed}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRunnerDocker 创建 Docker 模式执行器(工具预装在镜像中,无需在线安装)
|
||||||
|
// image 为 Docker 镜像名,sudoPwd 用于非 root 用户执行 docker 命令。
|
||||||
|
func NewRunnerDocker(cfg Config, types []TestType, client *ssh.Client, image string, sudoPwd string) *Runner {
|
||||||
|
// Docker 模式下,镜像内已包含所有工具,直接标记全部可用。
|
||||||
|
// 这样 BuildMetrics 不会因缺少工具而禁用测试项。
|
||||||
|
tools := ToolSet{Tools: make(map[string]ToolInfo)}
|
||||||
|
for _, name := range requiredTools {
|
||||||
|
tools.Tools[name] = ToolInfo{Name: name, Path: "/usr/bin/" + name, Version: "docker"}
|
||||||
|
}
|
||||||
|
// 额外标记 lm-sensors、stressapptest(Docker 镜像预装)
|
||||||
|
tools.Tools["lm-sensors"] = ToolInfo{Name: "lm-sensors", Path: "/usr/bin/sensors", Version: "docker"}
|
||||||
|
tools.Tools["stressapptest"] = ToolInfo{Name: "stressapptest", Path: "/usr/bin/stressapptest", Version: "docker"}
|
||||||
|
metrics := BuildMetrics(types, tools)
|
||||||
|
return &Runner{
|
||||||
|
cfg: cfg,
|
||||||
|
types: types,
|
||||||
|
client: client,
|
||||||
|
tools: tools,
|
||||||
|
metrics: metrics,
|
||||||
|
useDocker: true,
|
||||||
|
dockerImage: image,
|
||||||
|
sudoPwd: sudoPwd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Run 执行压力测试(生成脚本 → 远程执行 → 解析结果)
|
// Run 执行压力测试(生成脚本 → 远程执行 → 解析结果)
|
||||||
func (r *Runner) Run(ip string) *Report {
|
func (r *Runner) Run(ip string) *Report {
|
||||||
report := &Report{IP: ip, StartTime: time.Now()}
|
report := &Report{IP: ip, StartTime: time.Now()}
|
||||||
@@ -74,8 +102,14 @@ func (r *Runner) Run(ip string) *Report {
|
|||||||
script := r.assembleScript()
|
script := r.assembleScript()
|
||||||
fmt.Printf(" [脚本] 拼装完成,%d 字节\n", len(script))
|
fmt.Printf(" [脚本] 拼装完成,%d 字节\n", len(script))
|
||||||
|
|
||||||
// 通过 heredoc 传到远程执行
|
// 通过 heredoc 传到远程执行(Docker 模式或原生模式)
|
||||||
scriptCmd := fmt.Sprintf("cat <<'AUTOCHECKSCRIPT' > /tmp/auto-check.sh\n%s\nAUTOCHECKSCRIPT\nchmod +x /tmp/auto-check.sh && bash /tmp/auto-check.sh 2>&1", script)
|
var scriptCmd string
|
||||||
|
if r.useDocker {
|
||||||
|
scriptCmd = r.buildDockerCmd(script)
|
||||||
|
fmt.Printf(" [模式] Docker 容器执行(镜像: %s)\n", r.dockerImage)
|
||||||
|
} else {
|
||||||
|
scriptCmd = fmt.Sprintf("cat <<'AUTOCHECKSCRIPT' > /tmp/auto-check.sh\n%s\nAUTOCHECKSCRIPT\nchmod +x /tmp/auto-check.sh && bash /tmp/auto-check.sh 2>&1", script)
|
||||||
|
}
|
||||||
output, err := sshclient.RunCommand(r.client, scriptCmd)
|
output, err := sshclient.RunCommand(r.client, scriptCmd)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -366,6 +400,39 @@ func (r *Runner) readScript(name string) []byte {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildDockerCmd 构造 Docker 执行命令。
|
||||||
|
// 将脚本通过 heredoc 传入 docker run,在容器内执行。
|
||||||
|
// 容器需要挂载 /sys(温度传感器)、/volume1(飞牛存储卷)等。
|
||||||
|
func (r *Runner) buildDockerCmd(script string) string {
|
||||||
|
// 构造 docker run 命令:
|
||||||
|
// --rm 容器结束后自动清理
|
||||||
|
// -i 交互式输入(通过 stdin 传入脚本)
|
||||||
|
// --privileged 允许访问硬件(温度传感器、内核日志)
|
||||||
|
// --pid=host 共享宿主机进程空间(top 可见)
|
||||||
|
// -v /sys:/sys:ro 温度传感器(只读)
|
||||||
|
// -v /volume1:/volume1 飞牛存储卷(磁盘测试)
|
||||||
|
dockerCmd := fmt.Sprintf(
|
||||||
|
"docker run --rm -i --privileged --pid=host "+
|
||||||
|
"-v /sys:/sys:ro "+
|
||||||
|
"-v /volume1:/volume1 "+
|
||||||
|
"%s bash -s",
|
||||||
|
r.dockerImage,
|
||||||
|
)
|
||||||
|
// 通过 heredoc 将脚本传入容器执行(使用 sudo 以获取 docker 权限)
|
||||||
|
heredoc := fmt.Sprintf("cat <<'AUTOCHECKSCRIPT' | %s\n%s\nAUTOCHECKSCRIPT", dockerCmd, script)
|
||||||
|
|
||||||
|
// 如果需要 sudo(非 root 用户)
|
||||||
|
runner := heredoc
|
||||||
|
if out, err := sshclient.RunCommand(r.client, "id -u"); err == nil && strings.TrimSpace(out) != "0" {
|
||||||
|
if r.sudoPwd != "" {
|
||||||
|
runner = fmt.Sprintf("echo '%s' | sudo -S bash -c %s", r.sudoPwd, shellQuote(heredoc))
|
||||||
|
} else {
|
||||||
|
runner = "sudo " + heredoc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return runner
|
||||||
|
}
|
||||||
|
|
||||||
// testPriority 测试执行顺序:单项测试在前,综合测试(full)最后,监控项最后
|
// testPriority 测试执行顺序:单项测试在前,综合测试(full)最后,监控项最后
|
||||||
func testPriority(m Metric) int {
|
func testPriority(m Metric) int {
|
||||||
if m.IsMonitor {
|
if m.IsMonitor {
|
||||||
@@ -432,6 +499,28 @@ func TestDevice(ip string, cfg config.Config) *Report {
|
|||||||
stressCfg.TempLimit = cfg.Stress.TempLimit
|
stressCfg.TempLimit = cfg.Stress.TempLimit
|
||||||
stressCfg.UseMirror = cfg.Stress.UseMirror
|
stressCfg.UseMirror = cfg.Stress.UseMirror
|
||||||
|
|
||||||
|
// 检测是否使用 Docker 模式(配置启用且远程主机支持)
|
||||||
|
if cfg.Docker.Enabled {
|
||||||
|
dockerInfo := DetectDocker(conn, cfg.Docker.Image, sudoPwd)
|
||||||
|
if dockerInfo.Available {
|
||||||
|
if dockerInfo.HasImage {
|
||||||
|
fmt.Printf(" [Docker] 镜像 %s 已存在,使用容器模式\n", cfg.Docker.Image)
|
||||||
|
return NewRunnerDocker(stressCfg, ParseTypes(cfg.Stress.Types), conn, cfg.Docker.Image, sudoPwd).Run(ip)
|
||||||
|
}
|
||||||
|
// 镜像不存在,尝试拉取(如果失败则回退到原生模式)
|
||||||
|
fmt.Printf(" [Docker] 镜像 %s 不存在,尝试拉取...\n", cfg.Docker.Image)
|
||||||
|
if out, err := runPrivileged(conn, fmt.Sprintf("docker pull %s 2>&1", cfg.Docker.Image), sudoPwd); err == nil {
|
||||||
|
fmt.Printf(" [Docker] 镜像拉取成功\n")
|
||||||
|
return NewRunnerDocker(stressCfg, ParseTypes(cfg.Stress.Types), conn, cfg.Docker.Image, sudoPwd).Run(ip)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" [Docker] 镜像拉取失败,回退到原生模式: %v\n%s\n", err, truncate(out, 200))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" [Docker] 目标机未安装 Docker,回退到原生模式\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原生模式:在线安装工具(回退路径)
|
||||||
return NewRunner(stressCfg, ParseTypes(cfg.Stress.Types), conn, cfg.Stress.AutoInstall, sudoPwd).Run(ip)
|
return NewRunner(stressCfg, ParseTypes(cfg.Stress.Types), conn, cfg.Stress.AutoInstall, sudoPwd).Run(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,43 @@ echo OK`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================
|
||||||
|
// Docker 容器化压测支持
|
||||||
|
// ============================
|
||||||
|
|
||||||
|
// DockerInfo 远程 Docker 环境信息
|
||||||
|
type DockerInfo struct {
|
||||||
|
Available bool // Docker 是否可用(已安装且有权限)
|
||||||
|
Image string // 镜像名(检测时传入,确认存在时回填)
|
||||||
|
HasImage bool // 目标镜像是否已存在本地(避免每次 pull)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetectDocker 检测远程主机的 Docker 环境。
|
||||||
|
// 包括:docker 命令是否存在、是否需要 sudo、镜像是否已存在。
|
||||||
|
// sudoPwd 为非 root 用户执行时的密码(用于 sudo docker)。
|
||||||
|
func DetectDocker(client *ssh.Client, image string, sudoPwd string) DockerInfo {
|
||||||
|
info := DockerInfo{Image: image}
|
||||||
|
|
||||||
|
// 1. 检测 docker 命令是否存在(支持 root 和 sudo)
|
||||||
|
if out, err := runPrivileged(client, "command -v docker", sudoPwd); err != nil || strings.TrimSpace(out) == "" {
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
info.Available = true
|
||||||
|
|
||||||
|
// 2. 检测 docker 服务是否运行(docker info 成功即可)
|
||||||
|
if _, err := runPrivileged(client, "docker info >/dev/null 2>&1", sudoPwd); err != nil {
|
||||||
|
info.Available = false
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检测目标镜像是否已存在本地(避免每次拉取)
|
||||||
|
if out, err := runPrivileged(client, fmt.Sprintf("docker image inspect %s >/dev/null 2>&1 && echo EXIST", image), sudoPwd); err == nil && strings.Contains(out, "EXIST") {
|
||||||
|
info.HasImage = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
// 远程工具检测
|
// 远程工具检测
|
||||||
// ============================
|
// ============================
|
||||||
|
|||||||
Reference in New Issue
Block a user