#!/usr/bin/env python3 """ Docker 监控 API 路由 """ import subprocess import json from flask import jsonify, request from utils import log_message def _run_docker(cmd, timeout=10): """执行 docker 命令并返回解析结果""" try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) if result.returncode != 0: return None, result.stderr.strip() return result.stdout.strip(), None except subprocess.TimeoutExpired: return None, '命令超时' except FileNotFoundError: return None, 'Docker 未安装' except Exception as e: return None, str(e) def register_docker_routes(app): """注册 Docker 监控相关路由""" @app.route('/api/docker/containers') def api_docker_containers(): """获取所有容器列表(含资源使用)""" # 获取容器列表 fmt = '{{.ID}}|{{.Names}}|{{.Image}}|{{.State}}|{{.Status}}|{{.Ports}}|{{.CreatedAt}}|{{.RunningFor}}' out, err = _run_docker(['docker', 'ps', '-a', '--format', fmt, '--no-trunc']) if out is None: return jsonify({'error': err, 'containers': []}) containers = [] for line in out.split('\n'): if not line.strip(): continue parts = line.split('|') if len(parts) < 8: continue cid = parts[0][:12] # 缩短ID containers.append({ 'id': cid, 'name': parts[1], 'image': parts[2], 'state': parts[3], 'status': parts[4], 'ports': parts[5], 'created': parts[6], 'runningFor': parts[7] }) # 批量获取运行中容器的stats running = [c for c in containers if c['state'] == 'running'] if running: try: stats_out, _ = _run_docker([ 'docker', 'stats', '--no-stream', '--no-trunc', '--format', '{{.Name}}|{{.CPUPerc}}|{{.MemPerc}}|{{.MemUsage}}|{{.NetIO}}|{{.BlockIO}}|{{.PIDs}}' ], timeout=15) if stats_out: stats_map = {} for line in stats_out.split('\n'): if not line.strip(): continue sp = line.split('|') if len(sp) >= 7: stats_map[sp[0]] = { 'cpuPercent': sp[1], 'memPercent': sp[2], 'memUsage': sp[3], 'netIO': sp[4], 'blockIO': sp[5], 'pids': sp[6] } for c in containers: if c['name'] in stats_map: c['stats'] = stats_map[c['name']] except: pass return jsonify({'containers': containers}) @app.route('/api/docker/container//stats') def api_docker_container_stats(container_id): """获取单个容器实时资源统计""" out, err = _run_docker([ 'docker', 'stats', '--no-stream', '--no-trunc', '--format', '{{.CPUPerc}}|{{.MemPerc}}|{{.MemUsage}}|{{.NetIO}}|{{.BlockIO}}|{{.PIDs}}', container_id ], timeout=10) if out is None: return jsonify({'error': err}), 500 if not out: return jsonify({'error': '容器不存在或未运行'}), 404 parts = out.split('|') return jsonify({ 'cpuPercent': parts[0] if len(parts) > 0 else '0%', 'memPercent': parts[1] if len(parts) > 1 else '0%', 'memUsage': parts[2] if len(parts) > 2 else '', 'netIO': parts[3] if len(parts) > 3 else '', 'blockIO': parts[4] if len(parts) > 4 else '', 'pids': parts[5] if len(parts) > 5 else '0' }) @app.route('/api/docker/images') def api_docker_images(): """获取所有镜像列表""" fmt = '{{.Repository}}|{{.Tag}}|{{.ID}}|{{.Size}}|{{.CreatedAt}}|{{.Containers}}' out, err = _run_docker(['docker', 'images', '--format', fmt, '--no-trunc']) if out is None: return jsonify({'error': err, 'images': []}) images = [] for line in out.split('\n'): if not line.strip(): continue parts = line.split('|') if len(parts) < 6: continue repo = parts[0] if parts[0] != '' else '' tag = parts[1] if parts[1] != '' else '' images.append({ 'repository': repo, 'tag': tag, 'id': parts[2][:12], 'size': parts[3], 'created': parts[4], 'containers': parts[5], 'dangling': not repo and not tag }) return jsonify({'images': images}) @app.route('/api/docker/system') def api_docker_system(): """获取 Docker 系统信息""" # 版本信息 ver_out, _ = _run_docker(['docker', 'version', '--format', '{{.Server.Version}}|{{.Server.Os}}|{{.Server.Arch}}'], timeout=5) # 磁盘使用 df_out, _ = _run_docker(['docker', 'system', 'df', '--format', '{{.Type}}|{{.TotalCount}}|{{.Active}}|{{.Size}}|{{.Reclaimable}}'], timeout=10) system_info = { 'version': '', 'os': '', 'arch': '', 'diskUsage': [] } if ver_out: ver_parts = ver_out.split('|') if len(ver_parts) >= 3: system_info.update({ 'version': ver_parts[0], 'os': ver_parts[1], 'arch': ver_parts[2] }) if df_out: for line in df_out.split('\n'): if not line.strip(): continue parts = line.split('|') if len(parts) >= 5: system_info['diskUsage'].append({ 'type': parts[0], 'total': parts[1], 'active': parts[2], 'size': parts[3], 'reclaimable': parts[4] }) return jsonify(system_info) @app.route('/api/docker/container//logs') def api_docker_container_logs(container_id): """获取容器日志(最近200行)""" lines = int(request.args.get('lines', 200)) lines = max(10, min(1000, lines)) out, err = _run_docker(['docker', 'logs', '--tail', str(lines), container_id], timeout=10) if out is None: return jsonify({'error': err}), 500 return jsonify({'logs': out, 'lines': lines}) @app.route('/api/docker/container//start', methods=['POST']) def api_docker_container_start(container_id): """启动容器""" out, err = _run_docker(['docker', 'start', container_id]) if out is None and err: return jsonify({'error': err}), 500 log_message(f"Docker 容器启动: {container_id}") return jsonify({'message': f'容器 {container_id} 已启动'}) @app.route('/api/docker/container//stop', methods=['POST']) def api_docker_container_stop(container_id): """停止容器""" out, err = _run_docker(['docker', 'stop', container_id]) if out is None and err: return jsonify({'error': err}), 500 log_message(f"Docker 容器停止: {container_id}") return jsonify({'message': f'容器 {container_id} 已停止'}) @app.route('/api/docker/container//restart', methods=['POST']) def api_docker_container_restart(container_id): """重启容器""" out, err = _run_docker(['docker', 'restart', container_id]) if out is None and err: return jsonify({'error': err}), 500 log_message(f"Docker 容器重启: {container_id}") return jsonify({'message': f'容器 {container_id} 已重启'}) @app.route('/api/docker/prune', methods=['POST']) def api_docker_prune(): """清理Docker资源""" target = request.json.get('target', 'all') if request.json else 'all' results = {} # 清理停止的容器 if target in ('all', 'containers'): out, _ = _run_docker(['docker', 'container', 'prune', '-f'], timeout=30) results['containers'] = out or '已清理' # 清理悬空镜像 if target in ('all', 'images'): out, _ = _run_docker(['docker', 'image', 'prune', '-f'], timeout=60) results['images'] = out or '已清理' # 清理构建缓存 if target in ('all', 'build'): out, _ = _run_docker(['docker', 'builder', 'prune', '-f'], timeout=60) results['build'] = out or '已清理' log_message(f"Docker 资源清理完成: {target}") return jsonify({'message': '清理完成', 'results': results})