提交 381348db authored 作者: 陈泽健's avatar 陈泽健

feat(container): 重构容器更新脚本并增强交互式部署功能

- 引入全局变量和日志函数,支持控制台和文件双重日志输出
- 新增容器描述映射,提升用户选择体验
- 重构交互式模式为四步骤流程:容器选择、文件检查、平台检测、容器停止
- 增强镜像文件搜索逻辑,支持多路径查找和手动输入
- 自动检测服务器平台类型(新统一平台/传统平台)
- 优化容器命名逻辑,自动递增容器编号避免冲突
- 增加部署前信息确认和日志记录功能
- 补充说明文档和需求文档,完善操作指引和功能描述
上级 079adfda
# -*- coding: utf-8 -*-
"""
根据模板用例 Excel,在同一个工作簿中新建 Sheet,并从 JSON 配置文件中读取用例数据写入。
后续只需要维护 JSON 文件即可复用。
"""
import os
import json
from copy import copy
from openpyxl import load_workbook
from openpyxl.styles import Alignment
# ===== 1. 配置路径(改为相对当前脚本的路径) =====
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TEMPLATE_PATH = os.path.join(BASE_DIR, "用例文件", "兰州中石化项目测试用例20251203.xlsx")
NEW_SHEET_NAME = "兰州登录MQTT用例" # 新建的 Sheet 名
CASES_FILE = os.path.join(BASE_DIR, "config", "兰州用例.json") # 用例配置文件(JSON)
# 与你表头对应的顺序(根据截图)
headers_order = [
"序号", "功能模块", "功能类别", "用例编号", "功能描述", "用例等级",
"功能编号", "用例名称", "预置条件", "操作步骤", "JSON", "预期结果",
"测试结果", "测试结论", "日志截屏", "备注",
]
def load_cases():
"""从 JSON 配置文件加载用例列表。"""
if not os.path.exists(CASES_FILE):
raise FileNotFoundError(f"找不到用例配置文件: {CASES_FILE}")
with open(CASES_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
if not isinstance(data, list):
raise ValueError("用例配置文件的根节点必须是列表(List)")
return data
def main():
if not os.path.exists(TEMPLATE_PATH):
print("找不到模板文件:", TEMPLATE_PATH)
return
try:
cases = load_cases()
except Exception as e:
print("加载用例配置失败:", e)
return
wb = load_workbook(TEMPLATE_PATH)
# 以第一个sheet作为模板
template_sheet = wb.worksheets[0]
# 如果新sheet已存在就先删除
if NEW_SHEET_NAME in wb.sheetnames:
ws_new = wb[NEW_SHEET_NAME]
wb.remove(ws_new)
ws_new = wb.create_sheet(NEW_SHEET_NAME)
# ===== 复制表头(仅值 + 简单样式) =====
# 假设模板表头在第3行(根据截图),如有偏差可以改成2或其它
header_row_index = 3
for col_idx, cell in enumerate(template_sheet[header_row_index], start=1):
new_cell = ws_new.cell(row=1, column=col_idx, value=cell.value)
if cell.has_style:
new_cell.font = copy(cell.font)
new_cell.fill = copy(cell.fill)
new_cell.border = copy(cell.border)
new_cell.alignment = copy(cell.alignment)
new_cell.number_format = cell.number_format
ws_new.freeze_panes = "B2"
# ===== 写入用例数据(预置条件/操作步骤自动换行,JSON列统一留空) =====
row = 2
for case in cases:
for col_idx, header in enumerate(headers_order, start=1):
val = case.get(header, "")
if header in ("预置条件", "操作步骤") and isinstance(val, str):
# 把分号+空格变成换行,Excel 中会显示为多行
val = val.replace("; ", "\n")
if header == "JSON":
# JSON 列统一留空
val = ""
ws_new.cell(row=row, column=col_idx, value=val)
row += 1
# ===== 对“预置条件”和“操作步骤”开启自动换行 =====
col_idx_pre = headers_order.index("预置条件") + 1
col_idx_steps = headers_order.index("操作步骤") + 1
for r in range(1, row):
cell_pre = ws_new.cell(row=r, column=col_idx_pre)
cell_steps = ws_new.cell(row=r, column=col_idx_steps)
cell_pre.alignment = Alignment(wrap_text=True, vertical="top")
cell_steps.alignment = Alignment(wrap_text=True, vertical="top")
# 调整列宽
for col in ws_new.columns:
max_len = 0
col_letter = col[0].column_letter
for c in col:
v = c.value
if v is None:
continue
l = len(str(v))
if l > max_len:
max_len = l
ws_new.column_dimensions[col_letter].width = min(max_len + 2, 60)
wb.save(TEMPLATE_PATH)
print("已在原文件中创建新Sheet:", NEW_SHEET_NAME)
if __name__ == "__main__":
main()
\ No newline at end of file
# 容器升级需求说明文档
## 📋 概述
本系统由三个核心 Shell 脚本组成,用于实现容器化服务的远程部署与升级管理:
1. **`remote_update.sh`**:远程升级控制脚本,负责从主服务器向目标服务器传输镜像和部署脚本,并触发远端部署流程,路径:E:\GithubData\自动化\ubains-module-test\辅助工具\脚本工具\远程容器更新\remote_update.sh
2. **`container_update.sh`**:容器部署执行脚本,支持交互式和命令行两种模式,可单独执行进行本地容器部署,路径:E:\GithubData\自动化\ubains-module-test\辅助工具\脚本工具\远程容器更新\container_update.sh
3. **`upload_to_nas.sh`**:镜像打包上传脚本,负责将容器镜像打包并上传至公司 NAS 网盘,路径:E:\GithubData\自动化\ubains-module-test\辅助工具\脚本工具\远程容器更新\upload_to_nas.sh
### 背景
目前系统支持多种容器服务(Java、Redis、EMQX、Python、Nacos、Nginx)的部署与升级,需要区分两种平台环境:
- **新统一平台**:使用 `/data/` 目录结构
- **传统平台**:使用 `/var/www/` 目录结构
**现有功能**
- ✅ 远程升级指定服务器的容器版本 `[已实现]`
1. java容器远程更新(传统平台和新统一平台已验证通过)
2. uemqx容器(传统平台和新统一平台已验证通过)
3. uredis容器(传统平台和新统一平台已验证通过)
4. upython容器()
5. nacos容器(新统一平台已验证通过)
6. nginx容器(新统一平台已验证通过)
7. mysql容器(未验证)
- ✅ 自动校验目标服务器架构(仅支持 x86)`[已实现]`
- ✅ 自动递增容器编号避免命名冲突 `[已实现]`
- ✅ 支持多种容器类型的差异化部署 `[已实现]`
- ✅ 支持预设服务器和手动输入服务器信息(IP/端口/用户名/密码)`[已实现]`
- ✅ EMQX 文件同步(配置、数据、日志目录)`[已实现]`
- ✅ Nginx 文件同步(配置、HTML、证书目录)`[已实现]`
- ✅ 自动校验目标服务器是新统一平台目录还是传统平台目录,通过宿主机上的目录来判断,存在/data/services目录的是新统一平台,不存在的则是传统平台 `[已实现]`
- ✅ 自动校验目标服务器上的容器是否已更新 `[已实现]`
- ✅ 远程更新完成后需要将目标服务器上的镜像包清理 `[已实现]`
- ⏸️ Python 文件同步 `[功能保留,暂不启用]`
- ✅ 将主服务器上的容器镜像及部署脚本打包上传至网盘 `[已实现]`
- ✅ container_update.sh 支持单独执行(交互式模式)`[已实现]`
**待实现功能**
- ❌ 最终将sh脚本改为电脑命令行工具上可执行的脚本格式 `[待最后开发]`
---
## 🎯 功能实现总览
> 最后更新时间:2025-12-07
### 远程升级功能 (`remote_update.sh`)
| 功能模块 | 描述 | 状态 |
|----------|------|------|
| 服务器选择 | 支持预设服务器列表和手动输入(IP/端口/用户名/密码) | ✅ 已实现 |
| 自定义端口 | 支持自定义 SSH 端口(默认 22) | ✅ 已实现 |
| 架构校验 | 校验目标服务器是否为 x86 架构 | ✅ 已实现 |
| 镜像传输 | 自动传输镜像文件和部署脚本 | ✅ 已实现 |
| 容器停止 | 自动停止远端旧容器 | ✅ 已实现 |
| 平台识别 | 自动检测目标服务器平台类型(检测 /data/services 目录) | ✅ 已实现 |
| 版本校验 | 自动校验远端容器镜像版本是否已更新 | ✅ 已实现 |
| EMQX 同步 | 同步 EMQX 配置、数据、日志目录 | ✅ 已实现 |
| Python 同步 | 同步 Python 代码和配置 | ⏸️ 暂停 |
| Nginx 同步 | 同步 Nginx 配置、HTML、证书 | ✅ 已实现 |
| 容器编号 | 自动递增容器编号 | ✅ 已实现 |
| 远端执行 | 调用远端部署脚本 | ✅ 已实现 |
| 镜像清理 | 部署完成后自动清理远端镜像包和部署脚本 | ✅ 已实现 |
### 容器部署功能 (`container_update.sh`)
| 容器类型 | 新平台 | 传统平台 | 备注 |
|----------|--------|----------|------|
| Java (ujava) | ✅ | ✅ | 完整端口映射和目录挂载 |
| Redis (uredis) | ✅ | ✅ | 支持配置迁移和端口释放 |
| EMQX (uemqx) | ✅ | ✅ | 多端口映射,完整目录挂载 |
| Python (upython) | ✅ | ✅ | 容器部署正常,暂不同步文件 |
| Nacos (unacos) | ✅ | ❌ | 仅支持新平台,单机模式 |
| Nginx (unginx) | ✅ | ❌ | 仅支持新平台,完整目录挂载 |
### 交互式部署功能 (`container_update.sh` 交互式模式)
| 步骤 | 功能描述 | 状态 |
|------|----------|------|
| 步骤1 | 询问更新哪个容器 | ✅ 已实现 |
| 步骤2 | 检查压缩包中是否存在所需镜像和配置文件 | ✅ 已实现 |
| 步骤3 | 判断当前服务器是传统平台还是新统一平台 | ✅ 已实现 |
| 步骤4 | 检查并停止正在运行的同类型容器(不删除) | ✅ 已实现 |
| 步骤5 | 备份原有配置文件 | ❌ 待开发 |
| 步骤6 | 更新配置文件并替换IP地址为当前服务器IP | ❌ 待开发 |
| 步骤7 | 执行容器部署操作 | ✅ 已实现 |
| 步骤8 | 验证容器部署是否成功 | ✅ 已实现 |
| 步骤9 | 记录操作日志到当前目录(日志审计) | ✅ 已实现 |
### 镜像上传功能 (`upload_to_nas.sh`)
| 功能模块 | 描述 | 状态 |
|----------|------|------|
| 目录打包 | 将指定目录压缩为 tar.gz 格式 | ✅ 已实现 |
| NAS 挂载 | 自动挂载公司 SMB 网盘 | ✅ 已实现 |
| 进度显示 | 打包和上传过程显示进度条 | ✅ 已实现 |
| 密码加密 | 网盘密码使用 base64 加密存储 | ✅ 已实现 |
### 待开发功能
| 功能 | 描述 | 状态 |
|------|------|------|
| 批量部署 | 一次升级多台服务器 | ❌ 待开发 |
| 版本管理 | 记录部署版本和时间 | ❌ 待开发 |
---
\ No newline at end of file
# 容器远程升级与镜像管理系统需求说明文档
## 📋 概述
本系统由三个核心 Shell 脚本组成,用于实现容器化服务的远程部署与升级管理:
1. **`remote_update.sh`**:远程升级控制脚本,负责从主服务器向目标服务器传输镜像和部署脚本,并触发远端部署流程
2. **`container_update.sh`**:容器部署执行脚本,支持交互式和命令行两种模式,可单独执行进行本地容器部署
3. **`upload_to_nas.sh`**:镜像打包上传脚本,负责将容器镜像打包并上传至公司 NAS 网盘
### 背景
目前系统支持多种容器服务(Java、Redis、EMQX、Python、Nacos、Nginx)的部署与升级,需要区分两种平台环境:
- **新统一平台**:使用 `/data/` 目录结构
- **传统平台**:使用 `/var/www/` 目录结构
**现有功能**
- ✅ 远程升级指定服务器的容器版本 `[已实现]`
1. java容器远程更新(传统平台和新统一平台已验证通过)
2. uemqx容器(传统平台和新统一平台已验证通过)
3. uredis容器(传统平台和新统一平台已验证通过)
4. upython容器()
5. nacos容器(新统一平台已验证通过)
6. nginx容器(新统一平台已验证通过)
7. mysql容器(未验证)
- ✅ 自动校验目标服务器架构(仅支持 x86)`[已实现]`
- ✅ 自动递增容器编号避免命名冲突 `[已实现]`
- ✅ 支持多种容器类型的差异化部署 `[已实现]`
- ✅ 支持预设服务器和手动输入服务器信息(IP/端口/用户名/密码)`[已实现]`
- ✅ EMQX 文件同步(配置、数据、日志目录)`[已实现]`
- ✅ Nginx 文件同步(配置、HTML、证书目录)`[已实现]`
- ✅ 自动校验目标服务器是新统一平台目录还是传统平台目录,通过宿主机上的目录来判断,存在/data/services目录的是新统一平台,不存在的则是传统平台 `[已实现]`
- ✅ 自动校验目标服务器上的容器是否已更新 `[已实现]`
- ✅ 远程更新完成后需要将目标服务器上的镜像包清理 `[已实现]`
- ⏸️ Python 文件同步 `[功能保留,暂不启用]`
- ✅ 将主服务器上的容器镜像及部署脚本打包上传至网盘 `[已实现]`
- ✅ container_update.sh 支持单独执行(交互式模式)`[已实现]`
**待实现功能**
- ❌ 最终将sh脚本改为电脑命令行工具上可执行的脚本格式 `[待最后开发]`
---
## 🎯 功能实现总览
> 最后更新时间:2025-12-07
### 远程升级功能 (`remote_update.sh`)
| 功能模块 | 描述 | 状态 |
|----------|------|------|
| 服务器选择 | 支持预设服务器列表和手动输入(IP/端口/用户名/密码) | ✅ 已实现 |
| 自定义端口 | 支持自定义 SSH 端口(默认 22) | ✅ 已实现 |
| 架构校验 | 校验目标服务器是否为 x86 架构 | ✅ 已实现 |
| 镜像传输 | 自动传输镜像文件和部署脚本 | ✅ 已实现 |
| 容器停止 | 自动停止远端旧容器 | ✅ 已实现 |
| 平台识别 | 自动检测目标服务器平台类型(检测 /data/services 目录) | ✅ 已实现 |
| 版本校验 | 自动校验远端容器镜像版本是否已更新 | ✅ 已实现 |
| EMQX 同步 | 同步 EMQX 配置、数据、日志目录 | ✅ 已实现 |
| Python 同步 | 同步 Python 代码和配置 | ⏸️ 暂停 |
| Nginx 同步 | 同步 Nginx 配置、HTML、证书 | ✅ 已实现 |
| 容器编号 | 自动递增容器编号 | ✅ 已实现 |
| 远端执行 | 调用远端部署脚本 | ✅ 已实现 |
| 镜像清理 | 部署完成后自动清理远端镜像包和部署脚本 | ✅ 已实现 |
### 容器部署功能 (`container_update.sh`)
| 容器类型 | 新平台 | 传统平台 | 备注 |
|----------|--------|----------|------|
| Java (ujava) | ✅ | ✅ | 完整端口映射和目录挂载 |
| Redis (uredis) | ✅ | ✅ | 支持配置迁移和端口释放 |
| EMQX (uemqx) | ✅ | ✅ | 多端口映射,完整目录挂载 |
| Python (upython) | ✅ | ✅ | 容器部署正常,暂不同步文件 |
| Nacos (unacos) | ✅ | ❌ | 仅支持新平台,单机模式 |
| Nginx (unginx) | ✅ | ❌ | 仅支持新平台,完整目录挂载 |
### 已新增功能
| 功能 | 描述 | 状态 |
|------|------|------|
| 平台自动检测 | 通过检测 /data/services 目录自动识别新/传统平台 | ✅ 已实现 |
| 版本校验 | 自动校验远端容器镜像是否已更新,避免重复部署 | ✅ 已实现 |
| 镜像清理 | 部署完成后自动清理远端镜像包和部署脚本 | ✅ 已实现 |
| 镜像打包上传 | 将镜像目录打包并上传至公司 NAS 网盘 | ✅ 已实现 |
| 交互式部署 | container_update.sh 支持交互式模式,可单独执行 | ✅ 已实现 |
### 镜像上传功能 (`upload_to_nas.sh`)
| 功能模块 | 描述 | 状态 |
|----------|------|------|
| 目录打包 | 将指定目录压缩为 tar.gz 格式 | ✅ 已实现 |
| NAS 挂载 | 自动挂载公司 SMB 网盘 | ✅ 已实现 |
| 进度显示 | 打包和上传过程显示进度条 | ✅ 已实现 |
| 密码加密 | 网盘密码使用 base64 加密存储 | ✅ 已实现 |
### 待开发功能
| 功能 | 描述 | 状态 |
|------|------|------|
| 批量部署 | 一次升级多台服务器 | ❌ 待开发 |
| 版本管理 | 记录部署版本和时间 | ❌ 待开发 |
---
## 📝 业务操作流程
### 一、远程容器升级操作流程
以下是运维人员在主服务器上执行远程容器升级的完整操作步骤:
#### 步骤 1:启动远程升级脚本
在主服务器的镜像目录下,执行远程升级脚本:
```bash
cd /path/to/container-images
./remote_update.sh
```
#### 步骤 2:选择要升级的容器类型
脚本会显示可选的容器列表,根据提示输入对应编号:
```
可选择的容器:
[1] ujava - Java 服务容器
[2] uemqx - EMQX 消息队列容器
[3] uredis - Redis 缓存容器
[4] upython - Python 服务容器
[5] nacos - Nacos 注册中心容器
[6] unginx - Nginx 反向代理容器
请输入容器编号: _
```
#### 步骤 3:选择目标服务器
脚本提供两种方式选择目标服务器:
**方式一:选择预设服务器**
```
可选择的目标服务器:
[1] 标准版预定运维服务器 (192.168.5.48 root)
[2] 阿曼项目预定服务器 (192.168.5.67 root)
[3] 标准版预定运维测试发布服务器 (192.168.5.47 root)
[0] 手动输入服务器信息
请输入服务器编号: _
```
**方式二:手动输入服务器信息(输入 0)**
```
请输入目标服务器 IP 地址: 192.168.1.100
请输入 SSH 端口号 [默认 22]: 22
请输入登录用户名 [默认 root]: root
请输入登录密码: ********
```
#### 步骤 4:等待架构校验
脚本会自动连接目标服务器,校验其 CPU 架构是否为 x86 系列:
```
[2025-12-07 10:30:00] [INFO] 开始校验远端架构 (192.168.1.100)
[2025-12-07 10:30:02] [INFO] 远端架构 x86_64 校验通过
```
> ⚠️ 如果目标服务器为 ARM 架构,脚本将终止执行并提示错误。
#### 步骤 5:等待文件传输
脚本自动将镜像文件和部署脚本传输到目标服务器:
```
[2025-12-07 10:30:05] [INFO] 创建远端目录 /home/containerUpdate
[2025-12-07 10:30:06] [INFO] 传输镜像与部署脚本到远端目录
java1.8.0_472.tar.gz 100% 1.2GB 50.0MB/s 00:24
container_update.sh 100% 45KB 10.0MB/s 00:00
```
#### 步骤 6:确认旧容器处理
脚本会自动检测并停止远端的旧版本容器:
```
[2025-12-07 10:31:00] [INFO] 查找并停止远端旧容器
[2025-12-07 10:31:02] [INFO] 检测到旧容器 ujava2,执行停止
[2025-12-07 10:31:05] [INFO] ✅ 旧容器 ujava2 已停止
```
#### 步骤 7:自动检测平台类型
脚本会自动检测目标服务器的平台类型(通过检查 `/data/services` 目录是否存在):
```
[2025-12-07 10:30:10] [INFO] 自动检测目标服务器平台类型...
[2025-12-07 10:30:11] [INFO] ✅ 检测到 /data/services 目录存在,自动识别为新统一平台
```
| 检测结果 | 平台类型 | 目录结构 |
|----------|----------|----------|
| 存在 `/data/services` | 新统一平台 | `/data/` 目录 |
| 不存在 `/data/services` | 传统平台 | `/var/www/` 目录 |
> 💡 如果自动检测失败,脚本会回退到手动确认模式。
#### 步骤 8:等待资产同步(部分容器)
根据容器类型,脚本会同步相关配置文件和数据目录:
| 容器 | 同步内容 | 状态 | 备注 |
|------|----------|------|------|
| EMQX | 配置文件、数据目录、日志目录 | ✅ 已实现 | 自动备份远端现有目录 |
| Python | ~~代码目录、配置文件~~ | ⏸️ 暂停 | **暂不同步,功能保留待后续启用** |
| Nginx | 配置文件、HTML、证书目录 | ✅ 已实现 | 仅支持新统一平台 |
```
[2025-12-07 10:31:10] [INFO] 准备同步 EMQX 目录
[2025-12-07 10:31:12] [INFO] 远端目录已备份到 /data/middleware/emqx_backup_20251207_103112
[2025-12-07 10:31:15] [INFO] 使用 rsync 同步 EMQX 目录
[2025-12-07 10:31:30] [INFO] EMQX 目录同步完成
```
#### 步骤 9:等待远端部署执行
脚本在远端服务器上执行容器部署:
```
[2025-12-07 10:31:35] [INFO] 新容器名称确定为 ujava3
[2025-12-07 10:31:36] [INFO] 开始执行远端部署脚本
[2025-12-07 10:31:40] [INFO] 🔍 检查 Java 镜像是否存在...
[2025-12-07 10:31:42] [INFO] ❌ 镜像不存在,开始加载离线包...
[2025-12-07 10:32:00] [INFO] 🎉 镜像加载成功
[2025-12-07 10:32:02] [INFO] 🚀 正在启动 Java 容器: ujava3 ...
[2025-12-07 10:32:10] [INFO] ✅ Java 容器启动成功,等待初始化...
[2025-12-07 10:32:18] [INFO] 🎉 Java 服务部署完成!
```
#### 步骤 10:确认部署结果
部署完成后,脚本会输出容器状态信息:
```
NAMES STATUS PORTS
ujava3 Up 8 seconds 0.0.0.0:8085->8085/tcp, ...
[2025-12-07 10:32:20] [INFO] 远端部署执行完成
```
#### 步骤 11:自动清理远端镜像包
部署完成后,脚本会自动清理远端服务器上的镜像包和部署脚本:
```
[2025-12-07 10:32:21] [INFO] 开始清理远端镜像包...
[2025-12-07 10:32:22] [INFO] ✅ 远端镜像包清理完成
[2025-12-07 10:32:22] [INFO] 已删除镜像包: /home/containerUpdate/java1.8.0_472.tar.gz
[2025-12-07 10:32:22] [INFO] 已删除部署脚本: /home/containerUpdate/container_update.sh
[2025-12-07 10:32:22] [INFO] 远程升级流程结束
```
> 💡 清理过程会自动删除镜像包、部署脚本,如果目录为空还会删除整个目录。
---
### 二、镜像打包上传网盘操作流程
以下是将容器镜像打包并上传至公司 NAS 网盘的完整操作步骤:
#### 步骤 1:启动打包上传脚本
在镜像目录下执行上传脚本:
```bash
cd /path/to/container-images
./upload_to_nas.sh
```
或指定要打包的目录:
```bash
./upload_to_nas.sh /data/temp/container-images
```
#### 步骤 2:等待目录打包
脚本会自动将目录压缩为 tar.gz 格式,并显示打包进度:
```
[2025-12-08 10:30:00] [INFO] ==================================================================
[2025-12-08 10:30:00] [INFO] 容器镜像打包上传工具
[2025-12-08 10:30:00] [INFO] ==================================================================
[2025-12-08 10:30:00] [INFO] 源目录: /data/temp/container-images
[2025-12-08 10:30:00] [INFO] 打包文件: /tmp/container-images_20251208_103000.tar.gz
[2025-12-08 10:30:00] [INFO] 目标网盘: \\192.168.9.9\home\容器镜像包
[2025-12-08 10:30:00] [INFO] ==================================================================
[2025-12-08 10:30:01] [INFO] [步骤 1/3] 打包目录...
[2025-12-08 10:30:01] [INFO] 开始打包目录: /data/temp/container-images
1.2GiB 0:00:24 [50.0MiB/s] [================================>] 100%
[2025-12-08 10:30:25] [INFO] ✅ 打包完成,文件大小: 1.2G
```
#### 步骤 3:等待 NAS 挂载
脚本自动挂载公司 NAS 网盘:
```
[2025-12-08 10:30:26] [INFO] [步骤 2/3] 挂载 NAS...
[2025-12-08 10:30:26] [INFO] 正在挂载 NAS: //192.168.9.9/home
[2025-12-08 10:30:27] [INFO] ✅ NAS 挂载成功
```
#### 步骤 4:等待文件上传
上传过程会显示进度条:
```
[2025-12-08 10:30:28] [INFO] [步骤 3/3] 上传文件...
[2025-12-08 10:30:28] [INFO] 开始上传文件到 NAS...
[2025-12-08 10:30:28] [INFO] 源文件: /tmp/container-images_20251208_103000.tar.gz
[2025-12-08 10:30:28] [INFO] 目标: //192.168.9.9/home/容器镜像包/container-images_20251208_103000.tar.gz
1.2GiB 0:01:30 [13.6MiB/s] [================================>] 100%
[2025-12-08 10:32:00] [INFO] ✅ 上传完成!
[2025-12-08 10:32:00] [INFO] 文件路径: \\192.168.9.9\home\容器镜像包\container-images_20251208_103000.tar.gz
[2025-12-08 10:32:00] [INFO] 文件大小: 1.2G
```
#### 步骤 5:确认上传结果
```
[2025-12-08 10:32:01] [INFO] 清理临时文件...
[2025-12-08 10:32:01] [INFO] ==================================================================
[2025-12-08 10:32:01] [INFO] 🎉 全部完成!
[2025-12-08 10:32:01] [INFO] ==================================================================
```
#### 网盘信息
| 配置项 | 值 |
|--------|-----|
| 服务器地址 | `\\192.168.9.9\home` |
| 账号 | 陈泽键 |
| 上传目录 | 容器镜像包/ |
| 协议 | SMB/CIFS |
#### 命令行选项
```bash
# 显示帮助
./upload_to_nas.sh -h
# 指定打包文件名
./upload_to_nas.sh -n java_container_v6 /data/temp/java
# 指定上传子目录
./upload_to_nas.sh -d 测试包 /data/temp/test
```
---
### 三、本地容器部署操作流程(交互式模式)
`container_update.sh` 脚本支持两种运行模式,可单独在目标服务器上执行:
#### 交互式模式(推荐)
直接运行脚本,无需参数:
```bash
./container_update.sh
```
##### 步骤 1:选择容器类型
```
[2025-12-08 10:00:00] [INFO] ==================================================================
[2025-12-08 10:00:00] [INFO] 容器部署工具 - 交互式模式
[2025-12-08 10:00:00] [INFO] ==================================================================
[2025-12-08 10:00:00] [INFO] 可选择的容器类型:
[1] ujava
[2] uemqx
[3] uredis
[4] upython
[5] unacos
[6] unginx
请输入容器编号 (1-6): 1
[2025-12-08 10:00:02] [INFO] 已选择容器类型: ujava
```
##### 步骤 2:自动检测平台类型
```
[2025-12-08 10:00:02] [INFO] 自动检测平台类型...
[2025-12-08 10:00:02] [INFO] ✅ 检测到 /data/services 目录,识别为新统一平台
当前识别为 new 平台,是否正确? (y/n) [默认 y]: y
```
##### 步骤 3:自动搜索镜像文件
```
[2025-12-08 10:00:03] [INFO] 搜索镜像文件: java1.8.0_472.tar.gz
[2025-12-08 10:00:03] [INFO] ✅ 找到镜像文件: /data/temp/java1.8.0_472.tar.gz
[2025-12-08 10:00:03] [INFO] 新容器名称: ujava3
```
##### 步骤 4:确认并执行部署
```
[2025-12-08 10:00:03] [INFO] ==================================================================
[2025-12-08 10:00:03] [INFO] 部署信息确认:
[2025-12-08 10:00:03] [INFO] 容器类型: ujava
[2025-12-08 10:00:03] [INFO] 容器名称: ujava3
[2025-12-08 10:00:03] [INFO] 平台类型: new
[2025-12-08 10:00:03] [INFO] 镜像文件: /data/temp/java1.8.0_472.tar.gz
[2025-12-08 10:00:03] [INFO] ==================================================================
确认开始部署? (y/n) [默认 y]: y
[2025-12-08 10:00:05] [INFO] 开始部署 ujava (容器: ujava3, 平台: new, 镜像: /data/temp/java1.8.0_472.tar.gz)
```
#### 命令行模式
适用于脚本调用或自动化场景:
```bash
# 基本用法
./container_update.sh ujava3 /data/temp/java1.8.0_472.tar.gz --new-platform
# 传统平台
./container_update.sh ujava3 /data/temp/java1.8.0_472.tar.gz
```
---
## 🔧 remote_update.sh 脚本详细分析
### 1. 脚本概述
| 属性 | 说明 |
|------|------|
| **文件位置** | `c:\Users\UBAINS\Desktop\新的容器镜像\remote_update.sh` |
| **脚本用途** | 远程升级指定服务器的容器版本 |
| **执行环境** | 主服务器(Linux/Bash 环境) |
### 2. 核心配置定义
#### 2.1 服务器信息配置
```bash
#!/usr/bin/env bash
# 远程升级指定服务器的容器版本脚本
# 实现内容:
# 1. 校验目标服务器架构是否为 x86
# 2. 将当前目录下的镜像文件与部署脚本同步至远端目录
# 3. 停止远端旧容器
# 4. 交互询问目标服务器是否属于新统一平台
# 5. 自动递增容器编号并执行远端部署脚本
set -euo pipefail
REMOTE_ARCH_ALLOW_REGEX='^(x86_64|amd64|i386|i686)$'
declare -A SERVER_IP SERVER_USER SERVER_PASS
SERVER_IP["1"]="192.168.5.48"
SERVER_USER["1"]="root"
SERVER_PASS["1"]="Ubains@123"
SERVER_IP["2"]="192.168.5.67"
SERVER_USER["2"]="root"
SERVER_PASS["2"]="Ubains@123"
SERVER_IP["3"]="192.168.5.47"
SERVER_USER["3"]="root"
SERVER_PASS["3"]="Ubains@1234"
declare -A SERVER_DESC
SERVER_DESC["1"]="标准版预定运维服务器"
SERVER_DESC["2"]="阿曼项目预定服务器"
SERVER_DESC["3"]="标准版预定运维测试发布服务器"
```
**配置说明**
| 配置项 | 说明 |
|--------|------|
| `REMOTE_ARCH_ALLOW_REGEX` | 允许的架构正则表达式(仅 x86 系列) |
| `SERVER_IP` | 目标服务器 IP 地址关联数组 |
| `SERVER_USER` | 目标服务器登录用户名 |
| `SERVER_PASS` | 目标服务器登录密码 |
| `SERVER_DESC` | 服务器描述信息 |
> 💡 **扩展功能**:除了预设服务器外,脚本支持手动输入目标服务器信息(输入编号 `0`),包括:
> - 服务器 IP 地址
> - SSH 端口号(默认 22)
> - 登录用户名(默认 root)
> - 登录密码
#### 2.2 容器与镜像映射配置
```bash
CONTAINER_OPTIONS=(ujava uemqx uredis upython nacos unginx)
declare -A CONTAINER_IMAGE
CONTAINER_IMAGE["ujava"]="java1.8.0_472.tar.gz"
CONTAINER_IMAGE["uemqx"]="uemqx5.8.4.tar.gz"
CONTAINER_IMAGE["uredis"]="redis8.2.2.tar.gz"
CONTAINER_IMAGE["upython"]="python_v15.tar.gz"
CONTAINER_IMAGE["nacos"]="nacos-server-v2.5.2.tar.gz"
CONTAINER_IMAGE["unginx"]="/data/temp/nginx-1.29.3.tar.gz"
```
**容器映射说明**
| 容器名称 | 镜像文件 | 说明 | 状态 |
|----------|----------|------|------|
| `ujava` | `java1.8.0_472.tar.gz` | Java 服务容器 | ✅ |
| `uemqx` | `uemqx5.8.4.tar.gz` | EMQX 消息队列容器 | ✅ |
| `uredis` | `redis8.2.2.tar.gz` | Redis 缓存容器 | ✅ |
| `upython` | `python_v15.tar.gz` | Python 服务容器 | ✅ |
| `nacos` | `nacos-server-v2.5.2.tar.gz` | Nacos 注册中心容器 | ✅ |
| `unginx` | `/data/temp/nginx-1.29.3.tar.gz` | Nginx 反向代理容器(使用绝对路径) | ✅ |
### 3. 核心功能模块
#### 3.1 SSH/SCP 执行函数
```bash
ssh_exec() {
sshpass -p "${REMOTE_PASS}" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_TIMEOUT} "$@"
}
scp_exec() {
sshpass -p "${REMOTE_PASS}" scp -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_TIMEOUT} "$@"
}
```
**功能说明**
- 使用 `sshpass` 实现非交互式密码认证
- 设置 `StrictHostKeyChecking=no` 跳过主机密钥验证
- 配置 30 秒连接超时
#### 3.2 EMQX 资产同步函数
```bash
sync_emqx_assets() {
local remote_dir="$1"
local platform_label="$2"
if [[ ! -d "${LOCAL_EMQX_DIR}" ]]; then
log ERROR "本地 EMQX 目录不存在: ${LOCAL_EMQX_DIR}"
exit 11
fi
log INFO "准备同步 EMQX 目录 (${LOCAL_EMQX_DIR} -> ${REMOTE_HOST}:${remote_dir})"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "mkdir -p '${remote_dir}'"
# 备份远端现有目录
local backup_cmd
backup_cmd=$(cat <<'EOF'
set -e
TARGET_DIR="$1"
if [[ -d "${TARGET_DIR}" && -n "$(ls -A "${TARGET_DIR}")" ]]; then
ts=$(date +%Y%m%d_%H%M%S)
backup="${TARGET_DIR}_backup_${ts}"
cp -r "${TARGET_DIR}" "${backup}"
echo "${backup}"
fi
EOF
)
local backup_path
backup_path="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "bash -s '${remote_dir}'" <<<"${backup_cmd}" || true)"
if [[ -n "${backup_path}" ]]; then
log INFO "远端目录已备份到 ${backup_path}"
else
log INFO "远端目录为空或不存在,跳过备份"
fi
# 优先使用 rsync,备选 scp
if command -v rsync >/dev/null 2>&1; then
log INFO "使用 rsync 同步 EMQX 目录"
sshpass -p "${REMOTE_PASS}" rsync -az --delete \
-e "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_TIMEOUT}" \
"${LOCAL_EMQX_DIR}/" "${REMOTE_USER}@${REMOTE_HOST}:${remote_dir}/"
else
log WARN "未检测到 rsync,改用 scp 拷贝 EMQX 目录"
scp_exec -r "${LOCAL_EMQX_DIR}/." "${REMOTE_USER}@${REMOTE_HOST}:${remote_dir}/"
fi
# 设置远端配置文件权限
log INFO "EMQX 目录同步完成,开始设置远端配置文件权限"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" \
"if [[ -d '${remote_dir}/config' ]]; then
cd '${remote_dir}/config' && ls *.conf >/dev/null 2>&1 && chmod +x *.conf || true
else
echo 'WARN: config 目录不存在'
fi"
log INFO "远端 EMQX 配置权限处理完成 (${platform_label} 平台)"
}
```
**同步流程**
1. 检查本地 EMQX 目录是否存在
2. 在远端创建目标目录
3. 备份远端现有目录(带时间戳)
4. 优先使用 `rsync` 同步(支持增量传输),备选 `scp`
5. 设置远端配置文件权限
#### 3.3 Python 资产同步函数
```bash
sync_python_assets() {
# ... 同步逻辑类似 EMQX
# 特殊处理:自动替换配置中的 IP 地址
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" \
"find '${remote_dir}' -type f -name '*.conf' -exec sed -i 's/192\\.168\\.5\\.44/${REMOTE_HOST}/g' {} +" || true
# 特殊处理:保留 authFile.uas 认证文件
}
```
**特殊处理**
- 自动将配置文件中的 `192.168.5.44` 替换为目标服务器 IP
- 从备份目录回拷 `authFile.uas` 认证文件
#### 3.4 Nginx 资产同步函数
```bash
sync_nginx_assets() {
# ... 同步逻辑
# 仅支持新统一平台
}
```
### 4. 主执行流程
#### 4.1 架构校验
```bash
log INFO "开始校验远端架构 (${REMOTE_HOST})"
ARCH_OUTPUT="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "uname -m" 2>&1)"
SSH_STATUS=$?
if [[ ${SSH_STATUS} -ne 0 ]]; then
log ERROR "无法获取远端架构,请检查网络或权限"
log ERROR "SSH 输出: ${ARCH_OUTPUT}"
exit 4
fi
ARCH="$(printf '%s' "${ARCH_OUTPUT}" | tr -d '\r' | awk 'NF {last=$0} END {print last}')"
if [[ ! "${ARCH}" =~ ${REMOTE_ARCH_ALLOW_REGEX} ]]; then
log ERROR "远端架构为 ${ARCH},非 x86,终止操作"
exit 5
fi
log INFO "远端架构 ${ARCH} 校验通过"
```
#### 4.2 镜像与脚本传输
```bash
log INFO "创建远端目录 ${REMOTE_DIR}"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "mkdir -p '${REMOTE_DIR}'"
log INFO "传输镜像与部署脚本到远端目录"
# nginx 使用绝对路径,需要特殊处理
if [[ "${IMAGE_FILE}" = /* ]]; then
# 绝对路径:先复制到临时位置,再传输
TEMP_IMAGE="/tmp/$(basename "${IMAGE_FILE}")"
cp "${IMAGE_FILE}" "${TEMP_IMAGE}"
scp_exec "${TEMP_IMAGE}" "${DEPLOY_SCRIPT}" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/"
rm -f "${TEMP_IMAGE}"
# 在远端将镜像文件移动到正确位置
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" \
"mkdir -p /data/temp && mv '${REMOTE_DIR}/$(basename "${IMAGE_FILE}")' /data/temp/"
else
# 相对路径:直接传输
scp_exec "${IMAGE_FILE}" "${DEPLOY_SCRIPT}" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/"
fi
```
#### 4.3 旧容器停止
```bash
log INFO "查找并停止远端旧容器"
CURRENT_CONTAINER=$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" \
"docker ps --format '{{.Names}}' | grep -E '^${CONTAINER_PREFIX}([0-9]+)?$' | sort -V | tail -n1" || true)
if [[ -n "${CURRENT_CONTAINER}" ]]; then
log INFO "检测到旧容器 ${CURRENT_CONTAINER},执行停止"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "docker stop '${CURRENT_CONTAINER}'"
else
log INFO "未找到匹配 ${CONTAINER_PREFIX}${CONTAINER_PREFIX}[数字] 的运行容器"
fi
```
#### 4.4 平台类型自动检测与资产同步
```bash
# 自动检测平台类型(通过检查 /data/services 目录是否存在)
log INFO "自动检测目标服务器平台类型..."
PLATFORM_CHECK_OUTPUT="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "[ -d /data/services ] && echo 'NEW_PLATFORM' || echo 'OLD_PLATFORM'" 2>/dev/null || echo "CHECK_FAILED")"
if [[ "${PLATFORM_CHECK_RESULT}" == "NEW_PLATFORM" ]]; then
PLATFORM_FLAG="--new-platform"
PLATFORM_TYPE="new"
log INFO "✅ 检测到 /data/services 目录存在,自动识别为新统一平台"
elif [[ "${PLATFORM_CHECK_RESULT}" == "OLD_PLATFORM" ]]; then
PLATFORM_FLAG=""
PLATFORM_TYPE="old"
log INFO "✅ 未检测到 /data/services 目录,自动识别为传统平台"
else
# 自动检测失败时回退到手动确认模式
read -rp "目标服务器是否为新统一平台? (y/n): " PLATFORM_INPUT
# ...
fi
if [[ "${CONTAINER_PREFIX}" == "uemqx" ]]; then
if [[ "${PLATFORM_TYPE}" == "new" ]]; then
REMOTE_EMQX_DIR="/data/middleware/emqx"
else
REMOTE_EMQX_DIR="/var/www/emqx"
fi
sync_emqx_assets "${REMOTE_EMQX_DIR}" "${PLATFORM_TYPE}"
fi
# ... Python 和 Nginx 类似处理
```
**平台目录对照表**
| 容器 | 新统一平台目录 | 传统平台目录 |
|------|----------------|--------------|
| EMQX | `/data/middleware/emqx` | `/var/www/emqx` |
| Python | `/data/services/api/python-cmdb` | `/var/www/html` |
| Nginx | `/data/middleware/nginx` | 仅支持新平台 |
#### 4.5 容器编号递增与远端部署执行
```bash
if [[ -n "${CURRENT_CONTAINER}" ]]; then
LAST_NUM="${CURRENT_CONTAINER##*[!0-9]}"
if [[ -z "${LAST_NUM}" ]]; then
LAST_NUM=0
fi
NEXT_NUM=$((LAST_NUM + 1))
else
NEXT_NUM=1
fi
NEW_CONTAINER="${CONTAINER_PREFIX}${NEXT_NUM}"
log INFO "新容器名称确定为 ${NEW_CONTAINER}"
# 构建远端执行命令
REMOTE_CMD=$(cat <<EOF
set -euo pipefail
cd '${REMOTE_DIR}'
sed -i 's/\r$//' '${DEPLOY_NAME}' 2>/dev/null || true
chmod +x '${DEPLOY_NAME}'
./'${DEPLOY_NAME}' '${NEW_CONTAINER}' '${REMOTE_IMAGE_PATH}'${PLATFORM_TAIL}
EOF
)
log INFO "开始执行远端部署脚本"
sshpass -p "${REMOTE_PASS}" ssh -tt \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=${SSH_TIMEOUT} \
"${REMOTE_USER}@${REMOTE_HOST}" "${REMOTE_CMD}"
```
---
## 🔧 container_update.sh 脚本详细分析
### 1. 脚本概述
| 属性 | 说明 |
|------|------|
| **文件位置** | `c:\Users\UBAINS\Desktop\新的容器镜像\container_update.sh` |
| **脚本用途** | 在目标服务器上执行容器部署 |
| **执行环境** | 目标服务器(由 remote_update.sh 触发) |
### 2. 命令行参数解析
```bash
main() {
if [[ $# -lt 2 ]]; then
usage
exit 1
fi
local container_name="$1"
local image_archive="$2"
shift 2
local platform_type="old"
while [[ $# -gt 0 ]]; do
case "$1" in
--new-platform)
platform_type="new"
;;
*)
usage
exit 1
;;
esac
shift
done
# 容器前缀解析
local container_prefix
if [[ "${container_name}" =~ ^([a-zA-Z]+)[0-9]+$ ]]; then
container_prefix="${BASH_REMATCH[1]}"
else
container_prefix="${container_name}"
fi
}
```
**参数说明**
| 参数 | 必选 | 说明 |
|------|------|------|
| `容器名称` | ✅ | 如 `ujava3`,用于指定 docker 容器名称 |
| `镜像包` | ✅ | 离线镜像 tar 包路径 |
| `--new-platform` | ❌ | 目标服务器为新统一平台时添加 |
### 3. 部署函数路由
```bash
local deploy_fn
case "${container_prefix}" in
ujava)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="java_newplatform_x86"
else
deploy_fn="java_oldplatform_x86"
fi
;;
uredis)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="redis_newplatform_x86"
else
deploy_fn="redis_oldplatform_x86"
fi
;;
uemqx)
# ...
;;
upython)
# ...
;;
unacos)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="nacos_newplatform_x86"
else
log ERROR "Nacos 服务仅支持新统一平台,请使用 --new-platform 参数"
exit 4
fi
;;
unginx)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="nginx_newplatform_x86"
else
log ERROR "Nginx 服务仅支持新统一平台,请使用 --new-platform 参数"
exit 4
fi
;;
esac
```
**部署函数映射表**
| 容器前缀 | 新平台函数 | 传统平台函数 | 实现状态 |
|----------|------------|--------------|----------|
| `ujava` | `java_newplatform_x86` | `java_oldplatform_x86` | ✅ 已实现 |
| `uredis` | `redis_newplatform_x86` | `redis_oldplatform_x86` | ✅ 已实现 |
| `uemqx` | `emqx_newplatform_x86` | `emqx_oldplatform_x86` | ✅ 已实现 |
| `upython` | `python_newplatform_x86` | `python_oldplatform_x86` | ✅ 已实现 (无文件同步) |
| `unacos` | `nacos_newplatform_x86` | ❌ 不支持 | ✅ 已实现 |
| `unginx` | `nginx_newplatform_x86` | ❌ 不支持 | ✅ 已实现 |
### 4. 端口释放函数
```bash
release_port_if_busy() {
local target_port="$1"
local publish_info
publish_info="$($sudoset lsof -i TCP:${target_port} -sTCP:LISTEN -t || true)"
if [[ -z "${publish_info}" ]]; then
log "INFO" "端口 ${target_port} 当前空闲"
return 0
fi
log "WARN" "检测到端口 ${target_port} 被占用,尝试释放"
while read -r pid; do
[[ -z "${pid}" ]] && continue
if $sudoset kill -9 "${pid}"; then
log "INFO" "已结束占用端口 ${target_port} 的进程 PID=${pid}"
else
log "ERROR" "结束进程 ${pid} 失败"
return 1
fi
done <<< "${publish_info}"
}
```
### 5. Java 容器部署函数详解
#### 5.1 新统一平台部署
```bash
java_newplatform_x86() {
# 配置项
local host_api="/data/services/api"
local host_web="/data/services/web"
local host_nginx_log="/data/middleware/nginx/nginx_log"
local host_fdfs_data="/var/fdfs/storage/data"
local port_args=(
"-p 8085:8085"
"-p 8993:8993" "-p 8994:8994" "-p 8995:8995"
"-p 8999:8999"
"-p 8719:8719" "-p 8720:8720"
"-p 9204:9204" "-p 9200:9200" "-p 9201:9201"
"-p 9905:9905" "-p 9911:9911" "-p 9908:9908"
"-p 9906:9906" "-p 9907:9907" "-p 9909:9909" "-p 9910:9910"
"-p 30880:30880" "-p 30881:30881" "-p 30882:30882"
"-p 30883:30883" "-p 30884:30884"
)
# 部署流程
# 1. 检查旧容器并停止
# 2. 检查/加载镜像
# 3. 启动新容器
# 4. 验证容器运行状态
}
```
**新平台 Java 容器端口映射**
| 端口 | 用途 |
|------|------|
| 8085 | 主 API 服务 |
| 8993-8995 | 辅助服务端口 |
| 8999 | 业务服务端口 |
| 8719-8720 | Sentinel 相关 |
| 9200-9204 | 内部通信端口 |
| 9905-9911 | 业务服务端口组 |
| 30880-30884 | 扩展服务端口 |
**挂载目录**
| 宿主机目录 | 容器目录 | 说明 |
|------------|----------|------|
| `/data/services/api` | `/var/www/java/api` | API 代码目录 |
| `/data/services/web` | `/var/www/java/web` | Web 资源目录 |
| `/data/middleware/nginx/nginx_log` | `/usr/local/nginx/logs` | Nginx 日志 |
| `/var/fdfs/storage/data` | `/var/fdfs/storage/data` | FastDFS 存储 |
#### 5.2 传统平台部署
```bash
java_oldplatform_x86() {
# 配置项
local host_java="/var/www/java"
local host_nginx_log="/var/www/java/nginx-conf.d/nginx_log"
local port_args=(
"-p554:554" # RTSP
"-p1935:1935" # RTMP
"-p10000:10000"
"-p2333:2333" "-p2334:2334"
"-p8997:8997" "-p8998:8998"
"-p8079:8079"
"-p443:443" # HTTPS
"-p8080:8080" # HTTP
"-p8085:8085" "-p8086:8086" "-p8087:8087" "-p8088:8088"
"-p8889:8889"
"-p8999:8999"
)
}
```
### 6. Redis 容器部署函数
```bash
redis_oldplatform_x86() {
# 特殊处理:备份旧配置并重命名
cp "${old_redis_conf}" "${old_redis_conf}bak"
mv "${old_redis_conf}" "${new_redis_conf}"
# 使用 host 网络模式
$sudoset docker run -itd \
--network host \
-v "${new_redis_conf}:/etc/redis/redis.conf" \
# ...
}
redis_newplatform_x86() {
# 配置路径差异
local redis_conf_host="/data/middleware/redis/config/redis.conf"
local redis_data_host="/data/middleware/redis/data"
}
```
**Redis 配置对比**
| 配置项 | 新平台 | 传统平台 |
|--------|--------|----------|
| 配置文件 | `/data/middleware/redis/config/redis.conf` | `/var/www/redis/redis-8.2.2.conf` |
| 数据目录 | `/data/middleware/redis/data` | `/var/www/redis/data` |
| 网络模式 | `host` | `host` |
### 7. EMQX 容器部署函数
```bash
emqx_newplatform_x86() {
# 端口映射
local ports=(1883 8083 8084 8883 18083)
# 目录挂载
-v "${emqx_config_host}:/opt/emqx/etc" \
-v "${emqx_data_host}:/opt/emqx/data" \
-v "${emqx_log_host}:/opt/emqx/log" \
}
```
**EMQX 端口说明**
| 端口 | 协议 | 用途 |
|------|------|------|
| 1883 | MQTT | TCP 连接 |
| 8083 | MQTT/WS | WebSocket 连接 |
| 8084 | MQTT/WSS | 安全 WebSocket |
| 8883 | MQTTS | 安全 MQTT |
| 18083 | HTTP | Dashboard 管理界面 |
### 8. Python 容器部署函数
```bash
python_newplatform_x86() {
local host_app_dir="/data/services/api/python-cmdb"
local ports=(-p 8000:8000 -p 8002:8002)
}
python_oldplatform_x86() {
local host_html_dir="/var/www/html"
local ports=(-p 8000:8000 -p 8002:8002 -p 8443:8443 -p 9009:9009)
# 特殊处理:容器启动后触发应用
$sudoset docker exec "${container_name}" /bin/bash -c \
"cd /var/www/html && nohup python manage.py runserver 0.0.0.0:8000 >/tmp/python_manage.log 2>&1 &" || true
}
```
### 9. Nacos 容器部署函数
```bash
nacos_newplatform_x86() {
# 单机模式部署
-e MODE=standalone \
-v "${host_data_dir}:/home/nacos" \
# 端口
-p 8848:8848 \ # HTTP API
-p 9848:9848 \ # gRPC
}
```
### 10. Nginx 容器部署函数
```bash
nginx_newplatform_x86() {
# 目录预检查与创建
local required_dirs=(
"/data/middleware/nginx/log"
"/data/middleware/nginx/data/cache"
"/data/middleware/nginx/data/html"
"/data/middleware/nginx/config"
"/data/services/web"
"/data/security/nginx_cert"
)
# 权限设置
$sudoset chown -R 1000:1000 /data/middleware/nginx/log
$sudoset chown -R 1000:1000 /data/middleware/nginx/data/cache
$sudoset chmod -R 700 /data/middleware/nginx/data/cache
}
```
---
## 🔄 完整部署流程图
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 主服务器执行 remote_update.sh │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤1] 交互选择 │
│ - 选择容器类型 (ujava/uemqx/uredis/upython/nacos/unginx) │
│ - 选择目标服务器: │
│ • 方式一:选择预设服务器 (1/2/3) │
│ • 方式二:输入 0 手动填写服务器信息 │
│ → 输入服务器 IP 地址 │
│ → 输入 SSH 端口号(默认 22) │
│ → 输入登录用户名和密码 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤2] 架构校验 │
│ - SSH 连接目标服务器(支持自定义端口) │
│ - 执行 uname -m 获取架构 │
│ - 校验是否为 x86_64/amd64/i386/i686 │
│ - 非 x86 架构则终止 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤3] 文件传输 │
│ - 创建远端目录 /home/containerUpdate │
│ - SCP 传输镜像文件 (*.tar.gz) │
│ - SCP 传输部署脚本 (container_update.sh) │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤4] 停止旧容器 │
│ - docker ps 查找 ${container_prefix}[数字] 格式的容器 │
│ - docker stop 停止最新版本容器 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤5] 平台类型自动检测 ✅ 已实现 │
│ - 检测远端 /data/services 目录是否存在 │
│ - 存在 → 自动识别为新统一平台,使用 /data/ 目录结构 │
│ - 不存在 → 自动识别为传统平台,使用 /var/www/ 目录结构 │
│ - 检测失败 → 回退到手动确认模式 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤6] 资产同步 (根据容器类型) [部分已实现] │
│ - EMQX: 同步配置文件、数据目录、日志目录 ✅ 已实现 │
│ - Python: 【暂不同步】代码保留,后续启用 ⏸️ 暂停 │
│ - Nginx: 同步配置、HTML、证书目录 ✅ 已实现 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤7] 计算新容器名称 │
│ - 获取当前最新容器编号 │
│ - 编号 + 1 = 新容器名称 │
│ - 例: ujava2 → ujava3 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤8] 执行远端部署 │
│ - SSH 连接目标服务器 │
│ - 处理脚本换行符 (dos2unix) │
│ - 执行: ./container_update.sh ${新容器名} ${镜像路径} [--new-platform] │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 目标服务器执行 container_update.sh │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [子步骤1] 解析参数,确定部署函数 │
│ - 解析容器前缀 (ujava/uredis/...) │
│ - 根据平台类型选择对应函数 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [子步骤2] 检查/停止同名容器 │
│ - docker ps -a 检查容器状态 │
│ - 运行中 → docker stop │
│ - 已存在但未运行 → 报错退出 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [子步骤3] 检查/加载镜像 │
│ - docker images 检查镜像是否存在 │
│ - 不存在 → docker load -i ${镜像包} │
│ - docker tag 设置镜像标签 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [子步骤4] 端口检查与释放 │
│ - lsof 检查端口占用 │
│ - kill -9 释放占用端口的进程 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [子步骤5] 启动新容器 │
│ - docker run -d 启动容器 │
│ - 配置端口映射、目录挂载、重启策略等 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [子步骤6] 验证部署结果 │
│ - sleep 等待容器初始化 │
│ - docker ps 检查容器是否运行 │
│ - 输出容器状态信息 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤9] 清理远端镜像包 ✅ 已实现 │
│ - 删除远端镜像包文件 │
│ - 删除远端部署脚本 │
│ - 如目录为空则删除目录 │
└─────────────────────────────────────────────────────────────────────────┘
[部署完成]
```
---
## 📦 镜像打包上传至网盘功能
### 1. 功能概述
`upload_to_nas.sh` 脚本实现将容器镜像目录打包并上传至公司 NAS 网盘的功能。
### 2. 脚本配置
```bash
#!/usr/bin/env bash
# 容器镜像打包上传至公司网盘脚本
# 网盘配置
NAS_SERVER="192.168.9.9"
NAS_SHARE="home"
NAS_USER="陈泽键"
# 密码使用 base64 加密存储
NAS_PASS_ENCRYPTED="Mm01ZUwuTUA="
NAS_UPLOAD_DIR="容器镜像包"
```
### 3. 核心功能模块
#### 3.1 目录打包
```bash
pack_current_dir() {
# 使用 pv 显示打包进度
tar -cf - -C "$(dirname "${source_dir}")" "$(basename "${source_dir}")" | \
pv -s "${dir_size}" -p -t -e -r | \
gzip > "${pack_name}"
}
```
#### 3.2 NAS 挂载
```bash
mount_nas() {
# 解密密码
nas_pass="$(echo "${NAS_PASS_ENCRYPTED}" | base64 -d)"
# 挂载 SMB/CIFS 共享
sudo mount -t cifs "//${NAS_SERVER}/${NAS_SHARE}" "${NAS_MOUNT_POINT}" \
-o "username=${NAS_USER},password=${nas_pass},iocharset=utf8,vers=2.0"
}
```
#### 3.3 文件上传(带进度显示)
```bash
upload_to_nas() {
# 使用 pv 显示上传进度
pv -p -t -e -r "${source_file}" | sudo tee "${target_file}" > /dev/null
}
```
### 4. 使用方法
```bash
# 打包并上传当前目录
./upload_to_nas.sh
# 打包并上传指定目录
./upload_to_nas.sh /data/temp/container-images
# 指定打包文件名
./upload_to_nas.sh -n java_container_v6 /data/temp/java
# 指定上传子目录
./upload_to_nas.sh -d 测试包 /data/temp/test
```
### 5. 依赖要求
| 依赖 | 用途 | 必需 |
|------|------|------|
| cifs-utils | SMB/CIFS 挂载支持 | ✅ 是 |
| pv | 进度条显示 | ❌ 否(无则不显示进度) |
安装依赖:
```bash
# CentOS/RHEL
yum install -y cifs-utils pv
# Ubuntu/Debian
apt install -y cifs-utils pv
```
---
## 🔧 container_update.sh 交互式模式
### 1. 功能概述
`container_update.sh` 现支持两种运行模式:
| 模式 | 触发方式 | 适用场景 |
|------|----------|----------|
| 交互式模式 | 无参数运行 | 手动在目标服务器执行部署 |
| 命令行模式 | 带参数运行 | 远程调用或自动化脚本 |
### 2. 交互式模式流程
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 执行 ./container_update.sh │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤1] 选择容器类型 │
│ - 显示可选容器列表 (1-6) │
│ - 用户输入编号选择 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤2] 自动检测平台类型 │
│ - 检测本地 /data/services 目录 │
│ - 自动识别新/传统平台 │
│ - 用户可手动修正 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤3] 搜索镜像文件 │
│ - 在常用路径搜索对应镜像 │
│ - 未找到则提示手动输入 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤4] 确定容器名称 │
│ - 检测现有同类型容器 │
│ - 自动递增编号 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤5] 确认并执行部署 │
│ - 显示部署信息摘要 │
│ - 用户确认后执行 │
└─────────────────────────────────────────────────────────────────────────┘
```
### 3. 镜像文件搜索路径
脚本会按以下顺序搜索镜像文件:
1. 当前目录 `./`
2. `/data/temp/`
3. `/home/containerUpdate/`
### 4. 容器与镜像映射
```bash
declare -A CONTAINER_IMAGE_MAP
CONTAINER_IMAGE_MAP["ujava"]="java1.8.0_472.tar.gz"
CONTAINER_IMAGE_MAP["uemqx"]="uemqx5.8.4.tar.gz"
CONTAINER_IMAGE_MAP["uredis"]="redis8.2.2.tar.gz"
CONTAINER_IMAGE_MAP["upython"]="python_v15.tar.gz"
CONTAINER_IMAGE_MAP["unacos"]="nacos-server-v2.5.2.tar.gz"
CONTAINER_IMAGE_MAP["unginx"]="nginx-1.29.3.tar.gz"
```
---
## 📦 待实现功能(已废弃的旧方案参考)
以下是早期规划的方案,已被 `upload_to_nas.sh` 替代:
### 旧方案:package_upload.sh
```bash
#!/usr/bin/env bash
# 镜像打包上传脚本(旧方案,已废弃)
# 配置区
PACKAGE_DIR="/data/temp/packages"
UPLOAD_REMOTE="webdav:ubains-packages" # rclone 远程配置名称
# 容器与镜像映射
declare -A CONTAINER_IMAGE
CONTAINER_IMAGE["ujava"]="java1.8.0_472.tar.gz"
CONTAINER_IMAGE["uemqx"]="uemqx5.8.4.tar.gz"
CONTAINER_IMAGE["uredis"]="redis8.2.2.tar.gz"
CONTAINER_IMAGE["upython"]="python_v15.tar.gz"
CONTAINER_IMAGE["nacos"]="nacos-server-v2.5.2.tar.gz"
CONTAINER_IMAGE["unginx"]="/data/temp/nginx-1.29.3.tar.gz"
# 打包函数
package_container() {
local container_type="$1"
local timestamp=$(date +%Y%m%d_%H%M%S)
local package_name="${container_type}_${timestamp}.tar.gz"
# 创建临时打包目录
local temp_dir=$(mktemp -d)
# 复制镜像文件
local image_file="${CONTAINER_IMAGE[${container_type}]}"
if [[ "${image_file}" = /* ]]; then
cp "${image_file}" "${temp_dir}/"
else
cp "./${image_file}" "${temp_dir}/"
fi
# 复制部署脚本
cp "./container_update.sh" "${temp_dir}/"
cp "./remote_update.sh" "${temp_dir}/"
# 添加版本说明文件
cat > "${temp_dir}/VERSION.txt" <<EOF
Container: ${container_type}
Image: ${image_file}
Package Time: ${timestamp}
EOF
# 打包
mkdir -p "${PACKAGE_DIR}"
tar -czvf "${PACKAGE_DIR}/${package_name}" -C "${temp_dir}" .
# 清理临时目录
rm -rf "${temp_dir}"
echo "${PACKAGE_DIR}/${package_name}"
}
# 上传函数
upload_to_cloud() {
local package_path="$1"
if command -v rclone >/dev/null 2>&1; then
rclone copy "${package_path}" "${UPLOAD_REMOTE}/"
else
log "ERROR" "未安装 rclone,无法上传"
return 1
fi
}
# 主流程
main() {
# 选择容器类型
# 打包
# 上传
# 清理本地打包文件(可选)
}
main "$@"
```
#### 2.2 支持的上传方式
| 上传方式 | 工具 | 配置要求 |
|----------|------|----------|
| WebDAV | rclone | 配置 WebDAV 远程 |
| 阿里云 OSS | rclone/ossutil | 配置 AccessKey |
| 腾讯云 COS | rclone/coscmd | 配置 SecretId |
| 百度网盘 | bypy | OAuth 授权 |
| FTP/SFTP | lftp/scp | 配置账号密码 |
---
## ⚠️ 注意事项与安全建议
### 1. 安全风险
| 风险项 | 当前状态 | 建议改进 |
|--------|----------|----------|
| 明文密码存储 | ⚠️ 脚本内硬编码密码 | 使用 SSH 密钥认证或加密配置文件 |
| `StrictHostKeyChecking=no` | ⚠️ 跳过主机验证 | 首次连接后保存 known_hosts |
| 端口释放 `kill -9` | ⚠️ 强制杀进程 | 增加进程确认机制 |
### 2. 运维建议
1. **日志记录**:建议将所有操作日志写入文件,便于问题排查
2. **回滚机制**:保留备份目录,支持一键回滚
3. **健康检查**:部署后增加服务健康检查(HTTP 探针/TCP 探针)
4. **通知机制**:部署完成后发送通知(钉钉/企业微信/邮件)
### 3. 扩展建议
1. **批量部署**:支持一次性升级多台服务器
2. **版本管理**:记录每次部署的镜像版本、部署时间
3. **配置分离**:将服务器信息、镜像映射等配置抽取到独立配置文件
---
## 📝 附录
### A. 退出码说明
| 退出码 | 含义 |
|--------|------|
| 0 | 成功 |
| 1 | 参数错误 |
| 2 | 镜像文件不存在 |
| 3 | 部署脚本不存在 |
| 4 | SSH 连接失败 |
| 5 | 架构不匹配 |
| 6 | 平台类型输入无效 |
| 7 | 服务器编号不存在 |
| 8 | sshpass 未安装 |
| 9 | 容器编号无效 |
| 10 | 容器未配置镜像映射 |
| 11-19 | 资产同步相关错误 |
| 20 | Nginx 仅支持新平台 |
### B. 常用命令速查
```bash
# 远程升级 Java 容器到服务器1(新平台)
./remote_update.sh
# 选择 1 (ujava) → 选择 1 (服务器) → 输入 y (新平台)
# 手动执行部署脚本
./container_update.sh ujava3 /home/containerUpdate/java1.8.0_472.tar.gz --new-platform
# 查看容器状态
docker ps --filter "name=ujava"
# 查看容器日志
docker logs -f ujava3
```
## 📝 业务操作流程
### 一、远程容器升级操作流程
### 二、镜像打包上传网盘操作流程
以下是将容器镜像打包并上传至公司 NAS 网盘的完整操作步骤:
#### 步骤 1:启动打包上传脚本
在镜像目录下执行上传脚本:
```bash
cd /path/to/container-images
./upload_to_nas.sh
```
或指定要打包的目录:
```bash
./upload_to_nas.sh /data/temp/container-images
```
#### 步骤 2:等待目录打包
脚本会自动将目录压缩为 tar.gz 格式,并显示打包进度:
```
[2025-12-08 10:30:00] [INFO] ==================================================================
[2025-12-08 10:30:00] [INFO] 容器镜像打包上传工具
[2025-12-08 10:30:00] [INFO] ==================================================================
[2025-12-08 10:30:00] [INFO] 源目录: /data/temp/container-images
[2025-12-08 10:30:00] [INFO] 打包文件: /tmp/container-images_20251208_103000.tar.gz
[2025-12-08 10:30:00] [INFO] 目标网盘: \\192.168.9.9\home\容器镜像包
[2025-12-08 10:30:00] [INFO] ==================================================================
[2025-12-08 10:30:01] [INFO] [步骤 1/3] 打包目录...
[2025-12-08 10:30:01] [INFO] 开始打包目录: /data/temp/container-images
1.2GiB 0:00:24 [50.0MiB/s] [================================>] 100%
[2025-12-08 10:30:25] [INFO] ✅ 打包完成,文件大小: 1.2G
```
#### 步骤 3:等待 NAS 挂载
脚本自动挂载公司 NAS 网盘:
```
[2025-12-08 10:30:26] [INFO] [步骤 2/3] 挂载 NAS...
[2025-12-08 10:30:26] [INFO] 正在挂载 NAS: //192.168.9.9/home
[2025-12-08 10:30:27] [INFO] ✅ NAS 挂载成功
```
#### 步骤 4:等待文件上传
上传过程会显示进度条:
```
[2025-12-08 10:30:28] [INFO] [步骤 3/3] 上传文件...
[2025-12-08 10:30:28] [INFO] 开始上传文件到 NAS...
[2025-12-08 10:30:28] [INFO] 源文件: /tmp/container-images_20251208_103000.tar.gz
[2025-12-08 10:30:28] [INFO] 目标: //192.168.9.9/home/容器镜像包/container-images_20251208_103000.tar.gz
1.2GiB 0:01:30 [13.6MiB/s] [================================>] 100%
[2025-12-08 10:32:00] [INFO] ✅ 上传完成!
[2025-12-08 10:32:00] [INFO] 文件路径: \\192.168.9.9\home\容器镜像包\container-images_20251208_103000.tar.gz
[2025-12-08 10:32:00] [INFO] 文件大小: 1.2G
```
#### 步骤 5:确认上传结果
```
[2025-12-08 10:32:01] [INFO] 清理临时文件...
[2025-12-08 10:32:01] [INFO] ==================================================================
[2025-12-08 10:32:01] [INFO] 🎉 全部完成!
[2025-12-08 10:32:01] [INFO] ==================================================================
```
#### 网盘信息
| 配置项 | 值 |
|--------|-----|
| 服务器地址 | `\\192.168.9.9\home` |
| 账号 | 陈泽键 |
| 上传目录 | 容器镜像包/ |
| 协议 | SMB/CIFS |
#### 命令行选项
```bash
# 显示帮助
./upload_to_nas.sh -h
# 指定打包文件名
./upload_to_nas.sh -n java_container_v6 /data/temp/java
# 指定上传子目录
./upload_to_nas.sh -d 测试包 /data/temp/test
```
---
\ No newline at end of file
## 📝 业务操作流程
### 一、远程容器升级操作流程
以下是运维人员在主服务器上执行远程容器升级的完整操作步骤:
#### 步骤 1:启动远程升级脚本
在主服务器的镜像目录下,执行远程升级脚本:
```bash
cd /path/to/container-images
./remote_update.sh
```
#### 步骤 2:选择要升级的容器类型
脚本会显示可选的容器列表,根据提示输入对应编号:
```
可选择的容器:
[1] ujava - Java 服务容器
[2] uemqx - EMQX 消息队列容器
[3] uredis - Redis 缓存容器
[4] upython - Python 服务容器
[5] nacos - Nacos 注册中心容器
[6] unginx - Nginx 反向代理容器
请输入容器编号: _
```
#### 步骤 3:选择目标服务器
脚本提供两种方式选择目标服务器:
**方式一:选择预设服务器**
```
可选择的目标服务器:
[1] 标准版预定运维服务器 (192.168.5.48 root)
[2] 阿曼项目预定服务器 (192.168.5.67 root)
[3] 标准版预定运维测试发布服务器 (192.168.5.47 root)
[0] 手动输入服务器信息
请输入服务器编号: _
```
**方式二:手动输入服务器信息(输入 0)**
```
请输入目标服务器 IP 地址: 192.168.1.100
请输入 SSH 端口号 [默认 22]: 22
请输入登录用户名 [默认 root]: root
请输入登录密码: ********
```
#### 步骤 4:等待架构校验
脚本会自动连接目标服务器,校验其 CPU 架构是否为 x86 系列:
```
[2025-12-07 10:30:00] [INFO] 开始校验远端架构 (192.168.1.100)
[2025-12-07 10:30:02] [INFO] 远端架构 x86_64 校验通过
```
> ⚠️ 如果目标服务器为 ARM 架构,脚本将终止执行并提示错误。
#### 步骤 5:等待文件传输
脚本自动将镜像文件和部署脚本传输到目标服务器:
```
[2025-12-07 10:30:05] [INFO] 创建远端目录 /home/containerUpdate
[2025-12-07 10:30:06] [INFO] 传输镜像与部署脚本到远端目录
java1.8.0_472.tar.gz 100% 1.2GB 50.0MB/s 00:24
container_update.sh 100% 45KB 10.0MB/s 00:00
```
#### 步骤 6:确认旧容器处理
脚本会自动检测并停止远端的旧版本容器:
```
[2025-12-07 10:31:00] [INFO] 查找并停止远端旧容器
[2025-12-07 10:31:02] [INFO] 检测到旧容器 ujava2,执行停止
[2025-12-07 10:31:05] [INFO] ✅ 旧容器 ujava2 已停止
```
#### 步骤 7:自动检测平台类型
脚本会自动检测目标服务器的平台类型(通过检查 `/data/services` 目录是否存在):
```
[2025-12-07 10:30:10] [INFO] 自动检测目标服务器平台类型...
[2025-12-07 10:30:11] [INFO] ✅ 检测到 /data/services 目录存在,自动识别为新统一平台
```
| 检测结果 | 平台类型 | 目录结构 |
|----------|----------|----------|
| 存在 `/data/services` | 新统一平台 | `/data/` 目录 |
| 不存在 `/data/services` | 传统平台 | `/var/www/` 目录 |
> 💡 如果自动检测失败,脚本会回退到手动确认模式。
#### 步骤 8:等待资产同步(部分容器)
根据容器类型,脚本会同步相关配置文件和数据目录:
| 容器 | 同步内容 | 状态 | 备注 |
|------|----------|------|------|
| EMQX | 配置文件、数据目录、日志目录 | ✅ 已实现 | 自动备份远端现有目录 |
| Python | ~~代码目录、配置文件~~ | ⏸️ 暂停 | **暂不同步,功能保留待后续启用** |
| Nginx | 配置文件、HTML、证书目录 | ✅ 已实现 | 仅支持新统一平台 |
```
[2025-12-07 10:31:10] [INFO] 准备同步 EMQX 目录
[2025-12-07 10:31:12] [INFO] 远端目录已备份到 /data/middleware/emqx_backup_20251207_103112
[2025-12-07 10:31:15] [INFO] 使用 rsync 同步 EMQX 目录
[2025-12-07 10:31:30] [INFO] EMQX 目录同步完成
```
#### 步骤 9:等待远端部署执行
脚本在远端服务器上执行容器部署:
```
[2025-12-07 10:31:35] [INFO] 新容器名称确定为 ujava3
[2025-12-07 10:31:36] [INFO] 开始执行远端部署脚本
[2025-12-07 10:31:40] [INFO] 🔍 检查 Java 镜像是否存在...
[2025-12-07 10:31:42] [INFO] ❌ 镜像不存在,开始加载离线包...
[2025-12-07 10:32:00] [INFO] 🎉 镜像加载成功
[2025-12-07 10:32:02] [INFO] 🚀 正在启动 Java 容器: ujava3 ...
[2025-12-07 10:32:10] [INFO] ✅ Java 容器启动成功,等待初始化...
[2025-12-07 10:32:18] [INFO] 🎉 Java 服务部署完成!
```
#### 步骤 10:确认部署结果
部署完成后,脚本会输出容器状态信息:
```
NAMES STATUS PORTS
ujava3 Up 8 seconds 0.0.0.0:8085->8085/tcp, ...
[2025-12-07 10:32:20] [INFO] 远端部署执行完成
```
#### 步骤 11:自动清理远端镜像包
部署完成后,脚本会自动清理远端服务器上的镜像包和部署脚本:
```
[2025-12-07 10:32:21] [INFO] 开始清理远端镜像包...
[2025-12-07 10:32:22] [INFO] ✅ 远端镜像包清理完成
[2025-12-07 10:32:22] [INFO] 已删除镜像包: /home/containerUpdate/java1.8.0_472.tar.gz
[2025-12-07 10:32:22] [INFO] 已删除部署脚本: /home/containerUpdate/container_update.sh
[2025-12-07 10:32:22] [INFO] 远程升级流程结束
```
> 💡 清理过程会自动删除镜像包、部署脚本,如果目录为空还会删除整个目录。
---
\ No newline at end of file
## 📝 业务操作流程
### 一、远程容器升级操作流程
### 三、本地容器部署操作流程(交互式模式)
#### 业务场景
技术人员拿到运维人员通过 `upload_to_nas.sh` 脚本上传到网盘的压缩文件后,在项目服务器上解压缩,解压缩后执行 `./container_update.sh` 命令进行容器更新操作。
**典型使用流程**
1. 运维人员在主服务器执行 `upload_to_nas.sh` 打包上传镜像到网盘
2. 技术人员从网盘下载压缩包到项目服务器
3. 解压缩:`tar -xzf container-images_xxx.tar.gz`
4. 进入解压目录:`cd container-images_xxx`
5. 执行部署脚本:`./container_update.sh`
#### 交互式模式需求流程
| 步骤 | 功能描述 | 状态 |
|------|----------|------|
| 步骤1 | 询问更新哪个容器 | ✅ 已实现 |
| 步骤2 | 判断压缩包中是否存在该容器更新所需的镜像以及新增文件 | ✅ 已实现 |
| 步骤3 | 判断当前服务器是传统平台还是新统一平台 | ✅ 已实现 |
| 步骤4 | 判断当前服务器上是否存在容器在运行,如果有就先停止(不做删除操作) | ✅ 已实现 |
| 步骤5 | 备份原有配置文件 | ❌ 待开发 |
| 步骤6 | 更新新的配置文件到指定目录,并将新配置文件内的IP地址改为当前服务器的IP地址 | ❌ 待开发 |
| 步骤7 | 进行容器更新部署操作 | ✅ 已实现 |
| 步骤8 | 查看容器是否部署完成 | ✅ 已实现 |
| 步骤9 | 记录所有操作的日志信息保存到当前目录下为日志审计文件 | ✅ 已实现 |
#### 交互式模式(推荐)
直接运行脚本,无需参数:
```bash
./container_update.sh
```
##### 步骤 1:选择要更新的容器
```
[2025-12-08 10:00:00] [INFO] ==================================================================
[2025-12-08 10:00:00] [INFO] 容器部署工具 - 交互式模式
[2025-12-08 10:00:00] [INFO] ==================================================================
[2025-12-08 10:00:00] [INFO] 日志文件: /path/to/container_update_20251208_100000.log
[2025-12-08 10:00:00] [INFO] 脚本目录: /path/to/container-images
[2025-12-08 10:00:00] [INFO] ==================================================================
[2025-12-08 10:00:00] [INFO] ==================================================================
[2025-12-08 10:00:00] [INFO] 步骤 1/4:选择要更新的容器
[2025-12-08 10:00:00] [INFO] ==================================================================
[2025-12-08 10:00:00] [INFO] 可选择的容器类型:
[1] ujava - Java 服务容器
[2] uemqx - EMQX 消息队列容器
[3] uredis - Redis 缓存容器
[4] upython - Python 服务容器
[5] unacos - Nacos 注册中心容器
[6] unginx - Nginx 反向代理容器
请输入容器编号 (1-6): 1
[2025-12-08 10:00:02] [INFO] ✅ 已选择容器类型: ujava (Java 服务容器)
```
##### 步骤 2:检查所需镜像和配置文件
```
[2025-12-08 10:00:02] [INFO] ==================================================================
[2025-12-08 10:00:02] [INFO] 步骤 2/4:检查所需镜像和配置文件
[2025-12-08 10:00:02] [INFO] ==================================================================
[2025-12-08 10:00:02] [INFO] 检查 ujava 容器所需文件...
[2025-12-08 10:00:02] [INFO] 所需镜像文件: java1.8.0_472.tar.gz
[2025-12-08 10:00:02] [INFO] ✅ 找到镜像文件: /path/to/java1.8.0_472.tar.gz
[2025-12-08 10:00:02] [INFO] 文件大小: 1.2G
[2025-12-08 10:00:02] [INFO] ✅ 部署脚本存在: /path/to/container_update.sh
```
##### 步骤 3:检测服务器平台类型
```
[2025-12-08 10:00:03] [INFO] ==================================================================
[2025-12-08 10:00:03] [INFO] 步骤 3/4:检测服务器平台类型
[2025-12-08 10:00:03] [INFO] ==================================================================
[2025-12-08 10:00:03] [INFO] 自动检测平台类型...
[2025-12-08 10:00:03] [INFO] 检测依据: /data/services 目录是否存在
[2025-12-08 10:00:03] [INFO] ✅ 检测到 /data/services 目录存在
[2025-12-08 10:00:03] [INFO] ✅ 识别为: 新统一平台 (使用 /data/ 目录结构)
当前识别为 [新统一平台],是否正确? (y/n) [默认 y]: y
[2025-12-08 10:00:04] [INFO] ✅ 平台类型确认: new
```
##### 步骤 4:检查并停止正在运行的容器
```
[2025-12-08 10:00:04] [INFO] ==================================================================
[2025-12-08 10:00:04] [INFO] 步骤 4/4:检查并停止正在运行的容器
[2025-12-08 10:00:04] [INFO] ==================================================================
[2025-12-08 10:00:04] [INFO] 检查是否存在 ujava 类型的容器在运行...
[2025-12-08 10:00:04] [INFO] 发现以下 ujava 类型的容器:
- ujava1 (状态: exited)
- ujava2 (状态: running)
[2025-12-08 10:00:04] [WARN] ⚠️ 检测到容器 ujava2 正在运行
是否停止该容器以便进行更新? (y/n) [默认 y]: y
[2025-12-08 10:00:05] [INFO] 正在停止容器 ujava2...
[2025-12-08 10:00:08] [INFO] ✅ 容器 ujava2 已停止
[2025-12-08 10:00:08] [INFO] ✅ 新容器将命名为: ujava3
```
##### 步骤 5:确认并执行部署
```
[2025-12-08 10:00:08] [INFO] ==================================================================
[2025-12-08 10:00:08] [INFO] 部署信息确认
[2025-12-08 10:00:08] [INFO] ==================================================================
[2025-12-08 10:00:08] [INFO] 容器类型: ujava (Java 服务容器)
[2025-12-08 10:00:08] [INFO] 新容器名: ujava3
[2025-12-08 10:00:08] [INFO] 平台类型: new
[2025-12-08 10:00:08] [INFO] 镜像文件: /path/to/java1.8.0_472.tar.gz
[2025-12-08 10:00:08] [INFO] 旧容器: ujava2 (已停止)
[2025-12-08 10:00:08] [INFO] ==================================================================
确认开始部署? (y/n) [默认 y]: y
[2025-12-08 10:00:10] [INFO] ==================================================================
[2025-12-08 10:00:10] [INFO] 开始执行部署
[2025-12-08 10:00:10] [INFO] ==================================================================
[2025-12-08 10:00:10] [INFO] 开始部署 ujava (容器: ujava3, 平台: new)
```
##### 步骤 6:部署完成
```
[2025-12-08 10:01:00] [INFO] ==================================================================
[2025-12-08 10:01:00] [INFO] 部署流程结束
[2025-12-08 10:01:00] [INFO] ==================================================================
[2025-12-08 10:01:00] [INFO] 日志文件已保存到: /path/to/container_update_20251208_100000.log
```
\ No newline at end of file
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# ================================
# 全局变量
# ================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_FILE="${SCRIPT_DIR}/container_update_$(date +%Y%m%d_%H%M%S).log"
INTERACTIVE_LOG_ENABLED=false # 交互式模式下启用日志
# ================================
# 日志函数(支持同时输出到控制台和文件)
# ================================
log() { log() {
local level="$1"; shift local level="$1"; shift
printf '[%(%F %T)T] [%s] %s\n' -1 "${level}" "$*" local message="$*"
local timestamp
timestamp=$(printf '%(%F %T)T' -1)
local log_line="[${timestamp}] [${level}] ${message}"
# 输出到控制台
printf '%s\n' "${log_line}"
# 如果启用了日志文件,同时写入文件
if [[ "${INTERACTIVE_LOG_ENABLED}" == "true" && -n "${LOG_FILE}" ]]; then
printf '%s\n' "${log_line}" >> "${LOG_FILE}"
fi
}
# 仅写入日志文件(不输出到控制台)
log_to_file() {
local level="$1"; shift
local message="$*"
local timestamp
timestamp=$(printf '%(%F %T)T' -1)
local log_line="[${timestamp}] [${level}] ${message}"
if [[ "${INTERACTIVE_LOG_ENABLED}" == "true" && -n "${LOG_FILE}" ]]; then
printf '%s\n' "${log_line}" >> "${LOG_FILE}"
fi
} }
usage() { usage() {
...@@ -15,9 +49,11 @@ usage() { ...@@ -15,9 +49,11 @@ usage() {
1. 交互式模式(无参数): 1. 交互式模式(无参数):
./container_update.sh ./container_update.sh
脚本会引导选择容器类型、平台类型,自动检测镜像文件 脚本会引导选择容器类型、平台类型,自动检测镜像文件
适用于技术人员从网盘下载压缩包后在项目服务器上执行
2. 命令行模式(带参数): 2. 命令行模式(带参数):
./container_update.sh <容器名称> <镜像包> [--new-platform] ./container_update.sh <容器名称> <镜像包> [--new-platform]
适用于远程调用或自动化脚本
参数说明: 参数说明:
容器名称 如 ujava3,用于指定 docker 容器名称 容器名称 如 ujava3,用于指定 docker 容器名称
...@@ -29,7 +65,9 @@ usage() { ...@@ -29,7 +65,9 @@ usage() {
EOF EOF
} }
# ================================
# 容器与镜像映射配置 # 容器与镜像映射配置
# ================================
declare -A CONTAINER_IMAGE_MAP declare -A CONTAINER_IMAGE_MAP
CONTAINER_IMAGE_MAP["ujava"]="java1.8.0_472.tar.gz" CONTAINER_IMAGE_MAP["ujava"]="java1.8.0_472.tar.gz"
CONTAINER_IMAGE_MAP["uemqx"]="uemqx5.8.4.tar.gz" CONTAINER_IMAGE_MAP["uemqx"]="uemqx5.8.4.tar.gz"
...@@ -38,157 +76,358 @@ CONTAINER_IMAGE_MAP["upython"]="python_v15.tar.gz" ...@@ -38,157 +76,358 @@ CONTAINER_IMAGE_MAP["upython"]="python_v15.tar.gz"
CONTAINER_IMAGE_MAP["unacos"]="nacos-server-v2.5.2.tar.gz" CONTAINER_IMAGE_MAP["unacos"]="nacos-server-v2.5.2.tar.gz"
CONTAINER_IMAGE_MAP["unginx"]="nginx-1.29.3.tar.gz" CONTAINER_IMAGE_MAP["unginx"]="nginx-1.29.3.tar.gz"
# 交互式模式 # 容器描述信息
interactive_mode() { declare -A CONTAINER_DESC_MAP
CONTAINER_DESC_MAP["ujava"]="Java 服务容器"
CONTAINER_DESC_MAP["uemqx"]="EMQX 消息队列容器"
CONTAINER_DESC_MAP["uredis"]="Redis 缓存容器"
CONTAINER_DESC_MAP["upython"]="Python 服务容器"
CONTAINER_DESC_MAP["unacos"]="Nacos 注册中心容器"
CONTAINER_DESC_MAP["unginx"]="Nginx 反向代理容器"
# ================================
# 步骤1:询问更新哪个容器
# ================================
select_container() {
log "INFO" "==================================================================" log "INFO" "=================================================================="
log "INFO" "容器部署工具 - 交互式模式" log "INFO" "步骤 1/4:选择要更新的容器"
log "INFO" "==================================================================" log "INFO" "=================================================================="
# 选择容器类型
local container_options=(ujava uemqx uredis upython unacos unginx) local container_options=(ujava uemqx uredis upython unacos unginx)
echo "" echo ""
log "INFO" "可选择的容器类型:" log "INFO" "可选择的容器类型:"
for idx in "${!container_options[@]}"; do for idx in "${!container_options[@]}"; do
local option_index=$((idx + 1)) local option_index=$((idx + 1))
printf " [%s] %s\n" "${option_index}" "${container_options[idx]}" local container_name="${container_options[idx]}"
local container_desc="${CONTAINER_DESC_MAP[${container_name}]}"
printf " [%s] %-10s - %s\n" "${option_index}" "${container_name}" "${container_desc}"
done done
echo "" echo ""
local container_key local container_key
read -rp "请输入容器编号 (1-6): " container_key read -rp "请输入容器编号 (1-6): " container_key
log_to_file "INFO" "用户输入容器编号: ${container_key}"
if ! [[ "${container_key}" =~ ^[1-6]$ ]]; then if ! [[ "${container_key}" =~ ^[1-6]$ ]]; then
log "ERROR" "容器编号无效,请输入 1-6" log "ERROR" "容器编号无效,请输入 1-6"
exit 1 return 1
fi fi
local container_prefix="${container_options[$((container_key - 1))]}" SELECTED_CONTAINER="${container_options[$((container_key - 1))]}"
log "INFO" "已选择容器类型: ${container_prefix}" log "INFO" "✅ 已选择容器类型: ${SELECTED_CONTAINER} (${CONTAINER_DESC_MAP[${SELECTED_CONTAINER}]})"
return 0
}
# ================================
# 步骤2:检查压缩包中是否存在所需镜像和配置文件
# ================================
check_required_files() {
log "INFO" "=================================================================="
log "INFO" "步骤 2/4:检查所需镜像和配置文件"
log "INFO" "=================================================================="
local container_prefix="$1"
local image_file="${CONTAINER_IMAGE_MAP[${container_prefix}]}"
log "INFO" "检查 ${container_prefix} 容器所需文件..."
log "INFO" " 所需镜像文件: ${image_file}"
# 搜索镜像文件的可能位置
local search_paths=(
"${SCRIPT_DIR}/${image_file}"
"$(pwd)/${image_file}"
"/data/temp/${image_file}"
"/home/containerUpdate/${image_file}"
)
FOUND_IMAGE_PATH=""
for path in "${search_paths[@]}"; do
if [[ -f "${path}" ]]; then
FOUND_IMAGE_PATH="${path}"
break
fi
done
# 如果没找到,尝试在当前目录递归搜索
if [[ -z "${FOUND_IMAGE_PATH}" ]]; then
log "INFO" "在常用路径未找到,尝试在当前目录搜索..."
local found_file
found_file=$(find "${SCRIPT_DIR}" -maxdepth 2 -name "${image_file}" -type f 2>/dev/null | head -n1)
if [[ -n "${found_file}" ]]; then
FOUND_IMAGE_PATH="${found_file}"
fi
fi
if [[ -z "${FOUND_IMAGE_PATH}" ]]; then
log "ERROR" "❌ 未找到镜像文件: ${image_file}"
log "ERROR" "请确保压缩包中包含该镜像文件,或手动指定路径"
echo ""
read -rp "是否手动输入镜像文件路径? (y/n) [默认 n]: " manual_input
manual_input="${manual_input:-n}"
if [[ "${manual_input}" =~ ^[yY]$ ]]; then
read -rp "请输入镜像文件完整路径: " FOUND_IMAGE_PATH
if [[ ! -f "${FOUND_IMAGE_PATH}" ]]; then
log "ERROR" "镜像文件不存在: ${FOUND_IMAGE_PATH}"
return 1
fi
else
return 1
fi
fi
log "INFO" "✅ 找到镜像文件: ${FOUND_IMAGE_PATH}"
# 检查镜像文件大小
local file_size
file_size=$(du -h "${FOUND_IMAGE_PATH}" | awk '{print $1}')
log "INFO" " 文件大小: ${file_size}"
# 检查部署脚本自身
if [[ -f "${SCRIPT_DIR}/container_update.sh" ]]; then
log "INFO" "✅ 部署脚本存在: ${SCRIPT_DIR}/container_update.sh"
fi
# 根据容器类型检查额外配置文件(可选)
case "${container_prefix}" in
uemqx)
log "INFO" "📋 EMQX 容器将使用远端已有配置或默认配置"
;;
unginx)
log "INFO" "📋 Nginx 容器将使用远端已有配置或默认配置"
;;
uredis)
log "INFO" "📋 Redis 容器将使用远端已有配置或默认配置"
;;
*)
log "INFO" "📋 ${container_prefix} 容器配置检查完成"
;;
esac
return 0
}
# ================================
# 步骤3:判断当前服务器是传统平台还是新统一平台
# ================================
detect_platform() {
log "INFO" "=================================================================="
log "INFO" "步骤 3/4:检测服务器平台类型"
log "INFO" "=================================================================="
local container_prefix="$1"
# 自动检测平台类型
local platform_type="old"
log "INFO" "自动检测平台类型..." log "INFO" "自动检测平台类型..."
log "INFO" " 检测依据: /data/services 目录是否存在"
if [[ -d "/data/services" ]]; then if [[ -d "/data/services" ]]; then
platform_type="new" DETECTED_PLATFORM="new"
log "INFO" "✅ 检测到 /data/services 目录,识别为新统一平台" log "INFO" "✅ 检测到 /data/services 目录存在"
log "INFO" "✅ 识别为: 新统一平台 (使用 /data/ 目录结构)"
else else
platform_type="old" DETECTED_PLATFORM="old"
log "INFO" "✅ 未检测到 /data/services 目录,识别为传统平台" log "INFO" "✅ 未检测到 /data/services 目录"
log "INFO" "✅ 识别为: 传统平台 (使用 /var/www/ 目录结构)"
fi fi
# 确认平台类型 # 确认平台类型
echo "" echo ""
read -rp "当前识别为 ${platform_type} 平台,是否正确? (y/n) [默认 y]: " platform_confirm local platform_display
if [[ "${DETECTED_PLATFORM}" == "new" ]]; then
platform_display="新统一平台"
else
platform_display="传统平台"
fi
read -rp "当前识别为 [${platform_display}],是否正确? (y/n) [默认 y]: " platform_confirm
platform_confirm="${platform_confirm:-y}" platform_confirm="${platform_confirm:-y}"
log_to_file "INFO" "用户确认平台类型: ${platform_confirm}"
if [[ "${platform_confirm}" =~ ^[nN]$ ]]; then if [[ "${platform_confirm}" =~ ^[nN]$ ]]; then
if [[ "${platform_type}" == "new" ]]; then if [[ "${DETECTED_PLATFORM}" == "new" ]]; then
platform_type="old" DETECTED_PLATFORM="old"
log "INFO" "⚠️ 已手动切换为: 传统平台"
else else
platform_type="new" DETECTED_PLATFORM="new"
log "INFO" "⚠️ 已手动切换为: 新统一平台"
fi fi
log "INFO" "已切换为 ${platform_type} 平台"
fi fi
# 检查 Nacos 和 Nginx 平台限制 # 检查 Nacos 和 Nginx 平台限制
if [[ "${container_prefix}" == "unacos" || "${container_prefix}" == "unginx" ]]; then if [[ "${container_prefix}" == "unacos" || "${container_prefix}" == "unginx" ]]; then
if [[ "${platform_type}" == "old" ]]; then if [[ "${DETECTED_PLATFORM}" == "old" ]]; then
log "ERROR" "${container_prefix} 仅支持新统一平台" log "ERROR" "${container_prefix} 仅支持新统一平台,无法在传统平台部署"
exit 4 return 1
fi fi
fi fi
# 查找镜像文件 log "INFO" "✅ 平台类型确认: ${DETECTED_PLATFORM}"
local image_file="${CONTAINER_IMAGE_MAP[${container_prefix}]}" return 0
local image_path="" }
# 搜索镜像文件的可能位置 # ================================
local search_paths=( # 步骤4:检查并停止正在运行的同类型容器
"$(pwd)/${image_file}" # ================================
"/data/temp/${image_file}" check_and_stop_running_container() {
"/home/containerUpdate/${image_file}" log "INFO" "=================================================================="
"$(pwd)" log "INFO" "步骤 4/4:检查并停止正在运行的容器"
) log "INFO" "=================================================================="
log "INFO" "搜索镜像文件: ${image_file}" local container_prefix="$1"
for path in "${search_paths[@]}"; do
if [[ -f "${path}" ]]; then log "INFO" "检查是否存在 ${container_prefix} 类型的容器在运行..."
image_path="${path}"
break # 查找所有匹配的容器(包括运行中和已停止的)
elif [[ -d "${path}" ]]; then local all_containers
# 在目录中搜索 all_containers=$(docker ps -a --format '{{.Names}}' 2>/dev/null | grep -E "^${container_prefix}[0-9]*$" | sort -V || true)
local found_file
found_file=$(find "${path}" -maxdepth 1 -name "${image_file}" -type f 2>/dev/null | head -n1) if [[ -z "${all_containers}" ]]; then
if [[ -n "${found_file}" ]]; then log "INFO" "✅ 未发现 ${container_prefix} 类型的容器"
image_path="${found_file}" RUNNING_CONTAINER=""
break NEW_CONTAINER_NAME="${container_prefix}1"
fi log "INFO" " 新容器将命名为: ${NEW_CONTAINER_NAME}"
return 0
fi fi
log "INFO" "发现以下 ${container_prefix} 类型的容器:"
echo "${all_containers}" | while read -r name; do
local status
status=$(docker inspect --format '{{.State.Status}}' "${name}" 2>/dev/null || echo "unknown")
printf " - %s (状态: %s)\n" "${name}" "${status}"
done done
if [[ -z "${image_path}" || ! -f "${image_path}" ]]; then # 查找正在运行的容器
log "WARN" "未找到默认镜像文件: ${image_file}" local running_containers
running_containers=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -E "^${container_prefix}[0-9]*$" | sort -V || true)
if [[ -n "${running_containers}" ]]; then
# 获取最新运行的容器
RUNNING_CONTAINER=$(echo "${running_containers}" | tail -n1)
log "WARN" "⚠️ 检测到容器 ${RUNNING_CONTAINER} 正在运行"
echo "" echo ""
read -rp "请输入镜像文件路径: " image_path read -rp "是否停止该容器以便进行更新? (y/n) [默认 y]: " stop_confirm
if [[ ! -f "${image_path}" ]]; then stop_confirm="${stop_confirm:-y}"
log "ERROR" "镜像文件不存在: ${image_path}" log_to_file "INFO" "用户确认停止容器: ${stop_confirm}"
exit 3
if [[ "${stop_confirm}" =~ ^[yY]$ ]]; then
log "INFO" "正在停止容器 ${RUNNING_CONTAINER}..."
if docker stop "${RUNNING_CONTAINER}" >/dev/null 2>&1; then
log "INFO" "✅ 容器 ${RUNNING_CONTAINER} 已停止"
else
log "ERROR" "❌ 停止容器 ${RUNNING_CONTAINER} 失败"
return 1
fi
else
log "WARN" "⚠️ 用户选择不停止容器,部署可能会因端口冲突而失败"
fi fi
else else
log "INFO" "✅ 找到镜像文件: ${image_path}" RUNNING_CONTAINER=""
log "INFO" "✅ 没有正在运行的 ${container_prefix} 类型容器"
fi fi
# 确定容器名称(自动递增编号) # 确定容器名称(自动递增编号)
local existing_containers local latest_container
existing_containers=$(docker ps -a --format '{{.Names}}' | grep -E "^${container_prefix}[0-9]*$" | sort -V | tail -n1 || true) latest_container=$(echo "${all_containers}" | tail -n1)
local new_container_name if [[ -n "${latest_container}" ]]; then
if [[ -n "${existing_containers}" ]]; then local last_num="${latest_container##*[!0-9]}"
local last_num="${existing_containers##*[!0-9]}"
if [[ -z "${last_num}" ]]; then if [[ -z "${last_num}" ]]; then
last_num=0 last_num=0
fi fi
local next_num=$((last_num + 1)) local next_num=$((last_num + 1))
new_container_name="${container_prefix}${next_num}" NEW_CONTAINER_NAME="${container_prefix}${next_num}"
else else
new_container_name="${container_prefix}1" NEW_CONTAINER_NAME="${container_prefix}1"
fi fi
log "INFO" "新容器名称: ${new_container_name}" log "INFO" "✅ 新容器将命名为: ${NEW_CONTAINER_NAME}"
return 0
}
# ================================
# 交互式模式主函数
# ================================
interactive_mode() {
# 启用日志记录
INTERACTIVE_LOG_ENABLED=true
log "INFO" "=================================================================="
log "INFO" "容器部署工具 - 交互式模式"
log "INFO" "=================================================================="
log "INFO" "日志文件: ${LOG_FILE}"
log "INFO" "脚本目录: ${SCRIPT_DIR}"
log "INFO" "=================================================================="
echo ""
# 步骤1:选择容器
if ! select_container; then
log "ERROR" "容器选择失败,退出"
exit 1
fi
echo ""
# 步骤2:检查所需文件
if ! check_required_files "${SELECTED_CONTAINER}"; then
log "ERROR" "文件检查失败,退出"
exit 2
fi
echo ""
# 确认部署 # 步骤3:检测平台类型
if ! detect_platform "${SELECTED_CONTAINER}"; then
log "ERROR" "平台检测失败,退出"
exit 3
fi
echo "" echo ""
# 步骤4:检查并停止运行中的容器
if ! check_and_stop_running_container "${SELECTED_CONTAINER}"; then
log "ERROR" "容器检查失败,退出"
exit 4
fi
echo ""
# 显示部署信息摘要
log "INFO" "==================================================================" log "INFO" "=================================================================="
log "INFO" "部署信息确认:" log "INFO" "部署信息确认"
log "INFO" " 容器类型: ${container_prefix}" log "INFO" "=================================================================="
log "INFO" " 容器名称: ${new_container_name}" log "INFO" " 容器类型: ${SELECTED_CONTAINER} (${CONTAINER_DESC_MAP[${SELECTED_CONTAINER}]})"
log "INFO" " 平台类型: ${platform_type}" log "INFO" " 新容器名: ${NEW_CONTAINER_NAME}"
log "INFO" " 镜像文件: ${image_path}" log "INFO" " 平台类型: ${DETECTED_PLATFORM}"
log "INFO" " 镜像文件: ${FOUND_IMAGE_PATH}"
if [[ -n "${RUNNING_CONTAINER}" ]]; then
log "INFO" " 旧容器: ${RUNNING_CONTAINER} (已停止)"
fi
log "INFO" "==================================================================" log "INFO" "=================================================================="
echo "" echo ""
read -rp "确认开始部署? (y/n) [默认 y]: " deploy_confirm read -rp "确认开始部署? (y/n) [默认 y]: " deploy_confirm
deploy_confirm="${deploy_confirm:-y}" deploy_confirm="${deploy_confirm:-y}"
log_to_file "INFO" "用户确认部署: ${deploy_confirm}"
if [[ ! "${deploy_confirm}" =~ ^[yY]$ ]]; then if [[ ! "${deploy_confirm}" =~ ^[yY]$ ]]; then
log "INFO" "用户取消部署" log "INFO" "用户取消部署"
exit 0 exit 0
fi fi
# 设置全局变量并执行部署 # 设置全局变量并执行部署
DEPLOY_CONTAINER_NAME="${new_container_name}" DEPLOY_CONTAINER_NAME="${NEW_CONTAINER_NAME}"
DEPLOY_IMAGE_TAR="${image_path}" DEPLOY_IMAGE_TAR="${FOUND_IMAGE_PATH}"
# 调用部署函数 # 调用部署函数
local deploy_fn local deploy_fn
case "${container_prefix}" in case "${SELECTED_CONTAINER}" in
ujava) ujava)
deploy_fn="java_${platform_type}platform_x86" deploy_fn="java_${DETECTED_PLATFORM}platform_x86"
;; ;;
uredis) uredis)
deploy_fn="redis_${platform_type}platform_x86" deploy_fn="redis_${DETECTED_PLATFORM}platform_x86"
;; ;;
uemqx) uemqx)
deploy_fn="emqx_${platform_type}platform_x86" deploy_fn="emqx_${DETECTED_PLATFORM}platform_x86"
;; ;;
upython) upython)
deploy_fn="python_${platform_type}platform_x86" deploy_fn="python_${DETECTED_PLATFORM}platform_x86"
;; ;;
unacos) unacos)
deploy_fn="nacos_newplatform_x86" deploy_fn="nacos_newplatform_x86"
...@@ -198,9 +437,18 @@ interactive_mode() { ...@@ -198,9 +437,18 @@ interactive_mode() {
;; ;;
esac esac
log "INFO" "开始部署 ${container_prefix} (容器: ${DEPLOY_CONTAINER_NAME}, 平台: ${platform_type}, 镜像: ${DEPLOY_IMAGE_TAR})" log "INFO" "=================================================================="
log "INFO" "开始执行部署"
log "INFO" "=================================================================="
log "INFO" "开始部署 ${SELECTED_CONTAINER} (容器: ${DEPLOY_CONTAINER_NAME}, 平台: ${DETECTED_PLATFORM})"
# 执行部署
"${deploy_fn}" "${deploy_fn}"
log "INFO" "=================================================================="
log "INFO" "部署流程结束" log "INFO" "部署流程结束"
log "INFO" "=================================================================="
log "INFO" "日志文件已保存到: ${LOG_FILE}"
} }
main() { main() {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论