MySQL 數(shù)據(jù)庫二進制版本安裝操作步驟:
1. 首先先上傳數(shù)據(jù)庫安裝包到linux操作系統(tǒng)
2. 添加一塊新的硬盤, 用來存儲數(shù)據(jù)庫數(shù)據(jù)信息
3. 格式化硬盤
mkfs.xfs /dev/sdb
4. 創(chuàng)建數(shù)據(jù)庫存放數(shù)據(jù)的路徑
mkdir -p /data
5. 掛載并檢查
mount /dev/sdb /data
df -h
6. 添加磁盤為永久掛載
vim /etc/fstab
/dev/sdb /data/ xfs defaults 0 0
umount -lf /data
mount -a
7. 加壓上傳的數(shù)據(jù)庫安裝包
[root@shell~]#cd /usr/local/
[root@shell/usr/local]#tar xf mysql-5.7.26-linux-glibc2.12-x86_64.tar.gz
8. 解壓完成, 創(chuàng)建連接文件 或者使用mv命令修改文件名稱
[root@shell/usr/local]#ln -s mysql-5.7.26-linux-glibc2.12-x86_64 mysql
9. 創(chuàng)建數(shù)據(jù)庫數(shù)據(jù)存放目錄
mkdir -p /data/mysql/data
10 創(chuàng)建MySQL數(shù)據(jù)庫虛擬用戶用來管理MySQL數(shù)據(jù)庫
useradd -M -s /sbin/nologin mysql
11. 進行授權(quán)路徑
[root@shell/usr/local]#chown -R mysql.mysql /usr/local/mysql /data/mysql/data/
12. 定義軟連接 讓系統(tǒng)可以直接調(diào)用數(shù)據(jù)庫命令
ln -s /usr/local/mysql/bin/* /usr/local/bin
13 . 編寫系統(tǒng)環(huán)境變量配置文件, 讓系統(tǒng)可以直接調(diào)用數(shù)據(jù)庫命令 并刷新讓其生效
vim /etc/profile
PATH=/usr/local/mysql/bin:$PATH
source /etc/profile
14. 復(fù)制mysql數(shù)據(jù)庫的啟動腳本到 /etc/init.d/目錄下 添加執(zhí)行權(quán)限, 添加到開機自啟動
[root@shell/usr/local]#cp /usr/local/mysql/support-files/mysql.server [root@shell/usr/local]#/etc/init.d/mysqld
[root@shell/usr/local]# chmod +x /etc/init.d/mysqld
[root@shell/usr/local]# chkconfig --add mysqld
15. 編寫mysql 數(shù)據(jù)庫主配置文件
vim /etc/my.cnf
[mysqld]
user=mysql
mysqlx=0
basedir=/usr/local/mysql
datadir=/data/mysql/data
port=3306
server_id=13
socket=/tmp/mysql.sock
[mysql]
socket=/tmp/mysql.sock
16. 初始化數(shù)據(jù)庫
mysqld --initialize-insecure --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql/data
17. 啟動數(shù)據(jù)庫
systemctl start mysqld
18. 檢查數(shù)據(jù)庫服務(wù)是否啟動成功
ps -ef |grep mysqld && netstat -anpt |grep mysqld
19.登錄數(shù)據(jù)庫測試
mysql 直接登錄
腳本化
#!/bin/bash
# MySQL生產(chǎn)環(huán)境安裝腳本
# 版本: 1.0
# 作者: 系統(tǒng)管理員
# 描述: 用于在生產(chǎn)環(huán)境安裝MySQL 9.4.0
set -euo pipefail # 嚴格模式: 遇到錯誤退出,使用未定義變量退出,管道錯誤退出
# 顏色定義
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 日志函數(shù)
log() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $*${NC}"
}
warn() {
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $*${NC}"
}
error() {
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $*${NC}" >&2
exit 1
}
# 檢查是否以root用戶運行
check_root() {
if [[ $EUID -ne 0 ]]; then
error "此腳本必須以root用戶運行"
fi
}
# 定義變量
MYSQL_TAR="mysql-9.4.0-linux-glibc2.28-x86_64.tar.xz"
MYSQL_DIR="/usr/local/mysql"
DATA_DIR="/data/mysql/data"
MYSQL_USER="mysql"
MYSQL_GROUP="mysql"
SERVICE_FILE="/etc/init.d/mysqld"
CONFIG_FILE="/etc/my.cnf"
# 檢查文件是否存在
check_files() {
log "檢查必要的文件..."
if [[ ! -f "$MYSQL_TAR" ]]; then
error "MySQL安裝包 $MYSQL_TAR 不存在"
fi
if [[ ! -d "/usr/local" ]]; then
error "/usr/local 目錄不存在"
fi
}
# 備份原有配置
backup_existing() {
log "備份現(xiàn)有MySQL配置..."
# 備份my.cnf
if [[ -f "$CONFIG_FILE" ]]; then
cp -p "$CONFIG_FILE" "${CONFIG_FILE}.bak.$(date +%Y%m%d%H%M%S)"
log "已備份 $CONFIG_FILE"
fi
# 備份服務(wù)腳本
if [[ -f "$SERVICE_FILE" ]]; then
cp -p "$SERVICE_FILE" "${SERVICE_FILE}.bak.$(date +%Y%m%d%H%M%S)"
log "已備份 $SERVICE_FILE"
fi
}
# 創(chuàng)建MySQL用戶和組
create_mysql_user() {
log "創(chuàng)建MySQL用戶和組..."
if ! id "$MYSQL_USER" &>/dev/null; then
groupadd -r "$MYSQL_GROUP" || warn "用戶組 $MYSQL_GROUP 可能已存在"
useradd -r -g "$MYSQL_GROUP" -s /bin/false "$MYSQL_USER" || warn "用戶 $MYSQL_USER 可能已存在"
log "MySQL用戶和組創(chuàng)建完成"
else
log "MySQL用戶已存在"
fi
}
# 創(chuàng)建數(shù)據(jù)目錄
create_data_dir() {
log "創(chuàng)建數(shù)據(jù)目錄..."
if [[ ! -d "$DATA_DIR" ]]; then
mkdir -p "$DATA_DIR"
log "數(shù)據(jù)目錄 $DATA_DIR 創(chuàng)建完成"
else
warn "數(shù)據(jù)目錄 $DATA_DIR 已存在"
fi
}
# 安裝MySQL
install_mysql() {
log "開始安裝MySQL..."
# 解壓安裝包
log "解壓MySQL安裝包..."
if ! tar xf "$MYSQL_TAR" -C /usr/local/; then
error "解壓MySQL安裝包失敗"
fi
# 重命名目錄
log "重命名MySQL目錄..."
local extracted_dir="/usr/local/mysql-9.4.0-linux-glibc2.28-x86_64"
if [[ -d "$extracted_dir" ]]; then
if [[ -d "$MYSQL_DIR" ]]; then
mv "$MYSQL_DIR" "${MYSQL_DIR}.bak.$(date +%Y%m%d%H%M%S)"
warn "原有MySQL目錄已備份"
fi
mv "$extracted_dir" "$MYSQL_DIR"
else
error "解壓后的目錄不存在: $extracted_dir"
fi
# 設(shè)置權(quán)限
log "設(shè)置目錄權(quán)限..."
chown -R "${MYSQL_USER}.${MYSQL_GROUP}" "$MYSQL_DIR" "/data"
# 創(chuàng)建符號鏈接
log "創(chuàng)建二進制文件符號鏈接..."
for bin_file in "$MYSQL_DIR"/bin/*; do
if [[ -x "$bin_file" ]]; then
ln -sf "$bin_file" "/usr/local/bin/"
fi
done
}
# 配置MySQL服務(wù)
configure_service() {
log "配置MySQL服務(wù)..."
# 復(fù)制服務(wù)腳本
if [[ -f "$MYSQL_DIR/support-files/mysql.server" ]]; then
cp -a "$MYSQL_DIR/support-files/mysql.server" "$SERVICE_FILE"
chmod +x "$SERVICE_FILE"
log "服務(wù)腳本已復(fù)制"
else
warn "MySQL服務(wù)腳本不存在,跳過復(fù)制"
fi
# 配置chkconfig
if command -v chkconfig &>/dev/null; then
chkconfig --add mysqld
log "MySQL服務(wù)已添加到chkconfig"
else
warn "chkconfig命令不存在,跳過服務(wù)配置"
fi
}
# 創(chuàng)建配置文件
create_config() {
log "創(chuàng)建MySQL配置文件..."
cat > "$CONFIG_FILE" << EOF
[mysqld]
basedir=$MYSQL_DIR
datadir=$DATA_DIR
port=3306
socket=/tmp/mysql.sock
user=$MYSQL_USER
log-error=$DATA_DIR/mysql.error.log
pid-file=$DATA_DIR/mysql.pid
# 性能調(diào)優(yōu)參數(shù) (根據(jù)實際情況調(diào)整)
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
max_connections=1000
innodb_buffer_pool_size=1G
innodb_log_file_size=256M
[mysql]
socket=/tmp/mysql.sock
default-character-set=utf8mb4
[client]
socket=/tmp/mysql.sock
EOF
log "MySQL配置文件已創(chuàng)建: $CONFIG_FILE"
}
# 移除沖突的MariaDB包
remove_mariadb() {
log "檢查并移除沖突的MariaDB包..."
local mariadb_packages
mariadb_packages=$(rpm -qa | grep -E '^mariadb' || true)
if [[ -n "$mariadb_packages" ]]; then
log "發(fā)現(xiàn)以下MariaDB包:"
echo "$mariadb_packages"
for package in $mariadb_packages; do
warn "移除包: $package"
rpm -e --nodeps "$package" || warn "移除包 $package 失敗"
done
else
log "未發(fā)現(xiàn)沖突的MariaDB包"
fi
}
# 初始化MySQL
initialize_mysql() {
log "初始化MySQL..."
# 檢查數(shù)據(jù)目錄是否為空
if [[ "$(ls -A $DATA_DIR 2>/dev/null)" ]]; then
warn "數(shù)據(jù)目錄 $DATA_DIR 不為空,跳過初始化"
return 0
fi
log "執(zhí)行MySQL初始化..."
if ! mysqld --initialize-insecure \
--user="$MYSQL_USER" \
--basedir="$MYSQL_DIR" \
--datadir="$DATA_DIR" 2>&1 | tee "/tmp/mysql_initialize.log"; then
error "MySQL初始化失敗,請檢查日志: /tmp/mysql_initialize.log"
fi
log "MySQL初始化完成"
}
# 啟動MySQL服務(wù)
start_mysql() {
log "啟動MySQL服務(wù)..."
# 嘗試使用systemctl
if command -v systemctl &>/dev/null; then
if systemctl start mysqld; then
log "使用systemctl啟動MySQL成功"
else
warn "systemctl啟動失敗,嘗試使用service命令"
service mysqld start || error "啟動MySQL服務(wù)失敗"
fi
elif command -v service &>/dev/null; then
service mysqld start || error "啟動MySQL服務(wù)失敗"
log "使用service啟動MySQL成功"
else
warn "無法找到systemctl或service命令,請手動啟動MySQL"
return 1
fi
# 檢查服務(wù)狀態(tài)
sleep 3
if pgrep mysqld >/dev/null; then
log "MySQL進程運行正常"
else
error "MySQL進程未運行"
fi
# 檢查端口監(jiān)聽
if netstat -anpt 2>/dev/null | grep -q ':3306.*LISTEN'; then
log "MySQL正在監(jiān)聽3306端口"
else
warn "MySQL未在3306端口監(jiān)聽,請檢查日志"
fi
}
# 安全設(shè)置
security_setup() {
log "執(zhí)行MySQL安全設(shè)置..."
# 等待MySQL完全啟動
sleep 5
# 設(shè)置root密碼(生產(chǎn)環(huán)境應(yīng)該從安全的地方獲取密碼)
local mysql_root_password
mysql_root_password=$(openssl rand -base64 16)
log "生成的MySQL root密碼: $mysql_root_password"
echo "MySQL root密碼: $mysql_root_password" > /root/mysql_root_password.txt
chmod 600 /root/mysql_root_password.txt
# 使用mysql_secure_installation的簡化版本
mysql -u root --skip-password << EOF
ALTER USER 'root'@'localhost' IDENTIFIED BY '${mysql_root_password}';
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
FLUSH PRIVILEGES;
EOF
log "MySQL安全設(shè)置完成,root密碼已保存到 /root/mysql_root_password.txt"
}
# 主函數(shù)
main() {
log "開始MySQL生產(chǎn)環(huán)境安裝..."
check_root
check_files
backup_existing
create_mysql_user
create_data_dir
remove_mariadb
install_mysql
configure_service
create_config
initialize_mysql
start_mysql
security_setup
log "MySQL安裝完成!"
log "請立即修改root密碼并執(zhí)行以下命令驗證安裝:"
echo " mysql -u root -p"
echo " systemctl status mysqld"
echo " netstat -anpt | grep 3306"
}
# 腳本執(zhí)行入口
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
#!/usr/bin/env python3
"""
MySQL生產(chǎn)環(huán)境安裝腳本
版本: 1.0
描述: 用于在生產(chǎn)環(huán)境安裝MySQL 9.4.0
"""
import os
import sys
import subprocess
import shutil
import time
import logging
import tarfile
import getpass
from pathlib import Path
from datetime import datetime
import secrets
import string
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='\033[92m[%(asctime)s] %(message)s\033[0m',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)
# 顏色代碼
class Colors:
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
BLUE = '\033[0;34m'
NC = '\033[0m' # No Color
# 自定義日志級別
def log_info(message):
logger.info(message)
def log_warning(message):
print(f"{Colors.YELLOW}[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] WARNING: {message}{Colors.NC}")
def log_error(message, exit_script=True):
print(f"{Colors.RED}[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] ERROR: {message}{Colors.NC}")
if exit_script:
sys.exit(1)
def run_command(cmd, check=True, capture_output=False):
"""執(zhí)行shell命令"""
try:
log_info(f"執(zhí)行命令: {cmd}")
if capture_output:
result = subprocess.run(cmd, shell=True, check=check,
capture_output=True, text=True)
return result
else:
result = subprocess.run(cmd, shell=True, check=check)
return result
except subprocess.CalledProcessError as e:
if check:
log_error(f"命令執(zhí)行失敗: {cmd}\n錯誤: {e}")
else:
log_warning(f"命令執(zhí)行失敗: {cmd}\n錯誤: {e}")
return None
except Exception as e:
if check:
log_error(f"執(zhí)行命令時發(fā)生異常: {cmd}\n異常: {e}")
else:
log_warning(f"執(zhí)行命令時發(fā)生異常: {cmd}\n異常: {e}")
return None
class MySQLInstaller:
def __init__(self):
self.mysql_tar = "mysql-9.4.0-linux-glibc2.28-x86_64.tar.xz"
self.mysql_dir = Path("/usr/local/mysql")
self.data_dir = Path("/data/mysql/data")
self.mysql_user = "mysql"
self.mysql_group = "mysql"
self.service_file = Path("/etc/init.d/mysqld")
self.config_file = Path("/etc/my.cnf")
def check_root(self):
"""檢查是否以root用戶運行"""
if os.geteuid() != 0:
log_error("此腳本必須以root用戶運行")
def check_files(self):
"""檢查必要的文件"""
log_info("檢查必要的文件...")
if not os.path.isfile(self.mysql_tar):
log_error(f"MySQL安裝包 {self.mysql_tar} 不存在")
if not os.path.isdir("/usr/local"):
log_error("/usr/local 目錄不存在")
log_info("文件檢查完成")
def backup_existing(self):
"""備份原有配置"""
log_info("備份現(xiàn)有MySQL配置...")
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
# 備份my.cnf
if self.config_file.exists():
backup_file = f"{self.config_file}.bak.{timestamp}"
shutil.copy2(self.config_file, backup_file)
log_info(f"已備份 {self.config_file} 到 {backup_file}")
# 備份服務(wù)腳本
if self.service_file.exists():
backup_file = f"{self.service_file}.bak.{timestamp}"
shutil.copy2(self.service_file, backup_file)
log_info(f"已備份 {self.service_file} 到 {backup_file}")
# 備份原有MySQL目錄
if self.mysql_dir.exists():
backup_dir = f"{self.mysql_dir}.bak.{timestamp}"
shutil.move(self.mysql_dir, backup_dir)
log_info(f"已備份原有MySQL目錄到 {backup_dir}")
def create_mysql_user(self):
"""創(chuàng)建MySQL用戶和組"""
log_info("創(chuàng)建MySQL用戶和組...")
try:
# 檢查用戶組是否存在
run_command(f"getent group {self.mysql_group}", check=False)
run_command(f"groupadd -r {self.mysql_group}", check=False)
log_info(f"用戶組 {self.mysql_group} 創(chuàng)建完成")
except Exception as e:
log_warning(f"創(chuàng)建用戶組時發(fā)生異常: {e}")
try:
# 檢查用戶是否存在
run_command(f"id {self.mysql_user}", check=False)
run_command(f"useradd -r -g {self.mysql_group} -s /bin/false {self.mysql_user}", check=False)
log_info(f"用戶 {self.mysql_user} 創(chuàng)建完成")
except Exception as e:
log_warning(f"創(chuàng)建用戶時發(fā)生異常: {e}")
def create_data_dir(self):
"""創(chuàng)建數(shù)據(jù)目錄"""
log_info("創(chuàng)建數(shù)據(jù)目錄...")
try:
self.data_dir.mkdir(parents=True, exist_ok=True)
log_info(f"數(shù)據(jù)目錄 {self.data_dir} 創(chuàng)建完成")
except Exception as e:
log_error(f"創(chuàng)建數(shù)據(jù)目錄失敗: {e}")
def install_mysql(self):
"""安裝MySQL"""
log_info("開始安裝MySQL...")
# 解壓安裝包
log_info("解壓MySQL安裝包...")
try:
with tarfile.open(self.mysql_tar, 'r:xz') as tar:
tar.extractall(path='/usr/local/')
log_info("MySQL安裝包解壓完成")
except Exception as e:
log_error(f"解壓MySQL安裝包失敗: {e}")
# 重命名目錄
log_info("重命名MySQL目錄...")
extracted_dir = Path("/usr/local/mysql-9.4.0-linux-glibc2.28-x86_64")
if not extracted_dir.exists():
log_error(f"解壓后的目錄不存在: {extracted_dir}")
if self.mysql_dir.exists():
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
backup_dir = Path(f"{self.mysql_dir}.bak.{timestamp}")
shutil.move(self.mysql_dir, backup_dir)
log_warning(f"原有MySQL目錄已備份到 {backup_dir}")
shutil.move(str(extracted_dir), str(self.mysql_dir))
log_info("MySQL目錄重命名完成")
# 設(shè)置權(quán)限
log_info("設(shè)置目錄權(quán)限...")
run_command(f"chown -R {self.mysql_user}.{self.mysql_group} {self.mysql_dir}")
run_command(f"chown -R {self.mysql_user}.{self.mysql_group} /data")
# 創(chuàng)建符號鏈接
log_info("創(chuàng)建二進制文件符號鏈接...")
bin_dir = self.mysql_dir / "bin"
if bin_dir.exists():
for bin_file in bin_dir.iterdir():
if bin_file.is_file() and os.access(bin_file, os.X_OK):
target_link = Path("/usr/local/bin") / bin_file.name
if target_link.exists() or target_link.is_symlink():
target_link.unlink()
target_link.symlink_to(bin_file)
log_info("二進制文件符號鏈接創(chuàng)建完成")
else:
log_warning(f"MySQL bin目錄不存在: {bin_dir}")
def configure_service(self):
"""配置MySQL服務(wù)"""
log_info("配置MySQL服務(wù)...")
# 復(fù)制服務(wù)腳本
service_source = self.mysql_dir / "support-files" / "mysql.server"
if service_source.exists():
shutil.copy2(service_source, self.service_file)
self.service_file.chmod(0o755)
log_info("服務(wù)腳本已復(fù)制并設(shè)置權(quán)限")
else:
log_warning(f"MySQL服務(wù)腳本不存在: {service_source}")
# 配置chkconfig
if shutil.which("chkconfig"):
run_command("chkconfig --add mysqld")
log_info("MySQL服務(wù)已添加到chkconfig")
else:
log_warning("chkconfig命令不存在,跳過服務(wù)配置")
def create_config(self):
"""創(chuàng)建MySQL配置文件"""
log_info("創(chuàng)建MySQL配置文件...")
config_content = f"""
[mysqld]
basedir={self.mysql_dir}
datadir={self.data_dir}
port=3306
socket=/tmp/mysql.sock
user={self.mysql_user}
log-error={self.data_dir}/mysql.error.log
pid-file={self.data_dir}/mysql.pid
# 性能調(diào)優(yōu)參數(shù) (根據(jù)實際情況調(diào)整)
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
max_connections=1000
innodb_buffer_pool_size=1G
innodb_log_file_size=256M
[mysql]
socket=/tmp/mysql.sock
default-character-set=utf8mb4
[client]
socket=/tmp/mysql.sock
"""
try:
with open(self.config_file, 'w') as f:
f.write(config_content)
log_info(f"MySQL配置文件已創(chuàng)建: {self.config_file}")
except Exception as e:
log_error(f"創(chuàng)建配置文件失敗: {e}")
def remove_mariadb(self):
"""移除沖突的MariaDB包"""
log_info("檢查并移除沖突的MariaDB包...")
try:
result = run_command("rpm -qa | grep -E '^mariadb'", check=False, capture_output=True)
if result and result.returncode == 0 and result.stdout.strip():
mariadb_packages = result.stdout.strip().split('\n')
log_info("發(fā)現(xiàn)以下MariaDB包:")
for package in mariadb_packages:
print(f" - {package}")
for package in mariadb_packages:
log_warning(f"移除包: {package}")
run_command(f"rpm -e --nodeps {package}", check=False)
else:
log_info("未發(fā)現(xiàn)沖突的MariaDB包")
except Exception as e:
log_warning(f"檢查MariaDB包時發(fā)生異常: {e}")
def initialize_mysql(self):
"""初始化MySQL"""
log_info("初始化MySQL...")
# 檢查數(shù)據(jù)目錄是否為空
try:
if any(self.data_dir.iterdir()):
log_warning(f"數(shù)據(jù)目錄 {self.data_dir} 不為空,跳過初始化")
return
except Exception as e:
log_error(f"檢查數(shù)據(jù)目錄失敗: {e}")
log_info("執(zhí)行MySQL初始化...")
init_cmd = (
f"mysqld --initialize-insecure "
f"--user={self.mysql_user} "
f"--basedir={self.mysql_dir} "
f"--datadir={self.data_dir}"
)
try:
with open("/tmp/mysql_initialize.log", "w") as log_file:
process = subprocess.Popen(
init_cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True
)
# 實時輸出日志
for line in process.stdout:
log_file.write(line)
log_file.flush()
process.wait()
if process.returncode != 0:
log_error(f"MySQL初始化失敗,請檢查日志: /tmp/mysql_initialize.log")
else:
log_info("MySQL初始化完成")
except Exception as e:
log_error(f"初始化過程中發(fā)生異常: {e}")
def generate_password(self, length=16):
"""生成隨機密碼"""
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
return ''.join(secrets.choice(alphabet) for _ in range(length))
def start_mysql(self):
"""啟動MySQL服務(wù)"""
log_info("啟動MySQL服務(wù)...")
# 嘗試使用systemctl
if shutil.which("systemctl"):
if run_command("systemctl start mysqld", check=False):
log_info("使用systemctl啟動MySQL成功")
else:
log_warning("systemctl啟動失敗,嘗試使用service命令")
if not run_command("service mysqld start", check=False):
log_error("啟動MySQL服務(wù)失敗")
elif shutil.which("service"):
if run_command("service mysqld start", check=False):
log_info("使用service啟動MySQL成功")
else:
log_error("啟動MySQL服務(wù)失敗")
else:
log_warning("無法找到systemctl或service命令,請手動啟動MySQL")
return False
# 檢查服務(wù)狀態(tài)
log_info("檢查MySQL服務(wù)狀態(tài)...")
time.sleep(3)
# 檢查進程
if run_command("pgrep mysqld", check=False, capture_output=True):
log_info("MySQL進程運行正常")
else:
log_error("MySQL進程未運行")
# 檢查端口監(jiān)聽
result = run_command("netstat -anpt 2>/dev/null | grep ':3306' | grep LISTEN",
check=False, capture_output=True)
if result and result.returncode == 0 and result.stdout.strip():
log_info("MySQL正在監(jiān)聽3306端口")
else:
log_warning("MySQL未在3306端口監(jiān)聽,請檢查日志")
return True
def security_setup(self):
"""安全設(shè)置"""
log_info("執(zhí)行MySQL安全設(shè)置...")
# 等待MySQL完全啟動
time.sleep(5)
# 生成隨機密碼
mysql_root_password = self.generate_password()
log_info(f"生成的MySQL root密碼: {mysql_root_password}")
# 保存密碼到文件
password_file = Path("/root/mysql_root_password.txt")
try:
with open(password_file, 'w') as f:
f.write(f"MySQL root密碼: {mysql_root_password}\n")
f.write(f"生成時間: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
password_file.chmod(0o600)
log_info(f"密碼已保存到 {password_file}")
except Exception as e:
log_error(f"保存密碼文件失敗: {e}")
# 執(zhí)行安全設(shè)置
security_commands = [
f"ALTER USER 'root'@'localhost' IDENTIFIED BY '{mysql_root_password}';",
"DELETE FROM mysql.user WHERE User='';",
"DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');",
"DROP DATABASE IF EXISTS test;",
"DELETE FROM mysql.db WHERE Db='test' OR Db='test\\\\_%';",
"FLUSH PRIVILEGES;"
]
# 由于初始化時使用了--initialize-insecure,首次連接不需要密碼
try:
for cmd in security_commands:
mysql_cmd = f"mysql -u root --skip-password -e \"{cmd}\""
run_command(mysql_cmd, check=False)
log_info("MySQL安全設(shè)置完成")
except Exception as e:
log_warning(f"執(zhí)行安全設(shè)置時發(fā)生異常: {e}")
def verify_installation(self):
"""驗證安裝"""
log_info("驗證MySQL安裝...")
checks = [
("檢查MySQL進程", "ps -ef | grep mysqld | grep -v grep"),
("檢查端口監(jiān)聽", "netstat -anpt | grep 3306"),
("檢查服務(wù)狀態(tài)", "systemctl status mysqld 2>/dev/null || service mysqld status 2>/dev/null"),
]
for check_name, cmd in checks:
log_info(check_name)
result = run_command(cmd, check=False, capture_output=True)
if result and result.returncode == 0:
print(f" {Colors.GREEN}?{Colors.NC} {check_name} - 成功")
else:
print(f" {Colors.RED}?{Colors.NC} {check_name} - 失敗")
def install(self):
"""執(zhí)行安裝流程"""
log_info("開始MySQL生產(chǎn)環(huán)境安裝...")
steps = [
("檢查root權(quán)限", self.check_root),
("檢查文件", self.check_files),
("備份現(xiàn)有配置", self.backup_existing),
("創(chuàng)建MySQL用戶", self.create_mysql_user),
("創(chuàng)建數(shù)據(jù)目錄", self.create_data_dir),
("移除MariaDB沖突包", self.remove_mariadb),
("安裝MySQL", self.install_mysql),
("配置服務(wù)", self.configure_service),
("創(chuàng)建配置文件", self.create_config),
("初始化MySQL", self.initialize_mysql),
("啟動MySQL服務(wù)", self.start_mysql),
("安全設(shè)置", self.security_setup),
("驗證安裝", self.verify_installation),
]
for step_name, step_func in steps:
log_info(f"{Colors.BLUE}=== {step_name} ==={Colors.NC}")
try:
step_func()
except Exception as e:
log_error(f"步驟 '{step_name}' 執(zhí)行失敗: {e}")
log_info(f"{Colors.GREEN}MySQL安裝完成!{Colors.NC}")
log_info("請立即修改root密碼并執(zhí)行以下命令驗證安裝:")
print(" mysql -u root -p")
print(" systemctl status mysqld")
print(" netstat -anpt | grep 3306")
print(f" 初始root密碼保存在: /root/mysql_root_password.txt")
def main():
"""主函數(shù)"""
installer = MySQLInstaller()
installer.install()
if __name__ == "__main__":
main()
批量拉取鏡像
#!/usr/bin/env python
"""
Docker鏡像批量拉取工具
支持并行拉取、錯誤重試、進度顯示等功能
"""
import argparse
import logging
import os
import subprocess
import sys
import multiprocessing
from typing import List, Set, Tuple
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[logging.StreamHandler(sys.stdout)]
)
logger = logging.getLogger(__name__)
class DockerPullManager:
def __init__(self, max_workers: int = None, retry_count: int = 2):
self.max_workers = max_workers or min(4, multiprocessing.cpu_count())
self.retry_count = retry_count
def run_cmd(self, cmd: str) -> Tuple[int, str, str]:
"""執(zhí)行shell命令"""
try:
process = subprocess.Popen(
cmd, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
out, error = process.communicate(timeout=1800)
return process.returncode, out.strip(), error.strip()
except subprocess.TimeoutExpired:
logger.error(f"Command timed out: {cmd}")
process.kill()
return 1, "", "Timeout"
except Exception as e:
logger.error(f"Command error: {cmd}, {e}")
return 1, "", str(e)
def get_image_list(self, file_path: str) -> Set[str]:
"""讀取鏡像列表"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"Image file not found: {file_path}")
images = set()
try:
with open(file_path, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if line and not line.startswith("#"):
images.add(line)
except IOError as e:
raise IOError(f"Failed to read {file_path}: {e}")
return images
def pull_single_image(self, image: str) -> Tuple[bool, str]:
"""拉取單個鏡像,支持重試"""
for attempt in range(self.retry_count + 1):
logger.info(f"Pulling {image} (attempt {attempt + 1})")
r, out, error = self.run_cmd(f"docker pull {image}")
if r == 0:
logger.info(f"? Success: {image}")
return True, out
else:
logger.warning(f"Attempt {attempt + 1} failed for {image}: {error}")
logger.error(f"? Failed: {image} after {self.retry_count + 1} attempts")
return False, error
def batch_pull(self, file_path: str) -> Tuple[int, int]:
"""批量拉取鏡像"""
try:
images = self.get_image_list(file_path)
except Exception as e:
logger.error(str(e))
return 0, 0
if not images:
logger.warning("No images found to pull")
return 0, 0
logger.info(f"Found {len(images)} unique images, starting pull with {self.max_workers} workers...")
success_count = 0
with multiprocessing.Pool(processes=self.max_workers) as pool:
results = [pool.apply_async(self.pull_single_image, (img,)) for img in images]
for i, (image, result) in enumerate(zip(images, results)):
try:
success, msg = result.get(timeout=1800)
if success:
success_count += 1
logger.info(f"Progress: {i+1}/{len(images)}")
except multiprocessing.TimeoutError:
logger.error(f"Timeout: {image}")
failed_count = len(images) - success_count
logger.info(f"Completed: {success_count} successful, {failed_count} failed")
return success_count, failed_count
def main():
parser = argparse.ArgumentParser(description='批量拉取Docker鏡像')
parser.add_argument('--file', '-f', default='images.txt',
help='鏡像列表文件路徑 (默認: images.txt)')
parser.add_argument('--workers', '-w', type=int,
help=f'并行工作進程數(shù) (默認: CPU核心數(shù),最大4)')
parser.add_argument('--retry', '-r', type=int, default=2,
help='失敗重試次數(shù) (默認: 2)')
parser.add_argument('--verbose', '-v', action='store_true',
help='詳細輸出')
args = parser.parse_args()
if args.verbose:
logger.setLevel(logging.DEBUG)
manager = DockerPullManager(max_workers=args.workers, retry_count=args.retry)
success, failed = manager.batch_pull(args.file)
sys.exit(1 if failed > 0 else 0)
if __name__ == '__main__':
main()
加強版本
#!/usr/bin/env python
"""
Docker鏡像批量拉取工具
支持并行拉取、錯誤重試、進度顯示、鏡像驗證等功能
增強版本包含更完善的錯誤處理和進度展示
"""
import argparse # 導(dǎo)入命令行參數(shù)解析模塊
import logging # 導(dǎo)入日志記錄模塊
import os # 導(dǎo)入操作系統(tǒng)功能模塊
import subprocess # 導(dǎo)入子進程管理模塊
import sys # 導(dǎo)入系統(tǒng)相關(guān)功能模塊
import time # 導(dǎo)入時間相關(guān)功能模塊
import multiprocessing # 導(dǎo)入多進程處理模塊
from typing import List, Set, Tuple, Dict, Optional # 導(dǎo)入類型提示功能
from datetime import datetime # 導(dǎo)入日期時間處理模塊
# 配置日志系統(tǒng),設(shè)置格式和輸出目標
logging.basicConfig(
level=logging.INFO, # 設(shè)置默認日志級別為INFO
format='%(asctime)s - %(levelname)s - %(message)s', # 設(shè)置日志格式:時間-級別-消息
handlers=[logging.StreamHandler(sys.stdout)] # 設(shè)置日志輸出到標準輸出
)
logger = logging.getLogger(__name__) # 獲取當(dāng)前模塊的日志記錄器
class DockerPullManager:
"""Docker鏡像拉取管理器,處理批量鏡像拉取任務(wù)"""
def __init__(self, max_workers: int = None, retry_count: int = 2, timeout: int = 1800):
"""
初始化Docker拉取管理器
Args:
max_workers: 最大并行工作進程數(shù),默認為min(4, CPU核心數(shù))
retry_count: 失敗重試次數(shù),默認為2次
timeout: 命令執(zhí)行超時時間(秒),默認30分鐘
"""
# 設(shè)置最大工作進程數(shù),取4和CPU核心數(shù)的較小值作為默認值
self.max_workers = max_workers or min(4, multiprocessing.cpu_count())
self.retry_count = retry_count # 設(shè)置重試次數(shù)
self.timeout = timeout # 設(shè)置超時時間
self.start_time = time.time() # 記錄開始時間,用于計算總耗時
self.results: Dict[str, bool] = {} # 存儲每個鏡像的拉取結(jié)果
def run_cmd(self, cmd: str) -> Tuple[int, str, str]:
"""
執(zhí)行shell命令并返回結(jié)果
Args:
cmd: 要執(zhí)行的shell命令
Returns:
返回值元組: (返回碼, 標準輸出, 標準錯誤)
"""
try:
# 創(chuàng)建子進程執(zhí)行命令,并捕獲標準輸出和標準錯誤
process = subprocess.Popen(
cmd, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
# 等待進程完成并獲取輸出,設(shè)置超時時間
out, error = process.communicate(timeout=self.timeout)
# 返回進程返回碼和輸出結(jié)果
return process.returncode, out.strip(), error.strip()
except subprocess.TimeoutExpired:
# 處理命令執(zhí)行超時情況
logger.error(f"命令執(zhí)行超時: {cmd}")
process.kill() # 終止超時進程
return 1, "", "執(zhí)行超時"
except Exception as e:
# 處理其他異常情況
logger.error(f"命令執(zhí)行錯誤: {cmd}, {e}")
return 1, "", str(e)
def get_image_list(self, file_path: str) -> Set[str]:
"""
從文件中讀取Docker鏡像列表
Args:
file_path: 包含鏡像列表的文件路徑
Returns:
唯一鏡像名稱集合
Raises:
FileNotFoundError: 文件不存在時拋出
IOError: 文件讀取錯誤時拋出
"""
# 檢查文件是否存在
if not os.path.exists(file_path):
raise FileNotFoundError(f"鏡像列表文件未找到: {file_path}")
images = set() # 使用集合存儲唯一的鏡像名稱
try:
# 打開文件并讀取內(nèi)容
with open(file_path, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
line = line.strip() # 去除行首尾空白字符
# 忽略空行和注釋行
if line and not line.startswith("#"):
images.add(line) # 添加到鏡像集合
except IOError as e:
# 處理文件讀取錯誤
raise IOError(f"讀取文件 {file_path} 失敗: {e}")
return images
def verify_image(self, image: str) -> bool:
"""
驗證鏡像是否成功拉取
Args:
image: 鏡像名稱
Returns:
驗證成功返回True,否則返回False
"""
# 執(zhí)行docker images命令檢查鏡像是否存在
cmd = f"docker images --format '{{{{.Repository}}}}:{{{{.Tag}}}}' | grep '{image}'"
r, out, _ = self.run_cmd(cmd)
return r == 0 and out.strip() != ""
def pull_single_image(self, image: str) -> Tuple[bool, str]:
"""
拉取單個Docker鏡像,支持多次重試
Args:
image: 要拉取的鏡像名稱
Returns:
元組: (是否成功, 輸出信息)
"""
# 記錄開始時間
start_time = time.time()
# 嘗試多次拉取鏡像
for attempt in range(self.retry_count + 1):
logger.info(f"正在拉取 {image} (第 {attempt + 1} 次嘗試)")
# 執(zhí)行docker pull命令
r, out, error = self.run_cmd(f"docker pull {image}")
if r == 0:
# 拉取成功
elapsed = time.time() - start_time
logger.info(f"? 成功: {image} (耗時: {elapsed:.2f}秒)")
# 驗證鏡像是否真的拉取成功
if self.verify_image(image):
return True, out
else:
logger.warning(f"鏡像驗證失敗: {image}")
else:
# 拉取失敗,記錄錯誤信息
logger.warning(f"第 {attempt + 1} 次嘗試失敗: {image}: {error}")
# 如果不是最后一次嘗試,等待一段時間后重試
if attempt < self.retry_count:
time.sleep(2) # 失敗后等待2秒再重試
# 所有嘗試都失敗
elapsed = time.time() - start_time
logger.error(f"? 失敗: {image} 經(jīng)過 {self.retry_count + 1} 次嘗試 (耗時: {elapsed:.2f}秒)")
return False, error
def format_time(self, seconds: float) -> str:
"""
格式化時間,將秒數(shù)轉(zhuǎn)換為更易讀的格式
Args:
seconds: 秒數(shù)
Returns:
格式化后的時間字符串
"""
# 將秒數(shù)轉(zhuǎn)換為更易讀的時間格式
if seconds < 60:
return f"{seconds:.1f}秒"
elif seconds < 3600:
return f"{seconds/60:.1f}分鐘"
else:
return f"{seconds/3600:.1f}小時"
def display_progress(self, completed: int, total: int, success: int, failed: int) -> None:
"""
顯示當(dāng)前進度信息
Args:
completed: 已完成的任務(wù)數(shù)
total: 總?cè)蝿?wù)數(shù)
success: 成功任務(wù)數(shù)
failed: 失敗任務(wù)數(shù)
"""
# 計算已用時間和預(yù)估剩余時間
elapsed = time.time() - self.start_time
progress = completed / total if total > 0 else 0
# 計算預(yù)估剩余時間
remaining = (elapsed / progress) * (1 - progress) if progress > 0 else 0
# 構(gòu)建進度條
bar_length = 30
filled_length = int(bar_length * progress)
bar = '█' * filled_length + '?' * (bar_length - filled_length)
# 顯示進度信息
logger.info(
f"進度: [{bar}] {completed}/{total} ({progress*100:.1f}%) "
f"成功: {success} 失敗: {failed} "
f"已用時間: {self.format_time(elapsed)} "
f"預(yù)計剩余: {self.format_time(remaining)}"
)
def batch_pull(self, file_path: str) -> Tuple[int, int]:
"""
批量拉取Docker鏡像
Args:
file_path: 包含鏡像列表的文件路徑
Returns:
元組: (成功數(shù)量, 失敗數(shù)量)
"""
try:
# 獲取鏡像列表
images = self.get_image_list(file_path)
except Exception as e:
# 處理獲取鏡像列表時的錯誤
logger.error(str(e))
return 0, 0
# 檢查鏡像列表是否為空
if not images:
logger.warning("未找到要拉取的鏡像")
return 0, 0
# 記錄開始時間和鏡像數(shù)量
self.start_time = time.time()
image_list = list(images) # 將集合轉(zhuǎn)換為列表,便于索引訪問
total_images = len(image_list)
logger.info(f"找到 {total_images} 個唯一鏡像,使用 {self.max_workers} 個工作進程開始拉取...")
# 初始化計數(shù)器
success_count = 0
failed_count = 0
completed_count = 0
# 創(chuàng)建進程池進行并行處理
with multiprocessing.Pool(processes=self.max_workers) as pool:
# 異步提交所有拉取任務(wù)
results = [pool.apply_async(self.pull_single_image, (img,)) for img in image_list]
# 處理每個任務(wù)的結(jié)果
for i, (image, result) in enumerate(zip(image_list, results)):
try:
# 獲取任務(wù)結(jié)果,設(shè)置超時時間
success, msg = result.get(timeout=self.timeout)
# 更新計數(shù)器
if success:
success_count += 1
else:
failed_count += 1
completed_count += 1
# 存儲結(jié)果
self.results[image] = success
# 每完成一個任務(wù)顯示一次進度
self.display_progress(completed_count, total_images, success_count, failed_count)
except multiprocessing.TimeoutError:
# 處理任務(wù)超時
logger.error(f"超時: {image}")
failed_count += 1
completed_count += 1
self.results[image] = False
except Exception as e:
# 處理其他異常
logger.error(f"處理 {image} 時出錯: {e}")
failed_count += 1
completed_count += 1
self.results[image] = False
# 計算總耗時
total_time = time.time() - self.start_time
# 顯示最終結(jié)果
logger.info(f"任務(wù)完成: 成功 {success_count} 個, 失敗 {failed_count} 個, 總耗時: {self.format_time(total_time)}")
# 如果有失敗的鏡像,顯示失敗列表
if failed_count > 0:
failed_images = [img for img, success in self.results.items() if not success]
logger.info(f"失敗鏡像列表: {', '.join(failed_images)}")
# 將失敗的鏡像寫入文件,方便后續(xù)重試
failed_file = f"failed_images_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
try:
with open(failed_file, 'w', encoding='utf-8') as f:
for img in failed_images:
f.write(f"{img}\n")
logger.info(f"已將失敗鏡像列表寫入文件: {failed_file}")
except Exception as e:
logger.error(f"寫入失敗鏡像列表時出錯: {e}")
return success_count, failed_count
def main():
"""主函數(shù),處理命令行參數(shù)并執(zhí)行批量拉取操作"""
# 創(chuàng)建命令行參數(shù)解析器
parser = argparse.ArgumentParser(description='批量拉取Docker鏡像工具')
# 添加命令行參數(shù)
parser.add_argument('--file', '-f', default='image.txt',
help='鏡像列表文件路徑 (默認: image.txt)')
parser.add_argument('--workers', '-w', type=int,
help=f'并行工作進程數(shù) (默認: CPU核心數(shù),最大4)')
parser.add_argument('--retry', '-r', type=int, default=2,
help='失敗重試次數(shù) (默認: 2)')
parser.add_argument('--timeout', '-t', type=int, default=1800,
help='單個鏡像拉取超時時間(秒) (默認: 1800)')
parser.add_argument('--verbose', '-v', action='store_true',
help='顯示詳細日志')
parser.add_argument('--verify', action='store_true',
help='拉取后驗證鏡像是否存在')
# 解析命令行參數(shù)
args = parser.parse_args()
# 如果指定了詳細模式,設(shè)置日志級別為DEBUG
if args.verbose:
logger.setLevel(logging.DEBUG)
logger.debug("已啟用詳細日志模式")
# 創(chuàng)建Docker拉取管理器實例
manager = DockerPullManager(
max_workers=args.workers, # 設(shè)置工作進程數(shù)
retry_count=args.retry, # 設(shè)置重試次數(shù)
timeout=args.timeout # 設(shè)置超時時間
)
# 執(zhí)行批量拉取操作
success, failed = manager.batch_pull(args.file)
# 根據(jù)失敗數(shù)量設(shè)置退出碼
sys.exit(1 if failed > 0 else 0)
# 當(dāng)腳本直接運行時執(zhí)行main函數(shù)
if __name__ == '__main__':
main()
批量拉取鏡像 shell版本
#!/bin/bash
# 批量Docker鏡像拉取工具
# 按批次順序拉取鏡像,每次拉取10個,成功后繼續(xù)下一批
set -euo pipefail
# 默認參數(shù)
BATCH_SIZE=10
RETRY_COUNT=2
TIMEOUT=1800
VERBOSE=false
SCRIPT_NAME=$(basename "$0")
# 顏色定義
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 全局變量
TOTAL_SUCCESS=0
TOTAL_FAILED=0
FAILED_IMAGES=()
START_TIME=$(date +%s)
# 日志函數(shù)
log_info() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] [INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] [WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR]${NC} $1"
}
log_debug() {
if [[ "$VERBOSE" == "true" ]]; then
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] [DEBUG]${NC} $1"
fi
}
# 顯示幫助信息
show_help() {
cat << EOF
批量Docker鏡像拉取工具
用法: $SCRIPT_NAME [選項] 鏡像列表文件
參數(shù):
鏡像列表文件 包含Docker鏡像列表的文件路徑
選項:
-b, --batch-size 每批拉取的鏡像數(shù)量 (默認: 10)
-r, --retry 失敗重試次數(shù) (默認: 2)
-t, --timeout 單個鏡像拉取超時時間(秒) (默認: 1800)
-v, --verbose 顯示詳細日志
-h, --help 顯示此幫助信息
鏡像列表文件格式:
每行一個鏡像名稱,支持注釋行(以#開頭)和空行
示例:
$SCRIPT_NAME images.txt
$SCRIPT_NAME -b 5 -r 3 images.txt
$SCRIPT_NAME -v images.txt
EOF
}
# 格式化時間
format_time() {
local seconds=$1
if [[ $seconds -lt 60 ]]; then
echo "${seconds}秒"
elif [[ $seconds -lt 3600 ]]; then
echo "$(echo "scale=1; $seconds/60" | bc)分鐘"
else
echo "$(echo "scale=1; $seconds/3600" | bc)小時"
fi
}
# 檢查文件是否存在
check_file_exists() {
local file_path=$1
if [[ ! -f "$file_path" ]]; then
log_error "鏡像列表文件未找到: $file_path"
exit 1
fi
}
# 讀取鏡像列表
read_image_list() {
local file_path=$1
check_file_exists "$file_path"
log_debug "開始讀取文件: $file_path"
# 使用grep和sed過濾非空行和非注釋行,然后輸出去重后的結(jié)果
grep -v '^[[:space:]]*$' "$file_path" | grep -v '^[[:space:]]*#' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sort -u
log_info "從 $file_path 讀取鏡像列表完成"
}
# 驗證鏡像是否成功拉取
verify_image() {
local image=$1
local result
result=$(docker images --format '{{.Repository}}:{{.Tag}}' | grep -E "^${image}$" || true)
if [[ -n "$result" ]]; then
return 0
else
return 1
fi
}
# 拉取單個鏡像
pull_single_image() {
local image=$1
local start_time=$(date +%s)
local attempt=0
local max_attempts=$((RETRY_COUNT + 1))
while [[ $attempt -lt $max_attempts ]]; do
((attempt++))
log_info "正在拉取 ${image} (第 ${attempt} 次嘗試)"
# 使用timeout命令設(shè)置超時
local pull_output
local pull_error
local pull_exit_code
if pull_output=$(timeout "${TIMEOUT}" docker pull "$image" 2>&1); then
pull_exit_code=0
pull_error=""
else
pull_exit_code=$?
pull_error="$pull_output"
pull_output=""
fi
if [[ $pull_exit_code -eq 0 ]]; then
local elapsed=$(($(date +%s) - start_time))
log_success "成功拉取 ${image} (耗時: ${elapsed}秒)"
# 驗證鏡像是否真的拉取成功
if verify_image "$image"; then
return 0
else
log_warning "鏡像驗證失敗: $image"
fi
else
log_warning "第 ${attempt} 次嘗試失敗: $image"
if [[ -n "$pull_error" ]]; then
log_warning "錯誤信息: $pull_error"
fi
fi
# 如果不是最后一次嘗試,等待后重試
if [[ $attempt -lt $max_attempts ]]; then
log_info "等待2秒后重試..."
sleep 2
fi
done
# 所有嘗試都失敗
local elapsed=$(($(date +%s) - start_time))
log_error "失敗: $image 經(jīng)過 ${RETRY_COUNT} 次嘗試 (耗時: ${elapsed}秒)"
return 1
}
# 拉取一批鏡像
pull_batch() {
local -n batch_images_ref=$1
local batch_num=$2
local total_batches=$3
local batch_start_time=$(date +%s)
local success_count=0
local failed_count=0
local batch_size=${#batch_images_ref[@]}
echo
log_info "========================================"
log_info "開始第 ${batch_num}/${total_batches} 批次拉取 (${batch_size} 個鏡像)"
log_info "========================================"
echo
for i in "${!batch_images_ref[@]}"; do
local image="${batch_images_ref[$i]}"
local image_num=$((i + 1))
echo
log_info "[${batch_num}/${total_batches}] 鏡像 ${image_num}/${batch_size}: ${image}"
if pull_single_image "$image"; then
((success_count++))
else
((failed_count++))
FAILED_IMAGES+=("$image")
fi
done
local batch_elapsed=$(($(date +%s) - batch_start_time))
echo
log_info "第 ${batch_num} 批次完成:"
log_info " 成功: ${success_count} 個"
log_info " 失敗: ${failed_count} 個"
log_info " 耗時: ${batch_elapsed} 秒"
echo "$success_count $failed_count"
}
# 顯示最終結(jié)果
show_final_results() {
local total_images=$1
local total_time=$(($(date +%s) - START_TIME))
echo
log_info "========================================"
log_info "批量拉取任務(wù)完成!"
log_info "========================================"
log_info "總鏡像數(shù): $total_images"
log_info "成功拉取: $TOTAL_SUCCESS"
log_info "拉取失敗: $TOTAL_FAILED"
local success_rate
if [[ $total_images -gt 0 ]]; then
success_rate=$(echo "scale=1; $TOTAL_SUCCESS * 100 / $total_images" | bc)
else
success_rate="0.0"
fi
log_info "成功率: ${success_rate}%"
log_info "總耗時: $(format_time $total_time)"
log_info "結(jié)束時間: $(date '+%Y-%m-%d %H:%M:%S')"
if [[ ${#FAILED_IMAGES[@]} -gt 0 ]]; then
echo
log_info "失敗的鏡像列表:"
for i in "${!FAILED_IMAGES[@]}"; do
log_info " $((i + 1)). ${FAILED_IMAGES[$i]}"
done
fi
}
# 主函數(shù)
main() {
local file_path=""
# 解析命令行參數(shù)
while [[ $# -gt 0 ]]; do
case $1 in
-b|--batch-size)
BATCH_SIZE="$2"
if ! [[ "$BATCH_SIZE" =~ ^[1-9][0-9]*$ ]]; then
log_error "批次大小必須是正整數(shù)"
exit 1
fi
shift 2
;;
-r|--retry)
RETRY_COUNT="$2"
if ! [[ "$RETRY_COUNT" =~ ^[0-9]+$ ]]; then
log_error "重試次數(shù)必須是非負整數(shù)"
exit 1
fi
shift 2
;;
-t|--timeout)
TIMEOUT="$2"
if ! [[ "$TIMEOUT" =~ ^[1-9][0-9]*$ ]]; then
log_error "超時時間必須是正整數(shù)"
exit 1
fi
shift 2
;;
-v|--verbose)
VERBOSE=true
shift
;;
-h|--help)
show_help
exit 0
;;
*)
if [[ -z "$file_path" ]]; then
file_path="$1"
else
log_error "未知參數(shù): $1"
show_help
exit 1
fi
shift
;;
esac
done
if [[ -z "$file_path" ]]; then
log_error "請?zhí)峁╃R像列表文件路徑"
show_help
exit 1
fi
# 檢查依賴
if ! command -v docker &> /dev/null; then
log_error "Docker 未安裝或未在PATH中"
exit 1
fi
if ! command -v bc &> /dev/null; then
log_error "bc 計算器未安裝,請安裝 bc 工具"
exit 1
fi
# 讀取鏡像列表
local images=()
# 直接從文件讀取鏡像列表
while IFS= read -r line; do
# 去除前后空格
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# 忽略空行和注釋行
if [[ -n "$line" && ! "$line" =~ ^[[:space:]]*# ]]; then
images+=("$line")
fi
done < "$file_path"
if [[ ${#images[@]} -eq 0 ]]; then
log_warning "沒有找到要拉取的鏡像"
exit 0
fi
log_info "從 $file_path 讀取到 ${#images[@]} 個鏡像"
# 計算批次數(shù)量
local total_images=${#images[@]}
local total_batches=$(( (total_images + BATCH_SIZE - 1) / BATCH_SIZE ))
echo
log_info "開始批量拉取任務(wù):"
log_info " 總鏡像數(shù): $total_images"
log_info " 批次大小: $BATCH_SIZE"
log_info " 總批次數(shù): $total_batches"
log_info " 開始時間: $(date '+%Y-%m-%d %H:%M:%S')"
# 按批次處理鏡像
for ((batch_num = 1; batch_num <= total_batches; batch_num++)); do
local start_idx=$(((batch_num - 1) * BATCH_SIZE))
local end_idx=$((start_idx + BATCH_SIZE))
if [[ $end_idx -gt $total_images ]]; then
end_idx=$total_images
fi
# 提取當(dāng)前批次的鏡像
local batch_images=("${images[@]:$start_idx:$((end_idx - start_idx))}")
# 拉取當(dāng)前批次
local result
result=$(pull_batch batch_images "$batch_num" "$total_batches")
read -r success failed <<< "$result"
TOTAL_SUCCESS=$((TOTAL_SUCCESS + success))
TOTAL_FAILED=$((TOTAL_FAILED + failed))
# 如果不是最后一批,顯示進度
if [[ $batch_num -lt $total_batches ]]; then
local completed_images=$((batch_num * BATCH_SIZE))
local progress
progress=$(echo "scale=1; $completed_images * 100 / $total_images" | bc)
local elapsed=$(($(date +%s) - START_TIME))
echo
log_info "當(dāng)前進度: ${completed_images}/${total_images} (${progress}%)"
log_info "累計成功: $TOTAL_SUCCESS, 累計失敗: $TOTAL_FAILED"
log_info "已用時間: $(format_time $elapsed)"
log_info "準備開始下一批次..."
sleep 1 # 批次間短暫休息
fi
done
# 顯示最終結(jié)果
show_final_results "$total_images"
}
# 捕獲中斷信號
trap 'log_info "用戶中斷操作"; exit 1' INT TERM
# 運行主函數(shù)
main "$@"