暂无图片
暂无图片
3
暂无图片
暂无图片
暂无图片

自建数据库运维平台,30秒安装一套数据库,支持一键安装和一键备份!源码公布!

原创 陈举超 2025-06-27
382

image.png
前几天借助AI手搓了一个简单的数据库运维平台,只支持一键安装MySQL数据库。
原文如下:

https://mp.weixin.qq.com/s/Y71e0MXMUBwlP24DIOZ4bA

今天新增了数据库备份功能,计划后面在添加一键巡检的功能。

数据库运维平台:

登录平台

http://192.168.1.5:5000/

添加了备份MySQL的功能

image.png

一键安装

通过平台,先一键安装一套新的测试MySQL,耗时不到30秒。
image.png
不到30秒安装完成
image.png

Mysql root默认密码: MyNewPass!123

[root@cjc-db-03 ~]# systemctl status mysqld.service 
● mysqld.service - MySQL Server
   Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)
   Active: active (running) since Fri 2025-06-27 17:18:24 CST; 2min 55s ago
     Docs: man:mysqld(8)
           http://dev.mysql.com/doc/refman/en/using-systemd.html
  Process: 5776 ExecStartPre=/usr/bin/mysqld_pre_systemd (code=exited, status=0/SUCCESS)
 Main PID: 5804 (mysqld)
   Status: "Server is operational"
   CGroup: /system.slice/mysqld.service
           └─5804 /usr/sbin/mysqld

Jun 27 17:18:22 cjc-db-03 systemd[1]: Starting MySQL Server...
Jun 27 17:18:24 cjc-db-03 systemd[1]: Started MySQL Server.

创建测试用户

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.42    |
+-----------+
1 row in set (0.00 sec)

mysql> create user cjc@'%' identified with mysql_native_password by "MyNewPass!123";
Query OK, 0 rows affected (0.00 sec)

mysql> grant all privileges on *.* to cjc@'%';
Query OK, 0 rows affected (0.00 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

创建测试数据

mysql> use cjc;
Database changed
mysql> create table t1 as select * from information_schema.processlist;
Query OK, 2 rows affected, 1 warning (0.02 sec)
Records: 2  Duplicates: 0  Warnings: 1

mysql> create table t2 as select * from mysql.user;
Query OK, 5 rows affected (0.02 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> create table t3 as select * from mysql.plugin;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

通过平台,一键备份

image.png
image.png
执行备份
image.png
备份成功
检查备份文件

[cjc@cjc-db-02 backups]$ ls -lrth
total 24K
-rw-rw-r-- 1 cjc cjc 2.4K Jun 27 17:52 backup_cjc_t1_20250627_175241.sql
-rw-rw-r-- 1 cjc cjc 7.8K Jun 27 17:52 backup_cjc_t2_20250627_175241.sql
-rw-rw-r-- 1 cjc cjc 2.4K Jun 27 18:03 backup_cjc_t1_20250627_180329.sql
-rw-rw-r-- 1 cjc cjc 7.8K Jun 27 18:03 backup_cjc_t2_20250627_180329.sql

[cjc@cjc-db-02 backups]$ more backup_cjc_t1_20250627_180329.sql
……

image.png

架构:

~/script_runner/
├── app.py                 # Flask 主应用
├── tasks.db               # SQLite 数据库
├── backups/               # 备份文件存储目录
├── scripts/               # MySQL 安装脚本
│   ├── install_deps.sh
│   ├── download_mysql.sh
│   ├── setup_mysql.sh
│   ├── configure_repl.sh
│   └── start_service.sh
└── templates/             # HTML 模板
    ├── index.html         # 安装页面
    └── backup.html        # 备份页面

deepseek_mermaid_20250627_72ec41.png

详细代码如下:

app.py

from flask import Flask, render_template, request
import sqlite3
import paramiko
import os
import time
from datetime import datetime

app = Flask(__name__)

# 配置
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
SCRIPTS_DIR = os.path.join(BASE_DIR, "scripts")
DB_PATH = os.path.join(BASE_DIR, "tasks.db")

# 初始化数据库
def init_db():
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS execution_logs (
                 id INTEGER PRIMARY KEY AUTOINCREMENT,
                 host TEXT NOT NULL,
                 script_name TEXT NOT NULL,
                 output TEXT,
                 start_time TIMESTAMP,
                 end_time TIMESTAMP,
                 task_type TEXT)''')
    conn.commit()
    conn.close()

# 执行远程脚本
def execute_remote_script(host, username, password, script_name):
    log_output = ""
    start_time = datetime.now()
    
    try:
        # 连接SSH
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(host, username=username, password=password, timeout=10)
        
        # 上传脚本
        local_script = os.path.join(SCRIPTS_DIR, script_name)
        remote_script = f"/tmp/{script_name}"
        
        sftp = ssh.open_sftp()
        sftp.put(local_script, remote_script)
        sftp.chmod(remote_script, 0o755)  # 设置可执行权限
        sftp.close()
        
        # 执行脚本
        log_output += f"开始执行脚本: {script_name}\n"
        command = f"bash {remote_script}"
        stdin, stdout, stderr = ssh.exec_command(command)
        
        # 获取输出
        stdout_output = stdout.read().decode('utf-8')
        stderr_output = stderr.read().decode('utf-8')
        
        log_output += stdout_output
        if stderr_output:
            log_output += "\n错误输出:\n" + stderr_output
        
        # 清理临时文件
        ssh.exec_command(f"rm -f {remote_script}")
        
        log_output += f"\n脚本执行完成: {script_name}\n"
        status = "success"
        
    except Exception as e:
        log_output += f"\n执行脚本时出错: {str(e)}\n"
        status = "failed"
    finally:
        end_time = datetime.now()
        ssh.close() if 'ssh' in locals() else None
    
    # 保存日志到数据库
    save_log_to_db(host, script_name, log_output, start_time, end_time)
    
    return log_output

# 保存日志到数据库
def save_log_to_db(host, script_name, output, start_time, end_time):
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute('''INSERT INTO execution_logs 
                (host, script_name, output, start_time, end_time) 
                VALUES (?, ?, ?, ?, ?)''',
              (host, script_name, output, start_time, end_time))
    conn.commit()
    conn.close()

# app.py 中新增函数

def perform_mysql_backup(ssh_host, ssh_user, ssh_pass, 
                         db_host, db_port, db_name, tables, db_user, db_pass):
    """执行MySQL备份操作"""
    log_output = ""
    start_time = datetime.now()
    
    try:
        # 连接SSH
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(ssh_host, username=ssh_user, password=ssh_pass, timeout=10)
        
        # 创建备份文件名
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_dir = os.path.join(BASE_DIR, "backups")
        os.makedirs(backup_dir, exist_ok=True)
        
        # 处理表名
        table_list = [t.strip() for t in tables.split(",")]
        backup_files = []  # 存储成功备份的文件名
        
        # 执行备份
        for table in table_list:
            backup_file = f"backup_{db_name}_{table}_{timestamp}.sql"
            local_path = os.path.join(backup_dir, backup_file)
            
            # 构建mysqldump命令
            dump_cmd = (
                f"mysqldump -h{db_host} -P{db_port} -u{db_user} -p'{db_pass}' "
                f"{db_name} {table} > /tmp/{backup_file}"
            )
            
            log_output += f"备份表: {table}...\n"
            
            # 执行备份命令
            stdin, stdout, stderr = ssh.exec_command(dump_cmd)
            
            # 获取输出
            stdout_output = stdout.read().decode('utf-8')
            stderr_output = stderr.read().decode('utf-8')
            
            # 检查是否有真正的错误(忽略安全警告)
            if stderr_output and "Warning" not in stderr_output:
                # 有真正的错误
                log_output += f"备份错误: {stderr_output}\n"
                raise Exception(f"备份表 {table} 失败")
            
            # 下载备份文件
            sftp = ssh.open_sftp()
            sftp.get(f"/tmp/{backup_file}", local_path)
            sftp.remove(f"/tmp/{backup_file}")  # 清理临时文件
            sftp.close()
            
            log_output += f"成功备份到: {local_path}\n"
            backup_files.append(backup_file)
        
        status = "success"
        
    except Exception as e:
        log_output += f"\n备份过程中出错: {str(e)}\n"
        status = "failed"
        backup_files = []
    finally:
        end_time = datetime.now()
        ssh.close() if 'ssh' in locals() else None
    
    # 保存日志到数据库
    save_backup_log(ssh_host, db_name, tables, log_output, start_time, end_time, status)
    
    return log_output, backup_files

def save_backup_log(host, db_name, tables, output, start_time, end_time, status):
    """保存备份日志到数据库"""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute('''INSERT INTO execution_logs 
                (host, script_name, output, start_time, end_time, task_type) 
                VALUES (?, ?, ?, ?, ?, ?)''',
              (host, f"backup_{db_name}", output, start_time, end_time, "backup"))
    conn.commit()
    conn.close()

# 路由定义
@app.route('/')
def index():
    return render_template('index.html', logs="请填写信息并执行脚本")

# 新增:备份页面路由
@app.route('/backup')
def backup_page():
    return render_template('backup.html', logs="请填写信息并执行备份")

@app.route('/execute', methods=['POST'])
def execute_scripts():
    host = request.form['host']
    username = request.form['username']
    password = request.form['password']
    scripts = request.form.getlist('scripts')
    
    if not scripts:
        return render_template('index.html', logs="错误:未选择任何脚本!")
    
    full_log = f"===== 开始执行MySQL安装任务 =====\n"
    full_log += f"目标主机: {host}\n"
    full_log += f"执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
    full_log += f"将执行 {len(scripts)} 个脚本\n\n"
    
    for script in scripts:
        script_log = execute_remote_script(host, username, password, script)
        full_log += script_log
        full_log += "\n" + "="*50 + "\n"
    
    full_log += "\n===== 所有脚本执行完成! ====="
    
    return render_template('index.html', logs=full_log)


# 执行备份
@app.route('/do_backup', methods=['POST'])
def execute_backup():
    # 获取表单数据
    host = request.form['host']
    username = request.form['username']
    password = request.form['password']
    db_host = request.form['db_host']
    db_port = request.form['db_port']
    db_name = request.form['db_name']
    tables = request.form['tables']
    db_user = request.form['db_user']
    db_pass = request.form['db_pass']
    
    full_log = f"===== 开始MySQL备份任务 =====\n"
    full_log += f"目标主机: {host}\n"
    full_log += f"数据库: {db_name}\n"
    full_log += f"表: {tables}\n"
    full_log += f"执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
    
    # 执行备份
    backup_log = perform_mysql_backup(
        host, username, password,
        db_host, db_port, db_name, tables, db_user, db_pass
    )
    
    full_log += format(backup_log)
    full_log += "\n===== 备份任务完成! ====="
    
    return render_template('backup.html', logs=full_log)


if __name__ == '__main__':
    init_db()
    app.run(host='0.0.0.0', port=5000, debug=True)

backup.html

<!DOCTYPE html>
<html>
<head>
    <title>MySQL备份工具</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        .navbar { background: #333; overflow: hidden; margin-bottom: 20px; }
        .navbar a { float: left; color: white; text-align: center; padding: 14px 16px; text-decoration: none; }
        .navbar a:hover { background: #ddd; color: black; }
        .form-group { margin-bottom: 15px; }
        label { display: block; margin-bottom: 5px; font-weight: bold; }
        input[type="text"], input[type="password"] { 
            width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; 
        }
        button { 
            background: #2196F3; color: white; border: none; padding: 10px 20px; 
            border-radius: 4px; cursor: pointer; font-size: 16px; 
        }
        button:hover { background: #0b7dda; }
        .logs { 
            background: #333; color: #fff; padding: 15px; border-radius: 4px; 
            margin-top: 20px; max-height: 400px; overflow-y: auto; 
        }
    </style>
</head>
<body>
    
    <h1>MySQL数据库备份</h1>
    
    <form action="/do_backup" method="POST">

        <div class="navbar">
            <a href="/">安装MySQL</a>
            <a href="/backup">备份MySQL</a>
        </div>

        <h3>SSH连接信息:</h3>
        <div class="form-group">
            <label for="host">目标主机IP:</label>
            <input type="text" id="host" name="host" placeholder="例如: 192.168.1.100" required>
        </div>
        
        <div class="form-group">
            <label for="username">SSH用户名:</label>
            <input type="text" id="username" name="username" placeholder="例如: root" required>
        </div>
        
        <div class="form-group">
            <label for="password">SSH密码:</label>
            <input type="password" id="password" name="password" placeholder="输入密码" required>
        </div>
        
        <h3>数据库信息:</h3>
        <div class="form-group">
            <label for="db_host">数据库IP:</label>
            <input type="text" id="db_host" name="db_host" value="localhost" required>
        </div>
        
        <div class="form-group">
            <label for="db_port">数据库端口:</label>
            <input type="text" id="db_port" name="db_port" value="3306" required>
        </div>
        
        <div class="form-group">
            <label for="db_name">数据库名称:</label>
            <input type="text" id="db_name" name="db_name" placeholder="例如: mydb" required>
        </div>
        
        <div class="form-group">
            <label for="tables">表名(多个表用逗号分隔):</label>
            <input type="text" id="tables" name="tables" placeholder="例如: users,products" required>
        </div>
        
        <div class="form-group">
            <label for="db_user">数据库用户名:</label>
            <input type="text" id="db_user" name="db_user" value="root" required>
        </div>
        
        <div class="form-group">
            <label for="db_pass">数据库密码:</label>
            <input type="password" id="db_pass" name="db_pass" placeholder="数据库密码" required>
        </div>
        
        <button type="submit">执行备份</button>
    </form>
    
    <h3>备份日志:</h3>
    <pre class="logs">{{ logs }}</pre>
</body>
</html>

index.html

<!DOCTYPE html>
<html>
<head>
    <title>简易MySQL安装平台</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        h1 { text-align: center; color: #333; }
        form { background: #f9f9f9; padding: 20px; border-radius: 5px; }
        .form-group { margin-bottom: 15px; }
        label { display: block; margin-bottom: 5px; font-weight: bold; }
        input[type="text"], input[type="password"] { 
            width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; 
        }
        .script-item { margin: 10px 0; padding: 10px; background: #fff; border-left: 4px solid #4CAF50; }
        button { 
            background: #4CAF50; color: white; border: none; padding: 10px 20px; 
            border-radius: 4px; cursor: pointer; font-size: 16px; 
        }
        button:hover { background: #45a049; }
        .logs { 
            background: #333; color: #fff; padding: 15px; border-radius: 4px; 
            margin-top: 20px; max-height: 400px; overflow-y: auto; 
        }
    </style>
</head>
<body>
    <h1>智库DBA:MySQL自动化安装平台</h1>
    
    <form action="/execute" method="POST">
	<div class="navbar">
    	    <a href="/">安装MySQL</a>
    	    <a href="/backup">备份MySQL</a>
	</div>

        <div class="form-group">
            <label for="host">目标主机IP:</label>
            <input type="text" id="host" name="host" placeholder="例如: 192.168.1.100" required>
        </div>
        
        <div class="form-group">
            <label for="username">SSH用户名:</label>
            <input type="text" id="username" name="username" placeholder="例如: root" required>
        </div>
        
        <div class="form-group">
            <label for="password">SSH密码:</label>
            <input type="password" id="password" name="password" placeholder="输入密码" required>
        </div>
        
        <h3>选择要执行的MySQL安装步骤:</h3>
        
        <div class="script-item">
            <input type="checkbox" id="script1" name="scripts" value="install_deps.sh" checked>
            <label for="script1">1. 安装系统依赖</label>
        </div>
        
        <div class="script-item">
            <input type="checkbox" id="script2" name="scripts" value="download_mysql.sh" checked>
            <label for="script2">2. 下载并安装MySQL</label>
        </div>
        
        <div class="script-item">
            <input type="checkbox" id="script3" name="scripts" value="setup_mysql.sh" checked>
            <label for="script3">3. 初始化MySQL配置</label>
        </div>
        
        <div class="script-item">
            <input type="checkbox" id="script4" name="scripts" value="configure_repl.sh">
            <label for="script4">4. 配置复制(可选)</label>
        </div>
        
        <div class="script-item">
            <input type="checkbox" id="script5" name="scripts" value="start_service.sh" checked>
            <label for="script5">5. 启动MySQL服务</label>
        </div>
        
        <button type="submit">执行安装</button>
    </form>
    
    <h3>执行日志:</h3>
    <pre class="logs">{{ logs }}</pre>
</body>
</html>

欢迎关注我的公众号《IT小Chen
image.png
近期和多位专家联合创建了两个知识星球,限时优惠100元,感兴趣的可以看看,支持72小时内无理由退款。
星球优惠券 1.png
星球优惠券 2.png

最后修改时间:2025-06-27 15:17:36
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论