青年数据库学习互助会

30秒安装一套MySQL数据库,支持远程数据库一键安装和一键备份!这个数据库运维平台,源码公布!

Image
前几天借助AI手搓了一个简单的数据库运维平台,只支持一键安装MySQL数据库。
200行代码,手搓一个简单的数据库运维平台,支持远程一键安装数据库!
今天新增了数据库备份功能,计划后面在添加一键巡检的功能。
先看效果:
登录平台
http://192.168.1.5:5000/

添加了备份MySQL的功能

Image

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

Image

Image

不到30秒安装完成

Image

Mysql root默认密码: MyNewPass!123

[root@cjc-db-03 ~]# systemctl status mysqld.service● mysqld.service - MySQL ServerLoaded: 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 agoDocs: man:mysqld(8)http://dev.mysql.com/doc/refman/en/using-systemd.htmlProcess: 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/mysqldJun 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 changedmysql> create table t1 as select * from information_schema.processlist;Query OK, 2 rows affected, 1 warning (0.02 sec)Records: 2Duplicates: 0Warnings: 1mysql> create table t2 as select * from mysql.user;Query OK, 5 rows affected (0.02 sec)Records: 5Duplicates: 0Warnings: 0mysql> create table t3 as select * from mysql.plugin;Query OK, 0 rows affected (0.01 sec)Records: 0Duplicates: 0Warnings: 0

通过平台,一键备份

Image

Image

执行备份

Image

备份成功

检查备份文件

[cjc@cjc-db-02 backups]$ ls -lrthtotal 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

架构介绍

~/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        # 备份页面

Image

详细代码如下:

app.py

[cjc@cjc-db-02 script_runner]$ cat app.py from flask import Flask, render_template, requestimport sqlite3import paramikoimport osimport timefrom datetime import datetimeapp = 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_filesdef 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 += 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>    <formaction="/do_backup"method="POST">        <divclass="navbar">            <ahref="/">安装MySQL</a>            <ahref="/backup">备份MySQL</a>        </div>        <h3>SSH连接信息:</h3>        <divclass="form-group">            <labelfor="host">目标主机IP:</label>            <inputtype="text"id="host"name="host"placeholder="例如: 192.168.1.100"required>        </div>        <divclass="form-group">            <labelfor="username">SSH用户名:</label>            <inputtype="text"id="username"name="username"placeholder="例如: root"required>        </div>        <divclass="form-group">            <labelfor="password">SSH密码:</label>            <inputtype="password"id="password"name="password"placeholder="输入密码"required>        </div>        <h3>数据库信息:</h3>        <divclass="form-group">            <labelfor="db_host">数据库IP:</label>            <inputtype="text"id="db_host"name="db_host"value="localhost"required>        </div>        <divclass="form-group">            <labelfor="db_port">数据库端口:</label>            <inputtype="text"id="db_port"name="db_port"value="3306"required>        </div>        <divclass="form-group">            <labelfor="db_name">数据库名称:</label>            <inputtype="text"id="db_name"name="db_name"placeholder="例如: mydb"required>        </div>        <divclass="form-group">            <labelfor="tables">表名(多个表用逗号分隔):</label>            <inputtype="text"id="tables"name="tables"placeholder="例如: users,products"required>        </div>        <divclass="form-group">            <labelfor="db_user">数据库用户名:</label>            <inputtype="text"id="db_user"name="db_user"value="root"required>        </div>        <divclass="form-group">            <labelfor="db_pass">数据库密码:</label>            <inputtype="password"id="db_pass"name="db_pass"placeholder="数据库密码"required>        </div>        <buttontype="submit">执行备份</button>    </form>    <h3>备份日志:</h3>    <preclass="logs">{{ logs }}</pre></body></html>

欢迎关注我的公众号《IT小Chen》

Image

知识星球,限时优惠

近期和多位专家联合创建了两个知识星球,一个是面试方向的,有大量的面试真题和解析,另一个是AI运维方向的。限时优惠100元,感兴趣的可以看看,支持72小时内无理由退款。

Image
Image