Docker容器迁移实践指南
Docker容器有三种主要的存储方式:特点:1.1.3 数据卷(Volumes)1.1.4 tmpfs挂载1.2 数据卷的特性1.2.1 数据卷的优势核心特性:1.3 容器的存储图示1.4 迁移方式简介1.4.1 容器迁移的三种方式迁移方式适用场景优点缺点镜像迁移应用代码+环境迁移简单快速,完整环境不包含运行时数据数据卷迁移数据库、文件等持久化数据数据完整,独立迁移需配合容器迁移完整容器迁移开发环
一、容器的存储介绍
1.1 容器存储方式
Docker容器有三种主要的存储方式:
1.1.1 分层存储(Layer Storage)
# 容器镜像的分层结构示例
FROM ubuntu:22.04 # 基础层
RUN apt-get update # 新的只读层
RUN apt-get install nginx # 新的只读层
COPY app /var/www/html # 新的只读层
CMD ["nginx", "-g", "daemon off;"] # 新的只读层
特点:
- 只读层(RO Layers):镜像的每一层都是只读的
- 可写层(RW Layer):容器运行时新增的读写层
- 分层共享:多个容器可以共享相同的只读层
1.1.2 绑定挂载(Bind Mounts)
# 将主机目录挂载到容器
docker run -v /host/path:/container/path nginx
# 或使用--mount参数
docker run --mount type=bind,source=/host/path,target=/container/path nginx
1.1.3 数据卷(Volumes)
# 创建数据卷
docker volume create my-volume
# 使用数据卷
docker run -v my-volume:/container/path nginx
# 查看数据卷信息
docker volume inspect my-volume
1.1.4 tmpfs挂载
# 使用内存作为临时存储
docker run --tmpfs /tmp nginx
# 限制tmpfs大小
docker run --tmpfs /tmp:size=100m,mode=1777 nginx
1.2 数据卷的特性
1.2.1 数据卷的优势
# docker-compose.yml中数据卷的使用示例
version: '3.8'
services:
web:
image: nginx
volumes:
- web-data:/usr/share/nginx/html
- ./config:/etc/nginx/conf.d
volumes:
web-data:
driver: local
driver_opts:
type: none
device: /data/web
o: bind
核心特性:
- 持久化:数据卷独立于容器生命周期
- 共享性:多个容器可共享同一数据卷
- 可移植性:支持备份、恢复和迁移
- 性能:本地存储,性能优于绑定挂载
- 管理:可通过Docker命令管理
1.2.2 数据卷生命周期管理
# 数据卷的完整生命周期命令
# 1. 创建
docker volume create myapp-data
# 2. 查看
docker volume ls
docker volume inspect myapp-data
# 3. 使用
docker run -d --name app -v myapp-data:/data myapp
# 4. 备份
docker run --rm -v myapp-data:/source -v $(pwd):/backup ubuntu tar czf /backup/backup.tar.gz -C /source .
# 5. 恢复
docker run --rm -v myapp-data:/target -v $(pwd):/backup ubuntu tar xzf /backup/backup.tar.gz -C /target
# 6. 清理
docker volume rm myapp-data
docker volume prune # 清理未使用的数据卷
1.3 容器的存储图示
容器存储结构示意图:
┌─────────────────────────────────────────────────────────┐
│ Container │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Read/Write │ │ tmpfs │ │ Bind Mount │ │
│ │ Layer │ │ Mount │ │ /host │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────┐│
│ │ Container Data Volumes ││
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ ││
│ │ │ Volume A │ │ Volume B │ │ Volume C │ ││
│ │ │/var/lib/mysql│ │/var/www/html │ │ /logs │ ││
│ │ └──────────────┘ └──────────────┘ └──────────┘ ││
│ └─────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Image │ │ Image │ │ Image │ │
│ │ Layer 3 │ │ Layer 2 │ │ Layer 1 │ │
│ │ (RO) │ │ (RO) │ │ (RO) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
1.4 迁移方式简介
1.4.1 容器迁移的三种方式
| 迁移方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 镜像迁移 | 应用代码+环境迁移 | 简单快速,完整环境 | 不包含运行时数据 |
| 数据卷迁移 | 数据库、文件等持久化数据 | 数据完整,独立迁移 | 需配合容器迁移 |
| 完整容器迁移 | 开发环境、测试环境 | 完全一致,开箱即用 | 体积大,效率低 |
1.4.2 迁移工具对比
# 1. Docker原生命令
docker save -o myimage.tar myimage:tag # 保存镜像
docker load -i myimage.tar # 加载镜像
docker export -o mycontainer.tar mycontainer # 导出容器
docker import mycontainer.tar # 导入容器
# 2. Docker Compose迁移
docker-compose config > docker-compose-export.yml # 导出配置
docker-compose up -d # 重新部署
# 3. 第三方工具
# docker-migrate: https://github.com/docker/migrate
# Portainer: 图形化迁移工具
二、本次实践介绍
2.1 本次实践简介
实践目标
将运行在源服务器上的Web应用容器(包含Nginx服务、网站文件和MySQL数据库)完整迁移到目标服务器,确保服务无缝切换。
实践场景
- 场景1:服务器硬件升级迁移
- 场景2:开发环境到生产环境迁移
- 场景3:数据中心迁移
技术要点
迁移组件:
- Web服务器: Nginx 1.24
- 应用代码: Node.js应用
- 数据库: MySQL 8.0
- 配置文件: nginx.conf, my.cnf
- 持久化数据: 数据库数据、上传文件
2.2 本次实践环境介绍
2.2.1 源服务器环境
# 环境检查脚本
#!/bin/bash
echo "=== 源服务器环境检查 ==="
echo "1. Docker版本:"
docker --version
echo ""
echo "2. 运行中的容器:"
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
echo ""
echo "3. 容器详情:"
for container in $(docker ps -q); do
echo "容器ID: $container"
docker inspect $container | grep -A5 Mounts
echo ""
done
echo "4. 磁盘使用情况:"
df -h | grep -E "(Filesystem|/var/lib/docker)"
2.2.2 目标服务器环境
# 目标服务器准备脚本
#!/bin/bash
echo "=== 目标服务器准备 ==="
# 1. 安装Docker
if ! command -v docker &> /dev/null; then
echo "安装Docker..."
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
fi
# 2. 安装Docker Compose
if ! command -v docker-compose &> /dev/null; then
echo "安装Docker Compose..."
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
fi
# 3. 创建目录结构
echo "创建数据目录..."
sudo mkdir -p /data/docker/{volumes,backups,configs}
sudo chmod -R 755 /data/docker
# 4. 配置Docker
echo "配置Docker..."
sudo tee /etc/docker/daemon.json <<EOF
{
"data-root": "/data/docker",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn"
]
}
EOF
# 5. 重启Docker
sudo systemctl restart docker
sudo systemctl enable docker
echo "环境准备完成!"
2.2.3 网络架构
迁移网络架构示意图:
源服务器 (192.168.1.100) 目标服务器 (192.168.1.200)
├── Docker容器 ├── Docker容器(迁移后)
│ ├── nginx:1.24 (80:8080) │ ├── nginx:1.24 (80:8080)
│ ├── mysql:8.0 (3306:3306) │ ├── mysql:8.0 (3306:3306)
│ └── app:nodejs (3000:3000) │ └── app:nodejs (3000:3000)
├── 数据卷 ├── 数据卷(迁移后)
│ ├── mysql-data │ ├── mysql-data
│ └── app-data │ └── app-data
└── 配置文件 └── 配置文件(迁移后)
├── nginx.conf ├── nginx.conf
└── my.cnf └── my.cnf
三、A类实验内容
A类模拟试验1:基础容器迁移
3.1.1 实验目标
迁移一个简单的Nginx Web服务器容器,包含自定义首页和配置文件。
3.1.2 实验步骤
# 步骤1:在源服务器创建测试容器
echo "=== 创建测试容器 ==="
# 创建测试目录
mkdir -p ~/nginx-test/{html,conf}
# 创建自定义首页
cat > ~/nginx-test/html/index.html <<EOF
<!DOCTYPE html>
<html>
<head>
<title>迁移测试页面</title>
</head>
<body>
<h1>Hello from Source Server!</h1>
<p>服务器时间: <span id="time"></span></p>
<script>
document.getElementById('time').textContent = new Date().toLocaleString();
</script>
</body>
</html>
EOF
# 创建自定义配置
cat > ~/nginx-test/conf/nginx.conf <<EOF
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" '
'\$status \$body_bytes_sent "\$http_referer" '
'"\$http_user_agent" "\$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /status {
stub_status on;
access_log off;
}
}
}
EOF
# 运行容器
docker run -d \
--name nginx-test \
-p 8080:80 \
-v ~/nginx-test/html:/usr/share/nginx/html \
-v ~/nginx-test/conf/nginx.conf:/etc/nginx/nginx.conf \
nginx:1.24
echo "测试容器已启动,访问 http://localhost:8080 验证"
A类模拟试验2:数据卷容器迁移
3.2.1 实验目标
迁移包含数据卷的MySQL容器,确保数据完整性。
3.2.2 实验步骤
# 步骤1:创建带数据卷的MySQL容器
echo "=== 创建MySQL测试容器 ==="
# 创建数据卷
docker volume create mysql-test-data
# 运行MySQL容器
docker run -d \
--name mysql-test \
-e MYSQL_ROOT_PASSWORD=Test@123 \
-e MYSQL_DATABASE=migration_test \
-e MYSQL_USER=testuser \
-e MYSQL_PASSWORD=Test@123 \
-v mysql-test-data:/var/lib/mysql \
mysql:8.0
# 等待MySQL启动
sleep 30
# 创建测试数据
docker exec mysql-test mysql -u root -pTest@123 -e "
CREATE DATABASE IF NOT EXISTS migration_db;
USE migration_db;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO users (username, email) VALUES
('alice', 'alice@example.com'),
('bob', 'bob@example.com'),
('charlie', 'charlie@example.com');
SELECT * FROM users;
"
echo "MySQL容器已创建,包含测试数据"
A类模拟试验3:多容器应用迁移
3.3.1 实验目标
迁移由多个容器组成的完整应用(Web + API + Database)。
3.3.2 实验步骤
# 步骤1:使用Docker Compose创建多容器应用
echo "=== 创建多容器应用 ==="
# 创建项目目录
mkdir -p ~/multi-app/{web,api,database}
# 创建docker-compose.yml
cat > ~/multi-app/docker-compose.yml <<EOF
version: '3.8'
services:
database:
image: postgres:15
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: App@123
volumes:
- postgres-data:/var/lib/postgresql/data
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- app-network
api:
build: ./api
environment:
DATABASE_URL: postgresql://appuser:App@123@database:5432/appdb
ports:
- "3000:3000"
depends_on:
- database
networks:
- app-network
web:
image: nginx:1.24
ports:
- "80:80"
volumes:
- ./web/html:/usr/share/nginx/html
- ./web/nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- api
networks:
- app-network
volumes:
postgres-data:
networks:
app-network:
driver: bridge
EOF
# 创建API Dockerfile
cat > ~/multi-app/api/Dockerfile <<EOF
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
EOF
# 创建API代码
cat > ~/multi-app/api/index.js <<EOF
const express = require('express');
const { Pool } = require('pg');
const app = express();
const port = 3000;
const pool = new Pool({
user: 'appuser',
host: 'database',
database: 'appdb',
password: 'App@123',
port: 5432,
});
app.get('/api/users', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM users');
res.json(result.rows);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
app.listen(port, () => {
console.log(\`API server listening at http://localhost:\${port}\`);
});
EOF
# 创建数据库初始化脚本
cat > ~/multi-app/database/init.sql <<EOF
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO users (name, email) VALUES
('迁移测试用户1', 'test1@example.com'),
('迁移测试用户2', 'test2@example.com'),
('迁移测试用户3', 'test3@example.com')
ON CONFLICT (email) DO NOTHING;
EOF
# 创建Web页面
cat > ~/multi-app/web/html/index.html <<EOF
<!DOCTYPE html>
<html>
<head>
<title>多容器应用</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.user { border: 1px solid #ccc; padding: 10px; margin: 5px; }
</style>
</head>
<body>
<h1>多容器应用迁移测试</h1>
<div id="users"></div>
<div id="status"></div>
<script>
async function loadUsers() {
try {
const response = await fetch('http://localhost:3000/api/users');
const users = await response.json();
const container = document.getElementById('users');
container.innerHTML = '<h2>用户列表:</h2>' +
users.map(u => \`
<div class="user">
<strong>\${u.name}</strong><br>
Email: \${u.email}<br>
创建时间: \${new Date(u.created_at).toLocaleString()}
</div>
\`).join('');
} catch (error) {
console.error('加载用户失败:', error);
}
}
async function checkHealth() {
try {
const response = await fetch('http://localhost:3000/health');
const status = await response.json();
document.getElementById('status').innerHTML = \`
<h3>系统状态</h3>
<p>状态: \${status.status}</p>
<p>时间: \${new Date(status.timestamp).toLocaleString()}</p>
\`;
} catch (error) {
document.getElementById('status').innerHTML =
'<p style="color: red">API服务不可用</p>';
}
}
// 初始加载
loadUsers();
checkHealth();
// 每10秒刷新一次
setInterval(() => {
loadUsers();
checkHealth();
}, 10000);
</script>
</body>
</html>
EOF
# 创建Nginx配置
cat > ~/multi-app/web/nginx.conf <<EOF
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
try_files \$uri \$uri/ =404;
}
location /api {
proxy_pass http://api:3000;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
}
}
EOF
# 启动应用
cd ~/multi-app
docker-compose up -d
echo "多容器应用已启动,访问 http://localhost 验证"
四、迁移源服务器操作
4.1 查看迁移容器状态
#!/bin/bash
echo "=== 源服务器容器状态检查 ==="
echo ""
# 1. 查看所有容器状态
echo "1. 当前运行中的容器:"
docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}" | tee containers-status.txt
echo ""
echo "2. 所有容器(包括停止的):"
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}\t{{.CreatedAt}}" | tee all-containers.txt
echo ""
echo "3. 容器资源使用情况:"
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}" | tee containers-resources.txt
echo ""
echo "4. 检查容器数据卷:"
for container in $(docker ps -q); do
container_name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
echo "容器: $container_name"
# 检查挂载点
mounts=$(docker inspect --format='{{range .Mounts}}{{.Type}} {{.Source}}:{{.Destination}} {{end}}' $container)
if [ -n "$mounts" ]; then
echo " 挂载: $mounts"
else
echo " 无挂载"
fi
# 检查数据卷
volumes=$(docker inspect --format='{{range .Config.Volumes}}{{.}} {{end}}' $container)
if [ -n "$volumes" ]; then
echo " 数据卷: $volumes"
fi
echo ""
done | tee containers-volumes.txt
echo ""
echo "5. 网络配置检查:"
echo "容器网络配置:" > containers-network.txt
for container in $(docker ps -q); do
container_name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
echo "容器: $container_name" >> containers-network.txt
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}} {{.Gateway}} {{end}}' $container >> containers-network.txt
echo "" >> containers-network.txt
done
cat containers-network.txt
echo ""
echo "6. 环境变量检查:"
echo "关键环境变量:" > containers-env.txt
for container in $(docker ps -q); do
container_name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
echo "容器: $container_name" >> containers-env.txt
docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' $container | grep -E "(PASS|KEY|SECRET|TOKEN|URL|DB|HOST)" >> containers-env.txt
echo "" >> containers-env.txt
done
cat containers-env.txt
echo ""
echo "=== 检查完成 ==="
echo "报告文件已保存:"
ls -la *-status.txt *-containers.txt *-resources.txt *-volumes.txt *-network.txt *-env.txt
4.2 将容器转化为镜像
#!/bin/bash
echo "=== 容器转镜像操作 ==="
echo ""
# 创建备份目录
BACKUP_DIR="/backup/docker-migration-$(date +%Y%m%d-%H%M%S)"
mkdir -p $BACKUP_DIR
cd $BACKUP_DIR
echo "备份目录: $BACKUP_DIR"
echo ""
# 方法1:通过commit创建镜像
echo "方法1:使用docker commit保存容器状态"
for container in $(docker ps -q); do
container_name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
image_name="migration/${container_name}:$(date +%Y%m%d)"
echo "正在保存容器: $container_name -> $image_name"
# 暂停容器(可选,确保数据一致性)
# docker pause $container
# 提交为镜像
docker commit \
--author "Migration Tool" \
--message "Migrated from $container_name on $(date)" \
$container \
$image_name
# 恢复容器
# docker unpause $container
# 验证镜像
docker inspect $image_name > "${container_name}-image.json"
echo " 已保存: $image_name"
echo " 镜像信息: ${container_name}-image.json"
echo ""
done
# 方法2:通过Dockerfile重建镜像
echo "方法2:生成Dockerfile用于重建"
for container in $(docker ps -q); do
container_name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
echo "为容器 $container_name 生成Dockerfile..."
# 获取基础镜像
base_image=$(docker inspect --format='{{.Config.Image}}' $container)
# 获取环境变量
env_vars=$(docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' $container | grep -v "^$")
# 获取工作目录
workdir=$(docker inspect --format='{{.Config.WorkingDir}}' $container)
# 获取命令
cmd=$(docker inspect --format='{{json .Config.Cmd}}' $container)
entrypoint=$(docker inspect --format='{{json .Config.Entrypoint}}' $container)
# 生成Dockerfile
cat > "${container_name}.Dockerfile" <<EOF
# Dockerfile for $container_name
# Generated by migration tool on $(date)
# Source container: $container_name
FROM $base_image
# 环境变量
$(echo "$env_vars" | while read var; do
echo "ENV $var"
done)
# 工作目录
$(if [ -n "$workdir" ] && [ "$workdir" != "<no value>" ]; then
echo "WORKDIR $workdir"
fi)
# 复制变更(需要额外处理)
# 这里需要手动添加需要复制的文件
# 入口点
$(if [ "$entrypoint" != "null" ]; then
echo "ENTRYPOINT $entrypoint"
fi)
# 命令
$(if [ "$cmd" != "null" ]; then
echo "CMD $cmd"
fi)
EOF
echo " 已生成: ${container_name}.Dockerfile"
# 提取容器中的文件变更
echo " 提取容器文件变更..."
docker diff $container > "${container_name}-diff.txt"
# 保存容器配置
docker inspect $container > "${container_name}-config.json"
echo ""
done
echo "=== 容器转镜像完成 ==="
echo "所有文件保存在: $BACKUP_DIR"
ls -la $BACKUP_DIR/
4.3 将生成镜像保存为tar文件
#!/bin/bash
echo "=== 镜像打包操作 ==="
echo ""
BACKUP_DIR="/backup/docker-migration-$(date +%Y%m%d-%H%M%S)"
mkdir -p $BACKUP_DIR/images
cd $BACKUP_DIR
echo "打包目录: $BACKUP_DIR/images"
echo ""
# 方法1:打包所有镜像
echo "方法1:打包所有相关镜像"
echo "收集镜像列表..."
# 获取所有运行中容器使用的镜像
IMAGE_LIST=""
for container in $(docker ps -q); do
image_id=$(docker inspect --format='{{.Image}}' $container)
image_name=$(docker inspect --format='{{.Config.Image}}' $container)
IMAGE_LIST="$IMAGE_LIST $image_name"
done
# 去重
IMAGE_LIST=$(echo $IMAGE_LIST | tr ' ' '\n' | sort -u | tr '\n' ' ')
echo "需要打包的镜像:"
echo "$IMAGE_LIST"
# 保存每个镜像
for image in $IMAGE_LIST; do
echo ""
echo "打包镜像: $image"
# 清理镜像名称中的非法字符
safe_name=$(echo $image | sed 's/[\/:]/-/g')
tar_file="images/${safe_name}.tar"
# 保存镜像
docker save -o $tar_file $image
# 计算大小
file_size=$(du -h $tar_file | cut -f1)
# 验证文件
if tar -tf $tar_file >/dev/null 2>&1; then
echo " ✓ 已保存: $tar_file ($file_size)"
# 生成校验和
md5sum $tar_file > "${tar_file}.md5"
sha256sum $tar_file > "${tar_file}.sha256"
# 保存镜像信息
docker inspect $image > "images/${safe_name}-info.json"
else
echo " ✗ 保存失败: $tar_file"
fi
done
# 方法2:打包迁移创建的镜像
echo ""
echo "方法2:打包迁移创建的镜像"
MIGRATION_IMAGES=$(docker images --filter "reference=migration/*" --format "{{.Repository}}:{{.Tag}}")
if [ -n "$MIGRATION_IMAGES" ]; then
echo "迁移镜像列表:"
echo "$MIGRATION_IMAGES"
for image in $MIGRATION_IMAGES; do
echo ""
echo "打包迁移镜像: $image"
safe_name=$(echo $image | sed 's/[\/:]/-/g')
tar_file="images/migration-${safe_name}.tar"
docker save -o $tar_file $image
file_size=$(du -h $tar_file | cut -f1)
echo " ✓ 已保存: $tar_file ($file_size)"
# 校验和
md5sum $tar_file > "${tar_file}.md5"
sha256sum $tar_file > "${tar_file}.sha256"
done
else
echo "未找到迁移镜像"
fi
# 方法3:打包所有镜像(完整备份)
echo ""
echo "方法3:完整镜像备份"
ALL_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -v "<none>")
echo "完整镜像列表(共 $(echo "$ALL_IMAGES" | wc -l) 个)"
# 可以选择性打包,这里只打包前5个作为示例
echo "$ALL_IMAGES" | head -5 | while read image; do
if [ -n "$image" ]; then
safe_name=$(echo $image | sed 's/[\/:]/-/g')
tar_file="images/full-${safe_name}.tar"
echo "打包: $image"
docker save -o $tar_file $image 2>/dev/null
if [ $? -eq 0 ]; then
file_size=$(du -h $tar_file 2>/dev/null | cut -f1)
echo " ✓ $tar_file ($file_size)"
fi
fi
done
# 创建打包清单
echo ""
echo "创建打包清单..."
cat > images/MANIFEST.md <<EOF
# Docker镜像打包清单
## 打包信息
- 打包时间: $(date)
- 源服务器: $(hostname)
- Docker版本: $(docker --version)
## 镜像清单
$(for tar_file in images/*.tar; do
if [ -f "$tar_file" ]; then
echo "### $(basename $tar_file)"
echo "- 文件: \`$(basename $tar_file)\`"
echo "- 大小: $(du -h $tar_file | cut -f1)"
echo "- MD5: \`$(cat ${tar_file}.md5 2>/dev/null | cut -d' ' -f1 || echo "N/A")\`"
echo "- SHA256: \`$(cat ${tar_file}.sha256 2>/dev/null | cut -d' ' -f1 || echo "N/A")\`"
echo ""
fi
done)
## 容器状态
\`\`\`
$(docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}")
\`\`\`
## 数据卷
$(docker volume ls --format "{{.Name}}" | while read volume; do
echo "- $volume"
done)
EOF
echo ""
echo "=== 镜像打包完成 ==="
echo "总计打包文件:"
ls -lh images/*.tar | wc -l
echo "总大小:"
du -sh images/
echo ""
echo "详细清单: $BACKUP_DIR/images/MANIFEST.md"
4.4 将镜像包拷贝到新服务器
#!/bin/bash
echo "=== 镜像传输操作 ==="
echo ""
BACKUP_DIR="/backup/docker-migration-$(date +%Y%m%d)"
TARGET_SERVER="user@target-server-ip"
TARGET_PATH="/backup/docker-migration"
# 检查参数
if [ $# -ge 1 ]; then
TARGET_SERVER="$1"
fi
if [ $# -ge 2 ]; then
TARGET_PATH="$2"
fi
echo "传输配置:"
echo " 源目录: $BACKUP_DIR"
echo " 目标服务器: $TARGET_SERVER"
echo " 目标路径: $TARGET_PATH"
echo ""
# 方法1:使用scp传输
echo "方法1:使用SCP传输"
echo "正在压缩备份文件..."
# 压缩备份目录
tar_filename="docker-migration-$(date +%Y%m%d-%H%M%S).tar.gz"
cd $(dirname $BACKUP_DIR)
tar -czf $tar_filename $(basename $BACKUP_DIR)
echo "压缩完成: $tar_filename ($(du -h $tar_filename | cut -f1))"
echo ""
echo "开始传输到目标服务器..."
echo "目标: $TARGET_SERVER:$TARGET_PATH"
# 检查目标服务器目录
ssh $TARGET_SERVER "mkdir -p $TARGET_PATH"
# 传输文件
echo "传输中..."
scp $tar_filename $TARGET_SERVER:$TARGET_PATH/
if [ $? -eq 0 ]; then
echo "✓ 传输成功"
# 在目标服务器解压
echo "在目标服务器解压..."
ssh $TARGET_SERVER "
cd $TARGET_PATH
echo '解压文件...'
tar -xzf $(basename $tar_filename)
echo '清理压缩包...'
rm -f $(basename $tar_filename)
echo '设置权限...'
chmod -R 755 $(basename $BACKUP_DIR)
echo '完成!'
"
# 验证传输
echo "验证传输..."
local_md5=$(md5sum $tar_filename | cut -d' ' -f1)
remote_md5=$(ssh $TARGET_SERVER "cd $TARGET_PATH && md5sum $(basename $tar_filename) 2>/dev/null | cut -d' ' -f1 || echo 'not found'")
if [ "$local_md5" = "$remote_md5" ] || [ "$remote_md5" = "not found" ]; then
echo "✓ 传输验证通过"
else
echo "⚠ MD5校验不匹配"
echo " 本地: $local_md5"
echo " 远程: $remote_md5"
fi
else
echo "✗ 传输失败"
exit 1
fi
# 方法2:使用rsync(增量传输)
echo ""
echo "方法2:使用rsync增量传输"
echo "正在同步镜像目录..."
rsync -avz --progress \
$BACKUP_DIR/images/ \
$TARGET_SERVER:$TARGET_PATH/images-rsync/
if [ $? -eq 0 ]; then
echo "✓ rsync同步完成"
# 验证文件数量
local_count=$(find $BACKUP_DIR/images -name "*.tar" | wc -l)
remote_count=$(ssh $TARGET_SERVER "find $TARGET_PATH/images-rsync -name '*.tar' 2>/dev/null | wc -l")
echo "文件数量对比:"
echo " 本地: $local_count 个tar文件"
echo " 远程: $remote_count 个tar文件"
if [ $local_count -eq $remote_count ]; then
echo "✓ 文件数量一致"
else
echo "⚠ 文件数量不一致"
fi
else
echo "✗ rsync同步失败"
fi
# 方法3:使用curl上传到HTTP服务器
echo ""
echo "方法3:上传到HTTP服务器(可选)"
read -p "是否上传到HTTP服务器?(y/N): " upload_choice
if [ "$upload_choice" = "y" ] || [ "$upload_choice" = "Y" ]; then
read -p "请输入HTTP上传URL: " upload_url
if [ -n "$upload_url" ]; then
echo "上传 $tar_filename 到 $upload_url"
# 使用curl上传
curl -X POST \
-F "file=@$tar_filename" \
-F "hostname=$(hostname)" \
-F "timestamp=$(date)" \
$upload_url
if [ $? -eq 0 ]; then
echo "✓ HTTP上传成功"
else
echo "✗ HTTP上传失败"
fi
fi
fi
# 创建传输报告
echo ""
echo "创建传输报告..."
cat > transfer-report.md <<EOF
# Docker镜像传输报告
## 传输信息
- 传输时间: $(date)
- 源服务器: $(hostname)
- 目标服务器: $TARGET_SERVER
- 目标路径: $TARGET_PATH
## 传输文件
- 压缩包: $tar_filename
- 大小: $(du -h $tar_filename | cut -f1)
- MD5: $(md5sum $tar_filename | cut -d' ' -f1)
## 传输方式
1. SCP传输: $(if [ $? -eq 0 ]; then echo "成功"; else echo "失败"; fi)
2. rsync同步: $(if command -v rsync &> /dev/null; then echo "已执行"; else echo "未执行"; fi)
## 验证信息
\`\`\`
本地文件数: $(find $BACKUP_DIR/images -name "*.tar" 2>/dev/null | wc -l)
远程文件数: $(ssh $TARGET_SERVER "find $TARGET_PATH -name '*.tar' 2>/dev/null | wc -l")
\`\`\`
## 后续步骤
1. 在目标服务器加载镜像: \`docker load -i <tar文件>\`
2. 恢复数据卷(如果需要)
3. 启动容器
EOF
echo ""
echo "=== 镜像传输完成 ==="
echo "传输报告: transfer-report.md"
echo "所有文件已传输到: $TARGET_SERVER:$TARGET_PATH"
五、迁移目的服务器操作
5.1 生成本地镜像
#!/bin/bash
echo "=== 目标服务器镜像恢复 ==="
echo ""
MIGRATION_DIR="/backup/docker-migration-$(date +%Y%m%d)"
if [ $# -ge 1 ]; then
MIGRATION_DIR="$1"
fi
if [ ! -d "$MIGRATION_DIR" ]; then
echo "错误: 迁移目录不存在: $MIGRATION_DIR"
echo "请先传输备份文件到目标服务器"
exit 1
fi
cd "$MIGRATION_DIR"
echo "迁移目录: $(pwd)"
echo ""
# 检查镜像文件
echo "检查镜像文件..."
IMAGE_FILES=$(find . -name "*.tar" -type f | sort)
if [ -z "$IMAGE_FILES" ]; then
echo "未找到镜像文件"
exit 1
fi
echo "找到 $(echo "$IMAGE_FILES" | wc -l) 个镜像文件:"
echo "$IMAGE_FILES"
echo ""
# 创建镜像恢复目录
RESTORE_DIR="restored-images-$(date +%Y%m%d-%H%M%S)"
mkdir -p $RESTORE_DIR
cd $RESTORE_DIR
echo "开始恢复镜像..."
echo "恢复目录: $(pwd)"
echo ""
# 方法1:逐个加载镜像
echo "方法1:逐个加载镜像"
success_count=0
fail_count=0
for tar_file in $IMAGE_FILES; do
echo ""
echo "加载镜像: $tar_file"
# 获取镜像名
base_name=$(basename "$tar_file")
# 验证文件完整性
if ! tar -tf "../$tar_file" >/dev/null 2>&1; then
echo " ✗ 文件损坏或格式错误"
fail_count=$((fail_count + 1))
continue
fi
# 加载镜像
echo " 加载中..."
start_time=$(date +%s)
docker load -i "../$tar_file"
if [ $? -eq 0 ]; then
end_time=$(date +%s)
duration=$((end_time - start_time))
# 获取加载的镜像名
loaded_image=$(docker images --format "{{.Repository}}:{{.Tag}}" | tail -1)
echo " ✓ 加载成功: $loaded_image (耗时: ${duration}s)"
# 记录到清单
echo "$(date) | $base_name | $loaded_image | 成功" >> load-success.log
success_count=$((success_count + 1))
# 标记镜像标签
docker tag $loaded_image "restored/${loaded_image##*/}"
else
echo " ✗ 加载失败"
echo "$(date) | $base_name | N/A | 失败" >> load-fail.log
fail_count=$((fail_count + 1))
fi
done
# 方法2:批量验证镜像
echo ""
echo "方法2:验证恢复的镜像"
echo "恢复的镜像列表:"
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedAt}}" | grep -E "(restored|migration)" > restored-images.txt
cat restored-images.txt
echo ""
# 检查镜像完整性
echo "检查镜像完整性..."
for image in $(docker images --filter "reference=restored/*" --format "{{.Repository}}:{{.Tag}}"); do
echo "验证镜像: $image"
# 尝试创建临时容器验证
if docker run --rm $image echo "验证成功" 2>/dev/null; then
echo " ✓ 镜像可正常运行"
echo "$image | 验证通过" >> image-validation.log
else
echo " ⚠ 镜像运行测试失败"
echo "$image | 验证失败" >> image-validation.log
fi
done
# 方法3:重构镜像(如果需要)
echo ""
echo "方法3:从Dockerfile重构镜像(可选)"
DOCKERFILES=$(find .. -name "*.Dockerfile" -type f)
if [ -n "$DOCKERFILES" ]; then
echo "找到 $(echo "$DOCKERFILES" | wc -l) 个Dockerfile"
for dockerfile in $DOCKERFILES; do
echo ""
echo "重构: $(basename $dockerfile)"
# 提取镜像名
image_name="rebuilt/$(basename $dockerfile .Dockerfile):latest"
# 检查是否有对应的diff文件
diff_file="../$(basename $dockerfile .Dockerfile)-diff.txt"
if [ -f "$diff_file" ]; then
echo " 发现变更文件: $(basename $diff_file)"
echo " 变更内容:"
head -5 "$diff_file"
fi
# 尝试重构(需要手动处理文件复制)
echo " 注意: 需要手动处理文件复制步骤"
echo " 参考Dockerfile: $dockerfile"
done
else
echo "未找到Dockerfile文件"
fi
# 创建恢复报告
echo ""
echo "=== 镜像恢复完成 ==="
echo "恢复统计:"
echo " 成功: $success_count 个镜像"
echo " 失败: $fail_count 个镜像"
echo ""
echo "恢复报告:"
cat > restore-report.md <<EOF
# Docker镜像恢复报告
## 恢复信息
- 恢复时间: $(date)
- 目标服务器: $(hostname)
- Docker版本: $(docker --version)
- 恢复目录: $(pwd)
## 恢复统计
- 总镜像文件: $(echo "$IMAGE_FILES" | wc -l) 个
- 成功加载: $success_count 个
- 加载失败: $fail_count 个
## 恢复的镜像
\`\`\`
$(docker images --filter "reference=restored/*" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}")
\`\`\`
## 成功清单
$(if [ -f "load-success.log" ]; then
echo "\`\`\`"
cat load-success.log
echo "\`\`\`"
else
echo "无成功记录"
fi)
## 失败清单
$(if [ -f "load-fail.log" ] && [ -s "load-fail.log" ]; then
echo "\`\`\`"
cat load-fail.log
echo "\`\`\`"
else
echo "无失败记录"
fi)
## 验证结果
$(if [ -f "image-validation.log" ]; then
echo "\`\`\`"
cat image-validation.log
echo "\`\`\`"
else
echo "无验证记录"
fi)
## 后续步骤
1. 检查数据卷是否需要恢复
2. 启动容器: \`docker run --name <name> -p <ports> <image>\`
3. 验证服务运行状态
EOF
cat restore-report.md
echo ""
echo "详细报告保存为: restore-report.md"
5.2 查看本地镜像
#!/bin/bash
echo "=== 本地镜像检查 ==="
echo ""
# 创建检查报告目录
CHECK_DIR="image-check-$(date +%Y%m%d-%H%M%S)"
mkdir -p $CHECK_DIR
cd $CHECK_DIR
echo "检查目录: $(pwd)"
echo ""
# 1. 基本镜像信息
echo "1. 所有镜像列表:"
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedAt}}\t{{.Size}}" > all-images.txt
# 显示关键镜像
echo "关键镜像(恢复的镜像):"
docker images --filter "reference=restored/*" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | tee restored-images.txt
echo ""
echo "迁移相关镜像:"
docker images --filter "reference=migration/*" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | tee migration-images.txt
echo ""
echo "基础镜像:"
docker images --filter "reference=*:latest" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | tee base-images.txt
# 2. 镜像详细信息
echo ""
echo "2. 镜像详细信息检查:"
for image in $(docker images --filter "reference=restored/*" --format "{{.Repository}}:{{.Tag}}"); do
echo ""
echo "检查镜像: $image"
# 保存详细信息
safe_name=$(echo $image | sed 's/[\/:]/-/g')
docker inspect $image > "${safe_name}-inspect.json"
# 提取关键信息
echo " 架构: $(docker inspect --format='{{.Architecture}}' $image)"
echo " 操作系统: $(docker inspect --format='{{.Os}}' $image)"
echo " 创建时间: $(docker inspect --format='{{.Created}}' $image)"
echo " 环境变量:"
docker inspect --format='{{range .Config.Env}}{{println " " .}}{{end}}' $image | head -5
# 检查层数
layers=$(docker inspect --format='{{.RootFS.Layers}}' $image | wc -w)
echo " 层数: $layers"
# 检查暴露端口
ports=$(docker inspect --format='{{.Config.ExposedPorts}}' $image)
if [ "$ports" != "map[]" ] && [ "$ports" != "<no value>" ]; then
echo " 暴露端口: $ports"
fi
# 检查启动命令
cmd=$(docker inspect --format='{{.Config.Cmd}}' $image)
if [ "$cmd" != "<no value>" ] && [ "$cmd" != "[]" ]; then
echo " 启动命令: $cmd"
fi
done
# 3. 镜像依赖关系分析
echo ""
echo "3. 镜像依赖关系分析:"
# 找出所有基础镜像
BASE_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -E "(ubuntu|alpine|centos|debian|nginx|mysql|postgres|node|python)" | sort -u)
echo "基础镜像列表:" > image-dependencies.md
for base in $BASE_IMAGES; do
echo "- $base" >> image-dependencies.md
# 找出基于此镜像的容器
dependent_images=$(docker images --format "{{.Repository}}:{{.Tag}}" | xargs -I {} sh -c 'docker inspect --format="{{.Parent}}" {} 2>/dev/null | grep -q "$base" && echo " - {}"')
if [ -n "$dependent_images" ]; then
echo "$dependent_images" >> image-dependencies.md
fi
done
# 4. 镜像安全扫描(简单版本)
echo ""
echo "4. 镜像安全检查:"
for image in $(docker images --filter "reference=restored/*" --format "{{.Repository}}:{{.Tag}}"); do
echo ""
echo "扫描: $image"
# 检查是否有root用户
docker run --rm $image sh -c 'id' 2>/dev/null | grep -q "uid=0" && echo " ⚠ 以root用户运行"
# 检查是否有敏感文件
docker run --rm $image sh -c 'find / -name "*.pem" -o -name "*.key" -o -name "*pass*" 2>/dev/null | head -3' | while read file; do
if [ -n "$file" ]; then
echo " ⚠ 发现可能敏感文件: $file"
fi
done
# 检查暴露的端口
docker inspect --format='{{range $p, $conf := .Config.ExposedPorts}}{{$p}} {{end}}' $image | while read port; do
if [ -n "$port" ]; then
echo " ℹ 暴露端口: $port"
fi
done
done > image-security.txt
# 5. 镜像存储分析
echo ""
echo "5. 镜像存储分析:"
echo "镜像磁盘使用统计:" > image-storage.txt
docker system df -v >> image-storage.txt
echo "各镜像大小排序:"
docker images --format "table {{.Size}}\t{{.Repository}}:{{.Tag}}" | sort -hr | head -10 | tee top-images-size.txt
# 6. 创建综合报告
echo ""
echo "=== 镜像检查完成 ==="
cat > image-check-report.md <<EOF
# 本地镜像检查报告
## 检查时间
$(date)
## 镜像统计
- 总镜像数: $(docker images | wc -l)
- 恢复的镜像: $(docker images --filter "reference=restored/*" | wc -l)
- 迁移镜像: $(docker images --filter "reference=migration/*" | wc -l)
## 存储使用
\`\`\`
$(docker system df)
\`\`\`
## 最大镜像(Top 10)
$(cat top-images-size.txt | sed 's/^/\- /')
## 恢复镜像详情
$(for image in $(docker images --filter "reference=restored/*" --format "{{.Repository}}:{{.Tag}}"); do
echo "### $image"
echo "- ID: \`$(docker inspect --format='{{.Id}}' $image | cut -c1-12)\`"
echo "- 创建时间: $(docker inspect --format='{{.Created}}' $image)"
echo "- 大小: $(docker inspect --format='{{.Size}}' $image | numfmt --to=iec)"
echo "- 层数: $(docker inspect --format='{{.RootFS.Layers}}' $image | wc -w)"
echo ""
done)
## 安全检查摘要
$(if [ -f "image-security.txt" ]; then
echo "\`\`\`"
cat image-security.txt
echo "\`\`\`"
else
echo "无安全问题发现"
fi)
## 依赖关系
详细依赖关系见: image-dependencies.md
## 建议
1. 清理未使用的镜像: \`docker image prune\`
2. 考虑使用多阶段构建减小镜像大小
3. 定期更新基础镜像以获取安全补丁
EOF
echo "详细报告已生成:"
ls -la *.md *.txt *.json
echo ""
echo "主报告: image-check-report.md"
5.3 在新服务器上运行容器
#!/bin/bash
echo "=== 在新服务器上运行容器 ==="
echo ""
RUN_DIR="container-run-$(date +%Y%m%d-%H%M%S)"
mkdir -p $RUN_DIR
cd $RUN_DIR
echo "运行目录: $(pwd)"
echo ""
# 1. 检查可用的恢复镜像
echo "1. 可用镜像列表:"
AVAILABLE_IMAGES=$(docker images --filter "reference=restored/*" --format "{{.Repository}}:{{.Tag}}")
if [ -z "$AVAILABLE_IMAGES" ]; then
echo "未找到恢复的镜像"
echo "请先执行镜像恢复步骤"
exit 1
fi
echo "$AVAILABLE_IMAGES"
echo ""
# 2. 创建容器运行配置
echo "2. 创建容器运行配置"
cat > run-containers.sh <<'EOF'
#!/bin/bash
# 容器运行脚本
# 自动从恢复的镜像启动容器
set -e
echo "开始启动容器..."
echo ""
# 定义容器配置
declare -A CONTAINER_CONFIG
# Web服务器容器
CONTAINER_CONFIG[web]="
--name nginx-restored
-p 80:80
-p 443:443
--restart unless-stopped
-v web-data:/usr/share/nginx/html
-v web-logs:/var/log/nginx
restored/nginx:latest
"
# 数据库容器
CONTAINER_CONFIG[database]="
--name mysql-restored
-p 3306:3306
--restart unless-stopped
-e MYSQL_ROOT_PASSWORD=Restored@123
-e MYSQL_DATABASE=restored_db
-v mysql-data:/var/lib/mysql
restored/mysql:latest
"
# 应用容器
CONTAINER_CONFIG[app]="
--name app-restored
-p 3000:3000
--restart unless-stopped
-e DATABASE_URL=mysql://root:Restored@123@mysql-restored:3306/restored_db
-v app-logs:/app/logs
restored/app:latest
"
# 创建数据卷
echo "创建数据卷..."
for volume in web-data web-logs mysql-data app-logs; do
if ! docker volume ls | grep -q $volume; then
docker volume create $volume
echo " 创建: $volume"
else
echo " 已存在: $volume"
fi
done
echo ""
# 启动容器
for container in web database app; do
if docker ps -a --format "{{.Names}}" | grep -q "${container}-restored"; then
echo "容器 ${container}-restored 已存在,重新创建..."
docker rm -f ${container}-restored
fi
echo "启动 ${container}-restored..."
# 使用eval展开配置
eval "docker run -d ${CONTAINER_CONFIG[$container]}"
if [ $? -eq 0 ]; then
echo " ✓ 启动成功"
else
echo " ✗ 启动失败"
fi
echo ""
done
# 等待服务启动
echo "等待服务启动..."
sleep 10
# 检查容器状态
echo "容器状态:"
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
echo "容器启动完成!"
EOF
chmod +x run-containers.sh
# 3. 创建docker-compose配置
echo "3. 创建docker-compose配置"
cat > docker-compose.restored.yml <<'EOF'
version: '3.8'
services:
mysql-restored:
image: restored/mysql:latest
container_name: mysql-restored
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: Restored@123
MYSQL_DATABASE: restored_db
MYSQL_USER: restored_user
MYSQL_PASSWORD: Restored@123
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- restored-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
app-restored:
image: restored/app:latest
container_name: app-restored
restart: unless-stopped
environment:
DATABASE_URL: mysql://root:Restored@123@mysql-restored:3306/restored_db
NODE_ENV: production
ports:
- "3000:3000"
volumes:
- app-data:/app/data
- app-logs:/app/logs
depends_on:
mysql-restored:
condition: service_healthy
networks:
- restored-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
nginx-restored:
image: restored/nginx:latest
container_name: nginx-restored
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- web-html:/usr/share/nginx/html
- nginx-conf:/etc/nginx/conf.d
- nginx-logs:/var/log/nginx
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- app-restored
networks:
- restored-network
volumes:
mysql-data:
driver: local
app-data:
driver: local
app-logs:
driver: local
web-html:
driver: local
nginx-conf:
driver: local
nginx-logs:
driver: local
networks:
restored-network:
driver: bridge
EOF
# 4. 创建初始化脚本
echo "4. 创建初始化脚本"
mkdir -p mysql nginx
cat > mysql/init.sql <<'EOF'
-- 初始化数据库
CREATE DATABASE IF NOT EXISTS restored_app;
USE restored_app;
-- 创建用户表
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 创建产品表
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL,
stock INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 插入测试数据
INSERT INTO users (username, email) VALUES
('admin', 'admin@restored.com'),
('user1', 'user1@restored.com'),
('user2', 'user2@restored.com')
ON DUPLICATE KEY UPDATE updated_at = CURRENT_TIMESTAMP;
INSERT INTO products (name, description, price, stock) VALUES
('迁移测试产品1', '从源服务器迁移的产品1', 99.99, 100),
('迁移测试产品2', '从源服务器迁移的产品2', 199.99, 50),
('迁移测试产品3', '从源服务器迁移的产品3', 299.99, 25)
ON DUPLICATE KEY UPDATE price = VALUES(price);
-- 创建视图
CREATE OR REPLACE VIEW user_summary AS
SELECT
COUNT(*) as total_users,
MAX(created_at) as latest_user,
DATE(created_at) as date_joined,
COUNT(*) as users_per_day
FROM users
GROUP BY DATE(created_at);
EOF
cat > nginx/default.conf <<'EOF'
server {
listen 80;
server_name localhost;
# 重定向到HTTPS(如果配置了SSL)
# return 301 https://$host$request_uri;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ =404;
}
location /api {
proxy_pass http://app-restored:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
# HTTPS配置示例(需要SSL证书)
# server {
# listen 443 ssl http2;
# server_name localhost;
#
# ssl_certificate /etc/nginx/ssl/server.crt;
# ssl_certificate_key /etc/nginx/ssl/server.key;
#
# # SSL配置...
# }
EOF
# 5. 创建健康检查脚本
echo "5. 创建健康检查脚本"
cat > health-check.sh <<'EOF'
#!/bin/bash
echo "容器健康检查"
echo "=============="
echo ""
# 检查Docker服务
echo "1. Docker服务状态:"
systemctl is-active docker
echo ""
# 检查所有容器状态
echo "2. 所有容器状态:"
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}\t{{.RunningFor}}"
echo ""
# 检查运行中的容器
echo "3. 运行中的容器:"
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
echo ""
# 容器资源使用
echo "4. 容器资源使用:"
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.PIDs}}"
echo ""
# 网络检查
echo "5. 网络连接:"
for container in $(docker ps -q); do
name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
ip=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $container)
echo " $name: $ip"
# 检查端口监听
echo " 监听端口:"
docker exec $container netstat -tuln 2>/dev/null | grep LISTEN | awk '{print " "$4}'
done
echo ""
# 服务连通性检查
echo "6. 服务连通性检查:"
# MySQL检查
if docker ps --format "{{.Names}}" | grep -q mysql; then
echo " MySQL:"
if docker exec mysql-restored mysqladmin ping -h localhost --silent; then
echo " ✓ 服务正常"
# 检查数据库
db_count=$(docker exec mysql-restored mysql -uroot -pRestored@123 -e "SHOW DATABASES;" 2>/dev/null | grep -c "Database")
echo " 数据库数量: $db_count"
else
echo " ✗ 服务异常"
fi
fi
# Web应用检查
if docker ps --format "{{.Names}}" | grep -q app-restored; then
echo " Web应用:"
if curl -s http://localhost:3000/health > /dev/null; then
echo " ✓ 服务正常"
response=$(curl -s http://localhost:3000/health)
echo " 健康状态: $response"
else
echo " ✗ 服务异常"
fi
fi
# Nginx检查
if docker ps --format "{{.Names}}" | grep -q nginx-restored; then
echo " Nginx:"
if curl -s http://localhost > /dev/null; then
echo " ✓ 服务正常"
http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost)
echo " HTTP状态码: $http_code"
else
echo " ✗ 服务异常"
fi
fi
# 日志检查
echo ""
echo "7. 最近错误日志:"
for container in $(docker ps -q); do
name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
echo " $name:"
docker logs --tail=5 $container 2>&1 | grep -i error | head -3 | sed 's/^/ /'
done
echo ""
echo "健康检查完成!"
EOF
chmod +x health-check.sh
# 6. 创建启动说明
echo "6. 创建启动说明"
cat > README.md <<'EOF'
# 容器运行说明
## 文件结构
.
├── run-containers.sh # 快速启动脚本
├── docker-compose.restored.yml # Docker Compose配置
├── health-check.sh # 健康检查脚本
├── mysql/
│ └── init.sql # 数据库初始化脚本
├── nginx/
│ └── default.conf # Nginx配置
└── README.md # 本说明文件
## 启动方式
### 方式一:使用快速启动脚本
```bash
# 启动所有容器
./run-containers.sh
# 检查状态
./health-check.sh
方式二:使用Docker Compose
# 启动服务
docker-compose -f docker-compose.restored.yml up -d
# 查看日志
docker-compose -f docker-compose.restored.yml logs -f
# 停止服务
docker-compose -f docker-compose.restored.yml down
# 停止并清理数据卷
docker-compose -f docker-compose.restored.yml down -v
方式三:手动启动
# 1. 启动MySQL
docker run -d \
--name mysql-restored \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=Restored@123 \
-v mysql-data:/var/lib/mysql \
restored/mysql:latest
# 2. 启动应用
docker run -d \
--name app-restored \
-p 3000:3000 \
-e DATABASE_URL=mysql://root:Restored@123@mysql-restored:3306/restored_db \
restored/app:latest
# 3. 启动Nginx
docker run -d \
--name nginx-restored \
-p 80:80 \
--link app-restored:app \
restored/nginx:latest
服务访问
- Web界面: http://localhost
- API服务: http://localhost:3000
- MySQL: localhost:3306 (root/Restored@123)
- Nginx状态: http://localhost/status
数据卷
- mysql-data: MySQL数据存储
- app-data: 应用数据存储
- app-logs: 应用日志
- web-html: 静态文件
- nginx-conf: Nginx配置
- nginx-logs: Nginx日志
维护命令
# 查看容器日志
docker logs -f nginx-restored
# 进入容器
docker exec -it mysql-restored mysql -uroot -p
# 备份数据
docker exec mysql-restored mysqldump -uroot -pRestored@123 --all-databases > backup.sql
# 监控资源
docker stats
故障排除
- 如果服务启动失败,检查端口是否被占用
- 查看容器日志:
docker logs <容器名> - 检查容器状态:
docker ps -a - 重启容器:
docker restart <容器名>
安全建议
- 修改默认密码
- 配置防火墙规则
- 定期更新镜像
- 启用SSL/TLS
# 7. 执行启动
echo "7. 执行启动"
echo ""
echo "请选择启动方式:"
echo "1. 使用快速启动脚本"
echo "2. 使用Docker Compose"
echo "3. 手动启动"
echo ""
read -p "请选择 (1-3): " choice
case $choice in
1)
echo "使用快速启动脚本..."
./run-containers.sh
;;
2)
echo "使用Docker Compose..."
docker-compose -f docker-compose.restored.yml up -d
;;
3)
echo "请参考README.md中的手动启动步骤"
;;
*)
echo "无效选择"
;;
esac
echo ""
echo "=== 容器运行配置完成 ==="
echo "所有配置已保存到: $(pwd)"
echo "请查看README.md获取详细说明"
5.4 查看新容器状态
#!/bin/bash
echo "=== 新容器状态监控 ==="
echo ""
MONITOR_DIR="container-monitor-$(date +%Y%m%d-%H%M%S)"
mkdir -p $MONITOR_DIR
cd $MONITOR_DIR
echo "监控目录: $(pwd)"
echo ""
# 1. 实时状态监控
echo "1. 实时状态监控"
echo "按Ctrl+C退出监控"
echo ""
# 创建监控脚本
cat > realtime-monitor.sh <<'EOF'
#!/bin/bash
# 实时容器监控
INTERVAL=5 # 监控间隔(秒)
DURATION=300 # 监控持续时间(秒),0表示无限
echo "开始容器实时监控 (间隔: ${INTERVAL}s)"
echo ""
end_time=$((SECONDS + DURATION)) if [ $DURATION -gt 0 ]; then end_time=0; fi
while true; do
clear
echo "================================================"
echo "容器实时监控 - $(date)"
echo "================================================"
echo ""
# 运行容器状态
echo "运行中的容器:"
echo "----------------------------------------------------------------"
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}" | tail -n +2
echo ""
# 资源使用
echo "资源使用情况:"
echo "----------------------------------------------------------------"
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
echo ""
# 网络统计
echo "网络连接统计:"
echo "----------------------------------------------------------------"
for container in $(docker ps -q); do
name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
echo "$name:"
# TCP连接数
tcp_conn=$(docker exec $container sh -c "netstat -an | grep ESTABLISHED | wc -l" 2>/dev/null || echo "N/A")
echo " ESTABLISHED连接: $tcp_conn"
# 端口监听
listening=$(docker exec $container sh -c "netstat -tuln | grep LISTEN | wc -l" 2>/dev/null || echo "N/A")
echo " 监听端口数: $listening"
done
echo ""
# 服务健康检查
echo "服务健康状态:"
echo "----------------------------------------------------------------"
# MySQL健康检查
if docker ps --format "{{.Names}}" | grep -q mysql; then
if docker exec mysql-restored mysqladmin ping -h localhost --silent 2>/dev/null; then
echo "✓ MySQL: 健康"
else
echo "✗ MySQL: 异常"
fi
fi
# Web应用健康检查
if docker ps --format "{{.Names}}" | grep -q app-restored; then
if curl -s -f http://localhost:3000/health > /dev/null 2>&1; then
echo "✓ Web应用: 健康"
else
echo "✗ Web应用: 异常"
fi
fi
# Nginx健康检查
if docker ps --format "{{.Names}}" | grep -q nginx; then
if curl -s -f http://localhost > /dev/null 2>&1; then
echo "✓ Nginx: 健康"
else
echo "✗ Nginx: 异常"
fi
fi
echo ""
# 检查监控时间
if [ $DURATION -gt 0 ] && [ $SECONDS -ge $end_time ]; then
echo "监控时间到"
break
fi
echo "下一次更新: ${INTERVAL}秒后..."
sleep $INTERVAL
done
EOF
chmod +x realtime-monitor.sh
# 2. 生成状态报告
echo "2. 生成详细状态报告"
cat > generate-report.sh <<'EOF'
#!/bin/bash
echo "生成容器状态报告..."
echo ""
REPORT_FILE="container-status-report-$(date +%Y%m%d-%H%M%S).md"
cat > $REPORT_FILE <<REPORTEOF
# 容器状态报告
## 报告信息
- 生成时间: $(date)
- 服务器: $(hostname)
- Docker版本: $(docker --version)
## 系统概览
\`\`\`
$(docker system df)
\`\`\`
## 容器状态
### 所有容器
\`\`\`
$(docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}\t{{.CreatedAt}}")
\`\`\`
### 运行中的容器
\`\`\`
$(docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.RunningFor}}")
\`\`\`
## 详细容器信息
EOF
# 为每个容器添加详细信息
for container in $(docker ps -a -q); do
name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
cat >> $REPORT_FILE <<CONTAINEREOF
### $name
**基本信息:**
- 容器ID: \`$(docker inspect --format='{{.Id}}' $container | cut -c1-12)\`
- 镜像: \`$(docker inspect --format='{{.Config.Image}}' $container)\`
- 状态: $(docker inspect --format='{{.State.Status}}' $container)
- 运行时长: $(docker inspect --format='{{.State.StartedAt}}' $container | xargs -I {} date -d {} "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "N/A")
**资源限制:**
- 内存限制: $(docker inspect --format='{{.HostConfig.Memory}}' $container | numfmt --to=iec 2>/dev/null || echo "无限制")
- CPU限制: $(docker inspect --format='{{.HostConfig.NanoCpus}}' $container | awk '{if($1>0) print $1/1000000000 " CPUs"; else print "无限制"}')
- 重启策略: $(docker inspect --format='{{.HostConfig.RestartPolicy.Name}}' $container)
**网络配置:**
$(docker inspect --format='{{range .NetworkSettings.Networks}}IP地址: {{.IPAddress}}
网关: {{.Gateway}}
MAC地址: {{.MacAddress}}
{{end}}' $container)
**挂载卷:**
\`\`\`
$(docker inspect --format='{{range .Mounts}}{{.Type}}: {{.Source}} -> {{.Destination}}
{{end}}' $container)
\`\`\`
**环境变量:**
\`\`\`
$(docker inspect --format='{{range .Config.Env}}{{.}}
{{end}}' $container | grep -E "(PASS|KEY|SECRET|DB|HOST|PORT)" | head -10)
\`\`\`
---
CONTAINEREOF
done
# 添加性能数据
cat >> $REPORT_FILE <<PERFORMANCEEOF
## 性能数据
### 当前资源使用
\`\`\`
$(docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}")
\`\`\`
### 日志信息
EOF
# 添加日志摘要
for container in $(docker ps -q); do
name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
cat >> $REPORT_FILE <<LOGEOF
#### $name 最近日志
\`\`\`
$(docker logs --tail=20 $container 2>&1 | tail -5)
\`\`\`
LOGEOF
done
cat >> $REPORT_FILE <<ENDOF
## 建议
1. **资源优化:**
- 检查内存使用,适当调整限制
- 监控CPU使用率,考虑增加资源
2. **安全建议:**
- 检查暴露的端口
- 验证环境变量安全性
- 更新基础镜像
3. **维护建议:**
- 定期清理未使用的容器和镜像
- 配置日志轮转
- 设置监控告警
## 检查清单
- [ ] 所有容器正常运行
- [ ] 网络连接正常
- [ ] 存储卷正确挂载
- [ ] 服务端口可访问
- [ ] 日志无异常错误
- [ ] 资源使用在合理范围
---
*报告生成完成*
REPORTEOF
echo "报告已生成: $REPORT_FILE"
EOF
chmod +x generate-report.sh
# 3. 创建告警检查脚本
echo "3. 创建告警检查脚本"
cat > alert-check.sh <<'EOF'
#!/bin/bash
echo "容器告警检查"
echo "============="
echo ""
ALERT=false
ALERT_MESSAGES=()
# 1. 检查容器状态
echo "检查容器状态..."
for container in $(docker ps -a --filter "status=exited" --format "{{.Names}}"); do
exit_code=$(docker inspect --format='{{.State.ExitCode}}' $container)
if [ $exit_code -ne 0 ]; then
ALERT=true
ALERT_MESSAGES+=("容器 $container 异常退出,退出码: $exit_code")
echo "✗ $container: 异常退出 (code: $exit_code)"
else
echo "✓ $container: 正常退出"
fi
done
# 2. 检查重启次数
echo ""
echo "检查容器重启..."
for container in $(docker ps -a --format "{{.Names}}"); do
restart_count=$(docker inspect --format='{{.RestartCount}}' $container)
if [ $restart_count -gt 5 ]; then
ALERT=true
ALERT_MESSAGES+=("容器 $container 重启次数过多: $restart_count")
echo "⚠ $container: 重启 $restart_count 次"
fi
done
# 3. 检查资源使用
echo ""
echo "检查资源使用..."
docker stats --no-stream --format "{{.Name}} {{.CPUPerc}} {{.MemPerc}}" | while read line; do
name=$(echo $line | awk '{print $1}')
cpu=$(echo $line | awk '{print $2}' | sed 's/%//')
mem=$(echo $line | awk '{print $3}' | sed 's/%//')
if [ $(echo "$cpu > 80" | bc) -eq 1 ]; then
ALERT=true
ALERT_MESSAGES+=("容器 $name CPU使用率过高: ${cpu}%")
echo "⚠ $name: CPU使用率 ${cpu}%"
fi
if [ $(echo "$mem > 80" | bc) -eq 1 ]; then
ALERT=true
ALERT_MESSAGES+=("容器 $name 内存使用率过高: ${mem}%")
echo "⚠ $name: 内存使用率 ${mem}%"
fi
done
# 4. 检查端口冲突
echo ""
echo "检查端口冲突..."
used_ports=""
for container in $(docker ps -q); do
ports=$(docker port $container | awk '{print $3}' | cut -d':' -f2)
for port in $ports; do
if echo "$used_ports" | grep -q "\b$port\b"; then
ALERT=true
ALERT_MESSAGES+=("端口 $port 被多个容器使用")
echo "⚠ 端口冲突: $port"
fi
used_ports="$used_ports $port"
done
done
# 5. 检查磁盘空间
echo ""
echo "检查磁盘空间..."
docker system df | tail -n +2 | while read line; do
type=$(echo $line | awk '{print $1}')
usage=$(echo $line | awk '{print $5}' | sed 's/%//')
if [ $(echo "$usage > 85" | bc) -eq 1 ]; then
ALERT=true
ALERT_MESSAGES+=("$type 磁盘使用率过高: ${usage}%")
echo "⚠ $type: 磁盘使用率 ${usage}%"
fi
done
# 6. 输出结果
echo ""
echo "========================================="
if [ "$ALERT" = true ]; then
echo "发现告警!"
echo ""
echo "告警信息:"
for msg in "${ALERT_MESSAGES[@]}"; do
echo "• $msg"
done
# 发送告警(示例)
# echo "发送邮件告警..."
# echo "${ALERT_MESSAGES[@]}" | mail -s "Docker容器告警" admin@example.com
exit 1
else
echo "所有检查通过,无告警"
exit 0
fi
EOF
chmod +x alert-check.sh
# 4. 创建监控面板
echo "4. 创建监控面板"
cat > monitor-dashboard.sh <<'EOF'
#!/bin/bash
# 简易监控面板
clear
while true; do
echo "╔══════════════════════════════════════════════════════════╗"
echo "║ Docker容器监控面板 ║"
echo "╠══════════════════════════════════════════════════════════╣"
echo "║ 选项: ║"
echo "║ 1. 实时状态监控 ║"
echo "║ 2. 生成状态报告 ║"
echo "║ 3. 告警检查 ║"
echo "║ 4. 查看容器日志 ║"
echo "║ 5. 资源使用详情 ║"
echo "║ 6. 网络连接查看 ║"
echo "║ 7. 数据卷状态 ║"
echo "║ 8. 退出 ║"
echo "╚══════════════════════════════════════════════════════════╝"
echo ""
read -p "请选择操作 (1-8): " choice
case $choice in
1)
./realtime-monitor.sh
;;
2)
./generate-report.sh
;;
3)
./alert-check.sh
read -p "按回车键继续..."
;;
4)
echo "选择要查看日志的容器:"
select container in $(docker ps --format "{{.Names}}") "返回"; do
if [ "$container" = "返回" ]; then
break
elif [ -n "$container" ]; then
echo "查看 $container 的日志 (Ctrl+C退出):"
docker logs -f $container
fi
done
;;
5)
echo "资源使用详情:"
echo "------------------------------------------------"
docker stats --no-stream
echo ""
read -p "按回车键继续..."
;;
6)
echo "网络连接:"
echo "------------------------------------------------"
docker network ls
echo ""
echo "容器网络详情:"
for container in $(docker ps -q); do
name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
ip=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $container)
echo "$name: $ip"
done
echo ""
read -p "按回车键继续..."
;;
7)
echo "数据卷状态:"
echo "------------------------------------------------"
docker volume ls
echo ""
echo "数据卷使用情况:"
for volume in $(docker volume ls -q); do
mount=$(docker volume inspect --format='{{.Mountpoint}}' $volume)
size=$(sudo du -sh $mount 2>/dev/null | cut -f1 || echo "未知")
echo "$volume: $mount ($size)"
done
echo ""
read -p "按回车键继续..."
;;
8)
echo "退出监控面板"
exit 0
;;
*)
echo "无效选择"
;;
esac
clear
done
EOF
chmod +x monitor-dashboard.sh
# 5. 创建定期检查任务
echo "5. 创建定期检查任务"
cat > setup-cron.sh <<'EOF'
#!/bin/bash
echo "设置定期检查任务"
echo ""
# 创建日志目录
mkdir -p /var/log/docker-monitor
# 添加每日检查任务
(crontab -l 2>/dev/null; echo "# Docker容器每日检查") | crontab -
(crontab -l 2>/dev/null; echo "0 2 * * * $(pwd)/generate-report.sh >> /var/log/docker-monitor/daily-check.log 2>&1") | crontab -
# 添加每小时告警检查
(crontab -l 2>/dev/null; echo "# Docker容器每小时告警检查") | crontab -
(crontab -l 2>/dev/null; echo "0 * * * * $(pwd)/alert-check.sh >> /var/log/docker-monitor/hourly-alert.log 2>&1") | crontab -
# 添加日志轮转配置
cat > /etc/logrotate.d/docker-monitor <<LOGROTATE
/var/log/docker-monitor/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 644 root root
}
LOGROTATE
echo "定期任务已设置:"
echo "1. 每日凌晨2点生成状态报告"
echo "2. 每小时执行告警检查"
echo "3. 日志自动轮转(保留30天)"
echo ""
echo "日志目录: /var/log/docker-monitor"
EOF
chmod +x setup-cron.sh
echo ""
echo "=== 容器状态监控配置完成 ==="
echo "可用的监控工具:"
echo "1. ./realtime-monitor.sh # 实时监控"
echo "2. ./generate-report.sh # 生成报告"
echo "3. ./alert-check.sh # 告警检查"
echo "4. ./monitor-dashboard.sh # 监控面板"
echo "5. ./setup-cron.sh # 设置定期任务"
echo ""
echo "监控目录: $(pwd)"
5.5 测试网站内容
#!/bin/bash
echo "=== 网站内容测试 ==="
echo ""
TEST_DIR="website-test-$(date +%Y%m%d-%H%M%S)"
mkdir -p $TEST_DIR
cd $TEST_DIR
echo "测试目录: $(pwd)"
echo ""
# 1. 基础连通性测试
echo "1. 基础连通性测试"
echo "================="
cat > basic-connectivity.sh <<'EOF'
#!/bin/bash
echo "基础连通性测试"
echo ""
# 定义测试端点
declare -A ENDPOINTS=(
["首页"]="http://localhost"
["API健康检查"]="http://localhost:3000/health"
["Nginx状态"]="http://localhost/status"
["API用户列表"]="http://localhost:3000/api/users"
)
# 测试函数
test_endpoint() {
local name=$1
local url=$2
echo -n "测试 $name ($url)... "
# 使用curl测试
response=$(curl -s -o /dev/null -w "%{http_code} %{time_total}s" -I $url 2>&1)
http_code=$(echo $response | awk '{print $1}')
response_time=$(echo $response | awk '{print $2}')
if [[ $http_code =~ ^2[0-9]{2}$ ]] || [[ $http_code =~ ^3[0-9]{2}$ ]]; then
echo "✓ 成功 ($http_code, ${response_time}s)"
return 0
else
echo "✗ 失败 ($http_code, ${response_time}s)"
return 1
fi
}
# 执行测试
FAIL_COUNT=0
TOTAL_TESTS=0
for name in "${!ENDPOINTS[@]}"; do
test_endpoint "$name" "${ENDPOINTS[$name]}"
if [ $? -ne 0 ]; then
FAIL_COUNT=$((FAIL_COUNT + 1))
fi
TOTAL_TESTS=$((TOTAL_TESTS + 1))
done
echo ""
echo "测试完成: $TOTAL_TESTS 个测试, $FAIL_COUNT 个失败"
if [ $FAIL_COUNT -eq 0 ]; then
echo "✓ 所有基础连通性测试通过"
exit 0
else
echo "✗ 部分测试失败"
exit 1
fi
EOF
chmod +x basic-connectivity.sh
# 2. 功能完整性测试
echo "2. 功能完整性测试"
echo "================="
cat > functionality-test.sh <<'EOF'
#!/bin/bash
echo "功能完整性测试"
echo ""
TEST_RESULTS=""
# 测试1: 数据库连接
echo "测试1: 数据库连接"
if docker exec mysql-restored mysql -uroot -pRestored@123 -e "SELECT 1" > /dev/null 2>&1; then
echo "✓ 数据库连接正常"
TEST_RESULTS="${TEST_RESULTS}数据库连接: 通过\n"
else
echo "✗ 数据库连接失败"
TEST_RESULTS="${TEST_RESULTS}数据库连接: 失败\n"
fi
# 测试2: 数据库数据完整性
echo ""
echo "测试2: 数据库数据完整性"
db_tables=$(docker exec mysql-restored mysql -uroot -pRestored@123 -e "SHOW TABLES FROM restored_db" 2>/dev/null | wc -l)
if [ $db_tables -gt 0 ]; then
echo "✓ 数据库表存在 ($((db_tables-1)) 个表)"
# 检查用户表数据
user_count=$(docker exec mysql-restored mysql -uroot -pRestored@123 -e "SELECT COUNT(*) FROM restored_db.users" 2>/dev/null | tail -1)
if [ $user_count -gt 0 ]; then
echo "✓ 用户数据存在 ($user_count 个用户)"
TEST_RESULTS="${TEST_RESULTS}数据库数据: 通过\n"
else
echo "⚠ 用户表为空"
TEST_RESULTS="${TEST_RESULTS}数据库数据: 警告\n"
fi
else
echo "✗ 数据库表不存在"
TEST_RESULTS="${TEST_RESULTS}数据库数据: 失败\n"
fi
# 测试3: API功能测试
echo ""
echo "测试3: API功能测试"
# 测试健康检查端点
if curl -s http://localhost:3000/health | grep -q '"status":"ok"'; then
echo "✓ API健康检查正常"
# 测试用户API
users_response=$(curl -s http://localhost:3000/api/users)
if echo "$users_response" | grep -q "username"; then
user_count=$(echo "$users_response" | grep -c "username")
echo "✓ 用户API正常 ($user_count 个用户)"
TEST_RESULTS="${TEST_RESULTS}API功能: 通过\n"
else
echo "⚠ 用户API返回异常"
TEST_RESULTS="${TEST_RESULTS}API功能: 警告\n"
fi
else
echo "✗ API健康检查失败"
TEST_RESULTS="${TEST_RESULTS}API功能: 失败\n"
fi
# 测试4: 网站内容测试
echo ""
echo "测试4: 网站内容测试"
homepage_content=$(curl -s http://localhost)
if [ -n "$homepage_content" ]; then
echo "✓ 网站首页可访问"
# 检查页面标题
if echo "$homepage_content" | grep -q "<title>"; then
title=$(echo "$homepage_content" | grep -o "<title>[^<]*</title>" | sed 's/<title>//;s/<\/title>//')
echo "✓ 页面标题: '$title'"
TEST_RESULTS="${TEST_RESULTS}网站内容: 通过\n"
else
echo "⚠ 页面缺少标题"
TEST_RESULTS="${TEST_RESULTS}网站内容: 警告\n"
fi
else
echo "✗ 网站首页无法访问"
TEST_RESULTS="${TEST_RESULTS}网站内容: 失败\n"
fi
# 测试5: Nginx配置测试
echo ""
echo "测试5: Nginx配置测试"
# 检查Nginx配置文件
if docker exec nginx-restored nginx -t > /dev/null 2>&1; then
echo "✓ Nginx配置语法正确"
# 检查静态文件服务
if curl -s -o /dev/null -w "%{http_code}" http://localhost | grep -q "200"; then
echo "✓ 静态文件服务正常"
TEST_RESULTS="${TEST_RESULTS}Nginx配置: 通过\n"
else
echo "⚠ 静态文件服务异常"
TEST_RESULTS="${TEST_RESULTS}Nginx配置: 警告\n"
fi
else
nginx_error=$(docker exec nginx-restored nginx -t 2>&1)
echo "✗ Nginx配置错误: $nginx_error"
TEST_RESULTS="${TEST_RESULTS}Nginx配置: 失败\n"
fi
# 输出测试总结
echo ""
echo "══════════════════════════════════════════════════════════"
echo "功能完整性测试总结"
echo "══════════════════════════════════════════════════════════"
echo -e "$TEST_RESULTS"
echo "══════════════════════════════════════════════════════════"
# 统计结果
pass_count=$(echo -e "$TEST_RESULTS" | grep -c "通过")
warn_count=$(echo -e "$TEST_RESULTS" | grep -c "警告")
fail_count=$(echo -e "$TEST_RESULTS" | grep -c "失败")
echo ""
echo "总计:"
echo " ✓ 通过: $pass_count"
echo " ⚠ 警告: $warn_count"
echo " ✗ 失败: $fail_count"
if [ $fail_count -eq 0 ]; then
if [ $warn_count -eq 0 ]; then
echo "✅ 所有功能测试完全通过"
exit 0
else
echo "⚠ 功能测试通过,但有警告需要关注"
exit 0
fi
else
echo "❌ 功能测试存在失败项"
exit 1
fi
EOF
chmod +x functionality-test.sh
# 3. 性能测试
echo "3. 性能测试"
echo "============"
cat > performance-test.sh <<'EOF'
#!/bin/bash
echo "网站性能测试"
echo ""
PERF_RESULTS="performance-results-$(date +%Y%m%d-%H%M%S).txt"
# 测试参数
CONCURRENT_USERS=10
REQUESTS_PER_USER=100
TEST_URLS="http://localhost http://localhost:3000/health http://localhost:3000/api/users"
echo "性能测试配置:"
echo " 并发用户数: $CONCURRENT_USERS"
echo " 每用户请求数: $REQUESTS_PER_USER"
echo " 测试URL: $TEST_URLS"
echo " 结果文件: $PERF_RESULTS"
echo ""
# 检查是否安装了ab(Apache Bench)
if ! command -v ab &> /dev/null; then
echo "安装Apache Bench..."
if command -v apt-get &> /dev/null; then
sudo apt-get update
sudo apt-get install -y apache2-utils
elif command -v yum &> /dev/null; then
sudo yum install -y httpd-tools
else
echo "无法安装ab,跳过性能测试"
exit 0
fi
fi
# 性能测试函数
run_performance_test() {
local url=$1
local name=$2
echo ""
echo "测试: $name ($url)"
echo "------------------------------------------------"
# 运行ab测试
ab -n $((CONCURRENT_USERS * REQUESTS_PER_USER)) \
-c $CONCURRENT_USERS \
-k \
-q \
"$url" 2>&1 | tee -a $PERF_RESULTS
# 提取关键指标
echo "" >> $PERF_RESULTS
echo "================================================" >> $PERF_RESULTS
echo "" >> $PERF_RESULTS
}
# 执行性能测试
echo "开始性能测试..." > $PERF_RESULTS
echo "测试时间: $(date)" >> $PERF_RESULTS
echo "" >> $PERF_RESULTS
for url in $TEST_URLS; do
# 提取主机名和路径作为测试名称
if [[ $url == "http://localhost" ]]; then
name="网站首页"
elif [[ $url == "http://localhost:3000/health" ]]; then
name="API健康检查"
elif [[ $url == "http://localhost:3000/api/users" ]]; then
name="API用户列表"
else
name="其他端点"
fi
run_performance_test "$url" "$name"
# 间隔一下,避免压测太密集
sleep 2
done
# 分析性能结果
echo ""
echo "性能测试结果分析:"
echo "══════════════════════════════════════════════════════════"
if [ -f "$PERF_RESULTS" ]; then
# 提取关键指标
echo "从 $PERF_RESULTS 提取关键指标:"
echo ""
# 请求成功率
success_rate=$(grep "Complete requests" $PERF_RESULTS | tail -1 | awk '{print $3"/"$2" ("$3/$2*100"%)"}')
echo "请求成功率: $success_rate"
# 失败请求数
failed_requests=$(grep "Failed requests" $PERF_RESULTS | tail -1 | awk '{print $3}')
echo "失败请求数: $failed_requests"
# 每秒请求数
requests_per_sec=$(grep "Requests per second" $PERF_RESULTS | tail -1 | awk '{print $4}')
echo "每秒请求数: $requests_per_sec"
# 平均响应时间
time_per_request=$(grep "Time per request" $PERF_RESULTS | grep "mean" | tail -1 | awk '{print $4" "$5}')
echo "平均响应时间: $time_per_request"
# 最长响应时间
max_time=$(grep "100%" $PERF_RESULTS | tail -1 | awk '{print $2}')
echo "最长响应时间: ${max_time}ms"
# 传输速率
transfer_rate=$(grep "Transfer rate" $PERF_RESULTS | tail -1 | awk '{print $3" "$4}')
echo "传输速率: $transfer_rate"
echo ""
echo "性能评估:"
# 简单评估逻辑
rps_num=$(echo $requests_per_sec | sed 's/\..*//')
if [ $rps_num -gt 100 ]; then
echo " ✓ 吞吐量优秀 (>100 req/s)"
elif [ $rps_num -gt 50 ]; then
echo " ✓ 吞吐量良好 (50-100 req/s)"
elif [ $rps_num -gt 10 ]; then
echo " ⚠ 吞吐量一般 (10-50 req/s)"
else
echo " ✗ 吞吐量较低 (<10 req/s)"
fi
avg_time_num=$(echo $time_per_request | sed 's/\..*//')
if [ $avg_time_num -lt 100 ]; then
echo " ✓ 响应时间优秀 (<100ms)"
elif [ $avg_time_num -lt 500 ]; then
echo " ✓ 响应时间良好 (100-500ms)"
elif [ $avg_time_num -lt 1000 ]; then
echo " ⚠ 响应时间一般 (500-1000ms)"
else
echo " ✗ 响应时间较慢 (>1000ms)"
fi
if [ $failed_requests -eq 0 ]; then
echo " ✓ 请求成功率优秀 (100%)"
elif [ $failed_requests -lt 10 ]; then
echo " ⚠ 请求成功率良好 (<10失败)"
else
echo " ✗ 请求成功率较差 (≥10失败)"
fi
else
echo "性能测试结果文件不存在"
fi
echo ""
echo "详细性能报告: $PERF_RESULTS"
EOF
chmod +x performance-test.sh
# 4. 安全测试
echo "4. 安全测试"
echo "============"
cat > security-test.sh <<'EOF'
#!/bin/bash
echo "网站安全测试"
echo ""
SECURITY_REPORT="security-report-$(date +%Y%m%d-%H%M%S).md"
cat > $SECURITY_REPORT <<REPORTEOF
# 网站安全测试报告
## 测试信息
- 测试时间: $(date)
- 目标服务器: $(hostname)
- 测试URL: http://localhost
## 1. 信息泄露测试
### HTTP头信息
\`\`\`
$(curl -I http://localhost 2>&1)
\`\`\`
### 敏感文件扫描
扫描常见敏感文件:
REPORTEOF
# 检查常见敏感文件
SENSITIVE_PATHS=(
"/.git"
"/.env"
"/config.php"
"/phpinfo.php"
"/admin"
"/backup"
"/README.md"
"/CHANGELOG.md"
)
for path in "${SENSITIVE_PATHS[@]}"; do
status_code=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost$path")
if [[ $status_code =~ ^2[0-9]{2}$ ]] || [[ $status_code =~ ^3[0-9]{2}$ ]]; then
echo "- ⚠ \`$path\`: 可访问 ($status_code)" >> $SECURITY_REPORT
elif [ $status_code -ne 404 ]; then
echo "- ℹ \`$path\`: 返回 $status_code" >> $SECURITY_REPORT
fi
done
cat >> $SECURITY_REPORT <<REPORTEOF
## 2. SSL/TLS测试(如果启用HTTPS)
\`\`\`
$(if curl -s -f https://localhost > /dev/null 2>&1; then
echo "HTTPS已启用"
echo "证书信息:"
openssl s_client -connect localhost:443 -servername localhost 2>/dev/null | openssl x509 -noout -dates -subject
else
echo "HTTPS未启用"
fi)
\`\`\`
## 3. 安全头检查
\`\`\`
$(curl -s -D - http://localhost -o /dev/null | grep -iE "(security|protection|x-)" || echo "未发现安全头")
\`\`\`
## 4. CORS配置检查
\`\`\`
$(curl -s -X OPTIONS -H "Origin: http://evil.com" -D - http://localhost -o /dev/null | grep -i "access-control" || echo "未发现CORS头")
\`\`\`
## 5. 容器安全扫描
### 运行容器列表
\`\`\`
$(docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}")
\`\`\`
### 以root用户运行的容器
\`\`\`
$(for container in $(docker ps -q); do
user=$(docker exec $container whoami 2>/dev/null || echo "unknown")
if [ "$user" = "root" ]; then
name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
echo "$name: 以root用户运行"
fi
done)
\`\`\`
### 暴露的端口
\`\`\`
$(for container in $(docker ps -q); do
name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
ports=$(docker port $container)
if [ -n "$ports" ]; then
echo "$name:"
echo "$ports" | sed 's/^/ /'
fi
done)
\`\`\`
## 6. 数据库安全
### MySQL配置检查
\`\`\`
$(docker exec mysql-restored mysql -uroot -pRestored@123 -e "SHOW VARIABLES LIKE '%ssl%'; SHOW VARIABLES LIKE '%auth%';" 2>/dev/null || echo "无法连接数据库")
\`\`\`
## 7. 建议的安全改进
### 高优先级
1. **启用HTTPS** - 为网站配置SSL证书
2. **修改默认密码** - 更改数据库默认密码
3. **限制端口暴露** - 只暴露必要的端口
### 中优先级
1. **添加安全头** - 配置CSP、HSTS等安全头
2. **最小权限原则** - 容器不要以root用户运行
3. **定期更新** - 保持镜像和系统更新
### 低优先级
1. **配置WAF** - 添加Web应用防火墙
2. **日志审计** - 配置完整的日志记录和监控
3. **备份策略** - 实施定期备份和恢复测试
## 8. 风险评估
| 风险项 | 等级 | 描述 | 建议 |
|--------|------|------|------|
| HTTP明文传输 | 高 | 所有通信未加密 | 启用HTTPS |
| 默认密码 | 高 | 使用默认数据库密码 | 立即修改 |
| Root用户运行 | 中 | 容器以root运行 | 使用非root用户 |
| 端口暴露过多 | 中 | 暴露不必要的端口 | 限制端口范围 |
| 缺少安全头 | 低 | 缺少安全相关HTTP头 | 添加安全头 |
## 总结
本次安全测试发现了 $(grep -c "⚠" $SECURITY_REPORT) 个警告项和 $(grep -c "高风险" $SECURITY_REPORT) 个高风险项。
**建议立即处理高风险项,特别是启用HTTPS和修改默认密码。**
---
*报告生成完成*
REPORTEOF
echo "安全测试报告已生成: $SECURITY_REPORT"
echo ""
echo "查看报告: cat $SECURITY_REPORT"
EOF
chmod +x security-test.sh
# 5. 综合测试套件
echo "5. 综合测试套件"
echo "================"
cat > comprehensive-test.sh <<'EOF'
#!/bin/bash
echo "网站综合测试套件"
echo "=================="
echo ""
# 创建测试结果目录
RESULTS_DIR="test-results-$(date +%Y%m%d-%H%M%S)"
mkdir -p $RESULTS_DIR
echo "测试结果将保存到: $RESULTS_DIR"
echo ""
# 测试计数器
TOTAL_TESTS=0
PASSED_TESTS=0
FAILED_TESTS=0
WARNING_TESTS=0
# 运行测试并记录结果
run_test() {
local test_name=$1
local test_script=$2
local test_file="$RESULTS_DIR/${test_name}.log"
echo "运行测试: $test_name"
echo "----------------------------------------"
# 运行测试脚本
if [ -f "$test_script" ] && [ -x "$test_script" ]; then
$test_script > "$test_file" 2>&1
exit_code=$?
# 分析结果
if [ $exit_code -eq 0 ]; then
result="✓ 通过"
PASSED_TESTS=$((PASSED_TESTS + 1))
elif [ $exit_code -eq 1 ]; then
result="✗ 失败"
FAILED_TESTS=$((FAILED_TESTS + 1))
else
result="⚠ 警告"
WARNING_TESTS=$((WARNING_TESTS + 1))
fi
echo "$result"
# 显示摘要
echo "结果摘要:"
tail -10 "$test_file"
else
echo "✗ 测试脚本不存在或不可执行"
FAILED_TESTS=$((FAILED_TESTS + 1))
fi
TOTAL_TESTS=$((TOTAL_TESTS + 1))
echo ""
}
# 执行所有测试
run_test "基础连通性" "./basic-connectivity.sh"
run_test "功能完整性" "./functionality-test.sh"
run_test "性能测试" "./performance-test.sh"
run_test "安全测试" "./security-test.sh"
# 生成综合报告
cat > "$RESULTS_DIR/comprehensive-report.md" <<REPORTEOF
# 网站综合测试报告
## 测试概述
- 测试时间: $(date)
- 测试环境: $(hostname)
- Docker版本: $(docker --version)
- 总测试数: $TOTAL_TESTS
## 测试结果统计
| 测试类型 | 结果 | 详情 |
|----------|------|------|
| 基础连通性 | $(if [ -f "$RESULTS_DIR/基础连通性.log" ]; then tail -1 "$RESULTS_DIR/基础连通性.log"; else echo "未执行"; fi) | [查看详情](#基础连通性) |
| 功能完整性 | $(if [ -f "$RESULTS_DIR/功能完整性.log" ]; then grep -E "^(✅|⚠|❌)" "$RESULTS_DIR/功能完整性.log" | tail -1; else echo "未执行"; fi) | [查看详情](#功能完整性) |
| 性能测试 | $(if [ -f "$RESULTS_DIR/性能测试.log" ]; then grep -E "^(性能评估|吞吐量|响应时间)" "$RESULTS_DIR/性能测试.log" | head -3 | sed 's/^/- /'; else echo "未执行"; fi) | [查看详情](#性能测试) |
| 安全测试 | $(if [ -f "$RESULTS_DIR/安全测试.log" ]; then echo "报告已生成"; else echo "未执行"; fi) | [查看详情](#安全测试) |
## 总体评估
**测试结果:** $PASSED_TESTS 通过, $WARNING_TESTS 警告, $FAILED_TESTS 失败
$(if [ $FAILED_TESTS -eq 0 ]; then
if [ $WARNING_TESTS -eq 0 ]; then
echo "✅ **所有测试通过** - 网站运行状态良好"
else
echo "⚠ **测试通过但有警告** - 需要注意警告项"
fi
else
echo "❌ **存在测试失败** - 需要立即处理"
fi)
## 详细测试结果
### 基础连通性
\`\`\`
$(if [ -f "$RESULTS_DIR/基础连通性.log" ]; then cat "$RESULTS_DIR/基础连通性.log"; else echo "测试未执行"; fi)
\`\`\`
### 功能完整性
\`\`\`
$(if [ -f "$RESULTS_DIR/功能完整性.log" ]; then cat "$RESULTS_DIR/功能完整性.log"; else echo "测试未执行"; fi)
\`\`\`
### 性能测试
\`\`\`
$(if [ -f "$RESULTS_DIR/性能测试.log" ]; then
grep -A5 "性能测试结果分析" "$RESULTS_DIR/性能测试.log" ||
tail -20 "$RESULTS_DIR/性能测试.log"
else
echo "测试未执行";
fi)
\`\`\`
### 安全测试
安全测试报告: [security-report-*.md]($(ls $RESULTS_DIR/security-report-*.md 2>/dev/null | head -1 | xargs basename 2>/dev/null || echo "未生成"))
## 建议
### 立即处理项
$(if [ $FAILED_TESTS -gt 0 ]; then
echo "1. 修复失败的测试项"
grep -l "✗" $RESULTS_DIR/*.log | while read file; do
echo " - $(basename $file .log): $(grep "✗" "$file" | head -3 | sed 's/✗/ ✗/')"
done
else
echo "无"
fi)
### 建议改进项
$(if [ $WARNING_TESTS -gt 0 ]; then
echo "1. 处理警告项"
grep -l "⚠" $RESULTS_DIR/*.log | while read file; do
echo " - $(basename $file .log)"
done
else
echo "无"
fi)
### 优化建议
1. 根据性能测试结果优化配置
2. 实施安全测试报告中的建议
3. 建立定期测试机制
## 测试文件
所有详细测试日志可在 $RESULTS_DIR 目录查看
---
*报告生成完成*
REPORTEOF
echo ""
echo "══════════════════════════════════════════════════════════"
echo "综合测试完成"
echo "══════════════════════════════════════════════════════════"
echo ""
echo "测试结果:"
echo " 总计: $TOTAL_TESTS 个测试"
echo " 通过: $PASSED_TESTS"
echo " 警告: $WARNING_TESTS"
echo " 失败: $FAILED_TESTS"
echo ""
echo "详细报告: $RESULTS_DIR/comprehensive-report.md"
echo "所有测试日志: $RESULTS_DIR/"
echo ""
echo "══════════════════════════════════════════════════════════"
# 根据测试结果退出
if [ $FAILED_TESTS -eq 0 ]; then
echo "✅ 测试套件执行完成,网站运行正常"
exit 0
else
echo "❌ 测试套件发现失败项,需要处理"
exit 1
fi
EOF
chmod +x comprehensive-test.sh
# 6. 创建测试说明
echo "6. 创建测试说明"
cat > TESTING.md <<'EOF'
# 网站测试指南
## 测试套件说明
本目录包含完整的网站测试套件,用于验证迁移后网站的运行状态。
### 可用测试脚本
1. **基础测试**
- `./basic-connectivity.sh` - 基础连通性测试
- 测试网站是否能正常访问
- 检查关键端点是否响应
2. **功能测试**
- `./functionality-test.sh` - 功能完整性测试
- 验证数据库连接
- 测试API功能
- 检查网站内容
3. **性能测试**
- `./performance-test.sh` - 性能测试
- 压力测试网站性能
- 测量响应时间和吞吐量
4. **安全测试**
- `./security-test.sh` - 安全测试
- 检查安全漏洞
- 验证安全配置
5. **综合测试**
- `./comprehensive-test.sh` - 综合测试套件
- 运行所有测试并生成报告
- 提供整体评估
### 快速开始
#### 方法一:运行所有测试
```bash
./comprehensive-test.sh
方法二:分步测试
# 1. 基础测试
./basic-connectivity.sh
# 2. 功能测试
./functionality-test.sh
# 3. 性能测试
./performance-test.sh
# 4. 安全测试
./security-test.sh
测试环境要求
-
Docker环境
- Docker已安装并运行
- 容器已启动并运行
-
网络访问
- 本地网络可访问
- 端口80、3000、3306已开放
-
工具依赖
- curl (必须)
- ab (Apache Bench,性能测试需要)
- mysql-client (可选,用于数据库测试)
测试结果解读
结果代码
- ✓ 通过 - 测试项完全通过
- ⚠ 警告 - 测试通过但有需要注意的问题
- ✗ 失败 - 测试项失败,需要处理
报告文件
所有测试结果都会保存到 test-results-<时间戳>/ 目录:
.log文件 - 详细测试日志.md文件 - 格式化报告comprehensive-report.md- 综合报告
常见问题
Q1: 性能测试失败
原因: 可能未安装ab工具
解决: sudo apt-get install apache2-utils 或 sudo yum install httpd-tools
Q2: 数据库连接失败
原因: 数据库密码不正确或服务未启动
解决: 检查数据库容器状态和密码配置
Q3: 网站无法访问
原因: 容器未启动或端口被占用
解决:
# 检查容器状态
docker ps
# 检查端口占用
netstat -tuln | grep -E "(80|3000|3306)"
# 重启容器
docker-compose -f docker-compose.restored.yml restart
自动化测试
定期测试
# 添加到crontab,每天凌晨3点运行
0 3 * * * cd /path/to/test && ./comprehensive-test.sh
CI/CD集成
# 在CI/CD流水线中添加测试步骤
- name: 运行网站测试
run: |
cd website-test
./comprehensive-test.sh
自定义测试
如需添加自定义测试,可修改相应脚本或创建新的测试脚本:
# 示例:创建自定义测试
cat > custom-test.sh <<'CUSTOM'
#!/bin/bash
echo "自定义测试"
# 添加测试逻辑
CUSTOM
chmod +x custom-test.sh
联系方式
如有问题或建议,请联系:
- 维护者: [你的名字]
- 邮箱: [你的邮箱]
- 文档版本: 1.0
最后更新: $(date)
EOF
echo “”
echo “=== 网站测试套件配置完成 ===”
echo “可用的测试脚本:”
echo “1. ./basic-connectivity.sh # 基础连通性”
echo “2. ./functionality-test.sh # 功能测试”
echo “3. ./performance-test.sh # 性能测试”
echo “4. ./security-test.sh # 安全测试”
echo “5. ./comprehensive-test.sh # 综合测试”
echo “”
echo “测试说明: TESTING.md”
echo “测试目录: $(pwd)”
## 六、本次实践总结
### 6.1 迁移的方式总结
#### 6.1.1 迁移方法对比
| 迁移方法 | 适用场景 | 优点 | 缺点 | 推荐指数 |
|---------|---------|------|------|----------|
| **镜像迁移法** | 应用代码+环境迁移 | 简单快速,完整环境 | 不包含运行时数据 | ★★★★★ |
| **数据卷迁移法** | 持久化数据迁移 | 数据完整,独立迁移 | 需配合容器迁移 | ★★★★☆ |
| **容器导出导入法** | 完整容器迁移 | 完全一致,开箱即用 | 体积大,效率低 | ★★★☆☆ |
| **Docker Compose迁移** | 多容器应用迁移 | 配置化,易于管理 | 依赖环境一致性 | ★★★★★ |
| **注册中心迁移** | 生产环境迁移 | 标准化,可版本控制 | 需要配置仓库 | ★★★★☆ |
#### 6.1.2 迁移流程最佳实践
```mermaid
graph TD
A[迁移规划] --> B[源服务器分析]
B --> C[选择迁移方法]
C --> D{迁移方式选择}
D -->|简单应用| E[镜像迁移]
D -->|数据库应用| F[数据卷+镜像迁移]
D -->|复杂应用| G[Docker Compose迁移]
E --> H[执行迁移]
F --> H
G --> H
H --> I[目标服务器验证]
I --> J[功能测试]
J --> K[性能测试]
K --> L[安全测试]
L --> M[迁移完成]
6.1.3 迁移检查清单
# Docker容器迁移检查清单
## 迁移前准备
- [ ] 确认源服务器容器状态
- [ ] 备份重要数据
- [ ] 记录容器配置(端口、卷、环境变量)
- [ ] 检查网络配置
- [ ] 验证目标服务器环境
## 迁移执行
- [ ] 选择合适的迁移方法
- [ ] 执行数据备份
- [ ] 传输备份文件
- [ ] 在目标服务器恢复
- [ ] 验证数据完整性
## 迁移后验证
- [ ] 容器启动状态检查
- [ ] 服务连通性测试
- [ ] 功能完整性验证
- [ ] 性能基准测试
- [ ] 安全配置检查
## 监控和优化
- [ ] 设置监控告警
- [ ] 配置日志收集
- [ ] 优化资源配置
- [ ] 制定回滚方案
6.2 镜像的保存和导出区别
6.2.1 核心概念对比
# Docker save 和 export 的区别演示
# 1. docker save - 保存镜像(推荐用于迁移)
# 保存完整的镜像,包括所有层、标签、历史记录
docker save -o myimage.tar myimage:tag
# 特点:
# - 保存完整镜像
# - 包含元数据
# - 可以保存多个标签
# - 保持分层结构
# 2. docker export - 导出容器
# 导出容器的文件系统,不包含镜像历史
docker export -o mycontainer.tar mycontainer
# 特点:
# - 只导出文件系统
# - 不包含历史
# - 扁平化结构
# - 文件较小
# 3. docker commit - 创建新镜像
# 从容器创建新镜像
docker commit mycontainer newimage:tag
# 特点:
# - 创建新镜像
# - 包含容器变更
# - 保留分层结构
6.2.2 详细对比表
| 特性 | docker save | docker export | docker commit |
|---|---|---|---|
| 操作对象 | 镜像 | 容器 | 容器 |
| 输出内容 | 完整镜像(包括所有层) | 容器文件系统快照 | 新镜像 |
| 元数据 | 保留(标签、历史、配置) | 不保留 | 保留(可修改) |
| 分层结构 | 保留 | 扁平化(单层) | 保留(新增层) |
| 文件大小 | 较大(包含所有层) | 较小(仅当前状态) | 中等(基础镜像+变更) |
| 恢复方式 | docker load | docker import | 自动创建 |
| 使用场景 | 镜像备份、迁移 | 容器快照、文件传输 | 保存容器状态 |
| 版本控制 | 支持(多层) | 不支持 | 支持 |
| 迁移完整性 | 高 | 中 | 高 |
6.2.3 实际应用示例
#!/bin/bash
echo "=== 镜像保存和导出对比实验 ==="
echo ""
# 实验1: 使用docker save
echo "实验1: docker save保存镜像"
docker pull nginx:1.24-alpine
docker save -o nginx-image.tar nginx:1.24-alpine
image_size=$(du -h nginx-image.tar | cut -f1)
echo " 保存的镜像大小: $image_size"
# 检查保存的内容
echo " 镜像内容结构:"
tar -tf nginx-image.tar | head -5
echo " 总文件数: $(tar -tf nginx-image.tar | wc -l)"
echo ""
# 实验2: 使用docker export
echo "实验2: docker export导出容器"
docker run -d --name nginx-test nginx:1.24-alpine
sleep 2
docker export -o nginx-container.tar nginx-test
container_size=$(du -h nginx-container.tar | cut -f1)
echo " 导出的容器大小: $container_size"
# 检查导出的内容
echo " 容器内容结构:"
tar -tf nginx-container.tar | head -5
echo " 总文件数: $(tar -tf nginx-container.tar | wc -l)"
echo ""
# 实验3: 使用docker commit
echo "实验3: docker commit创建镜像"
# 在容器中做一些修改
docker exec nginx-test sh -c "echo 'Test File' > /usr/share/nginx/html/test.txt"
docker commit nginx-test nginx-modified:latest
docker images nginx-modified:latest --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
# 清理
docker rm -f nginx-test
rm -f nginx-image.tar nginx-container.tar
echo ""
echo "=== 实验结果总结 ==="
echo "1. docker save: 适合镜像迁移,保持完整结构"
echo "2. docker export: 适合容器快照,文件较小"
echo "3. docker commit: 适合保存容器状态"
6.2.4 选择建议
## 如何选择迁移方式?
### 场景1:开发环境迁移
**推荐**: `docker commit` + `docker save`
**理由**:
- 保存开发过程中的所有变更
- 保持开发环境的完整状态
- 便于在其他机器恢复工作环境
### 场景2:生产环境迁移
**推荐**: `docker save` + 数据卷迁移
**理由**:
- 确保镜像完整性
- 分离应用和数据,便于管理
- 支持版本回滚
### 场景3:紧急备份
**推荐**: `docker export`
**理由**:
- 快速备份当前状态
- 文件较小,传输快
- 适合临时应急
### 场景4:CI/CD流水线
**推荐**: 镜像仓库推送/拉取
**理由**:
- 标准化流程
- 版本控制
- 自动化部署
6.3 Docker迁移相关命令
6.3.1 核心命令速查表
# ======================
# 镜像操作命令
# ======================
# 查看镜像
docker images
docker image ls
docker images --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}\t{{.Size}}"
# 保存镜像到文件
docker save -o backup.tar image:tag
docker save image:tag | gzip > backup.tar.gz
# 从文件加载镜像
docker load -i backup.tar
docker load < backup.tar
# 导出容器到文件
docker export -o container.tar container_name
# 从文件导入容器
docker import container.tar new_image:tag
cat container.tar | docker import - new_image:tag
# 从容器创建镜像
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
docker commit --author "John Doe" --message "Updated config" mycontainer myimage:v2
# 删除镜像
docker rmi image:tag
docker image rm image:tag
docker image prune # 删除悬空镜像
# ======================
# 容器操作命令
# ======================
# 查看容器
docker ps
docker ps -a # 包括停止的容器
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
docker ps --filter "status=running"
docker ps -q # 只显示ID
# 停止/启动容器
docker stop container_name
docker start container_name
docker restart container_name
docker pause container_name
docker unpause container_name
# 删除容器
docker rm container_name
docker rm -f container_name # 强制删除运行中的容器
docker container prune # 删除所有停止的容器
# 进入容器
docker exec -it container_name /bin/bash
docker exec -it container_name sh
docker exec container_name command
# 查看容器日志
docker logs container_name
docker logs -f container_name # 实时日志
docker logs --tail 100 container_name
docker logs --since 1h container_name
# 查看容器信息
docker inspect container_name
docker inspect --format='{{.NetworkSettings.IPAddress}}' container_name
docker inspect --format='{{range .Mounts}}{{.Source}}:{{.Destination}} {{end}}' container_name
# ======================
# 数据卷操作命令
# ======================
# 查看数据卷
docker volume ls
docker volume inspect volume_name
# 创建数据卷
docker volume create volume_name
docker volume create --driver local --opt type=nfs --opt device=:/nfs volume_name
# 删除数据卷
docker volume rm volume_name
docker volume prune # 删除未使用的数据卷
# 备份数据卷
docker run --rm -v source_volume:/source -v $(pwd):/backup ubuntu tar czf /backup/backup.tar.gz -C /source .
# 恢复数据卷
docker run --rm -v target_volume:/target -v $(pwd):/backup ubuntu tar xzf /backup/backup.tar.gz -C /target
# ======================
# 网络操作命令
# ======================
# 查看网络
docker network ls
docker network inspect network_name
# 创建网络
docker network create network_name
docker network create --driver bridge --subnet 172.25.0.0/16 network_name
# 连接容器到网络
docker network connect network_name container_name
docker network disconnect network_name container_name
# 删除网络
docker network rm network_name
# ======================
# Docker Compose命令
# ======================
# 启动服务
docker-compose up -d
docker-compose up --build -d # 重新构建镜像
# 停止服务
docker-compose down
docker-compose down -v # 同时删除数据卷
# 查看服务状态
docker-compose ps
docker-compose logs
docker-compose logs -f service_name
# 执行命令
docker-compose exec service_name command
docker-compose exec web bash
# 扩展服务
docker-compose up -d --scale web=3
# ======================
# 系统管理命令
# ======================
# 查看系统信息
docker system df
docker system df -v # 详细信息
docker info
# 清理资源
docker system prune # 清理所有未使用的资源
docker system prune -a # 清理所有未使用的镜像
docker system prune --volumes # 清理数据卷
# 查看资源使用
docker stats
docker stats --no-stream
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
# ======================
# 迁移专用命令
# ======================
# 1. 完整迁移流程
# 备份镜像
docker save -o all-images.tar $(docker images -q)
# 备份数据卷
for volume in $(docker volume ls -q); do
docker run --rm -v $volume:/data -v $(pwd):/backup alpine tar czf /backup/$volume.tar.gz -C /data .
done
# 2. 快速迁移脚本
# 保存所有容器为镜像
for container in $(docker ps -q); do
name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
docker commit $container $name:migrated
done
# 3. 批量操作
# 停止所有容器
docker stop $(docker ps -q)
# 删除所有容器
docker rm -f $(docker ps -aq)
# 删除所有镜像
docker rmi -f $(docker images -q)
6.3.2 常用命令组合
#!/bin/bash
# Docker迁移常用命令组合
# 1. 备份所有运行中的容器
echo "1. 备份所有运行中的容器"
for container in $(docker ps -q); do
container_name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
image_name="backup/${container_name}:$(date +%Y%m%d)"
# 提交为镜像
docker commit $container $image_name
# 保存为文件
safe_name=$(echo $image_name | sed 's/[\/:]/-/g')
docker save -o "${safe_name}.tar" $image_name
echo "已备份: $container_name -> ${safe_name}.tar"
done
# 2. 恢复备份的容器
echo ""
echo "2. 恢复备份的容器"
for tar_file in backup-*.tar; do
if [ -f "$tar_file" ]; then
# 加载镜像
docker load -i $tar_file
# 获取镜像名
image_name=$(docker load -i $tar_file 2>&1 | grep "Loaded image:" | awk '{print $3}')
# 运行容器(需要根据实际情况调整参数)
container_name=$(echo $image_name | cut -d'/' -f2 | cut -d':' -f1)
docker run -d --name "restored_${container_name}" $image_name
echo "已恢复: $container_name"
fi
done
# 3. 迁移数据卷
echo ""
echo "3. 迁移数据卷"
# 备份所有数据卷
mkdir -p volume_backups
for volume in $(docker volume ls -q); do
echo "备份数据卷: $volume"
docker run --rm \
-v $volume:/source \
-v $(pwd)/volume_backups:/backup \
alpine tar czf /backup/${volume}.tar.gz -C /source .
done
# 4. 生成迁移报告
echo ""
echo "4. 生成迁移报告"
cat > migration-report.md <<EOF
# Docker迁移报告
## 迁移时间
$(date)
## 源服务器信息
- 主机名: $(hostname)
- Docker版本: $(docker --version)
- 镜像数量: $(docker images | wc -l)
- 容器数量: $(docker ps -a | wc -l)
- 数据卷数量: $(docker volume ls | wc -l)
## 迁移内容
### 备份的镜像
$(docker images --filter "reference=backup/*" --format "- {{.Repository}}:{{.Tag}} ({{.Size}})")
### 备份的数据卷
$(ls -lh volume_backups/*.tar.gz 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' || echo "无数据卷备份")
### 容器状态
\`\`\`
$(docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}")
\`\`\`
## 迁移步骤
1. 备份所有运行中的容器为镜像
2. 保存镜像为tar文件
3. 备份数据卷
4. 传输到目标服务器
5. 在目标服务器恢复
## 注意事项
- 检查容器端口映射
- 验证数据完整性
- 测试服务功能
- 监控系统资源
---
*报告生成完成*
EOF
echo "迁移报告已生成: migration-report.md"
6.3.3 故障排除命令
#!/bin/bash
# Docker迁移故障排除命令
echo "=== Docker迁移故障排除 ==="
echo ""
# 1. 检查Docker服务状态
echo "1. 检查Docker服务状态"
systemctl status docker | head -10
echo ""
# 2. 检查容器日志
echo "2. 检查容器日志(最后10行)"
for container in $(docker ps -q); do
name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
echo "容器: $name"
docker logs --tail 10 $container 2>&1 | grep -E "(ERROR|error|Error|FAIL|fail|exception)" || echo " 无错误日志"
echo ""
done
# 3. 检查端口冲突
echo "3. 检查端口冲突"
netstat -tuln | grep -E ":80|:443|:3306|:3000" | while read line; do
echo "端口占用: $line"
done
echo ""
# 4. 检查磁盘空间
echo "4. 检查磁盘空间"
df -h / /var/lib/docker 2>/dev/null
echo ""
# 5. 检查镜像完整性
echo "5. 检查镜像完整性"
for image in $(docker images -q); do
if ! docker inspect $image >/dev/null 2>&1; then
echo "损坏的镜像: $image"
fi
done
echo ""
# 6. 检查网络连接
echo "6. 检查网络连接"
for container in $(docker ps -q); do
name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
echo "容器 $name 网络测试:"
docker exec $container sh -c "ping -c 1 8.8.8.8 2>&1 | grep -E '(packet loss|unreachable)'" || echo " 网络正常"
echo ""
done
# 7. 常见问题解决
echo "7. 常见问题解决命令"
cat <<'TROUBLESHOOTING'
# 问题1: 容器启动失败
docker logs [容器名/ID] # 查看启动日志
docker inspect [容器名/ID] # 查看详细配置
# 问题2: 端口被占用
netstat -tuln | grep :端口号 # 查看端口占用
lsof -i :端口号 # 查看进程信息
# 问题3: 磁盘空间不足
docker system df # 查看Docker磁盘使用
docker system prune # 清理未使用的资源
docker image prune -a # 删除所有未使用的镜像
# 问题4: 网络连接问题
docker network ls # 查看网络列表
docker network inspect [网络名] # 查看网络详情
docker exec [容器名] ping [目标] # 测试容器网络
# 问题5: 数据卷权限问题
docker run -it --rm -v [卷名]:/data alpine ls -la /data # 检查数据卷内容
docker run -it --rm -v [卷名]:/data alpine chown -R 1000:1000 /data # 修改权限
# 问题6: 镜像拉取失败
docker pull [镜像名] # 重新拉取
docker system prune -a # 清理后重试
检查Docker配置文件: /etc/docker/daemon.json
# 问题7: 容器资源不足
docker stats # 查看资源使用
docker update --memory 512m [容器名] # 调整内存限制
docker update --cpus 1.5 [容器名] # 调整CPU限制
TROUBLESHOOTING
6.3.4 自动化迁移脚本
#!/bin/bash
# 自动化Docker迁移脚本
# 用法: ./auto-migrate.sh [源服务器] [目标服务器]
set -e
SOURCE_SERVER=${1:-"user@source-server"}
TARGET_SERVER=${2:-"user@target-server"}
BACKUP_DIR="/tmp/docker-migration-$(date +%Y%m%d-%H%M%S)"
echo "开始自动化Docker迁移"
echo "源服务器: $SOURCE_SERVER"
echo "目标服务器: $TARGET_SERVER"
echo "备份目录: $BACKUP_DIR"
echo ""
# 在源服务器执行备份
echo "=== 在源服务器备份 ==="
ssh $SOURCE_SERVER << EOF
# 创建备份目录
mkdir -p $BACKUP_DIR
echo "1. 备份Docker镜像..."
docker save -o $BACKUP_DIR/all-images.tar \$(docker images -q)
echo "2. 备份数据卷..."
mkdir -p $BACKUP_DIR/volumes
for volume in \$(docker volume ls -q); do
echo " 备份数据卷: \$volume"
docker run --rm \
-v \$volume:/source \
-v $BACKUP_DIR/volumes:/backup \
alpine tar czf /backup/\${volume}.tar.gz -C /source .
done
echo "3. 备份容器配置..."
docker ps -a --format "{{.Names}}" > $BACKUP_DIR/containers.txt
for container in \$(docker ps -a -q); do
docker inspect \$container > $BACKUP_DIR/\${container}-inspect.json
done
echo "4. 生成备份报告..."
cat > $BACKUP_DIR/backup-report.md <<REPORT
# Docker备份报告
## 备份时间
\$(date)
## 系统信息
- 主机名: \$(hostname)
- Docker版本: \$(docker --version)
## 备份内容
- 镜像: \$(docker images | wc -l) 个
- 容器: \$(docker ps -a | wc -l) 个
- 数据卷: \$(docker volume ls | wc -l) 个
## 文件列表
\$(ls -lh $BACKUP_DIR/)
REPORT
echo "备份完成!备份目录: $BACKUP_DIR"
EOF
# 传输备份文件到目标服务器
echo ""
echo "=== 传输备份文件 ==="
scp -r $SOURCE_SERVER:$BACKUP_DIR $BACKUP_DIR
echo "文件已传输到本地: $BACKUP_DIR"
# 在目标服务器恢复
echo ""
echo "=== 在目标服务器恢复 ==="
ssh $TARGET_SERVER << EOF
echo "1. 检查目标服务器环境..."
if ! command -v docker &> /dev/null; then
echo "Docker未安装,开始安装..."
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
fi
echo "2. 恢复Docker镜像..."
docker load -i $BACKUP_DIR/all-images.tar
echo "3. 恢复数据卷..."
for volume_file in $BACKUP_DIR/volumes/*.tar.gz; do
if [ -f "\$volume_file" ]; then
volume_name=\$(basename \$volume_file .tar.gz)
echo " 恢复数据卷: \$volume_name"
# 创建数据卷
docker volume create \$volume_name
# 恢复数据
docker run --rm \
-v \$volume_name:/target \
-v $BACKUP_DIR/volumes:/backup \
alpine tar xzf /backup/\${volume_name}.tar.gz -C /target
fi
done
echo "4. 生成恢复报告..."
cat > $BACKUP_DIR/restore-report.md <<REPORT
# Docker恢复报告
## 恢复时间
\$(date)
## 目标服务器信息
- 主机名: \$(hostname)
- Docker版本: \$(docker --version)
## 恢复内容
- 已加载镜像: \$(docker images | wc -l) 个
- 已恢复数据卷: \$(docker volume ls | wc -l) 个
## 下一步
需要根据容器配置手动启动容器:
\$(cat $BACKUP_DIR/containers.txt | while read container; do
echo "- \$container"
done)
REPORT
echo "恢复完成!请查看报告: $BACKUP_DIR/restore-report.md"
EOF
echo ""
echo "=== 自动化迁移完成 ==="
echo "迁移总结:"
echo "1. 备份文件保存在: $BACKUP_DIR"
echo "2. 需要在目标服务器手动启动容器"
echo "3. 建议进行功能测试验证迁移效果"
echo ""
echo "完成!"
这个完整的Docker迁移实践指南涵盖了从基础概念到高级实践的各个方面,包括详细的步骤说明、代码示例、故障排除方法和最佳实践建议。您可以根据实际需求调整和扩展这些内容。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)