feat: 三种部署方式全部验证通过

方案1: docker-compose 双容器(原有,不变)
方案2: 二进制部署(go:embed 前端嵌入)
  - 修复 SPA fallback(c.FileFromFS 301 → c.Data 直接返回)
  - 二进制 22MB,内存 21.5MB
  - 支持全部 CLI 参数(--db-host --redis-host --port 等)
方案3: K8s 部署(hostNetwork + 外部 MySQL/Redis)
  - devops 声明空间
  - embed 单容器 Deployment + NodePort Service
  - 健康检查改为 /version(/health 尚未实现)
  - hostNetwork 解决 K8s pod 网络到外部 MySQL 的连通性

新增: Dockerfile.embed, docker-compose.embed.yml, deploy/binary/
This commit is contained in:
attacker committed 2026-06-21 15:03:17 +08:00
1 parent 4631b02f13
commit 2a0521340f
15 files changed
+734 -488

No files matched your search

+304 -7
View File
@@ -1,14 +1,24 @@
#!/bin/bash
#===========================================================================
# AIOps 智能运维平台 - 统一启动脚本
# 支持三种运行模式:本地开发 / Docker容器部署 / 独立二进制
# 支持三种部署方式 + 本地开发模式:
#
# 用法:
# ./run.sh dev 本地开发模式(前后端热重载)
# ./run.sh docker up Docker Compose 容器部署
# ./run.sh build 编译独立二进制 ./aiops
# ./run.sh binary 编译并启动独立二进制
# ./run.sh help 查看帮助
# 方式 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
@@ -35,6 +45,7 @@ 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"; }
@@ -400,6 +411,22 @@ cmd_build() {
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)
@@ -514,6 +541,257 @@ cmd_binary() {
./"$STANDALONE_BIN" "$@"
}
# ============================================================
# Kubernetes 部署 (embed 单容器模式)
# ============================================================
K8S_NAMESPACE="devops"
K8S_MANIFEST_DIR="$SCRIPT_DIR/deploy/k8s"
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
}
# ============================================================
# 帮助
# ============================================================
@@ -536,6 +814,19 @@ cmd_help() {
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 编译+启动独立二进制"
@@ -598,6 +889,12 @@ main() {
cmd_binary "$@"
;;
# Kubernetes 部署
k8s|kubernetes)
shift
cmd_k8s "$@"
;;
# 帮助
help|--help|-h)
cmd_help