youdata-update
SKILL.md
YouData服务更新Skill
本skill专门用于管理YouData服务的镜像更新和配置维护,支持离线镜像更新、配置替换和服务重启。
功能特性
-
镜像管理
- 加载离线镜像文件
- 备份旧镜像(添加_bak标签)
- 更新服务使用新镜像
-
配置管理
- 解压并替换配置文件
- 备份原始配置
- 验证配置完整性
-
服务管理
- 强制重启服务
- 服务状态验证
- 更新回滚支持
使用场景
- 定期更新YouData服务镜像
- 紧急修复和安全更新
- 配置变更部署
- 服务维护和重启
⚠️ 重要注意事项
镜像Tag冲突处理
当离线镜像包中的镜像tag与现有镜像tag相同时(例如都是 localhost:5000/yddocker/web:lts-9.16):
- 脚本会先备份老镜像(添加
_bak标签) - 然后加载新镜像(会覆盖原tag)
- 只生成一个备份标签(
_bak,不带时间戳)
备份策略
- 镜像备份:
原镜像名:原tag_bak(用于快速回滚) - 配置备份: 直接重命名
/youdata/config→/youdata/config_bak - 配置源: 自动查找并复制
config-*目录到/youdata/config
执行顺序
- ✅ 环境检查
- ✅ 创建备份目录
- ✅ 备份当前配置(重命名为config_bak)
- ✅ 解压并查找文件
- ✅ 加载新镜像
- ✅ 复制配置目录(config-* → /youdata/config)
- ✅ 备份旧镜像(添加_bak标签)
- ✅ 更新服务
- ✅ 验证服务状态
- ✅ 解压镜像文件
- ✅ 备份老镜像(关键步骤!)
- ✅ 加载新镜像(可能覆盖原tag)
- ✅ 替换配置文件
- ✅ 强制重启服务
- ✅ 验证服务状态
- ✅ 清理临时文件
准备工作
1. 文件结构要求
离线镜像包应包含:
镜像文件.tar.gz
├── 镜像文件(如:youdata-web:latest)
└── config/目录(配置文件)
2. 环境要求
- Docker Swarm集群已就绪
- 当前节点为Swarm管理节点
- 有足够的磁盘空间存储备份
- 对/youdata目录有写入权限
核心脚本
主要更新脚本:update-youdata.sh
这是通用版本,支持多种镜像包结构:
支持的镜像文件格式:
web-*.tar(如:web-lts-9.16-cfa3d8.tar)*.tar(任何.tar文件)image-*.tardocker-*.tar
支持的配置目录格式:
config-*(如:config-260325,config-20240330)conf-*configuration-*config(普通config目录)
修复内容:
- ✅ 只生成一个备份镜像tag:
_bak(不带时间戳) - ✅ 直接mv备份config目录:
/youdata/config→/youdata/config_bak - ✅ 自动查找并复制配置目录
- ✅ 修复了镜像tag格式问题
#!/bin/bash
# YouData服务更新脚本 - 通用版
# 处理通用镜像包结构:
# 1. 查找镜像文件(web-*.tar, *.tar, image-*.tar等)
# 2. 查找配置目录(config-*, conf-*, configuration-*等)
# 用法: ./update-youdata.sh <镜像文件路径>
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 服务定义
SERVICES=("youdata_web" "youdata_inner-web")
CONFIG_DIR="/youdata/config"
BACKUP_DIR="/youdata/backup"
# 显示用法
show_usage() {
echo -e "${YELLOW}用法:${NC}"
echo " $0 <镜像文件路径>"
echo ""
echo -e "${YELLOW}示例:${NC}"
echo " $0 /root/web-9.16-260325-1747-4ad267-e041af-cfa3d8.tar.gz"
echo ""
exit 1
}
# 检查参数
check_args() {
if [ $# -ne 1 ]; then
show_usage
fi
IMAGE_FILE=$1
if [ ! -f "$IMAGE_FILE" ]; then
echo -e "${RED}错误: 镜像文件不存在: $IMAGE_FILE${NC}"
exit 1
fi
echo -e "${BLUE}镜像文件: $IMAGE_FILE${NC}"
echo "文件大小: $(du -h "$IMAGE_FILE" | cut -f1)"
}
# 检查环境
check_environment() {
echo -e "${BLUE}=== 环境检查 ===${NC}"
# 检查Docker
if ! command -v docker &> /dev/null; then
echo -e "${RED}错误: Docker未安装${NC}"
exit 1
fi
if ! docker info &> /dev/null; then
echo -e "${RED}错误: Docker守护进程未运行${NC}"
exit 1
fi
# 检查Swarm模式
if ! docker info --format '{{.Swarm.LocalNodeState}}' | grep -q "active"; then
echo -e "${RED}错误: 当前节点不在Swarm集群中${NC}"
exit 1
fi
# 检查服务是否存在
for service in "${SERVICES[@]}"; do
if ! docker service ls --format "{{.Name}}" | grep -q "^$service$"; then
echo -e "${RED}错误: 服务 '$service' 不存在${NC}"
exit 1
fi
done
echo -e "${GREEN}✅ 环境检查通过${NC}"
}
# 创建备份目录
create_backup_dir() {
echo -e "${BLUE}=== 创建备份目录 ===${NC}"
mkdir -p "$BACKUP_DIR"
echo "备份目录: $BACKUP_DIR"
echo -e "${GREEN}✅ 备份目录创建完成${NC}"
}
# 备份当前配置(直接重命名为config_bak)
backup_config() {
echo -e "${BLUE}=== 备份当前配置 ===${NC}"
if [ -d "$CONFIG_DIR" ]; then
echo "配置目录: $CONFIG_DIR"
# 直接重命名为config_bak
CONFIG_BAK="${CONFIG_DIR}_bak"
echo "重命名为: $CONFIG_BAK"
# 如果已存在config_bak,先删除
if [ -d "$CONFIG_BAK" ]; then
echo "删除旧的备份目录: $CONFIG_BAK"
rm -rf "$CONFIG_BAK"
fi
mv "$CONFIG_DIR" "$CONFIG_BAK"
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ 配置备份完成${NC}"
echo "备份位置: $CONFIG_BAK"
else
echo -e "${RED}❌ 配置备份失败${NC}"
exit 1
fi
else
echo -e "${YELLOW}警告: 配置目录不存在: $CONFIG_DIR${NC}"
echo "将创建新的配置目录"
fi
}
# 解压镜像包并处理文件
extract_and_process() {
echo -e "${BLUE}=== 解压并处理文件 ===${NC}"
# 创建临时目录
TEMP_DIR="/tmp/youdata-extract-$(date +%s)"
mkdir -p "$TEMP_DIR"
echo "解压到临时目录: $TEMP_DIR"
# 解压整个tar.gz包
tar xzf "$IMAGE_FILE" -C "$TEMP_DIR"
if [ $? -ne 0 ]; then
echo -e "${RED}❌ 解压失败${NC}"
rm -rf "$TEMP_DIR"
exit 1
fi
echo -e "${GREEN}✅ 解压完成${NC}"
# 显示解压内容
echo "解压内容:"
find "$TEMP_DIR" -maxdepth 2 -type f -o -type d | sort
# 查找镜像文件(web-lts-9.16-cfa3d8.tar)
IMAGE_TAR=$(find "$TEMP_DIR" -name "web-*.tar" -o -name "*.tar" | head -1)
if [ -z "$IMAGE_TAR" ]; then
echo -e "${RED}错误: 未找到镜像文件(web-*.tar)${NC}"
rm -rf "$TEMP_DIR"
exit 1
fi
echo "找到镜像文件: $(basename "$IMAGE_TAR")"
# 查找config目录(config-260325)
CONFIG_SOURCE=$(find "$TEMP_DIR" -type d -name "config-*" | head -1)
if [ -z "$CONFIG_SOURCE" ]; then
echo -e "${YELLOW}警告: 未找到config-*目录${NC}"
CONFIG_SOURCE=""
else
echo "找到配置目录: $(basename "$CONFIG_SOURCE")"
fi
# 加载镜像
load_image "$IMAGE_TAR"
# 处理配置
if [ -n "$CONFIG_SOURCE" ]; then
process_config "$CONFIG_SOURCE"
fi
# 清理临时目录
echo "清理临时目录..."
rm -rf "$TEMP_DIR"
echo -e "${GREEN}✅ 临时目录已清理${NC}"
}
# 加载镜像
load_image() {
local image_tar=$1
echo -e "${BLUE}=== 加载Docker镜像 ===${NC}"
echo "加载镜像: $(basename "$image_tar")"
# 加载镜像
LOAD_OUTPUT=$(docker load -i "$image_tar" 2>&1)
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ 镜像加载成功${NC}"
# 获取加载的镜像名称
NEW_IMAGE=$(echo "$LOAD_OUTPUT" | grep "Loaded image:" | awk '{print $3}')
if [ -n "$NEW_IMAGE" ]; then
echo "镜像名称: $NEW_IMAGE"
else
# 尝试从文件名推断
IMAGE_BASENAME=$(basename "$image_tar" .tar)
NEW_IMAGE="localhost:5000/yddocker/web:$IMAGE_BASENAME"
echo -e "${YELLOW}警告: 无法确定镜像名称,使用推断名称: $NEW_IMAGE${NC}"
fi
else
echo -e "${RED}❌ 镜像加载失败${NC}"
echo "错误信息: $LOAD_OUTPUT"
exit 1
fi
}
# 处理配置目录
process_config() {
local config_source=$1
echo -e "${BLUE}=== 处理配置目录 ===${NC}"
echo "源配置目录: $config_source"
echo "目标配置目录: $CONFIG_DIR"
# 创建目标目录的父目录
mkdir -p "$(dirname "$CONFIG_DIR")"
# 复制config-260325目录到/youdata/config
echo "复制配置目录..."
cp -r "$config_source" "$CONFIG_DIR"
if [ $? -eq 0 ] && [ -d "$CONFIG_DIR" ]; then
echo -e "${GREEN}✅ 配置复制完成${NC}"
# 设置权限
chmod -R 755 "$CONFIG_DIR" 2>/dev/null
echo "配置权限已设置"
# 统计文件数量
CONFIG_COUNT=$(find "$CONFIG_DIR" -type f 2>/dev/null | wc -l)
echo "配置文件数量: $CONFIG_COUNT"
else
echo -e "${RED}❌ 配置复制失败${NC}"
exit 1
fi
}
# 备份旧镜像(只生成_bak标签,修复tag格式)
backup_old_images() {
echo -e "${BLUE}=== 备份旧镜像 ===${NC}"
for service in "${SERVICES[@]}"; do
echo "处理服务: $service"
# 获取当前服务使用的镜像
CURRENT_IMAGE=$(docker service inspect "$service" --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}' 2>/dev/null || echo "")
if [ -z "$CURRENT_IMAGE" ]; then
echo -e " ${YELLOW}警告: 无法获取服务镜像${NC}"
continue
fi
echo " 当前镜像: $CURRENT_IMAGE"
# 正确解析镜像名称和tag(修复tag格式问题)
# 使用awk计算冒号数量,正确处理 localhost:5000/yddocker/web:lts-9.16 格式
LAST_COLON=$(echo "$CURRENT_IMAGE" | awk -F: '{print NF-1}')
if [ "$LAST_COLON" -eq 1 ]; then
# 格式: name:tag
IMAGE_NAME="${CURRENT_IMAGE%:*}"
IMAGE_TAG="${CURRENT_IMAGE#*:}"
elif [ "$LAST_COLON" -eq 2 ]; then
# 格式: registry:port/name:tag
IMAGE_TAG="${CURRENT_IMAGE##*:}" # 获取最后一个冒号之后的部分
IMAGE_NAME="${CURRENT_IMAGE%:*}" # 删除最后一个冒号及之后的部分
else
echo -e " ${RED}错误: 无法解析镜像格式${NC}"
continue
fi
# 生成备份镜像tag:_bak
BACKUP_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}_bak"
echo " 备份镜像: $BACKUP_IMAGE"
# 如果已存在_bak镜像,先删除
if docker image inspect "$BACKUP_IMAGE" &>/dev/null; then
echo " 删除旧的备份镜像: $BACKUP_IMAGE"
docker rmi "$BACKUP_IMAGE" 2>/dev/null || true
fi
# 创建备份
docker tag "$CURRENT_IMAGE" "$BACKUP_IMAGE" 2>&1
if [ $? -eq 0 ]; then
echo -e " ${GREEN}✅ 镜像备份完成${NC}"
# 保存备份信息
echo "$(date): $service - $CURRENT_IMAGE -> $BACKUP_IMAGE" >> "$BACKUP_DIR/image-backup.log"
else
echo -e " ${YELLOW}警告: 镜像备份失败${NC}"
fi
done
}
# 更新服务
update_services() {
echo -e "${BLUE}=== 更新服务 ===${NC}"
for service in "${SERVICES[@]}"; do
echo "更新服务: $service"
if [ -z "$NEW_IMAGE" ]; then
echo -e "${RED}错误: 新镜像名称未确定${NC}"
exit 1
fi
echo " 使用新镜像: $NEW_IMAGE"
docker service update \
--image "$NEW_IMAGE" \
--force \
--update-parallelism 1 \
--update-delay 10s \
"$service" 2>&1
if [ $? -eq 0 ]; then
echo -e " ${GREEN}✅ 服务更新完成${NC}"
else
echo -e " ${RED}❌ 服务更新失败${NC}"
exit 1
fi
done
}
# 验证服务状态
verify_services() {
echo -e "${BLUE}=== 验证服务状态 ===${NC}"
echo "等待服务稳定..."
sleep 10
for service in "${SERVICES[@]}"; do
echo -n "检查服务: $service ... "
REPLICAS=$(docker service inspect "$service" --format '{{.Spec.Mode.Replicated.Replicas}}' 2>/dev/null || echo "0")
RUNNING=$(docker service ps "$service" --filter "desired-state=running" --format "{{.CurrentState}}" 2>/dev/null | grep -c "Running" || echo "0")
if [ "$RUNNING" -eq "$REPLICAS" ] && [ "$REPLICAS" -gt 0 ]; then
echo -e "${GREEN}正常${NC}"
else
echo -e "${YELLOW}异常${NC}"
fi
done
}
# 显示摘要
show_summary() {
echo -e "\n${BLUE}════════════════════════════════════════${NC}"
echo -e "${BLUE} 更新摘要 ${NC}"
echo -e "${BLUE}════════════════════════════════════════${NC}"
echo ""
echo -e "${GREEN}✅ 更新完成${NC}"
echo ""
echo -e "${BLUE}基本信息:${NC}"
echo " 时间: $(date)"
echo " 镜像文件: $(basename "$IMAGE_FILE")"
echo " 新镜像: $NEW_IMAGE"
echo ""
echo -e "${BLUE}更新服务:${NC}"
for service in "${SERVICES[@]}"; do
echo " • $service"
done
echo ""
echo -e "${BLUE}备份信息:${NC}"
if [ -d "${CONFIG_DIR}_bak" ]; then
echo " 配置备份: ${CONFIG_DIR}_bak"
fi
echo " 镜像备份:"
for service in "${SERVICES[@]}"; do
CURRENT_IMAGE=$(docker service inspect "$service" --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}' 2>/dev/null || echo "")
if [ -n "$CURRENT_IMAGE" ]; then
# 使用相同的解析逻辑
LAST_COLON=$(echo "$CURRENT_IMAGE" | awk -F: '{print NF-1}')
if [ "$LAST_COLON" -eq 1 ]; then
IMAGE_NAME="${CURRENT_IMAGE%:*}"
IMAGE_TAG="${CURRENT_IMAGE#*:}"
elif [ "$LAST_COLON" -eq 2 ]; then
IMAGE_TAG="${CURRENT_IMAGE##*:}"
IMAGE_NAME="${CURRENT_IMAGE%:*}"
fi
BACKUP_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}_bak"
if docker image inspect "$BACKUP_IMAGE" &>/dev/null; then
echo " $BACKUP_IMAGE"
fi
fi
done
echo -e "\n${BLUE}════════════════════════════════════════${NC}"
}
# 主函数
main() {
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo -e "${GREEN} YouData服务更新工具 ${NC}"
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo ""
# 执行步骤
check_args "$@"
check_environment
create_backup_dir
backup_config # 直接重命名config目录为config_bak
extract_and_process # 解压并处理文件(镜像+配置)
backup_old_images # 备份旧镜像(修复tag格式)
update_services # 更新服务
verify_services # 验证服务状态
show_summary # 显示摘要
echo ""
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo -e "${GREEN} 所有操作已完成 ${NC}"
echo -e "${GREEN}════════════════════════════════════════${NC}"
}
# 执行主函数
main "$@"
辅助脚本
回滚脚本:rollback-youdata.sh
#!/bin/bash
# YouData服务回滚脚本
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
SERVICES=("youdata_web" "youdata_inner-web")
BACKUP_DIR="/youdata/backup"
# 显示备份的镜像
list_backups() {
echo -e "${BLUE}可用的镜像备份:${NC}"
if [ -f "$BACKUP_DIR/image-backup.log" ]; then
tail -10 "$BACKUP_DIR/image-backup.log"
else
echo -e "${YELLOW}未找到备份记录${NC}"
fi
echo -e "\n${BLUE}可用的配置备份:${NC}"
if ls "$BACKUP_DIR"/config-backup-*.tar.gz 2>/dev/null; then
ls -lh "$BACKUP_DIR"/config-backup-*.tar.gz | tail -5
else
echo -e "${YELLOW}未找到配置备份${NC}"
fi
}
# 回滚到指定备份
rollback_to_backup() {
local BACKUP_TYPE=$1
local BACKUP_FILE=$2
case $BACKUP_TYPE in
"config")
echo -e "${BLUE}回滚配置...${NC}"
# 停止服务
for service in "${SERVICES[@]}"; do
echo "停止服务: $service"
docker service scale $service=0
done
# 恢复配置
echo "恢复配置: $BACKUP_FILE"
tar xzf "$BACKUP_FILE" -C /
# 启动服务
for service in "${SERVICES[@]}"; do
echo "启动服务: $service"
docker service scale $service=1
done
echo -e "${GREEN}✅ 配置回滚完成${NC}"
;;
*)
echo -e "${RED}错误: 不支持的备份类型${NC}"
;;
esac
}
# 主函数
main() {
echo -e "${GREEN}YouData服务回滚工具${NC}"
case ${1:-"list"} in
"list")
list_backups
;;
"config")
if [ $# -ne 2 ]; then
echo -e "${RED}用法: $0 config <备份文件>${NC}"
exit 1
fi
rollback_to_backup "config" "$2"
;;
*)
echo -e "${YELLOW}用法:${NC}"
echo " $0 list # 列出备份"
echo " $0 config <备份文件> # 回滚配置"
;;
esac
}
main "$@"
状态检查脚本:check-youdata.sh
#!/bin/bash
# YouData服务状态检查脚本
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
SERVICES=("youdata_web" "youdata_inner-web")
# 检查服务状态