提交 8a91d8c5 authored 作者: 陈泽健's avatar 陈泽健

feat(自动化部署): Tengine VIP 入口部署脚本与文档(arm/x86)

- arm/x86 架构 deploy_tengine_vip.sh:Tengine VIP 入口部署脚本
- tengine VIP 入口容器部署文档(arm/x86)
- arm onlyoffice 自动安装脚本
上级 e8774f05
#!/bin/bash
# --- 颜色定义 ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # 无颜色
# --- 配置 ---
PORT=18080
IMAGE_FILE="./onlyoffice-documentserver-arm64.tar.gz"
CONTAINER_NAME="onlyoffice-document-server"
JWT_SECRET="dNrprU&2S"
# --- 辅助函数 ---
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# ==========================================
# 【新增】0. 检查 CPU 架构
# ==========================================
log_info "检查服务器 CPU 架构..."
ARCH=$(uname -m)
if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
log_success "当前架构为 $ARCH (ARM64),符合要求。"
else
log_error "当前服务器架构为 [$ARCH],不支持运行此 OnlyOffice ARM 镜像!"
log_error "当前 OnlyOffice 镜像仅支持 ARM64 (aarch64) 架构。"
log_error "如需在 x86 架构上运行,请使用 x86 版部署脚本。"
exit 1
fi
log_info "开始部署 OnlyOffice..."
# 1. 设置内核参数
log_info "正在配置内核参数..."
sysctl -w vm.max_map_count=262144 >/dev/null 2>&1
sysctl -w kernel.unprivileged_userns_clone=1 >/dev/null 2>&1 || true
# 2. 检查 Swap (仅提示)
log_info "检查 Swap 空间..."
free -h
SWAP_TOTAL=$(free -m | awk '/^Swap:/{print $2}')
if [ "$SWAP_TOTAL" -eq 0 ]; then
log_warn "未检测到 Swap!建议手动配置以防内存不足。"
else
log_success "Swap 空间正常 (${SWAP_TOTAL}MB)"
fi
# 3. 检查 Docker
log_info "检查 Docker 服务..."
if ! docker info >/dev/null 2>&1; then
log_error "Docker 未运行,请启动 Docker 后重试。"
exit 1
fi
# 4. 加载镜像
log_info "正在加载镜像文件:$IMAGE_FILE"
if [ ! -f "$IMAGE_FILE" ]; then
log_error "镜像文件不存在:$IMAGE_FILE"
exit 1
fi
if [[ "$IMAGE_FILE" == *.gz ]]; then
gunzip -c "$IMAGE_FILE" | docker load
else
docker load -i "$IMAGE_FILE"
fi
if [ $? -ne 0 ]; then
log_error "镜像加载失败!"
exit 1
fi
log_success "镜像加载成功"
# 5. 检查端口冲突
log_info "检查端口 $PORT 是否被占用..."
if command -v ss >/dev/null 2>&1; then
CHECK_CMD="ss -tulpn"
elif command -v netstat >/dev/null 2>&1; then
CHECK_CMD="netstat -tulpn"
else
CHECK_CMD=""
fi
if [ -n "$CHECK_CMD" ] && $CHECK_CMD 2>/dev/null | grep -q ":$PORT "; then
log_error "端口 $PORT 已被占用!请修改 PORT 变量或关闭占用程序。"
exit 1
else
log_success "端口 $PORT 可用"
fi
# 6. 准备数据目录
log_info "创建数据目录..."
mkdir -p /app/onlyoffice/{logs,log4j,mysql,rabbitmq,redis,documentserver-data}
# 7. 启动容器
log_info "正在启动容器..."
docker run -d --restart=always \
--name $CONTAINER_NAME \
-p $PORT:80 \
-v /app/onlyoffice/logs:/var/log/onlyoffice \
-v /app/onlyoffice/log4j:/var/log/onlyoffice/documentserver/log4js \
-v /app/onlyoffice/mysql:/var/lib/mysql \
-v /app/onlyoffice/rabbitmq:/var/lib/rabbitmq \
-v /app/onlyoffice/redis:/var/lib/redis \
-v /app/onlyoffice/documentserver-data:/var/www/onlyoffice/Data \
-e JWT_ENABLED=false \
-e JWT_SECRET=$JWT_SECRET \
-e JWT_HEADER=Authorization \
--shm-size="1g" \
--memory="4g" \
--ulimit nofile=65536:65536 \
--security-opt seccomp=unconfined \
onlyoffice/documentserver:latest
if [ $? -ne 0 ]; then
log_error "容器启动失败!"
exit 1
fi
log_success "✅ 容器已启动,正在进入实时日志监控 (按 Ctrl+C 退出日志)..."
echo ""
# 8. 【核心修改】直接实时打印日志
docker logs -f $CONTAINER_NAME
\ No newline at end of file
#!/bin/bash
# ============================================================
# Tengine VIP 入口容器一键部署脚本 (ARM)
# 适用于:openKylin / openEuler (aarch64)
# 角色:统一平台集群入口(VIP)
# - 443: HTTPS 反向代理 + 负载均衡
# - 1883: MQTT TCP 负载均衡
# 版本:2.0 — 使用预编译流式镜像 utengine:v1-stream,内置 tengine.conf
# 日期:2026-07-15
# ============================================================
set -e
# ==================== 配置变量 ====================
# ARM 服务器 docker 需 sudo(openkylin 用户)
DOCKER_CMD="sudo docker"
DEPLOY_DIR="/data/temp/tengine"
IMAGE_FILE="${DEPLOY_DIR}/utengine_v1_stream_arm.tar"
IMAGE_NAME="utengine:v1-stream" # 已含 --with-stream + 内置 tengine.conf
CONTAINER_NAME="utengine"
OLD_CONTAINER="unginx"
# 可选:自定义 tengine.conf(如 EMQX 节点 IP 与默认不同)
CUSTOM_MAIN_CONF="${DEPLOY_DIR}/tengine.conf"
HOST_HTTPS_PORT="443"
HOST_MQTT_PORT="1883"
VIP_IP="192.168.9.92"
declare -a MOUNTS=(
"/etc/localtime:/etc/localtime:ro"
"/data/security/nginx_cert:/data/security/nginx_cert:ro"
"/data/services/web:/data/services/web:rw"
"/data/middleware/nginx/config:/etc/tengine/conf.d:rw"
"/data/middleware/nginx/main:/etc/tengine/main:rw"
"/data/middleware/nginx/data/html:/usr/local/tengine/html:rw"
"/data/middleware/nginx/data/cache:/var/cache/nginx:rw"
"/data/middleware/nginx/log:/var/log/tengine:rw"
)
# ==================== 颜色输出 ====================
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
# ==================== 前置检查 ====================
preflight_check() {
log_step "执行前置检查..."
if ! sudo docker version &> /dev/null; then
log_error "Docker 未安装或 sudo 权限不足"
exit 1
fi
if [ ! -f "$IMAGE_FILE" ]; then
log_error "镜像文件不存在: $IMAGE_FILE"
echo " 请将 utengine_v1_stream_arm.tar 放到 ${DEPLOY_DIR}/"
exit 1
fi
for mount in "${MOUNTS[@]}"; do
IFS=':' read -r src dest mode <<< "$mount"
if [ ! -d "$src" ] && [ ! -f "$src" ]; then
sudo mkdir -p "$src" 2>/dev/null || log_warn "无法创建 $src,请手动创建"
fi
done
log_info "前置检查通过"
}
# ==================== 加载镜像 ====================
load_image() {
if ${DOCKER_CMD} image inspect "$IMAGE_NAME" &> /dev/null; then
log_warn "镜像 $IMAGE_NAME 已存在"
read -p "是否重新加载?[y/N]: " reload
if [ "$reload" != "y" ] && [ "$reload" != "Y" ]; then
log_info "跳过加载,使用已有镜像"
return 0
fi
${DOCKER_CMD} rmi "$IMAGE_NAME" 2>/dev/null || true
fi
log_step "加载镜像..."
${DOCKER_CMD} load -i "$IMAGE_FILE"
log_info "镜像加载完成"
}
# ==================== 停止旧容器 ====================
stop_old_container() {
log_step "停止旧容器..."
if ${DOCKER_CMD} ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
log_info "停止现有 ${CONTAINER_NAME} 容器..."
${DOCKER_CMD} stop "$CONTAINER_NAME" 2>/dev/null || true
${DOCKER_CMD} rm "$CONTAINER_NAME" 2>/dev/null || true
elif ${DOCKER_CMD} ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
${DOCKER_CMD} rm "$CONTAINER_NAME" 2>/dev/null || true
fi
if ${DOCKER_CMD} ps --format '{{.Names}}' | grep -q "^${OLD_CONTAINER}$"; then
log_info "停止旧 ${OLD_CONTAINER} 容器..."
${DOCKER_CMD} stop "$OLD_CONTAINER"
log_info "${OLD_CONTAINER} 已停止"
fi
}
# ==================== 启动 Tengine VIP 容器 ====================
start_tengine() {
log_step "启动 Tengine VIP 容器..."
${DOCKER_CMD} run -d --name ${CONTAINER_NAME} \
--restart always --network bridge \
-p ${HOST_HTTPS_PORT}:${HOST_HTTPS_PORT} \
-p ${HOST_MQTT_PORT}:${HOST_MQTT_PORT} \
-v /etc/localtime:/etc/localtime:ro \
-v /data/security/nginx_cert:/data/security/nginx_cert:ro \
-v /data/services/web:/data/services/web:rw \
-v /data/middleware/nginx/config:/etc/tengine/conf.d:rw \
-v /data/middleware/nginx/data/html:/usr/local/tengine/html:rw \
-v /data/middleware/nginx/data/cache:/var/cache/nginx:rw \
-v /data/middleware/nginx/log:/var/log/tengine:rw \
${IMAGE_NAME}
sleep 3
if ! ${DOCKER_CMD} ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
log_error "容器启动失败"
${DOCKER_CMD} logs "$CONTAINER_NAME" 2>&1 | tail -20
exit 1
fi
log_info "容器已启动"
# tengine.conf 映射:如果 main 目录没有 tengine.conf,从镜像导出
if [ ! -f "/data/middleware/nginx/main/tengine.conf" ]; then
log_step "main/tengine.conf 不存在,从镜像导出..."
${DOCKER_CMD} cp "${CONTAINER_NAME}:/etc/tengine/tengine.conf" /data/middleware/nginx/main/tengine.conf
log_info "tengine.conf 已导出到 /data/middleware/nginx/main/tengine.conf"
else
log_step "main/tengine.conf 已存在,替换容器内配置..."
${DOCKER_CMD} cp /data/middleware/nginx/main/tengine.conf "${CONTAINER_NAME}:/etc/tengine/tengine.conf"
if ${DOCKER_CMD} exec "$CONTAINER_NAME" nginx -t 2>&1; then
${DOCKER_CMD} restart "$CONTAINER_NAME" 2>&1
sleep 3
log_info "已加载自定义配置(容器已重启,stream 端口已生效)"
else
log_error "tengine.conf 语法错误,使用镜像默认配置"
${DOCKER_CMD} cp "${CONTAINER_NAME}:/etc/tengine/tengine.conf" /data/middleware/nginx/main/tengine.conf.failed
${DOCKER_CMD} start "$CONTAINER_NAME" 2>&1
fi
fi
}
# ==================== 验证部署 ====================
verify_deployment() {
log_step "验证部署..."
echo ""
echo "=== 容器状态 ==="
${DOCKER_CMD} ps --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
echo "=== HTTPS 测试 ==="
if curl -skI --connect-timeout 5 https://127.0.0.1/ 2>&1 | head -3; then
log_info "HTTPS 响应正常"
else
log_warn "HTTPS 测试异常"
fi
echo ""
echo "=== MQTT 端口测试 ==="
if command -v python3 &> /dev/null; then
if python3 -c "import socket; s=socket.socket(); s.settimeout(3); s.connect(('127.0.0.1',${HOST_MQTT_PORT})); print('OK'); s.close()" 2>/dev/null; then
log_info "MQTT ${HOST_MQTT_PORT} 端口可连接"
else
log_warn "MQTT 端口测试失败"
${DOCKER_CMD} exec "$CONTAINER_NAME" cat /proc/net/tcp 2>/dev/null | grep ':075B' && \
log_info "容器内 1883 已监听 (/proc/net/tcp 确认)" || \
log_error "容器内 1883 未监听!"
fi
fi
echo ""
echo "=== 编译模块 ==="
${DOCKER_CMD} exec "$CONTAINER_NAME" nginx -V 2>&1 | grep "with-stream"
echo ""
log_info "======== VIP 入口部署完成 ========"
echo ""
echo " HTTPS: https://${VIP_IP}:${HOST_HTTPS_PORT}"
echo " MQTT: tcp://${VIP_IP}:${HOST_MQTT_PORT}"
echo " 镜像: ${IMAGE_NAME}"
echo " HTTP配置: /data/middleware/nginx/config/unified443.conf"
echo " 日志: /data/middleware/nginx/log/"
echo " 回滚: ${DOCKER_CMD} stop ${CONTAINER_NAME} && ${DOCKER_CMD} start ${OLD_CONTAINER}"
echo ""
echo " ⚠️ 改 unified443.conf 后: ${DOCKER_CMD} exec ${CONTAINER_NAME} nginx -s reload"
echo " ⚠️ 改 tengine.conf (EMQX节点) 后: ${DOCKER_CMD} restart ${CONTAINER_NAME}"
echo ""
}
# ==================== 主流程 ====================
main() {
echo ""
echo "============================================"
echo " Tengine VIP 入口部署 (ARM) v2.0"
echo " 日期: $(date '+%Y-%m-%d %H:%M:%S')"
echo " VIP: ${VIP_IP}"
echo "============================================"
echo ""
preflight_check
load_image
stop_old_container
start_tengine
verify_deployment
}
main "$@"
# Tengine VIP 入口容器部署文档(ARM)
> **版本**:v1.0
> **日期**:2026-07-14
> **适用系统**:openEuler 24.03 (aarch64)
> **角色**:统一平台集群 VIP 入口
---
## 1. 与 X86 版本的差异
| 项目 | X86 版 | ARM 版(本文档) |
|------|--------|-----------------|
| 系统 | UOS Server 20 / 麒麟 V10 | openKylin / openEuler |
| 镜像包 | `utengine_v1_stream_x86.tar` | `utengine_v1_stream_arm.tar` |
| 镜像名 | `utengine:v1-stream` | `utengine:v1-stream` |
| docker 命令 | `docker` | `sudo docker`(openkylin 用户) |
| 部署逻辑 | 相同 | 相同 |
> **完整部署说明请参考**:[`../x86架构/tengine/tengine_VIP入口容器部署文档.md`](../x86架构/tengine/tengine_VIP入口容器部署文档.md)
本文档仅说明 ARM 版特有事项。
---
## 2. 部署步骤
```bash
# 1. 修改脚本中的后端节点 IP
vi /data/temp/tengine/deploy_tengine_vip.sh
# 修改 BACKEND_NODE1/2/3 和 VIP_IP
# 2. 执行部署(需要 sudo 权限)
cd /data/temp/tengine
chmod +x deploy_tengine_vip.sh
./deploy_tengine_vip.sh
```
---
## 3. ARM 特有注意事项
### 3.1 sudo 权限
ARM 服务器使用 `admin` 用户,docker 命令需 sudo。脚本中已封装为 `DOCKER_CMD="sudo docker"`,执行脚本时需要 admin 用户有免密 sudo 权限。
如未配置免密 sudo:
```bash
# 临时配置(root 执行)
echo "admin ALL=(ALL) NOPASSWD: /usr/bin/docker" >> /etc/sudoers
```
### 3.2 镜像架构
ARM 镜像必须是在 aarch64 环境编译的。如果从 x86 迁移,需要重新编译 tengine:
```bash
# 在 ARM 服务器上
cd /data/temp/tengine-3.1.0
sudo yum install -y gcc make pcre2-devel openssl-devel zlib-devel
./configure \
--prefix=/usr/local/tengine \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--with-http_sub_module \
--with-stream \
--with-stream_ssl_module \
--conf-path=/etc/tengine/tengine.conf \
--error-log-path=/var/log/tengine/error.log \
--http-log-path=/var/log/tengine/access.log \
--pid-path=/var/run/tengine.pid
make -j$(nproc)
sudo make install
# 验证 stream 模块
/usr/local/tengine/sbin/nginx -V 2>&1 | grep stream
```
### 3.3 端口验证
ARM 上 `ss` 可能需要 sudo:
```bash
sudo ss -tlnp | grep -E ":443|:1883"
```
---
## 4. 其余说明
以下内容与 X86 版完全一致,请参考 X86 文档:
- 架构图
- tengine.conf 关键配置说明
- 重新编译 tengine 镜像步骤
- 客户端配置
- 故障排查
- 注意事项
**参考文档**[`../x86架构/tengine/tengine_VIP入口容器部署文档.md`](../x86架构/tengine/tengine_VIP入口容器部署文档.md)
#!/bin/bash
# ============================================================
# Tengine VIP 入口容器一键部署脚本 (X86)
# 适用于:UOS Server 20 / 麒麟 V10 (x86_64)
# 角色:统一平台集群入口(VIP)
# - 443: HTTPS 反向代理 + 负载均衡
# - 1883: MQTT TCP 负载均衡
# 版本:2.0 — 使用预编译流式镜像 utengine:v1-stream,内置 tengine.conf
# 日期:2026-07-15
# ============================================================
set -e
# ==================== 配置变量 ====================
DEPLOY_DIR="/data/temp/tengine"
IMAGE_FILE="${DEPLOY_DIR}/utengine_v1_stream_x86.tar"
IMAGE_NAME="utengine:v1-stream" # 已含 --with-stream + 内置 tengine.conf
CONTAINER_NAME="utengine"
OLD_CONTAINER="unginx"
# 可选:自定义 tengine.conf(如 EMQX 节点 IP 与默认不同)
CUSTOM_MAIN_CONF="${DEPLOY_DIR}/tengine.conf"
HOST_HTTPS_PORT="443"
HOST_MQTT_PORT="1883"
VIP_IP="192.168.5.40"
declare -a MOUNTS=(
"/etc/localtime:/etc/localtime:ro"
"/data/security/nginx_cert:/data/security/nginx_cert:ro"
"/data/services/web:/data/services/web:rw"
"/data/middleware/nginx/config:/etc/tengine/conf.d:rw"
"/data/middleware/nginx/main:/etc/tengine/main:rw"
"/data/middleware/nginx/data/html:/usr/local/tengine/html:rw"
"/data/middleware/nginx/data/cache:/var/cache/nginx:rw"
"/data/middleware/nginx/log:/var/log/tengine:rw"
)
# ==================== 颜色输出 ====================
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
# ==================== 前置检查 ====================
preflight_check() {
log_step "执行前置检查..."
if ! command -v docker &> /dev/null; then
log_error "Docker 未安装"
exit 1
fi
if [ ! -f "$IMAGE_FILE" ]; then
log_error "镜像文件不存在: $IMAGE_FILE"
echo " 请将 utengine_v1_stream_x86.tar 放到 ${DEPLOY_DIR}/"
exit 1
fi
for mount in "${MOUNTS[@]}"; do
IFS=':' read -r src dest mode <<< "$mount"
if [ ! -d "$src" ] && [ ! -f "$src" ]; then
# 首次部署目录可能不存在,自动创建
mkdir -p "$src" 2>/dev/null || log_warn "无法创建 $src,请手动创建"
fi
done
log_info "前置检查通过"
}
# ==================== 加载镜像 ====================
load_image() {
if docker image inspect "$IMAGE_NAME" &> /dev/null; then
log_warn "镜像 $IMAGE_NAME 已存在"
read -p "是否重新加载?[y/N]: " reload
if [ "$reload" != "y" ] && [ "$reload" != "Y" ]; then
log_info "跳过加载,使用已有镜像"
return 0
fi
docker rmi "$IMAGE_NAME" 2>/dev/null || true
fi
log_step "加载镜像..."
docker load -i "$IMAGE_FILE"
log_info "镜像加载完成 && 含 --with-stream"
}
# ==================== 停止旧容器 ====================
stop_old_container() {
log_step "停止旧容器..."
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
log_info "停止现有 ${CONTAINER_NAME} 容器..."
docker stop "$CONTAINER_NAME" 2>/dev/null || true
docker rm "$CONTAINER_NAME" 2>/dev/null || true
elif docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker rm "$CONTAINER_NAME" 2>/dev/null || true
fi
if docker ps --format '{{.Names}}' | grep -q "^${OLD_CONTAINER}$"; then
log_info "停止旧 ${OLD_CONTAINER} 容器..."
docker stop "$OLD_CONTAINER"
log_info "${OLD_CONTAINER} 已停止(保留容器,回滚: docker start ${OLD_CONTAINER})"
fi
}
# ==================== 启动 Tengine VIP 容器 ====================
start_tengine() {
log_step "启动 Tengine VIP 容器..."
local run_cmd="docker run -d --name ${CONTAINER_NAME}"
run_cmd+=" --restart always --network bridge"
run_cmd+=" -p ${HOST_HTTPS_PORT}:${HOST_HTTPS_PORT}"
run_cmd+=" -p ${HOST_MQTT_PORT}:${HOST_MQTT_PORT}"
for mount in "${MOUNTS[@]}"; do
IFS=':' read -r src dest mode <<< "$mount"
if [ "$mode" = "ro" ]; then
run_cmd+=" -v ${src}:${dest}:ro"
else
run_cmd+=" -v ${src}:${dest}"
fi
done
run_cmd+=" ${IMAGE_NAME}"
echo "执行: $run_cmd"
eval "$run_cmd"
sleep 3
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
log_error "容器启动失败"
docker logs "$CONTAINER_NAME" 2>&1 | tail -20
exit 1
fi
log_info "容器已启动"
# tengine.conf 映射:如果 main 目录没有 tengine.conf,从镜像导出
if [ ! -f "/data/middleware/nginx/main/tengine.conf" ]; then
log_step "main/tengine.conf 不存在,从镜像导出..."
docker cp "${CONTAINER_NAME}:/etc/tengine/tengine.conf" /data/middleware/nginx/main/tengine.conf
log_info "tengine.conf 已导出到 /data/middleware/nginx/main/tengine.conf"
else
log_step "main/tengine.conf 已存在,替换容器内配置..."
docker cp /data/middleware/nginx/main/tengine.conf "${CONTAINER_NAME}:/etc/tengine/tengine.conf"
if docker exec "$CONTAINER_NAME" nginx -t 2>&1; then
docker restart "$CONTAINER_NAME" 2>&1
sleep 3
log_info "已加载自定义配置(容器已重启,stream 端口已生效)"
else
log_error "tengine.conf 语法错误,使用镜像默认配置"
docker cp "${CONTAINER_NAME}:/etc/tengine/tengine.conf" /data/middleware/nginx/main/tengine.conf.failed
docker start "$CONTAINER_NAME" 2>&1
fi
fi
}
# ==================== 验证部署 ====================
verify_deployment() {
log_step "验证部署..."
echo ""
echo "=== 容器状态 ==="
docker ps --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
echo "=== HTTPS 测试 ==="
if curl -skI --connect-timeout 5 https://127.0.0.1/ 2>&1 | head -3; then
log_info "HTTPS 响应正常"
else
log_warn "HTTPS 测试异常"
fi
echo ""
echo "=== MQTT 端口测试 ==="
if command -v python3 &> /dev/null; then
if python3 -c "import socket; s=socket.socket(); s.settimeout(3); s.connect(('127.0.0.1',${HOST_MQTT_PORT})); print('OK'); s.close()" 2>/dev/null; then
log_info "MQTT ${HOST_MQTT_PORT} 端口可连接"
else
log_warn "MQTT 端口测试失败"
docker exec "$CONTAINER_NAME" cat /proc/net/tcp 2>/dev/null | grep ':075B' && \
log_info "容器内 1883 已监听 (/proc/net/tcp 确认)" || \
log_error "容器内 1883 未监听!"
fi
fi
echo ""
echo "=== 编译模块 ==="
docker exec "$CONTAINER_NAME" nginx -V 2>&1 | grep "with-stream"
echo ""
log_info "======== VIP 入口部署完成 ========"
echo ""
echo " HTTPS: https://${VIP_IP}:${HOST_HTTPS_PORT}"
echo " MQTT: tcp://${VIP_IP}:${HOST_MQTT_PORT}"
echo " 镜像: ${IMAGE_NAME}"
echo " HTTP配置: /data/middleware/nginx/config/unified443.conf"
echo " 日志: /data/middleware/nginx/log/"
echo " 回滚: docker stop ${CONTAINER_NAME} && docker start ${OLD_CONTAINER}"
echo ""
echo " ⚠️ 改 unified443.conf 后: docker exec ${CONTAINER_NAME} nginx -s reload"
echo " ⚠️ 改 tengine.conf (EMQX节点) 后: docker restart ${CONTAINER_NAME}"
echo ""
}
# ==================== 主流程 ====================
main() {
echo ""
echo "============================================"
echo " Tengine VIP 入口部署 (X86) v2.0"
echo " 日期: $(date '+%Y-%m-%d %H:%M:%S')"
echo " VIP: ${VIP_IP}"
echo "============================================"
echo ""
preflight_check
load_image
stop_old_container
start_tengine
verify_deployment
}
main "$@"
# Tengine VIP 入口容器部署文档
> **版本**:v1.0
> **日期**:2026-07-14
> **适用场景**:统一平台集群的 VIP 入口服务器(如 192.168.5.40)
> **前置条件**:后端三台服务器(5.41/5.42/5.43)已部署完整业务集群
---
## 1. 文档定位
本文档用于在 **VIP 入口服务器**上部署 Tengine 容器,承担两个职责:
| 端口 | 协议 | 职责 | 转发目标 |
|------|------|------|---------|
| **443** | HTTPS | HTTP 反向代理 + 负载均衡 | 后端三台 443 |
| **1883** | TCP | MQTT 四层负载均衡 | 后端三台 EMQX 1883 |
与单机版 `deploy_tengine.sh` 的区别:
| 项目 | 单机版 | VIP 版(本文档) |
|------|--------|-----------------|
| 角色 | 业务服务器 | 集群入口 |
| 端口映射 | 仅 443 | 443 + 1883 |
| tengine.conf | 仅 http 块 | http 块 + stream 块 |
| MQTT 负载 | 无 | 有(stream 模块) |
---
## 2. 架构
```
客户端 / 设备
┌───┴───┐
HTTPS │ │ MQTT
▼ ▼
┌─────────────────────────┐
│ VIP 入口 (5.40) │
│ Tengine 容器 │
│ ├─ 443 http{} 反向代理 │ → 后端 5.41/5.42/5.43:443 (ip_hash)
│ └─ 1883 stream{} TCP LB │ → 后端 5.41/5.42/5.43:1883 (hash)
└─────────────────────────┘
```
---
## 3. 前置条件
### 3.1 镜像要求
镜像 `utengine:v1-stream` **已含 tengine 3.1.0 + `--with-stream` + 内置 tengine.conf(含 stream 块)**,无需重编译。
镜像包位置:
```bash
# X86: /data/temp/tengine/utengine_v1_stream_x86.tar
# ARM: /data/temp/tengine/utengine_v1_stream_arm.tar
```
```bash
docker load -i /data/temp/tengine/utengine_v1_stream_x86.tar
docker run --rm utengine:v1-stream nginx -V 2>&1 | grep stream
# 输出应包含:--with-stream、--with-stream_ssl_module
```
### 3.2 目录准备
```bash
# 配置目录(HTTP 站点配置)
mkdir -p /data/middleware/nginx/config
mkdir -p /data/middleware/nginx/data/{html,cache}
mkdir -p /data/middleware/nginx/log
mkdir -p /data/security/nginx_cert
mkdir -p /data/services/web
# 镜像存放目录
mkdir -p /data/temp/tengine
```
### 3.3 unified443.conf 已就位
`/data/middleware/nginx/config/unified443.conf` 必须已配置为 VIP 整体代理模式(参考 `08_Nginx模拟VIP负载均衡配置方案.md`)。
---
## 4. 部署步骤
### 4.1 准备镜像
```bash
# 将 utengine_v1_stream_x86.tar 放到 /data/temp/tengine/
mkdir -p /data/temp/tengine
```
### 4.2 设置 VIP IP
编辑 `deploy_tengine_vip.sh`,修改 `VIP_IP`
```bash
VIP_IP="192.168.5.40" # 改为实际 VIP 地址
```
> **无需修改 EMQX 节点 IP** — 镜像内置 tengine.conf 已配置默认 EMQX 节点(5.41/5.42/5.43:1883)。如需更换,将自定义 tengine.conf 放到 `/data/temp/tengine/tengine.conf`,脚本自动检测并替换。
### 4.3 执行部署
```bash
cd /data/temp/tengine
chmod +x deploy_tengine_vip.sh
./deploy_tengine_vip.sh
```
### 4.3 验证
```bash
# 容器状态(应同时映射 443 和 1883)
docker ps --filter name=utengine
# HTTPS 测试
curl -skI https://192.168.5.40
# MQTT TCP 测试
python3 -c "import socket; s=socket.socket(); s.settimeout(3); s.connect(('192.168.5.40',1883)); print('MQTT OK'); s.close()"
```
---
## 5. tengine.conf 关键配置说明
脚本生成的主配置包含 `http``stream` 两个块:
```nginx
http {
# 加载 HTTP 站点配置(unified443.conf 等)
include /etc/tengine/conf.d/*.conf;
}
stream {
# EMQX 集群负载均衡
upstream emqx_cluster {
hash $remote_addr consistent; # 同一设备固定到同一 EMQX
server 192.168.5.41:1883 max_fails=3 fail_timeout=30s;
server 192.168.5.42:1883 max_fails=3 fail_timeout=30s;
server 192.168.5.43:1883 max_fails=3 fail_timeout=30s;
}
server {
listen 1883;
proxy_pass emqx_cluster;
proxy_connect_timeout 10s;
proxy_timeout 600s; # MQTT 长连接超时
}
}
```
### 关键设计点
| 配置 | 作用 |
|------|------|
| `stream {}` 块 | 与 `http {}` 平级,做 TCP 四层转发 |
| `hash $remote_addr consistent` | 同一客户端 IP 固定到同一 EMQX,避免 session 重建 |
| `proxy_timeout 600s` | MQTT 长连接可能持续数小时,超时设长 |
| `max_fails=3 fail_timeout=30s` | 3 次失败摘除,30 秒后重试 |
---
## 6. 重新编译 tengine 镜像(如镜像不含 stream 模块)
如果现有镜像 `nginx -V` 输出不含 `--with-stream`,需要重新编译。
### 6.1 准备源码
```bash
# 上传 tengine 源码到服务器
ls /data/temp/tengine-3.1.0/configure # 确认源码存在
```
### 6.2 编译(在容器内或宿主机)
```bash
cd /data/temp/tengine-3.1.0
# 安装编译依赖(麒麟)
yum install -y gcc make pcre2-devel openssl-devel zlib-devel
# 配置(关键:加 --with-stream)
./configure \
--prefix=/usr/local/tengine \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--with-http_sub_module \
--with-stream \
--with-stream_ssl_module \
--conf-path=/etc/tengine/tengine.conf \
--error-log-path=/var/log/tengine/error.log \
--http-log-path=/var/log/tengine/access.log \
--pid-path=/var/run/tengine.pid
# 编译安装
make -j$(nproc)
make install
# 验证
/usr/local/tengine/sbin/nginx -V 2>&1 | grep stream
```
### 6.3 重新打包镜像
```bash
# 基于原镜像,替换编译好的 tengine 二进制
docker run -d --name tengine_build utengine:v1 sleep 3600
docker cp /usr/local/tengine tengine_build:/usr/local/
docker commit tengine_build utengine:v1-stream
docker rm -f tengine_build
# 导出
docker save -o /data/temp/tengine/utengine_v1_x86.tar utengine:v1-stream
```
---
## 7. 客户端配置
### 7.1 HTTPS 客户端
浏览器访问:`https://192.168.5.40`
### 7.2 MQTT 客户端
设备端只需配置一个地址:
```properties
# MQTT broker 地址(VIP)
broker.url = tcp://192.168.5.40:1883
```
任一后端 EMQX 节点故障,客户端断线重连会自动切到其他节点。
---
## 8. 故障排查
### Q1:MQTT 客户端连不上 1883
```bash
# 1. 确认容器映射了 1883 端口
docker port utengine
# 应输出:1883/tcp -> 0.0.0.0:1883
# 2. 确认 tengine 监听了 1883
docker exec utengine cat /proc/net/tcp | grep 075B
# 075B 是 1883 的十六进制,应看到 LISTEN 状态(0A)
# 3. 确认后端 EMQX 可达
python3 -c "import socket; s=socket.socket(); s.settimeout(3); s.connect(('192.168.5.41',1883)); print('EMQX OK')"
# 4. 查看 stream 错误日志
docker exec utengine tail -20 /var/log/tengine/error.log
```
### Q2:stream 块配置了但 1883 没监听
**原因**`nginx -s reload` 对 stream 模块的 listen 不生效,需要重启容器:
```bash
docker restart utengine
```
### Q3:`nginx: [emerg] "stream" directive is not allowed here`
**原因**`stream {}` 块放在了 `http {}` 内部或 `conf.d/` 目录下。
**解决**`stream {}` 必须在 `tengine.conf` 顶层,与 `http {}` 平级,**不能**放在 `conf.d/*.conf` 里。
### Q4:HTTPS 正常但 MQTT 时断时连
**原因**`proxy_timeout` 设置过小,MQTT 长连接空闲超时被断开。
**解决**:增大 `proxy_timeout`(当前 600s,可调到 3600s)。
---
## 9. 注意事项
1. **stream 块必须在 tengine.conf 顶层** — 不能放在 conf.d 目录的子配置里
2. **改 stream 配置后要重启容器**`nginx -s reload` 对 stream listen 不生效
3. **hash 算法固定 session** — MQTT 有 session 状态,必须用 `hash $remote_addr`,不能用轮询
4. **5.40 是单点** — VIP 入口挂了全部不可用,如需更高可用需配合 Keepalived
5. **达梦数据库也在 5.40** — 部署时注意不要影响 dm8-server 容器(5236 端口)
6. **回滚** — 脚本保留了 `unginx` 旧容器,回滚命令:`docker stop utengine && docker start unginx`
---
## 10. 关联文档
- `08_Nginx模拟VIP负载均衡配置方案.md` — HTTPS VIP 配置
- `07_Nginx集群分发配置方案.md` — 后端三台集群分发
- `09_EMQX集群负载均衡方案(非国产-HAProxy).md` — 非国产备选方案
- `tengine容器部署文档.md` — 单机版部署
- `tengine负载均衡配置指导.md` — 负载均衡配置详解
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论