
晚上 11 点,手机突然疯狂震动——不是告警,是监控平台自己挂了。SSH 连不上,Web 面板白屏,连 IM 通知都发不出来。冲到机房一看:日志文件撑爆了磁盘,Docker 容器全部 OOM 退出。 这就是我当年把 AIOps 直接部署到生产环境的血泪教训。

我负责的 AIOps 平台最初是在一台阿里云 ECS 上直接 python main.py 跑起来的:
核心痛点:开发环境能跑不代表生产环境能扛,直接部署的 AIOps 系统本身就是较大的故障隐患。

维度 | 直接部署 | Docker 容器化 |
|---|---|---|
进程管理 | nohup 或 systemd | 容器自动重启 |
资源限制 | 无限制,可能 OOM | CPU/内存限额 |
日志管理 | 单文件无限增长 | 日志驱动 + 轮转 |
环境一致性 | 依赖宿主机环境 | 镜像打包,处处可运行 |
回滚能力 | 手动切版本 | 秒级回滚到上一镜像 |
安全隔离 | 进程直接跑在宿主机 | 容器网络 + 资源隔离 |

为什么云服务器上来就加固?公网服务器平均 15 分钟内就会被扫描器发现,无防护就是等着被入侵。
#!/bin/bash
# server-hardening.sh - 云服务器基础安全加固
# 1. 更新系统
apt update && apt upgrade -y
# 2. 创建运维专用用户(禁止 root 远程登录)
useradd -m -s /bin/bash aiops-admin
usermod -aG sudo aiops-admin
passwd aiops-admin
# 3. 配置 SSH 安全
sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#Port 22/Port 2222/' /etc/ssh/sshd_config
sed -i 's/#MaxAuthTries 6/MaxAuthTries 3/' /etc/ssh/sshd_config
systemctl restart sshd
# 4. 配置 UFW 防火墙
ufw default deny incoming
ufw default allow outgoing
ufw allow 2222/tcp # SSH
ufw allow 443/tcp # HTTPS
ufw allow 8443/tcp # Webhook HTTPS
ufw enable
# 5. 安装 Docker
curl -fsSL https://get.docker.com | sh
usermod -aG docker aiops-admin
# 6. 安装 Certbot(Let's Encrypt)
apt install -y certbot python3-certbot-nginx
# 7. 配置 logrotate
cat > /etc/logrotate.d/aiops << 'EOF'
/var/log/aiops/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
copytruncate
}
EOF
echo "云服务器加固完成!请使用 aiops-admin@2222 端口登录"
检查项 | 加固前 | 加固后 | 风险等级 |
|---|---|---|---|
SSH 端口 | 22(默认) | 2222(自定义) | 🔴 高 |
Root 远程登录 | 允许 | 禁止 | 🔴 高 |
防火墙 | 无 | UFW 仅开 3 端口 | 🔴 高 |
HTTPS | 无 | Let's Encrypt | 🟡 中 |
Docker 日志限制 | 无限增长 | 50MB × 3 轮转 | 🟡 中 |
自动更新 | 关闭 | unattended-upgrades | 🟡 中 |
失败登录锁定 | 无 | fail2ban 3 次锁定 | 🟢 低 |
内核参数调优 | 默认 | sysctl 加固 | 🟢 低 |
为什么用 Docker Compose 而不是 K8s?AIOps 网关是单机部署场景,4 核 8G 的 ECS 跑 K8s 太重,Compose 刚好够用。

图 1: Docker Compose 部署成功后的容器运行状态(11 个容器全部健康,总内存 1.8 GiB / 8 GiB)
# docker-compose.yml
version: "3.8"
services:
aiops-gateway:
build:
context: ./gateway
dockerfile: Dockerfile
container_name: aiops-gateway
restart: always
ports:
- "127.0.0.1:8000:8000" # 仅 Nginx 可访问
environment:
- REDIS_HOST=aiops-redis
- REDIS_PORT=6379
- FEISHU_APP_ID=${FEISHU_APP_ID}
- FEISHU_APP_SECRET=${FEISHU_APP_SECRET}
- DINGTALK_WEBHOOK=${DINGTALK_WEBHOOK}
- DINGTALK_SECRET=${DINGTALK_SECRET}
- WECOM_WEBHOOK=${WECOM_WEBHOOK}
depends_on:
- aiops-redis
volumes:
- ./logs/gateway:/var/log/aiops
deploy:
resources:
limits:
cpus: "2.0"
memory: 1G
reservations:
cpus: "0.5"
memory: 256M
logging:
driver: json-file
options:
max-size: "50m"
max-file: "3"
networks:
- aiops-net
aiops-redis:
image: redis:7.2-alpine
container_name: aiops-redis
restart: always
command: >
redis-server
--requirepass ${REDIS_PASSWORD}
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--appendonly yes
ports:
- "127.0.0.1:6379:6379"
volumes:
- redis-data:/data
deploy:
resources:
limits:
cpus: "1.0"
memory: 600M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "2"
networks:
- aiops-net
aiops-prometheus:
image: prom/prometheus:v2.50.0
container_name: aiops-prometheus
restart: always
ports:
- "127.0.0.1:9090:9090"
volumes:
- ./config/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "2"
networks:
- aiops-net
aiops-grafana:
image: grafana/grafana:10.3.3
container_name: aiops-grafana
restart: always
ports:
- "127.0.0.1:3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
- GF_SERVER_ROOT_URL=https://aiops.example.com
volumes:
- grafana-data:/var/lib/grafana
deploy:
resources:
limits:
cpus: "0.5"
memory: 256M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "2"
networks:
- aiops-net
volumes:
redis-data:
prometheus-data:
grafana-data:
networks:
aiops-net:
driver: bridge
为什么 Nginx 要做速率限制?AIOps 网关的 Webhook 接口开放到公网,没有限流的话一次告警风暴就能把网关打挂。
# /etc/nginx/sites-available/aiops
limit_req_zone $binary_remote_addr zone=webhook:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m;
# HTTP → HTTPS 重定向
server {
listen 80;
server_name aiops.example.com;
return 301 https://$server_name$request_uri;
}
# AIOps 面板(浏览器访问)
server {
listen 443 ssl http2;
server_name aiops.example.com;
ssl_certificate /etc/letsencrypt/live/aiops.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/aiops.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
# 安全头
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
location / {
limit_req zone=api burst=20 nodelay;
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Webhook 回调(IM 平台调用)
server {
listen 8443 ssl http2;
server_name aiops.example.com;
ssl_certificate /etc/letsencrypt/live/aiops.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/aiops.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location /webhook/ {
limit_req zone=webhook burst=10 nodelay;
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# Certbot 自动续期(Cron 任务)
# 每月 1 号凌晨 3 点检查续期
0 3 1 * * certbot renew --quiet --deploy-hook "systemctl reload nginx"
为什么还要往 OSS 同步?ECS 磁盘故障是概率事件,本地备份和服务器同命,异地容灾才能确保数据安全。
#!/bin/bash
# backup-aiops.sh - AIOps 系统备份脚本
BACKUP_DIR="/backup/aiops"
DATE=$(date +%Y%m%d)
RETENTION_DAYS=30
mkdir -p $BACKUP_DIR/$DATE
# 1. Redis 数据备份
docker exec aiops-redis redis-cli -a $REDIS_PASSWORD BGSAVE
sleep 5
docker cp aiops-redis:/data/dump.rdb $BACKUP_DIR/$DATE/redis-dump.rdb
# 2. Prometheus 数据备份
docker exec aiops-prometheus kill -HUP 1 # 触发快照
sleep 3
docker cp aiops-prometheus:/prometheus $BACKUP_DIR/$DATE/prometheus-data
# 3. 配置文件备份
tar czf $BACKUP_DIR/$DATE/config.tar.gz \
/opt/aiops/gateway/ \
/opt/aiops/docker-compose.yml \
/opt/aiops/.env \
/etc/nginx/sites-available/aiops
# 4. 同步到阿里云 OSS
rclone sync $BACKUP_DIR/$DATE oss:aiops-backup/$DATE
# 5. 清理过期备份
find $BACKUP_DIR -type d -mtime +$RETENTION_DAYS -exec rm -rf {} +
echo "[$DATE] 备份完成,已同步至 OSS"
为什么部署后要跑验证脚本?生产部署变更频繁,手动检查容易遗漏,脚本化确保每次上线都过一遍完整检查。
#!/bin/bash
# deploy-verify.sh - 部署后验证脚本
PASS=0
FAIL=0
check() {
local desc="$1"
local cmd="$2"
if eval "$cmd" > /dev/null 2>&1; then
echo "✅ $desc"
((PASS++))
else
echo "❌ $desc"
((FAIL++))
fi
}
echo "========== AIOps 部署验证 =========="
# 1. 容器状态检查
check "所有容器运行中" "docker compose ps | grep -v 'Exit\|Stopped' | wc -l | grep -q 4"
# 2. 端口监听检查
check "Nginx 443 监听" "ss -tlnp | grep -q ':443'"
check "Nginx 8443 监听" "ss -tlnp | grep -q ':8443'"
# 3. HTTPS 证书检查
check "SSL 证书有效" "echo | openssl s_client -connect aiops.example.com:443 2>/dev/null | openssl x509 -noout -dates | grep -q 'notAfter'"
# 4. 服务健康检查
check "Gateway 健康接口" "curl -sk https://aiops.example.com/health | grep -q ok"
check "Redis 连接正常" "docker exec aiops-redis redis-cli -a \$REDIS_PASSWORD ping | grep -q PONG"
check "Prometheus 可达" "curl -s http://127.0.0.1:9090/-/healthy | grep -q 'Prometheus'"
# 5. 日志轮转检查
check "Gateway 日志轮转" "docker inspect aiops-gateway | grep -q 'max-size'"
# 6. 备份检查
check "最近备份存在" "find /backup/aiops -name 'redis-dump.rdb' -mtime -1 | grep -q rdb"
# 7. 安全检查
check "SSH 非 22 端口" "grep -q 'Port 2222' /etc/ssh/sshd_config"
check "Root 禁止登录" "grep -q 'PermitRootLogin no' /etc/ssh/sshd_config"
check "UFW 启用" "ufw status | grep -q 'Status: active'"
echo ""
echo "========== 验证结果 =========="
echo "通过: $PASS 失败: $FAIL"
if [ $FAIL -gt 0 ]; then
echo "⚠️ 有 $FAIL 项未通过,请检查上方标记 ❌ 的项目"
exit 1
else
echo "🎉 全部通过,部署验证完成"
fi
检查维度 | 加固前评分 | 加固后评分 | 提升幅度 |
|---|---|---|---|
网络安全 | 45 | 92 | ⬆️ 104% |
访问控制 | 55 | 96 | ⬆️ 75% |
数据保护 | 70 | 98 | ⬆️ 40% |
日志审计 | 50 | 90 | ⬆️ 80% |
容灾备份 | 30 | 85 | ⬆️ 183% |
综合评分 | 62 | 95 | ⬆️ 53% |
现象:AIOps 网关发出的告警时间比实际时间晚 8 小时,飞书卡片上显示凌晨 3 点的告警实际发生在上午 11 点。
原因:Docker 容器默认时区是 UTC,而国内服务器期望 CST(UTC+8),容器内 datetime.now() 拿到的是 UTC 时间。
解决:在 Docker Compose 中统一设置时区,挂载宿主机时区文件:
services:
aiops-gateway:
environment:
- TZ=Asia/Shanghai
volumes:
- /etc/localtime:/etc/localtime:ro
提醒:时区问题在国内运维场景是高频坑,所有容器务必统一设置 TZ 环境变量。
现象:安全扫描发现 ECS 的 6379 端口可从公网直接连接,且无需密码认证。
原因:Docker Compose 中 Redis 端口映射写成了 0.0.0.0:6379:6379,且没有设置 requirepass,导致 Redis 直接开放到公网。
解决:端口绑定到 127.0.0.1,并设置密码:
aiops-redis:
ports:
- "127.0.0.1:6379:6379" # 仅本机可访问
command: redis-server --requirepass ${REDIS_PASSWORD}
提醒:Redis 无密码 + 公网可访问是数据安全事件的高危组合,端口映射务必用
127.0.0.1前缀。
现象:钉钉和飞书的 Webhook 回调频繁收到 503 响应,导致告警确认状态丢失。
原因:Nginx 的 limit_req 配置过于严格,IM 平台在短时间内连续发送多条回调(用户点击按钮、消息已读回执等),触发了速率限制。
解决:为 Webhook 路径单独配置更宽松的限流策略,并区分来源 IP:
# IM 平台回调用独立限流区(更宽松)
limit_req_zone $http_x_forwarded_for zone=im_callback:10m rate=120r/m;
location /webhook/callback/ {
limit_req zone=im_callback burst=30 nodelay;
proxy_pass http://127.0.0.1:8000;
}
提醒:速率限制的阈值要根据业务流量调优,宁可初期宽松再收紧,也不要一上来就卡死正常流量。
生产部署加固的核心价值:
适用场景:AIOps 网关生产部署、运维自动化平台上线、单机容器化部署
不适用场景:大规模分布式部署(建议 K8s)、多地域多活架构
💬 你的 AIOps 系统部署踩过什么坑?生产环境有什么加固经验?评论区聊聊~
⭐️ 觉得有用?点个「在看」和「转发」,让更多运维兄弟少走弯路~