Files
aiops/run.sh
T

917 lines
30 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
#===========================================================================
# AIOps 智能运维平台 - 统一启动脚本
# 支持三种部署方式 + 本地开发模式:
#
# 方式 1 — Docker Compose:
# ./run.sh docker up 构建+启动 (MySQL+Redis+Backend+Nginx)
#
# 方式 2 — Kubernetes (embed 单容器):
# ./run.sh k8s deploy 构建镜像+全量部署 (embed 前端嵌入后端)
# ./run.sh k8s upgrade 重新构建+滚动更新
# ./run.sh k8s status 查看资源状态
# ./run.sh k8s logs/exec 日志/进入容器
#
# 方式 3 — 独立二进制:
# ./run.sh build 编译独立二进制 (embed 前端)
# ./run.sh binary 编译并启动
#
# 本地开发:
# ./run.sh dev 前后端热重载
# ./run.sh help 查看完整帮助
#===========================================================================
set -e
# ============================================================
# 配置区
# ============================================================
PROJECT_NAME="AIOps 智能运维平台"
BACKEND_PORT=8080
FRONTEND_PORT=5173
BACKEND_DIR="backend"
FRONTEND_DIR="frontend"
BACKEND_BIN="aiops-server"
STANDALONE_BIN="aiops"
# 日志配置
MAX_LOG_FILES=3
MAX_LOG_SIZE=$((50 * 1024 * 1024)) # 50MB
# ============================================================
# 通用函数
# ============================================================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
success() { echo -e "${GREEN}[OK]${NC} $1"; }
warning() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERR]${NC} $1"; }
banner() { echo -e "${CYAN}$1${NC}"; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# PID 文件
BACKEND_PID_FILE="$SCRIPT_DIR/$BACKEND_DIR/.backend.pid"
FRONTEND_PID_FILE="$SCRIPT_DIR/$FRONTEND_DIR/.frontend.pid"
# 检测操作系统
detect_os() {
case "$(uname -s)" in
Darwin) echo "macos" ;;
Linux) echo "linux" ;;
*) echo "unknown" ;;
esac
}
OS=$(detect_os)
# 检查端口是否被占用
check_port() {
if [ "$OS" = "macos" ]; then
lsof -ti:$1 2>/dev/null | head -1
else
ss -tlnp 2>/dev/null | grep -q ":$1 " && echo "in-use" || echo ""
fi
}
# 释放端口
kill_port() {
local port=$1
if [ "$OS" = "macos" ]; then
local pids=$(lsof -ti:$port 2>/dev/null || echo "")
[ -n "$pids" ] && echo "$pids" | xargs kill -9 2>/dev/null || true
else
fuser -k ${port}/tcp 2>/dev/null || true
fi
sleep 0.5
}
# 日志轮转
rotate_log() {
local log_file=$1
[ ! -f "$log_file" ] && return 0
local file_size
if [ "$OS" = "macos" ]; then
file_size=$(stat -f%z "$log_file" 2>/dev/null || echo "0")
else
file_size=$(stat -c%s "$log_file" 2>/dev/null || echo "0")
fi
if [ "$file_size" -gt "$MAX_LOG_SIZE" ]; then
[ -f "${log_file}.${MAX_LOG_FILES}" ] && rm -f "${log_file}.${MAX_LOG_FILES}"
for i in $(seq $((MAX_LOG_FILES - 1)) -1 1); do
[ -f "${log_file}.${i}" ] && mv "${log_file}.${i}" "${log_file}.$((i + 1))"
done
mv "$log_file" "${log_file}.1"
info "日志已轮转: $log_file"
fi
}
# ============================================================
# 后端服务(本地开发模式)
# ============================================================
start_backend() {
info "启动后端服务..."
cd "$SCRIPT_DIR/$BACKEND_DIR"
mkdir -p logs
rotate_log "logs/server.log"
kill_port $BACKEND_PORT
export GIN_MODE=debug
info "编译 Go 程序..."
CGO_ENABLED=1 go build -o "$BACKEND_BIN" ./cmd/ 2>&1 || {
error "编译失败"
return 1
}
nohup ./"$BACKEND_BIN" >> logs/server.log 2>&1 &
echo $! > "$BACKEND_PID_FILE"
# 等待服务启动
local retries=0
while ! curl -s "http://localhost:$BACKEND_PORT/health" > /dev/null 2>&1; do
retries=$((retries + 1))
if [ $retries -ge 30 ]; then
error "后端启动超时,查看日志: $BACKEND_DIR/logs/server.log"
tail -20 logs/server.log 2>/dev/null
return 1
fi
sleep 1
done
success "后端已启动 (PID: $(cat "$BACKEND_PID_FILE"), 端口: $BACKEND_PORT)"
return 0
}
stop_backend() {
if [ -f "$BACKEND_PID_FILE" ]; then
local pid=$(cat "$BACKEND_PID_FILE" 2>/dev/null || echo "")
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null || true
success "后端已停止"
fi
rm -f "$BACKEND_PID_FILE"
fi
kill_port $BACKEND_PORT
}
# ============================================================
# 前端服务(本地开发模式)
# ============================================================
start_frontend() {
info "启动前端服务..."
cd "$SCRIPT_DIR/$FRONTEND_DIR"
mkdir -p logs
rotate_log "logs/dev.log"
kill_port $FRONTEND_PORT
if [ ! -d "node_modules" ]; then
warning "安装前端依赖..."
npm install
fi
nohup npm run dev >> logs/dev.log 2>&1 &
echo $! > "$FRONTEND_PID_FILE"
local retries=0
while ! curl -s "http://localhost:$FRONTEND_PORT" > /dev/null 2>&1; do
retries=$((retries + 1))
if [ $retries -ge 30 ]; then
error "前端启动超时,查看日志: $FRONTEND_DIR/logs/dev.log"
tail -20 logs/dev.log 2>/dev/null
return 1
fi
sleep 1
done
success "前端已启动 (PID: $(cat "$FRONTEND_PID_FILE"), 端口: $FRONTEND_PORT)"
return 0
}
stop_frontend() {
if [ -f "$FRONTEND_PID_FILE" ]; then
local pid=$(cat "$FRONTEND_PID_FILE" 2>/dev/null || echo "")
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null || true
success "前端已停止"
fi
rm -f "$FRONTEND_PID_FILE"
fi
kill_port $FRONTEND_PORT
}
# ============================================================
# 本地开发命令
# ============================================================
cmd_dev() {
banner "========== $PROJECT_NAME (本地开发) =========="
# 检查依赖
if ! command -v go &> /dev/null; then
error "Go 未安装,请先安装 Go 1.21+"
exit 1
fi
if ! command -v npm &> /dev/null; then
error "npm 未安装,请先安装 Node.js 20+"
exit 1
fi
if start_backend && start_frontend; then
echo ""
banner "========== 所有服务已启动 =========="
info "前端地址: http://localhost:$FRONTEND_PORT"
info "后端地址: http://localhost:$BACKEND_PORT"
info "健康检查: http://localhost:$BACKEND_PORT/health"
info "数据库: SQLite (backend/sqlite3.db)"
echo ""
info "使用 './run.sh stop' 停止服务"
else
error "服务启动失败"
exit 1
fi
}
cmd_stop() {
info "停止本地服务..."
stop_backend
stop_frontend
success "所有本地服务已停止"
}
cmd_restart() {
cmd_stop
sleep 1
cmd_dev
}
cmd_status() {
info "本地服务状态:"
if [ -n "$(check_port $BACKEND_PORT)" ]; then
success "后端: 运行中 (端口 $BACKEND_PORT)"
else
warning "后端: 未运行"
fi
if [ -n "$(check_port $FRONTEND_PORT)" ]; then
success "前端: 运行中 (端口 $FRONTEND_PORT)"
else
warning "前端: 未运行"
fi
if [ -f "$SCRIPT_DIR/$BACKEND_DIR/sqlite3.db" ]; then
local db_size=$(du -h "$SCRIPT_DIR/$BACKEND_DIR/sqlite3.db" | cut -f1)
info "数据库: SQLite (sqlite3.db, $db_size)"
fi
}
cmd_logs() {
info "实时日志 (Ctrl+C 退出):"
echo ""
local pids=()
if [ -f "$SCRIPT_DIR/$BACKEND_DIR/logs/server.log" ]; then
echo "=== 后端日志 ==="
tail -f "$SCRIPT_DIR/$BACKEND_DIR/logs/server.log" &
pids+=($!)
fi
if [ -f "$SCRIPT_DIR/$FRONTEND_DIR/logs/dev.log" ]; then
echo "=== 前端日志 ==="
tail -f "$SCRIPT_DIR/$FRONTEND_DIR/logs/dev.log" &
pids+=($!)
fi
trap "kill ${pids[*]} 2>/dev/null; exit 0" INT TERM
wait
}
# ============================================================
# Docker Compose 部署
# ============================================================
# 检测 Docker Compose 命令 (兼容 docker compose 插件和 docker-compose 独立二进制)
detect_compose_cmd() {
if docker compose version &>/dev/null 2>&1; then
echo "docker compose"
elif command -v docker-compose &>/dev/null; then
echo "docker-compose"
else
echo ""
fi
}
cmd_docker() {
local action=${1:-help}
if ! command -v docker &> /dev/null; then
error "Docker 未安装,请先安装 Docker"
exit 1
fi
DOCKER_COMPOSE=$(detect_compose_cmd)
if [ -z "$DOCKER_COMPOSE" ]; then
error "Docker Compose 未安装 (需要 docker compose 插件或 docker-compose)"
exit 1
fi
# 自动从 git tag 获取版本号
GIT_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
export VERSION="${GIT_TAG:-dev}"
export BUILD_TIME
info "使用: $DOCKER_COMPOSE"
info "版本: $VERSION (构建时间: $BUILD_TIME)"
# 启用 BuildKit 加速构建(cache mount 跨构建复用 go mod/apk 缓存)// Logan.Li
export DOCKER_BUILDKIT=1
export COMPOSE_DOCKER_CLI_BUILD=1
info "BuildKit: 已启用 (增量编译 + 缓存复用)"
case "$action" in
up)
banner "========== Docker Compose 启动 =========="
$DOCKER_COMPOSE up -d --build
echo ""
# 清理历史 build cache 和悬空镜像,防止占满磁盘
info "清理历史构建缓存..."
local before_prune=$(df -h / | awk 'NR==2{print $5}')
docker builder prune -f 2>/dev/null || true
docker image prune -f 2>/dev/null || true
local after_prune=$(df -h / | awk 'NR==2{print $5}')
info "磁盘使用: ${before_prune} → ${after_prune}"
echo ""
info "等待服务就绪..."
sleep 5
$DOCKER_COMPOSE ps
echo ""
info "前端地址: http://localhost:80"
info "后端地址: http://localhost:8080"
info "健康检查: http://localhost:8080/health"
;;
down)
info "停止 Docker Compose..."
$DOCKER_COMPOSE down
success "容器已停止"
;;
build)
info "构建 Docker 镜像..."
$DOCKER_COMPOSE build
success "镜像构建完成"
;;
restart)
info "重启 Docker Compose..."
$DOCKER_COMPOSE restart
success "容器已重启"
;;
logs)
info "Docker 实时日志 (Ctrl+C 退出):"
$DOCKER_COMPOSE logs -f
;;
ps|status)
$DOCKER_COMPOSE ps
;;
clean|prune)
banner "========== Docker 缓存清理 =========="
info "清理前磁盘使用:"
echo " $(df -h / | awk 'NR==2{print $3, "used /", $2, "total ("$5")"}')"
echo ""
info "清理 dangling 镜像..."
docker image prune -f 2>/dev/null || true
info "清理构建缓存..."
docker builder prune -f 2>/dev/null || true
info "清理未使用的网络..."
docker network prune -f 2>/dev/null || true
echo ""
info "清理后磁盘使用:"
echo " $(df -h / | awk 'NR==2{print $3, "used /", $2, "total ("$5")"}')"
success "清理完成"
;;
help|--help|-h)
echo "Docker Compose 部署命令:"
echo " ./run.sh docker up 构建并启动所有容器"
echo " ./run.sh docker down 停止并移除容器"
echo " ./run.sh docker build 仅构建镜像"
echo " ./run.sh docker restart 重启容器"
echo " ./run.sh docker logs 查看实时日志"
echo " ./run.sh docker ps 查看容器状态"
echo " ./run.sh docker clean 清理构建缓存/无用镜像"
;;
*)
error "未知 docker 子命令: $action"
cmd_docker help
exit 1
;;
esac
}
# ============================================================
# 编译独立二进制
# ============================================================
cmd_build() {
local target=${1:-binary}
case "$target" in
binary|aiops)
banner "========== 编译独立二进制 =========="
# 构建前端 → 拷贝到 embed 目录(go:embed 需要 static/ 下有文件)
info "构建前端 → embed..."
cd "$SCRIPT_DIR/$FRONTEND_DIR"
if [ ! -d "node_modules" ]; then
npm install --registry=https://registry.npmmirror.com
fi
npm run build 2>&1 | tail -5
# 拷贝前端构建产物到后端 embed 目录
cd "$SCRIPT_DIR"
rm -rf "$BACKEND_DIR/cmd/static"/*
cp -r "$FRONTEND_DIR/dist/"* "$BACKEND_DIR/cmd/static/"
local fe_files=$(find "$BACKEND_DIR/cmd/static" -type f | wc -l)
success "前端已嵌入 ($fe_files 文件)"
cd "$SCRIPT_DIR/$BACKEND_DIR"
# 确保 config.yaml 存在(二进制模式默认 SQLite)
if [ ! -f "config.yaml" ]; then
info "生成默认 config.yaml (SQLite + 无Redis)..."
cat > config.yaml << 'YAML'
mode: "release"
port: "8080"
log_level: "info"
database:
driver: "sqlite"
host: "localhost"
port: 3306
user: ""
password: ""
dbname: "data/aiops.db"
charset: "utf8mb4"
max_conn: 100
max_idle: 10
redis:
host: "localhost"
port: 6379
password: ""
db: 0
jwt:
secret: "CHANGE_ME_JWT_SECRET" # ⚠️ 生产环境必须替换
expire_hour: 24
websocket:
read_buffer_size: 1024
write_buffer_size: 1024
YAML
fi
# 静态编译(CGO_ENABLED=1 因为 SQLite 需要 CGO)
info "编译中..."
CGO_ENABLED=1 GOPROXY=https://goproxy.cn,direct GONOSUMCHECK='*' GOTOOLCHAIN=go1.22.2 go build -ldflags="-w -s" -o "$STANDALONE_BIN" ./cmd/
if [ -f "$STANDALONE_BIN" ]; then
local bin_size=$(du -h "$STANDALONE_BIN" | cut -f1)
success "编译完成: $BACKEND_DIR/$STANDALONE_BIN ($bin_size)"
echo ""
info "使用方法:"
echo " ./$BACKEND_DIR/$STANDALONE_BIN # 默认启动 (SQLite, 端口 8080)"
echo " ./$BACKEND_DIR/$STANDALONE_BIN --port 9090 # 自定义端口"
echo " ./$BACKEND_DIR/$STANDALONE_BIN --db-driver mysql \\"
echo " --db-host localhost --db-user root \\"
echo " --db-password pass --db-name aiops # MySQL 模式"
echo " ./$BACKEND_DIR/$STANDALONE_BIN --config config.yaml # 使用配置文件"
else
error "编译失败"
exit 1
fi
;;
agent)
banner "========== 编译 Agent =========="
cd "$SCRIPT_DIR/agent"
info "编译 Linux AMD64..."
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-w -s" -o aiops-agent-linux-amd64 .
success "aiops-agent-linux-amd64 ($(du -h aiops-agent-linux-amd64 | cut -f1))"
info "编译 Linux ARM64..."
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-w -s" -o aiops-agent-linux-arm64 .
success "aiops-agent-linux-arm64 ($(du -h aiops-agent-linux-arm64 | cut -f1))"
if [ "$OS" = "macos" ]; then
info "编译 macOS (当前平台)..."
CGO_ENABLED=0 go build -ldflags="-w -s" -o aiops-agent .
success "aiops-agent ($(du -h aiops-agent | cut -f1))"
fi
;;
frontend)
info "构建前端生产包..."
cd "$SCRIPT_DIR/$FRONTEND_DIR"
npm run build
success "前端构建完成: $FRONTEND_DIR/dist/"
;;
help|--help|-h)
echo "编译命令:"
echo " ./run.sh build 编译独立二进制 aiops (默认 SQLite)"
echo " ./run.sh build agent 交叉编译 Agent (amd64+arm64)"
echo " ./run.sh build frontend 构建前端生产包"
;;
*)
error "未知构建目标: $target"
cmd_build help
exit 1
;;
esac
}
# ============================================================
# 独立二进制模式
# ============================================================
cmd_binary() {
cd "$SCRIPT_DIR/$BACKEND_DIR"
# 先编译(如果不存在)
if [ ! -f "$STANDALONE_BIN" ]; then
info "二进制不存在,先编译..."
cmd_build binary
fi
banner "========== 启动独立二进制 =========="
mkdir -p data logs uploads
# 传递额外参数
./"$STANDALONE_BIN" "$@"
}
# ============================================================
# Kubernetes 部署 (embed 单容器模式)
# ============================================================
K8S_NAMESPACE="devops"
K8S_MANIFEST_DIR="$SCRIPT_DIR/deploy/kubernetes"
K8S_IMAGE="aiops:embed"
K8S_NODE_PORT=30080
# 检测 kubectl 和 Docker
check_k8s_deps() {
if ! command -v kubectl &>/dev/null; then
error "kubectl 未安装,请先安装 kubectl"
exit 1
fi
if ! kubectl cluster-info &>/dev/null 2>&1; then
error "无法连接 Kubernetes 集群,请检查 kubeconfig"
kubectl cluster-info 2>&1 | tail -3
exit 1
fi
}
# 检测容器运行时并加载镜像到集群
load_image_to_cluster() {
local image=$1
info "加载镜像到集群..."
# minikube
if command -v minikube &>/dev/null && minikube status &>/dev/null 2>&1; then
minikube image load "$image" 2>/dev/null && success "镜像已加载到 minikube" && return 0
fi
# kind
if command -v kind &>/dev/null && kind get clusters &>/dev/null 2>&1 | grep -q .; then
kind load docker-image "$image" 2>/dev/null && success "镜像已加载到 kind" && return 0
fi
# k3d
if command -v k3d &>/dev/null; then
k3d image import "$image" 2>/dev/null && success "镜像已加载到 k3d" && return 0
fi
# Docker Desktop K8s / 远程仓库 — 镜像通过 registry 共享
warning "未检测到 minikube/kind/k3d,假设镜像通过 registry 共享"
warning "如集群无法拉取镜像,请手动推送: docker push <registry>/$image"
}
# K8s 构建镜像
cmd_k8s_build() {
if ! command -v docker &>/dev/null; then
error "Docker 未安装"
exit 1
fi
banner "========== 构建 embed 镜像 =========="
info "镜像: $K8S_IMAGE"
# 版本信息
local version=$(git describe --tags --abbrev=0 2>/dev/null || echo "dev")
local build_time=$(date -u +%Y-%m-%dT%H:%M:%SZ)
info "构建中(前端 + 后端 embed)..."
docker build -f Dockerfile.embed \
--build-arg VERSION="$version" \
--build-arg BUILD_TIME="$build_time" \
-t "$K8S_IMAGE" . || {
error "镜像构建失败"
exit 1
}
local img_size=$(docker images "$K8S_IMAGE" --format "{{.Size}}" | head -1)
success "镜像构建完成: $K8S_IMAGE ($img_size)"
# 加载到本地集群
load_image_to_cluster "$K8S_IMAGE"
}
# K8s 部署
cmd_k8s() {
local action=${1:-help}
case "$action" in
deploy|up)
check_k8s_deps
banner "========== K8s 部署 ($K8S_NAMESPACE) =========="
# 1. 构建镜像
cmd_k8s_build
# 2. 部署基础设施
info "部署 Namespace + Secret + ConfigMap..."
kubectl apply -f "$K8S_MANIFEST_DIR/namespace.yaml"
kubectl apply -f "$K8S_MANIFEST_DIR/secret.yaml"
kubectl apply -f "$K8S_MANIFEST_DIR/configmap.yaml"
info "部署 MySQL + Redis..."
kubectl apply -f "$K8S_MANIFEST_DIR/mysql.yaml"
kubectl apply -f "$K8S_MANIFEST_DIR/redis.yaml"
# 3. 等待数据库就绪
info "等待 MySQL 就绪..."
kubectl -n "$K8S_NAMESPACE" rollout status statefulset/mysql --timeout=120s 2>/dev/null || {
warning "MySQL 启动较慢,继续部署应用(可在后台自行完成初始化)"
}
info "等待 Redis 就绪..."
kubectl -n "$K8S_NAMESPACE" rollout status statefulset/redis --timeout=60s 2>/dev/null || true
# 4. 部署应用
info "部署 AIOps 应用..."
kubectl apply -f "$K8S_MANIFEST_DIR/deployment.yaml"
kubectl -n "$K8S_NAMESPACE" rollout status deployment/aiops --timeout=180s 2>/dev/null || {
warning "应用正在启动,查看日志: ./run.sh k8s logs"
}
# 5. Ingress(可选)
if [ -f "$K8S_MANIFEST_DIR/ingress.yaml" ]; then
info "部署 Ingress(如集群无 Ingress Controller 会跳过)..."
kubectl apply -f "$K8S_MANIFEST_DIR/ingress.yaml" 2>/dev/null || true
fi
echo ""
success "部署完成!"
echo ""
kubectl -n "$K8S_NAMESPACE" get pods,svc
echo ""
local node_ip=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null)
info "访问地址: http://${node_ip:-<NODE_IP>}:$K8S_NODE_PORT"
info "健康检查: http://${node_ip:-<NODE_IP>}:$K8S_NODE_PORT/health"
;;
upgrade|update)
check_k8s_deps
banner "========== K8s 滚动更新 =========="
cmd_k8s_build
info "触发滚动更新..."
kubectl -n "$K8S_NAMESPACE" rollout restart deployment/aiops
kubectl -n "$K8S_NAMESPACE" rollout status deployment/aiops --timeout=180s
success "滚动更新完成"
;;
delete|down|remove)
check_k8s_deps
banner "========== K8s 清理 ($K8S_NAMESPACE) =========="
warning "这将删除 $K8S_NAMESPACE 命名空间中的所有资源"
warning "MySQL/Redis 持久卷(PVC)需手动删除"
echo ""
read -p "确认删除?[y/N] " confirm
if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
kubectl delete -f "$K8S_MANIFEST_DIR/" --ignore-not-found 2>/dev/null
echo ""
info "PVC 状态(需手动清理):"
kubectl -n "$K8S_NAMESPACE" get pvc 2>/dev/null || echo " 无 PVC"
success "清理完成"
else
info "已取消"
fi
;;
build|image)
cmd_k8s_build
;;
status|ps)
check_k8s_deps
banner "========== K8s 状态 ($K8S_NAMESPACE) =========="
echo ""
echo "--- Pods ---"
kubectl -n "$K8S_NAMESPACE" get pods -o wide 2>/dev/null
echo ""
echo "--- Services ---"
kubectl -n "$K8S_NAMESPACE" get svc 2>/dev/null
echo ""
echo "--- PVC ---"
kubectl -n "$K8S_NAMESPACE" get pvc 2>/dev/null
echo ""
echo "--- Ingress ---"
kubectl -n "$K8S_NAMESPACE" get ingress 2>/dev/null
;;
logs|log)
check_k8s_deps
local follow=${2:--f}
info "应用日志 (Ctrl+C 退出):"
kubectl -n "$K8S_NAMESPACE" logs $follow deployment/aiops --tail=100
;;
exec|shell|sh)
check_k8s_deps
info "进入容器 Shell..."
kubectl -n "$K8S_NAMESPACE" exec -it deployment/aiops -- bash || \
kubectl -n "$K8S_NAMESPACE" exec -it deployment/aiops -- sh
;;
mysql|db)
check_k8s_deps
info "进入 MySQL..."
kubectl -n "$K8S_NAMESPACE" exec -it mysql-0 -- mysql -uaiops -paiops_pass_2026 aiops
;;
redis)
check_k8s_deps
info "进入 Redis CLI..."
kubectl -n "$K8S_NAMESPACE" exec -it redis-0 -- redis-cli
;;
scale)
check_k8s_deps
local replicas=${2:-1}
info "扩缩容到 $replicas 副本..."
kubectl -n "$K8S_NAMESPACE" scale deployment/aiops --replicas="$replicas"
;;
port-forward|pf)
check_k8s_deps
local local_port=${2:-8080}
info "端口转发 localhost:$local_port → aiops:8080 (Ctrl+C 退出):"
kubectl -n "$K8S_NAMESPACE" port-forward svc/aiops "$local_port":8080
;;
help|--help|-h)
echo "K8s 部署命令 (embed 单容器模式):"
echo ""
echo " ${GREEN}部署${NC}"
echo " ./run.sh k8s deploy 构建镜像 + 全量部署"
echo " ./run.sh k8s upgrade 重新构建镜像 + 滚动更新"
echo " ./run.sh k8s delete 删除所有 K8s 资源(保留 PVC)"
echo ""
echo " ${CYAN}运维${NC}"
echo " ./run.sh k8s status 查看资源状态"
echo " ./run.sh k8s logs 查看应用日志(默认 follow)"
echo " ./run.sh k8s exec 进入容器 Shell"
echo " ./run.sh k8s mysql 进入 MySQL CLI"
echo " ./run.sh k8s redis 进入 Redis CLI"
echo " ./run.sh k8s scale 3 扩缩容到 3 副本"
echo " ./run.sh k8s port-forward 端口转发到本机 8080"
echo ""
echo " ${YELLOW}镜像${NC}"
echo " ./run.sh k8s build 仅构建 embed 镜像"
echo ""
echo " 镜像: $K8S_IMAGE"
echo " 命名空间: $K8S_NAMESPACE"
echo " NodePort: $K8S_NODE_PORT"
;;
*)
error "未知 k8s 子命令: $action"
cmd_k8s help
exit 1
;;
esac
}
# ============================================================
# 帮助
# ============================================================
cmd_help() {
echo ""
banner " $PROJECT_NAME 启动脚本"
echo ""
echo -e " ${GREEN}本地开发${NC}"
echo " ./run.sh dev 启动前后端开发服务 (SQLite, 端口 8080+5173)"
echo " ./run.sh stop 停止本地服务"
echo " ./run.sh restart 重启本地服务"
echo " ./run.sh status 查看本地服务状态"
echo " ./run.sh logs 查看实时日志"
echo ""
echo -e " ${CYAN}Docker 容器部署${NC}"
echo " ./run.sh docker up 构建并启动所有容器 (MySQL+Redis+Nginx)"
echo " ./run.sh docker down 停止并移除容器"
echo " ./run.sh docker build 仅构建镜像"
echo " ./run.sh docker restart 重启容器"
echo " ./run.sh docker logs 查看容器日志"
echo " ./run.sh docker ps 查看容器状态"
echo ""
echo -e " ${MAGENTA}Kubernetes 部署 (embed 单容器)${NC}"
echo " ./run.sh k8s deploy 构建镜像 + 全量部署到 K8s"
echo " ./run.sh k8s upgrade 重新构建 + 滚动更新"
echo " ./run.sh k8s delete 删除所有 K8s 资源"
echo " ./run.sh k8s status 查看资源状态"
echo " ./run.sh k8s logs 查看应用日志"
echo " ./run.sh k8s exec 进入容器 Shell"
echo " ./run.sh k8s mysql 进入 MySQL CLI"
echo " ./run.sh k8s redis 进入 Redis CLI"
echo " ./run.sh k8s scale 3 扩缩容"
echo " ./run.sh k8s port-forward 端口转发到本机"
echo " ./run.sh k8s build 仅构建 embed 镜像"
echo ""
echo -e " ${YELLOW}编译 & 独立二进制${NC}"
echo " ./run.sh build 编译独立二进制 ./$BACKEND_DIR/$STANDALONE_BIN"
echo " ./run.sh binary 编译+启动独立二进制"
echo " ./run.sh build agent 交叉编译 Agent (amd64+arm64)"
echo " ./run.sh build frontend 构建前端生产包"
echo ""
echo -e " ${BLUE}独立二进制用法${NC}"
echo " ./$BACKEND_DIR/$STANDALONE_BIN 默认启动 (SQLite, 端口 8080)"
echo " ./$BACKEND_DIR/$STANDALONE_BIN --port 9090 自定义端口"
echo " ./$BACKEND_DIR/$STANDALONE_BIN --config config.yaml 使用配置文件"
echo " ./$BACKEND_DIR/$STANDALONE_BIN --db-driver mysql \\"
echo " --db-host 127.0.0.1 --db-user root \\"
echo " --db-password pass --db-name aiops MySQL 模式"
echo ""
echo -e " ${GREEN}快捷操作${NC}"
echo " ./run.sh 等同于 ./run.sh dev"
echo " ./run.sh help 显示此帮助"
echo ""
}
# ============================================================
# 主入口
# ============================================================
main() {
local cmd=${1:-dev}
case "$cmd" in
# 本地开发
dev|start)
cmd_dev
;;
stop)
cmd_stop
;;
restart)
cmd_restart
;;
status)
cmd_status
;;
logs)
cmd_logs
;;
# Docker Compose
docker|compose)
shift
cmd_docker "$@"
;;
# 编译
build)
shift
cmd_build "$@"
;;
# 独立二进制
binary|bin)
shift
cmd_binary "$@"
;;
# Kubernetes 部署
k8s|kubernetes)
shift
cmd_k8s "$@"
;;
# 帮助
help|--help|-h)
cmd_help
;;
*)
error "未知命令: $cmd"
cmd_help
exit 1
;;
esac
}
main "$@"