skills/skills.netease.im/youdata-update

youdata-update

SKILL.md

YouData服务更新Skill

本skill专门用于管理YouData服务的镜像更新和配置维护,支持离线镜像更新、配置替换和服务重启。

功能特性

  1. 镜像管理

    • 加载离线镜像文件
    • 备份旧镜像(添加_bak标签)
    • 更新服务使用新镜像
  2. 配置管理

    • 解压并替换配置文件
    • 备份原始配置
    • 验证配置完整性
  3. 服务管理

    • 强制重启服务
    • 服务状态验证
    • 更新回滚支持

使用场景

  • 定期更新YouData服务镜像
  • 紧急修复和安全更新
  • 配置变更部署
  • 服务维护和重启

⚠️ 重要注意事项

镜像Tag冲突处理

当离线镜像包中的镜像tag与现有镜像tag相同时(例如都是 localhost:5000/yddocker/web:lts-9.16):

  1. 脚本会先备份老镜像(添加 _bak 标签)
  2. 然后加载新镜像(会覆盖原tag)
  3. 只生成一个备份标签_bak,不带时间戳)

备份策略

  • 镜像备份: 原镜像名:原tag_bak(用于快速回滚)
  • 配置备份: 直接重命名 /youdata/config/youdata/config_bak
  • 配置源: 自动查找并复制 config-* 目录到 /youdata/config

执行顺序

  1. ✅ 环境检查
  2. ✅ 创建备份目录
  3. ✅ 备份当前配置(重命名为config_bak)
  4. ✅ 解压并查找文件
  5. ✅ 加载新镜像
  6. ✅ 复制配置目录(config-* → /youdata/config)
  7. ✅ 备份旧镜像(添加_bak标签)
  8. ✅ 更新服务
  9. ✅ 验证服务状态
  10. ✅ 解压镜像文件
  11. 备份老镜像(关键步骤!)
  12. ✅ 加载新镜像(可能覆盖原tag)
  13. ✅ 替换配置文件
  14. ✅ 强制重启服务
  15. ✅ 验证服务状态
  16. ✅ 清理临时文件

准备工作

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-*.tar
  • docker-*.tar

支持的配置目录格式:

  • config-* (如: config-260325, config-20240330)
  • conf-*
  • configuration-*
  • config (普通config目录)

修复内容:

  1. ✅ 只生成一个备份镜像tag:_bak(不带时间戳)
  2. ✅ 直接mv备份config目录:/youdata/config/youdata/config_bak
  3. ✅ 自动查找并复制配置目录
  4. ✅ 修复了镜像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")

# 检查服务状态
Installs
3
First Seen
Apr 17, 2026