321 lines
9.5 KiB
Bash
321 lines
9.5 KiB
Bash
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
# ============================================================================
|
||
# DevOps AI Stack - 一键启动脚本
|
||
# 功能:启动完整的开发栈,包含前置检查、健康验证和回滚机制
|
||
# ============================================================================
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||
LOG_FILE="${PROJECT_ROOT}/logs/init-dev-$(date +%Y%m%d_%H%M%S).log"
|
||
STARTED_SERVICES=()
|
||
|
||
# 创建日志目录
|
||
mkdir -p "${PROJECT_ROOT}/logs"
|
||
|
||
# ============================================================================
|
||
# 日志和颜色输出
|
||
# ============================================================================
|
||
|
||
log() {
|
||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
|
||
}
|
||
|
||
log_info() {
|
||
echo -e "\033[36ℹ️ INFO\033[0m: $*" | tee -a "$LOG_FILE"
|
||
}
|
||
|
||
log_success() {
|
||
echo -e "\033[32✅ SUCCESS\033[0m: $*" | tee -a "$LOG_FILE"
|
||
}
|
||
|
||
log_warning() {
|
||
echo -e "\033[33⚠️ WARNING\033[0m: $*" | tee -a "$LOG_FILE"
|
||
}
|
||
|
||
log_error() {
|
||
echo -e "\033[31❌ ERROR\033[0m: $*" | tee -a "$LOG_FILE"
|
||
}
|
||
|
||
# ============================================================================
|
||
# 前置条件检查
|
||
# ============================================================================
|
||
|
||
check_requirements() {
|
||
log_info "检查系统要求..."
|
||
|
||
local missing_tools=()
|
||
|
||
# 检查必要工具
|
||
for tool in docker docker-compose mkcert curl; do
|
||
if ! command -v "$tool" &>/dev/null; then
|
||
missing_tools+=("$tool")
|
||
fi
|
||
done
|
||
|
||
if [[ ${#missing_tools[@]} -gt 0 ]]; then
|
||
log_error "缺少必要工具: ${missing_tools[*]}"
|
||
log_info "请安装: brew install ${missing_tools[*]}"
|
||
return 1
|
||
fi
|
||
|
||
# 检查Docker daemon
|
||
if ! docker ps &>/dev/null; then
|
||
log_error "Docker daemon 未运行"
|
||
log_info "请启动 Docker Desktop"
|
||
return 1
|
||
fi
|
||
|
||
# 检查磁盘空间 (至少需要10GB)
|
||
local free_space=$(df -BG "$PROJECT_ROOT" | awk 'NR==2 {print $4}' | sed 's/G//')
|
||
if [[ $free_space -lt 10 ]]; then
|
||
log_warning "可用磁盘空间: ${free_space}GB (建议 >=10GB)"
|
||
fi
|
||
|
||
log_success "系统要求检查通过"
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# 环境变量验证
|
||
# ============================================================================
|
||
|
||
verify_environment() {
|
||
log_info "验证环境变量配置..."
|
||
|
||
# 检查环境文件
|
||
local env_files=(
|
||
"${PROJECT_ROOT}/secrets/.env.core"
|
||
"${PROJECT_ROOT}/secrets/.env.ai"
|
||
)
|
||
|
||
for env_file in "${env_files[@]}"; do
|
||
if [[ ! -f "$env_file" ]]; then
|
||
log_warning "缺少配置文件: $env_file"
|
||
local example_file="${env_file}.example"
|
||
if [[ -f "$example_file" ]]; then
|
||
log_info "使用模板文件: $example_file"
|
||
cp "$example_file" "$env_file"
|
||
log_warning "请编辑 $env_file 配置必要的环境变量"
|
||
fi
|
||
fi
|
||
done
|
||
|
||
# 验证关键变量 (可选,因为大部分变量有默认值)
|
||
if [[ -f "${PROJECT_ROOT}/secrets/.env.core" ]]; then
|
||
# shellcheck source=/dev/null
|
||
source "${PROJECT_ROOT}/secrets/.env.core" || true
|
||
fi
|
||
|
||
log_success "环境变量配置检查完成"
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# 证书初始化
|
||
# ============================================================================
|
||
|
||
setup_certificates() {
|
||
log_info "设置 TLS 证书..."
|
||
|
||
local cert_dir="${PROJECT_ROOT}/infra/traefik/certs"
|
||
mkdir -p "$cert_dir"
|
||
|
||
# 检查证书是否存在
|
||
if [[ -f "${cert_dir}/cert.pem" ]] && [[ -f "${cert_dir}/key.pem" ]]; then
|
||
log_success "TLS 证书已存在"
|
||
return 0
|
||
fi
|
||
|
||
# 生成本地开发证书
|
||
if bash "${SCRIPT_DIR}/mkcert-init.sh" >> "$LOG_FILE" 2>&1; then
|
||
log_success "TLS 证书生成成功"
|
||
return 0
|
||
else
|
||
log_error "TLS 证书生成失败"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# ============================================================================
|
||
# 启动服务
|
||
# ============================================================================
|
||
|
||
start_service() {
|
||
local service_name=$1
|
||
local compose_file="${PROJECT_ROOT}/infra/compose/${service_name}.yml"
|
||
|
||
if [[ ! -f "$compose_file" ]]; then
|
||
log_error "找不到 Compose 文件: $compose_file"
|
||
return 1
|
||
fi
|
||
|
||
log_info "启动服务: $service_name"
|
||
|
||
if docker compose -f "$compose_file" up -d >> "$LOG_FILE" 2>&1; then
|
||
STARTED_SERVICES+=("$service_name")
|
||
log_success "$service_name 启动成功"
|
||
return 0
|
||
else
|
||
log_error "$service_name 启动失败"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# ============================================================================
|
||
# 服务健康检查
|
||
# ============================================================================
|
||
|
||
wait_for_service() {
|
||
local service_name=$1
|
||
local endpoint=$2
|
||
local max_retries=${3:-30}
|
||
local retry_interval=${4:-2}
|
||
|
||
log_info "等待 $service_name 就绪 (最多 $((max_retries * retry_interval)) 秒)..."
|
||
|
||
local retries=0
|
||
while [[ $retries -lt $max_retries ]]; do
|
||
if curl -sk "$endpoint" &>/dev/null; then
|
||
log_success "$service_name 已就绪"
|
||
return 0
|
||
fi
|
||
|
||
retries=$((retries + 1))
|
||
if [[ $((retries % 5)) -eq 0 ]]; then
|
||
log_info "仍在等待... ($retries/$max_retries)"
|
||
fi
|
||
sleep "$retry_interval"
|
||
done
|
||
|
||
log_warning "$service_name 启动缓慢,但继续启动其他服务"
|
||
return 1
|
||
}
|
||
|
||
verify_critical_services() {
|
||
log_info "验证关键服务状态..."
|
||
|
||
local services=(
|
||
"Traefik:https://traefik.dev.lan/ping"
|
||
"Gitea:https://git.dev.lan/api/healthz"
|
||
"Grafana:https://grafana.dev.lan/api/health"
|
||
)
|
||
|
||
local failed_services=()
|
||
|
||
for service in "${services[@]}"; do
|
||
IFS=':' read -r name endpoint <<< "$service"
|
||
if ! wait_for_service "$name" "$endpoint" 20 3; then
|
||
failed_services+=("$name")
|
||
fi
|
||
done
|
||
|
||
if [[ ${#failed_services[@]} -gt 0 ]]; then
|
||
log_warning "部分服务未就绪: ${failed_services[*]}"
|
||
log_info "建议等待 1-2 分钟后手动检查服务状态"
|
||
return 1
|
||
fi
|
||
|
||
log_success "所有关键服务已就绪"
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# 错误处理和回滚
|
||
# ============================================================================
|
||
|
||
cleanup_on_error() {
|
||
local exit_code=$?
|
||
|
||
if [[ $exit_code -ne 0 ]]; then
|
||
log_error "启动过程失败 (退出码: $exit_code)"
|
||
log_info "已启动的服务: ${STARTED_SERVICES[*]}"
|
||
log_info "是否需要回滚? 运行: ./infra/scripts/cleanup-dev.sh"
|
||
log_info "完整日志: $LOG_FILE"
|
||
fi
|
||
|
||
return "$exit_code"
|
||
}
|
||
|
||
trap cleanup_on_error EXIT
|
||
|
||
# ============================================================================
|
||
# 主启动流程
|
||
# ============================================================================
|
||
|
||
main() {
|
||
log "=========================================="
|
||
log "🚀 DevOps AI Stack 启动开始"
|
||
log "=========================================="
|
||
|
||
# 1. 前置检查
|
||
if ! check_requirements; then
|
||
return 1
|
||
fi
|
||
|
||
# 2. 验证环境
|
||
if ! verify_environment; then
|
||
return 1
|
||
fi
|
||
|
||
# 3. 证书设置
|
||
if ! setup_certificates; then
|
||
return 1
|
||
fi
|
||
|
||
# 4. 启动服务 (按依赖顺序)
|
||
local startup_order=(
|
||
"core" # Traefik + Gitea + Registry
|
||
"dev-cache" # 缓存服务
|
||
"observability" # 监控栈
|
||
"ai" # AI 网关和模型
|
||
"ai-visual" # AI 可视化
|
||
"taskflow" # 任务编排
|
||
)
|
||
|
||
for service in "${startup_order[@]}"; do
|
||
if ! start_service "$service"; then
|
||
log_warning "部分服务启动失败,继续尝试启动其他服务..."
|
||
fi
|
||
sleep 5 # 服务间隔
|
||
done
|
||
|
||
# 5. 等待所有服务初始化
|
||
log_info "等待服务初始化 (30 秒)..."
|
||
sleep 30
|
||
|
||
# 6. 验证关键服务
|
||
if ! verify_critical_services; then
|
||
log_warning "部分服务未完全就绪,请查看日志: $LOG_FILE"
|
||
fi
|
||
|
||
# 7. 打印访问信息
|
||
log ""
|
||
log "=========================================="
|
||
log_success "✅ DevOps AI Stack 启动完成!"
|
||
log "=========================================="
|
||
log ""
|
||
log "📍 服务访问地址 (使用 https):"
|
||
log " • Traefik Dashboard: https://traefik.dev.lan/"
|
||
log " • Gitea: https://git.dev.lan/"
|
||
log " • Grafana: https://grafana.dev.lan/"
|
||
log " • LiteLLM API: https://ai.dev.lan/v1"
|
||
log " • Langfuse: https://langfuse.dev.lan/"
|
||
log " • MLflow: https://mlflow.dev.lan/"
|
||
log " • Prefect: https://prefect.dev.lan/"
|
||
log " • MinIO: https://minio.dev.lan/"
|
||
log ""
|
||
log "📋 日志位置: $LOG_FILE"
|
||
log ""
|
||
log "💡 后续步骤:"
|
||
log " 1. 查看 IMPLEMENTATION_GUIDE.md 了解详细配置"
|
||
log " 2. 配置 Gitea Runner Token: ./infra/scripts/configure-gitea.sh"
|
||
log " 3. 导入 Grafana 仪表板: ./infra/scripts/setup-grafana.sh"
|
||
log ""
|
||
log "🆘 遇到问题? 查看日志: tail -f logs/init-dev-*.log"
|
||
log "=========================================="
|
||
}
|
||
|
||
main "$@"
|