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

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

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

    添加了备份MySQL的功能

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

    不到30秒安装完成

    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

          通过平台,一键备份

          执行备份

          备份成功

          检查备份文件

            [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
            ……

            架构介绍

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

              详细代码如下:

              app.py

                [cjc@cjc-db-02 script_runner]$ cat 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 += 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>

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

                  知识星球,限时优惠

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

                  文章转载自小周的数据库进阶之路,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                  评论