feat: 三种部署方式 — embed单容器 + 二进制部署 + K8s

新增三种部署方式(原有 docker-compose 不受影响):

1. Docker embed 单容器 (docker-compose.embed.yml):
   - go:embed 前端嵌入后端二进制
   - 不需要 nginx 容器,3容器→2容器
   - 镜像仅 54.8MB (vs 原来 1.16GB)
   - 用法: make docker-embed

2. 二进制部署 (deploy/binary/):
   - make build-binary 生成单二进制
   - install.sh 自动创建 systemd 服务
   - 适合无 Docker 环境
   - 用法: scp aiops + install.sh, bash install.sh install

3. Kubernetes 部署 (deploy/k8s/):
   - devops 命名空间全套 yaml
   - MySQL StatefulSet + Redis StatefulSet + Backend Deployment
   - ConfigMap + Secret + Ingress
   - 用法: kubectl apply -f deploy/k8s/

核心改动:
- backend/cmd/embed.go: go:embed 前端 + SPA fallback 路由
- backend/Dockerfile.embed: 四阶段构建 (前端→后端embed→agent→runtime)
- Makefile: build-binary + docker-embed 目标
- deploy/README.md: 四种部署方式对比文档
This commit is contained in:
Logan authored and attacker committed 2026-06-21 14:20:29 +08:00
1 parent 24f955832e
commit 4631b02f13
15 files changed
+1218 -3

No files matched your search

+3
View File
@@ -53,3 +53,6 @@ docker-data/
*.pid
agent/aiops-agent
agent/bin/
# embed 模式临时目录(前端构建产物,编译后清理)
backend/cmd/static/
+20 -3
View File
@@ -1,7 +1,7 @@
# AIOps 智能运维平台 - Makefile
# 使用方式: make help
.PHONY: help install dev build build-ui build-api status stop restart logs lint clean \
docker-up docker-down docker-build docker-logs
.PHONY: help install dev build build-ui build-api build-binary status stop restart logs lint clean \
docker-up docker-down docker-build docker-logs docker-embed
help:
@echo "AIOps 智能运维平台 - 可用命令:"
@@ -24,7 +24,10 @@ help:
@echo " make docker-down - 停止并移除所有容器"
@echo " make docker-build - 重新构建镜像(不启动)"
@echo " make docker-logs - 查看容器日志"
@echo " make docker-embed - 单容器部署(embed 模式,前后端合一)"
@echo ""
@echo " 二进制部署:"
@echo " make build-binary - 编译单二进制(前端嵌入后端)"
# ==================== 本地开发 ====================
install:
@@ -71,7 +74,7 @@ clean:
rm -f backend/logs/*.log frontend/logs/*.log
@echo "清理完成"
# ==================== Docker 部署 ====================
# ==================== Docker 部署(双容器:前端 nginx + 后端) ====================
docker-up:
docker-compose up -d --build
@@ -83,3 +86,17 @@ docker-build:
docker-logs:
docker-compose logs -f
# ==================== Docker 部署(单容器:embed 模式) ====================
docker-embed:
docker-compose -f docker-compose.embed.yml up -d --build
# ==================== 二进制部署(embed 模式) ====================
build-binary: build-ui
@echo "构建单二进制(前端嵌入后端)..."
@mkdir -p backend/cmd/static
@cp -r frontend/dist/* backend/cmd/static/
cd backend && CGO_ENABLED=0 go build -ldflags="-s -w" -o ../aiops ./cmd/
@rm -rf backend/cmd/static
@echo "✓ 二进制已生成: ./aiops"
@echo " 部署: scp aiops deploy/binary/install.sh root@<server>:/tmp/ && ssh root@<server> 'cd /tmp && bash install.sh install'"
+91
View File
@@ -0,0 +1,91 @@
# ============================================================
# AIOps 单容器 Dockerfile(embed 模式)
# 前端嵌入后端二进制,一个容器搞定前后端
# 用法: docker build -f backend/Dockerfile.embed -t aiops-embed .
# 署名 Logan.Li
# ============================================================
ARG VERSION=dev
ARG BUILD_TIME=unknown
# ==================== Stage 1: Frontend Builder ====================
FROM node:20-alpine AS frontend-builder
WORKDIR /app
ENV NODE_OPTIONS=--max-old-space-size=4096
# 使用国内 npm 镜像
RUN npm config set registry https://registry.npmmirror.com
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci --prefer-offline 2>/dev/null || npm install
COPY frontend/ ./
RUN npm run build
# ==================== Stage 2: Backend Builder ====================
FROM golang:1.24-alpine AS backend-builder
WORKDIR /app
RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories
RUN apk add --no-cache git ca-certificates tzdata
ENV GOPROXY=https://goproxy.cn,direct
ENV GOSUMDB=off
COPY backend/go.mod backend/go.sum ./
RUN go mod download
COPY backend/ ./
# 复制前端构建产物到 static 目录(供 go:embed 嵌入)
COPY --from=frontend-builder /app/dist ./cmd/static/
# 构建(前端已嵌入)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-w -s -X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME}" \
-o aiops ./cmd/
# ==================== Stage 3: Agent Builder ====================
FROM golang:1.24-alpine AS agent-builder
WORKDIR /agent
ENV GOPROXY=https://goproxy.cn,direct
ENV GOSUMDB=off
COPY agent/go.mod agent/go.sum ./
RUN go mod download
COPY agent/ ./
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/aiops-agent-linux-amd64 . && \
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o bin/aiops-agent-linux-arm64 . && \
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o bin/aiops-agent-darwin-arm64 .
# ==================== Stage 4: Runtime ====================
FROM alpine:latest
RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories
RUN apk --no-cache add ca-certificates tzdata curl
ENV TZ=Asia/Shanghai
WORKDIR /app
# 单个二进制(前端已嵌入)
COPY --from=backend-builder /app/aiops .
# Agent 二进制 + 安装脚本
RUN mkdir -p /app/agent/bin /app/agent/deploy
COPY --from=agent-builder /agent/bin/aiops-agent-linux-amd64 /app/agent/bin/
COPY --from=agent-builder /agent/bin/aiops-agent-linux-arm64 /app/agent/bin/
COPY --from=agent-builder /agent/bin/aiops-agent-darwin-arm64 /app/agent/bin/
COPY agent/deploy/install.sh /app/agent/deploy/
COPY agent/deploy/aiops-agent.service /app/agent/deploy/
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
CMD ["./aiops"]
+68
View File
@@ -0,0 +1,68 @@
package main
import (
"embed"
"io/fs"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
//go:embed all:static
var frontendFS embed.FS
// setupFrontend 配置前端静态文件服务(go:embed 模式) // Logan.Li
// 前端构建产物放在 backend/cmd/static/ 目录,编译时嵌入二进制
func setupFrontend(r *gin.Engine) {
// 获取 static 子目录的 fs.FS
staticFS, err := fs.Sub(frontendFS, "static")
if err != nil {
// static 目录不存在(开发模式或未构建前端),跳过
return
}
httpFS := http.FS(staticFS)
// 静态资源文件(js/css/图片等)直接返回
r.Use(func(c *gin.Context) {
// API 和 WebSocket 路径不走静态文件
path := c.Request.URL.Path
if strings.HasPrefix(path, "/api/") ||
strings.HasPrefix(path, "/ws/") ||
strings.HasPrefix(path, "/agent/") ||
strings.HasPrefix(path, "/health") ||
strings.HasPrefix(path, "/version") {
c.Next()
return
}
c.Next()
})
// NoRoute: SPA fallback — 非 API 请求尝试静态文件,找不到则返回 index.html
r.NoRoute(func(c *gin.Context) {
path := c.Request.URL.Path
// API/WS 路径返回 JSON 404
if strings.HasPrefix(path, "/api/") || strings.HasPrefix(path, "/ws/") || strings.HasPrefix(path, "/agent/") {
c.JSON(http.StatusNotFound, gin.H{"code": 404, "message": "接口不存在"})
return
}
// 尝试打开静态文件
cleanPath := strings.TrimPrefix(path, "/")
if cleanPath == "" {
cleanPath = "index.html"
}
f, err := staticFS.Open(cleanPath)
if err == nil {
f.Close()
c.Header("Cache-Control", "public, max-age=31536000")
c.FileFromFS(path, httpFS)
return
}
// SPA fallback → index.html(React Router history 模式)
c.FileFromFS("/index.html", httpFS)
})
}
+3
View File
@@ -162,6 +162,9 @@ func main() {
// 注册路由
routes.Register(r, db, log, cfg, cache)
// 前端静态文件服务(go:embed 模式,二进制部署用) // Logan.Li
setupFrontend(r)
// 创建HTTP服务器
srv := &http.Server{
Addr: ":" + cfg.Port,
+107
View File
@@ -0,0 +1,107 @@
# AIOps 三种部署方式
## 部署方式对比
| 方式 | 适用场景 | 优点 | 缺点 |
|------|---------|------|------|
| **Docker Compose(双容器)** | 生产环境 | 前后端分离,nginx 高性能 | 需要 4 个容器 |
| **Docker Compose(embed 单容器)** | 快速部署 | 1 个容器搞定,无需 nginx | 静态文件性能稍弱 |
| **二进制部署** | 无 Docker 环境 | 一个文件 + systemd | 需要自行管理 MySQL/Redis |
| **Kubernetes** | K8s 集群 | 标准化编排,弹性伸缩 | 需要 K8s 集群 |
---
## 方式一:Docker Compose(双容器,当前默认)
```bash
# 开发机(75)
cd /srv/aiops
make docker-up # 构建并启动
# 生产机(150)
cd /srv/aiops && git pull
make docker-up
```
架构:nginx(:80) → backend(:8080) + MySQL + Redis
---
## 方式二:Docker Compose embed 单容器
前端通过 Go `//go:embed` 嵌入后端二进制,不需要 nginx 容器。
```bash
cd /srv/aiops
docker-compose -f docker-compose.embed.yml up -d --build
# 或
make docker-embed
```
架构:aiops-app(:80→:8080) + MySQL + Redis(3 个容器)
---
## 方式三:二进制部署(无 Docker)
### 在开发机构建
```bash
cd /srv/aiops
make build-binary
# 生成 ./aiops 单二进制(~30MB,前端已嵌入)
```
### 在目标服务器部署
```bash
# 传两个文件到目标服务器
scp aiops deploy/binary/install.sh root@<target>:/tmp/
# 安装(自动创建 systemd 服务 + 默认配置)
ssh root@<target>
cd /tmp && bash install.sh install
# 修改数据库配置
vim /opt/aiops/config.yaml
# 启动
systemctl start aiops
```
### 管理
```bash
systemctl start aiops # 启动
systemctl stop aiops # 停止
systemctl restart aiops # 重启
systemctl status aiops # 状态
journalctl -u aiops -f # 日志
```
架构:aiops 二进制 + systemd(端口 8080 同时服务 API 和前端)
> 前提:目标服务器需已安装 MySQL 8.0 + Redis 7
---
## 方式四:Kubernetes 部署
```bash
cd /srv/aiops
kubectl apply -f deploy/k8s/
```
详见 [deploy/k8s/README.md](../k8s/README.md)
---
## 快速选择
| 环境 | 推荐方式 |
|------|---------|
| 开发测试(75) | 方式一 docker-compose |
| 生产环境(150) | 方式一 docker-compose |
| 客户现场快速演示 | 方式二 embed 单容器 |
| 内网无 Docker 服务器 | 方式三 二进制部署 |
| K8s 集群 | 方式四 Kubernetes |
+165
View File
@@ -0,0 +1,165 @@
#!/bin/bash
# ============================================================
# AIOps 二进制部署脚本(embed 模式)
# 一个二进制 = 前端 + 后端,无需 Docker/Nginx
# 署名 Logan.Li
# ============================================================
set -e
# 颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
# 默认路径
INSTALL_DIR="/opt/aiops"
CONFIG_FILE="$INSTALL_DIR/config.yaml"
SERVICE_FILE="/etc/systemd/system/aiops.service"
BINARY_NAME="aiops"
PID_FILE="/var/run/aiops.pid"
# ============================================================
# 命令处理
# ============================================================
case "$1" in
install)
info "AIOps 二进制安装"
echo ""
if [ "$(id -u)" -ne 0 ]; then
error "请使用 root 或 sudo 执行"
exit 1
fi
# 检查二进制
if [ ! -f "./${BINARY_NAME}" ]; then
error "当前目录找不到 ./${BINARY_NAME} 二进制文件"
echo " 请先在开发机执行: make build-binary"
exit 1
fi
# 创建安装目录
mkdir -p "$INSTALL_DIR/logs" "$INSTALL_DIR/uploads"
# 复制二进制
cp "./${BINARY_NAME}" "$INSTALL_DIR/"
chmod +x "$INSTALL_DIR/${BINARY_NAME}"
info "二进制已安装到 $INSTALL_DIR/${BINARY_NAME}"
# 生成默认配置(如果不存在)
if [ ! -f "$CONFIG_FILE" ]; then
cat > "$CONFIG_FILE" << 'EOF'
mode: release
port: "8080"
log_level: info
database:
driver: mysql
host: 127.0.0.1
port: 3306
user: aiops
password: aiops_pass_2026
dbname: aiops
charset: utf8mb4
max_conn: 100
max_idle: 10
redis:
driver: redis
host: 127.0.0.1
port: 6379
password: ""
db: 0
jwt:
secret: ezops-aiops-jwt-secret-2026
expire_hour: 24
EOF
info "默认配置已生成: $CONFIG_FILE"
warn "请根据实际环境修改数据库/Redis 配置"
fi
# 创建 systemd 服务
cat > "$SERVICE_FILE" << EOF
[Unit]
Description=AIOps Platform (Embed Mode)
After=network-online.target mysql.service redis.service
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=${INSTALL_DIR}
ExecStart=${INSTALL_DIR}/${BINARY_NAME}
Restart=always
RestartSec=5
LimitNOFILE=65536
Environment=GIN_MODE=release
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable aiops
info "systemd 服务已创建并启用开机自启"
echo ""
info "安装完成!"
echo ""
echo " 启动: systemctl start aiops"
echo " 状态: systemctl status aiops"
echo " 日志: journalctl -u aiops -f"
echo " 配置: vim $CONFIG_FILE"
echo " 访问: http://<IP>:8080"
echo ""
;;
start)
systemctl start aiops
info "AIOps 已启动"
;;
stop)
systemctl stop aiops
info "AIOps 已停止"
;;
restart)
systemctl restart aiops
info "AIOps 已重启"
;;
status)
systemctl status aiops --no-pager
;;
uninstall)
warn "即将卸载 AIOps(保留数据/配置)"
systemctl stop aiops 2>/dev/null
systemctl disable aiops
rm -f "$SERVICE_FILE"
rm -f "$INSTALL_DIR/${BINARY_NAME}"
systemctl daemon-reload
info "已卸载(配置/日志/数据保留在 $INSTALL_DIR)"
;;
*)
echo "AIOps 二进制部署工具"
echo ""
echo "用法: $0 {install|start|stop|restart|status|uninstall}"
echo ""
echo " install - 安装二进制 + 生成配置 + 创建 systemd 服务"
echo " start - 启动服务"
echo " stop - 停止服务"
echo " restart - 重启服务"
echo " status - 查看服务状态"
echo " uninstall - 卸载二进制(保留数据)"
echo ""
exit 1
;;
esac
+158
View File
@@ -0,0 +1,158 @@
# AIOps Kubernetes 部署
AIOps 平台在 Kubernetes 上的部署清单(Go/Gin 后端 + go:embed 前端 + MySQL 8.0 + Redis 7)。
所有资源部署在 **`devops`** 命名空间。
## 目录结构
| 文件 | 说明 |
|------|------|
| `namespace.yaml` | `devops` 命名空间 |
| `configmap.yaml` | 后端 `config.yaml`(ConfigMap)+ MySQL/JWT 密码(Secret) |
| `mysql.yaml` | MySQL 8.0 StatefulSet + Headless/ClusterIP Service + 8Gi 持久卷 |
| `redis.yaml` | Redis 7 StatefulSet + Headless/ClusterIP Service + 2Gi 持久卷 |
| `backend.yaml` | 后端 Deployment(端口 8080=API/WS,80=embed 前端)+ ClusterIP Service |
| `ingress.yaml` | Ingress:`/api`、`/ws` → 8080;`/` → 80 |
## 架构与端口
```
┌──────────────── Ingress (nginx) ────────────────┐
/api, /ws ──▶│ │──▶ aiops-backend:8080 (API + WebSocket)
/ ──▶│ │──▶ aiops-backend:80 (go:embed 前端)
└──────────────────────────────────────────────────┘
│
┌──────────────┴──────────────┐
▼ ▼
aiops-backend aiops-backend
│ (DB_HOST=mysql) │ (REDIS_HOST=redis)
▼ ▼
MySQL 8.0 (StatefulSet) Redis 7 (StatefulSet)
aiops / aiops_pass_2026 无密码, db 0
数据库 aiops, 8Gi PV 2Gi PV
```
- **8080**:API + WebSocket
- **80**:前端(go:embed 嵌入后端二进制,由后端进程提供静态资源)
- 配置文件 `config.yaml` 中 DB/Redis 连接均以环境变量 `${VAR}` 占位,由 Deployment 的 `env` 注入;密码走 Secret。
## 前置条件
1. 一个可用的 Kubernetes 集群(≥ 1.22)。
2. 已安装 **nginx Ingress Controller**。
3. 集群具备可用的 **StorageClass**(MySQL/Redis 的 `volumeClaimTemplates` 依赖动态卷供应)。
- minikube / kind 自带默认 StorageClass(如 `standard`)。
- 如需指定,取消各 StatefulSet 中 `storageClassName` 的注释。
4. 后端镜像 `aiops-backend:latest` 已构建并推送到集群可访问的镜像仓库(或本地 `kind load docker-image` / `minikube image load`)。
## 部署步骤
按顺序 apply(资源间存在依赖:Secret → 数据库 → 后端 → Ingress):
```bash
cd deploy/k8s
# 1. 命名空间(必须最先)
kubectl apply -f namespace.yaml
# 2. 配置 + 密码(ConfigMap / Secret)
kubectl apply -f configmap.yaml
# 3. MySQL(启动后自动建库 aiops 与用户 aiops)
kubectl apply -f mysql.yaml
# 4. Redis
kubectl apply -f redis.yaml
# 5. 等待数据库就绪
kubectl -n devops rollout status statefulset/mysql
kubectl -n devops rollout status statefulset/redis
# 6. 后端
kubectl apply -f backend.yaml
kubectl -n devops rollout status deployment/aiops-backend
# 7. Ingress(部署前修改 host 与 ingressClassName)
kubectl apply -f ingress.yaml
```
一键全量部署(文件名按字典序,namespace 会最先应用):
```bash
kubectl apply -f deploy/k8s/
```
## 访问
- 将 Ingress 中的 `host`(默认 `aiops.local`)解析到 Ingress Controller 的入口 IP:
```bash
echo "<INGRESS_IP> aiops.local" | sudo tee -a /etc/hosts
```
- 浏览器打开 `http://aiops.local` 即前端;API 在 `http://aiops.local/api`。
## 常用运维命令
```bash
# 查看所有资源
kubectl -n devops get all,configmap,secret,ingress,pvc
# 查看后端日志
kubectl -n devops logs -f deployment/aiops-backend
# 进入 MySQL
kubectl -n devops exec -it mysql-0 -- mysql -uaiops -paiops_pass_2026 aiops
# 进入 Redis
kubectl -n devops exec -it redis-0 -- redis-cli
# 进入后端容器
kubectl -n devops exec -it deployment/aiops-backend -- sh
# 查看 PVC 绑定状态
kubectl -n devops get pvc
```
## 配置与密码
| 项目 | 来源 | 值 |
|------|------|----|
| MySQL 业务账号 | Secret `aiops-secret` | 用户 `aiops` / 密码 `aiops_pass_2026` / 库 `aiops` |
| MySQL root | Secret `aiops-secret` | `aiops_root_2026` |
| JWT | Secret `aiops-secret` | `jwt-secret`(生产请替换为强随机值) |
| Redis | 无密码 | db `0` |
> ⚠️ 生产环境请勿使用仓库内默认密码。修改 `configmap.yaml` 中 Secret 的值后重新 apply,
> 或使用 `kubectl create secret` 覆盖。
## 资源限额
| 组件 | CPU 请求/上限 | 内存请求/上限 |
|------|---------------|---------------|
| backend | 100m / 500m | 512Mi / 1Gi |
| mysql | 250m / 1 | 1Gi / 2Gi |
| redis | 50m / 250m | 128Mi / 256Mi |
## 关于配置的环境变量占位
`config.yaml`(位于 ConfigMap)中 DB/Redis 的 `host/port/user/password/dbname`、JWT `secret`
均写成 `${ENV_VAR}` 占位形式,实际值在 `backend.yaml` 的 `env` 段注入:
非敏感的 host/port/user/dbname 直接给值(指向 K8s Service),密码则 `valueFrom` 引用 Secret。
后端基于 viper 读取配置。若 `${VAR}` 占位需要由环境变量直接替换嵌套键
(如 `DB_PASSWORD` → `database.password`),建议在程序启动时设置键名映射:
```go
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
```
否则可改为在 `config.yaml` 中直接写明连接串、密码仅由 Secret 注入。两种方式均可,
当前清单采用「环境变量占位 + Secret」的标准 K8s 模式。
## 清理
```bash
kubectl delete -f deploy/k8s/
# 持久卷需单独删除(PVC 由 StatefulSet 保护,删除 STS 后 PVC 仍保留)
kubectl -n devops delete pvc --all
```
+116
View File
@@ -0,0 +1,116 @@
# backend.yaml — AIOps 后端 (Deployment + Service)
# ----------------------------------------------------------------------------
# - 镜像 aiops-backend:latest(前端通过 go:embed 内嵌进同一二进制)
# - 暴露两个端口:
# 8080 = API + WebSocket
# 80 = go:embed 前端 (SPA 静态资源)
# - 配置: ConfigMap aiops-config 挂载到 /app/configs/config.yaml
# - 密码: Secret aiops-secret 以环境变量注入(DB_PASSWORD / JWT_SECRET)
# - Service: ClusterIP,对外暴露 80 与 8080
#
# 部署: kubectl apply -f backend.yaml
# ----------------------------------------------------------------------------
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: aiops-backend
namespace: devops
labels:
app.kubernetes.io/name: aiops
app.kubernetes.io/component: backend
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app.kubernetes.io/name: aiops
app.kubernetes.io/component: backend
template:
metadata:
labels:
app.kubernetes.io/name: aiops
app.kubernetes.io/component: backend
spec:
containers:
- name: aiops-backend
image: aiops-backend:latest
imagePullPolicy: IfNotPresent
env:
# ---- 数据库连接参数(对应 config.yaml 的 ${DB_*} 占位)----
- {name: DB_HOST, value: mysql}
- {name: DB_PORT, value: "3306"}
- {name: DB_USER, value: aiops}
- {name: DB_NAME, value: aiops}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: aiops-secret
key: database-password
# ---- Redis 连接参数(本环境无密码)----
- {name: REDIS_HOST, value: redis}
- {name: REDIS_PORT, value: "6379"}
- {name: REDIS_PASSWORD, value: ""}
# ---- JWT ----
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: aiops-secret
key: jwt-secret
- {name: TZ, value: Asia/Shanghai}
ports:
- {name: api, containerPort: 8080} # API + WebSocket
- {name: web, containerPort: 80} # go:embed 前端
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
cpu: 500m
memory: 1Gi
livenessProbe:
httpGet:
path: /health
port: api
initialDelaySeconds: 20
periodSeconds: 30
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: api
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
volumeMounts:
- name: config
mountPath: /app/configs
readOnly: true
volumes:
- name: config
configMap:
name: aiops-config
---
# Service — 对内暴露 80 (前端) 与 8080 (API)
apiVersion: v1
kind: Service
metadata:
name: aiops-backend
namespace: devops
labels:
app.kubernetes.io/name: aiops
app.kubernetes.io/component: backend
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: aiops
app.kubernetes.io/component: backend
ports:
- {name: api, port: 8080, targetPort: 8080}
- {name: web, port: 80, targetPort: 80}
+78
View File
@@ -0,0 +1,78 @@
# configmap.yaml — 后端配置 (ConfigMap) + 敏感信息 (Secret)
# ----------------------------------------------------------------------------
# ConfigMap aiops-config:
# - 内含 config.yaml,DB/Redis 连接信息以环境变量占位(${VAR})。
# - 后端 Deployment 将其挂载到 /app/configs/config.yaml(viper 搜索路径 ./configs)。
#
# Secret aiops-secret:
# - 存放 MySQL 密码与 JWT 密钥(明文敏感信息不放 ConfigMap)。
# - 由 backend Deployment / mysql StatefulSet 以环境变量注入。
#
# 部署: kubectl apply -f configmap.yaml
# ----------------------------------------------------------------------------
---
apiVersion: v1
kind: ConfigMap
metadata:
name: aiops-config
namespace: devops
labels:
app.kubernetes.io/name: aiops
app.kubernetes.io/component: backend
data:
config.yaml: |
# AIOps 后端配置
# DB/Redis 连接通过环境变量注入(见 backend Deployment 的 env 段)。
# 敏感字段 (password / jwt) 由 Secret aiops-secret 提供。
# 注意: viper.AutomaticEnv 默认不替换嵌套键中的点(.),如需 DATABASE_HOST ->
# database.host 生效,请在 backend 启动时设置:
# viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
mode: release
port: "8080"
log_level: info
# ---- 数据库 (MySQL) ----
database:
driver: mysql
host: "${DB_HOST}" # -> mysql (k8s Service)
port: 3306
user: "${DB_USER}" # -> aiops
password: "${DB_PASSWORD}" # <- Secret aiops-secret (database-password)
dbname: "${DB_NAME}" # -> aiops
charset: utf8mb4
max_conn: 100
max_idle: 10
# ---- Redis ----
redis:
driver: redis # redka(内嵌) / redis(外部)
host: "${REDIS_HOST}" # -> redis (k8s Service)
port: 6379
password: "${REDIS_PASSWORD}" # 本环境 Redis 无密码,留空
db: 0
# ---- JWT ----
jwt:
secret: "${JWT_SECRET}" # <- Secret aiops-secret (jwt-secret)
expire_hour: 24
# ---- WebSocket ----
websocket:
read_buffer_size: 1024
write_buffer_size: 1024
---
apiVersion: v1
kind: Secret
metadata:
name: aiops-secret
namespace: devops
labels:
app.kubernetes.io/name: aiops
type: Opaque
stringData:
# MySQL 业务账号密码
database-password: "aiops_pass_2026"
# MySQL root 密码(StatefulSet 初始化用)
mysql-root-password: "aiops_root_2026"
# JWT 签名密钥(生产请替换为强随机值)
jwt-secret: "aiops-jwt-secret-change-in-production-2026"
+59
View File
@@ -0,0 +1,59 @@
# ingress.yaml — Ingress 入口路由 (nginx-ingress)
# ----------------------------------------------------------------------------
# 路由规则:
# /api/* -> aiops-backend:8080 (后端 API)
# /ws/* -> aiops-backend:8080 (WebSocket 长连接)
# / -> aiops-backend:80 (go:embed 前端 SPA)
#
# 前提: 集群已安装 nginx Ingress Controller。
# 部署前请修改 host (aiops.local) 与 ingressClassName 为实际值。
# 部署: kubectl apply -f ingress.yaml
# ----------------------------------------------------------------------------
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: aiops-ingress
namespace: devops
labels:
app.kubernetes.io/name: aiops
annotations:
# WebSocket 长连接支持(/ws)
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# 文件上传大小(与后端 upload.max_size 配合)
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
# 显式转发 WebSocket 升级头
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
spec:
ingressClassName: nginx # 按集群实际 IngressClass 调整
rules:
- host: aiops.local # TODO: 修改为实际访问域名
http:
paths:
# ---- API ----
- path: /api
pathType: Prefix
backend:
service:
name: aiops-backend
port:
number: 8080
# ---- WebSocket ----
- path: /ws
pathType: Prefix
backend:
service:
name: aiops-backend
port:
number: 8080
# ---- 前端 (go:embed, 默认兜底) ----
- path: /
pathType: Prefix
backend:
service:
name: aiops-backend
port:
number: 80
+136
View File
@@ -0,0 +1,136 @@
# mysql.yaml — MySQL 8.0 数据库 (StatefulSet + Service + 持久化卷)
# ----------------------------------------------------------------------------
# 组件:
# - mysql-headless (Headless Service): 为 StatefulSet 提供稳定网络标识 mysql-0
# - mysql (ClusterIP Service): 供后端以 mysql:3306 连接
# - mysql (StatefulSet): 单实例 MySQL 8.0
# 账号: 用户 aiops / 密码 aiops_pass_2026 / 数据库 aiops (密码来自 aiops-secret)
# 持久化: volumeClaimTemplates 8Gi (依赖集群 StorageClass 动态供应)
#
# 部署: kubectl apply -f mysql.yaml
# ----------------------------------------------------------------------------
---
# Headless Service — StatefulSet 稳定标识
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
namespace: devops
labels:
app.kubernetes.io/name: mysql
app.kubernetes.io/component: database
spec:
clusterIP: None
selector:
app.kubernetes.io/name: mysql
ports:
- name: mysql
port: 3306
targetPort: 3306
---
# ClusterIP Service — 供应用连接
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: devops
labels:
app.kubernetes.io/name: mysql
app.kubernetes.io/component: database
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: mysql
ports:
- name: mysql
port: 3306
targetPort: 3306
---
# StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: devops
labels:
app.kubernetes.io/name: mysql
app.kubernetes.io/component: database
spec:
serviceName: mysql-headless
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: mysql
template:
metadata:
labels:
app.kubernetes.io/name: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
imagePullPolicy: IfNotPresent
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: aiops-secret
key: mysql-root-password
- name: MYSQL_DATABASE
value: aiops
- name: MYSQL_USER
value: aiops
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: aiops-secret
key: database-password
- name: TZ
value: Asia/Shanghai
args:
# 字符集
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
# 兼容旧客户端认证方式(8.0 默认 caching_sha2_password)
- --default-authentication-plugin=mysql_native_password
ports:
- name: mysql
containerPort: 3306
livenessProbe:
exec:
command:
- sh
- -c
- 'mysqladmin ping -h 127.0.0.1 -uroot -p"$MYSQL_ROOT_PASSWORD"'
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
command:
- sh
- -c
- 'mysqladmin ping -h 127.0.0.1 -uroot -p"$MYSQL_ROOT_PASSWORD"'
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
resources:
requests:
cpu: 250m
memory: 1Gi
limits:
cpu: "1"
memory: 2Gi
volumeMounts:
- name: data
mountPath: /var/lib/mysql
# ---- 持久化卷 (8Gi) ----
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 8Gi
# storageClassName: standard # 按集群实际 StorageClass 调整,留空使用默认
+12
View File
@@ -0,0 +1,12 @@
# namespace.yaml — 创建 devops 命名空间
# 所有 AIOps 资源(MySQL / Redis / 后端 / Ingress)统一部署在该命名空间下。
# 必须最先 apply,其它资源依赖该命名空间存在。
# 部署: kubectl apply -f namespace.yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: devops
labels:
app.kubernetes.io/name: aiops
app.kubernetes.io/part-of: aiops
+111
View File
@@ -0,0 +1,111 @@
# redis.yaml — Redis 7 缓存 (StatefulSet + Service + 持久化卷)
# ----------------------------------------------------------------------------
# 组件:
# - redis-headless (Headless Service): StatefulSet 稳定标识 redis-0
# - redis (ClusterIP Service): 供后端以 redis:6379 连接
# - redis (StatefulSet): 单实例 Redis 7,无密码,db 0
# 持久化: volumeClaimTemplates 2Gi + AOF/RDB 落盘
#
# 部署: kubectl apply -f redis.yaml
# ----------------------------------------------------------------------------
---
# Headless Service — StatefulSet 稳定标识
apiVersion: v1
kind: Service
metadata:
name: redis-headless
namespace: devops
labels:
app.kubernetes.io/name: redis
app.kubernetes.io/component: cache
spec:
clusterIP: None
selector:
app.kubernetes.io/name: redis
ports:
- name: redis
port: 6379
targetPort: 6379
---
# ClusterIP Service — 供应用连接
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: devops
labels:
app.kubernetes.io/name: redis
app.kubernetes.io/component: cache
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: redis
ports:
- name: redis
port: 6379
targetPort: 6379
---
# StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
namespace: devops
labels:
app.kubernetes.io/name: redis
app.kubernetes.io/component: cache
spec:
serviceName: redis-headless
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: redis
template:
metadata:
labels:
app.kubernetes.io/name: redis
spec:
containers:
- name: redis
image: redis:7-alpine
imagePullPolicy: IfNotPresent
command: ["redis-server"]
args:
- --appendonly yes # 开启 AOF
- --save 60 1000 # RDB: 60s 内 1000 次变更快照
- --databases 1 # 仅使用 db 0
- --maxmemory 256mb
- --maxmemory-policy allkeys-lru
ports:
- name: redis
containerPort: 6379
livenessProbe:
exec:
command: ["redis-cli", "ping"]
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
exec:
command: ["redis-cli", "ping"]
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
volumeMounts:
- name: data
mountPath: /data
# ---- 持久化卷 (2Gi) ----
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
# storageClassName: standard # 按集群实际 StorageClass 调整
+91
View File
@@ -0,0 +1,91 @@
# ============================================================
# AIOps 单容器 docker-compose(embed 模式)
# 前端嵌入后端二进制,不需要独立 nginx 容器
# 用法: docker-compose -f docker-compose.embed.yml up -d --build
# 署名 Logan.Li
# ============================================================
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: aiops-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: aiops
MYSQL_USER: aiops
MYSQL_PASSWORD: aiops_pass_2026
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
command: >
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
networks:
- aiops-network
redis:
image: redis:7-alpine
container_name: aiops-redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- aiops-network
# 单容器:后端 API (:8080) + 嵌入前端 (:8080)
# nginx 不再需要,gin 直接服务静态文件
aiops:
build:
context: .
dockerfile: backend/Dockerfile.embed
container_name: aiops-app
restart: unless-stopped
ports:
- "80:8080" # 前端 + API 都走 8080
- "8080:8080" # 也可以直接访问 8080
volumes:
- ./backend/config.yaml:/app/config.yaml
- app_logs:/app/logs
- app_uploads:/app/uploads
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- aiops-network
volumes:
mysql_data:
redis_data:
app_logs:
app_uploads:
networks:
aiops-network:
driver: bridge