使用 Docker 构建自定义 SeaTunnel Web 服务镜像
面向外部技术人员的技术分享文档。基于实际生产环境踩坑与调优后的最终方案编写。
声明:本文中本地环境是MacBook(Apple ARM),模拟生产环境部署是Ubuntu(Linux AMDx64),Docker仓库使用了阿里云镜像容器个人版(有免费额度),目的是为了验证自建打包私有Docker仓库实现生产环境部署的流程。
版本基线:SeaTunnel Web
1.0.3/ SeaTunnel Zeta Engine2.3.11/ Java 8
目录
一、背景说明
1.1 问题背景
SeaTunnel Web 是围绕 Apache SeaTunnel(数据集成引擎)的一站式可视化作业管理平台,包含前端(Vue)、后端(Spring Boot)与 Zeta 引擎集群三部分。在以下典型场景下,官方发布包无法直接满足交付需求:
- 自定义构建 :需要基于官方源码或发行包构建带私有化配置、定制化依赖的镜像,而不是直接使用公共镜像仓库的现成版本;
- 异构平台交付 :构建机是 Apple Silicon(ARM64)Mac,而生产服务器是 x86_64(AMD64)Linux,普通
docker build构建出的镜像无法在生产运行; - 离线 / 内网生产环境 :生产服务器无法访问 Docker Hub 与 Apache 官方源,镜像必须通过私有镜像仓库 (Registry/ACR 等)分发,服务器只
pull不构建; - 多组件协同:后端需要访问引擎集群的客户端 JAR、连接配置与连接器插件,组件之间通过共享目录与自定义网络协作,编排复杂度高。
1.2 目标
- 在 arm64 构建机上产出 linux/amd64 平台 的后端与引擎镜像,推送到私有仓库;
- 生产服务器通过
docker compose一键拉起 前端(nginx)+ 后端 + 双引擎集群 + 独立数据库 的完整环境; - 整个过程可复现、可文档化,后续版本升级、集群扩容可套用同一套流程。
1.3 整体思路
构建机 (arm64 Mac) 私有仓库 生产服务器 (amd64 Linux)
┌────────────────────┐ ┌──────────────┐ ┌─────────────────────────┐
│ mvn/npm 打包产物 │ buildx --push │ │ docker pull │ docker compose up -d │
│ buildx 交叉构建 │ ─────────────▶ │ amd64 镜像 │ ────────────▶ │ nginx + backend + 引擎集群 │
│ (--platform amd64) │ └──────────────┘ └─────────────────────────┘
└────────────────────┘
三条关键设计决策:
- 交叉构建 :统一使用
docker buildx build --platform linux/amd64,确保镜像平台与生产一致; - 双镜像分离 :
backend(Web 后端)+engine(Zeta 引擎)独立镜像、独立 Dockerfile,职责单一; - 共享目录协作 :引擎启动时将
lib / connectors / plugins复制到宿主机共享目录,后端以只读方式挂载该目录作为自己的SEATUNNEL_HOME,实现"引擎提供依赖、后端消费依赖"。
二、整体思路与架构
2.1 组件与端口
| 组件 | 镜像 | 内部端口 | 说明 |
|---|---|---|---|
| web-backend | seatunnel-web |
8801 | Spring Boot 后端,不直接对外 |
| engine-1 / engine-2 | seatunnel-engine |
5801 | Zeta 引擎集群节点(Hazelcast) |
| nginx | nginx:1.24-alpine |
80 | 唯一对外入口:前端静态资源 + API 反向代理 |
| MySQL | 独立实例 | 3306 | 业务数据库(外部提供) |
2.2 部署拓扑
浏览器
│
▼
Nginx(对外端口 80 / 示例映射 25280)
├── /ui/ ──▶ 前端静态资源 (ui-dist)
└── /seatunnel/api/v1 ──▶ web-backend:8801
│
├── MySQL (独立实例)
└── Hazelcast Client ──▶ engine-1:5801 / engine-2:5801 (集群)
2.3 共享目录机制(关键)
宿主机 ./data/engine-shared
│
├── 挂载到 engine-1/engine-2 的 /opt/seatunnel-shared(可写)
│ 引擎入口脚本启动时写入: lib/ connectors/ plugins/
│
└── 挂载到 web-backend 的 /opt/seatunnel(只读 :ro)
作为后端 SEATUNNEL_HOME,读取引擎提供的 JAR 与连接器
为什么引擎写、后端读 :后端以
:ro只读挂载共享目录,自身无法在容器内创建目录;该目录内容必须由引擎入口脚本在启动时保证写入(详见 [3.3 引擎启动脚本](#3.3 引擎启动脚本))。
三、镜像构建:后端与引擎
以下命令均在构建机执行。平台要求 :构建机若是 Apple Silicon(arm64)Mac,
必须 使用
buildx --platform linux/amd64交叉构建,否则推上仓库的是 arm64 镜像,生产 x86_64 服务器会报
The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64/v3)。
3.0 构建前准备
bash
# 1. 登录私有仓库(--push 与生产 pull 均需要;凭据保存在 ~/.docker/config.json)
docker login <REGISTRY_HOST>
# 2. 创建 buildx builder(docker-container 驱动)
# 国内网络需配置镜像加速,见 docker/buildkitd.toml(可自行添加 registry mirrors)
docker buildx create --name seatunnel-builder --config docker/buildkitd.toml --use
docker buildx inspect seatunnel-builder --bootstrap
# 3. 打包后端与前端产物
mvn clean package -DskipTests -Pci
# 产物: seatunnel-web-dist/target/apache-seatunnel-web-1.0.3-SNAPSHOT/
# 注意: maven-assembly 产物会嵌套一层同名目录
# .../apache-seatunnel-web-1.0.3-SNAPSHOT/apache-seatunnel-web-1.0.3-SNAPSHOT/{bin,libs,conf}
cd seatunnel-ui && npm install --registry=<NPM_MIRROR> && npm run build:prod && cd ..
# 产物: seatunnel-ui/dist/
提示:
inspect --bootstrap偶尔报context deadline exceeded,属 builder 容器刚创建完的瞬时超时,重跑一次即可,无需删除重建。
3.1 后端镜像 Dockerfile
文件:docker/backend.dockerfile(构建上下文 = 项目根目录)。为便于阅读省略 Apache License 头部注释:
dockerfile
# ============================================================
# SeaTunnel Web 后端 Dockerfile
# 构建上下文: 项目根目录
# 前置条件: mvn clean package -DskipTests -Pci
# ============================================================
FROM eclipse-temurin:8-jre
# 版本号,与 pom.xml 中 ${revision} 一致,可用 --build-arg 覆盖
ARG SEATUNNEL_WEB_VERSION=1.0.3-SNAPSHOT
# 基础环境变量
ENV DOCKER=true
ENV TZ=Asia/Shanghai
ENV SEATUNNEL_WEB_HOME=/opt/app/seatunnel-web
ENV JAVA_HOME=/opt/java/openjdk
WORKDIR $SEATUNNEL_WEB_HOME
# 复制后端构建产物(maven-assembly 产物嵌套一层同名目录,故 src 取内层目录)
COPY seatunnel-web-dist/target/apache-seatunnel-web-${SEATUNNEL_WEB_VERSION}/apache-seatunnel-web-${SEATUNNEL_WEB_VERSION}/ $SEATUNNEL_WEB_HOME/
RUN chmod +x $SEATUNNEL_WEB_HOME/bin/*.sh
# 复制前台启动脚本(容器内必须前台运行,否则进程退出会被 Docker 重启)
COPY docker/seatunnel-backend-foreground.sh $SEATUNNEL_WEB_HOME/bin/seatunnel-backend-foreground.sh
RUN chmod +x $SEATUNNEL_WEB_HOME/bin/seatunnel-backend-foreground.sh
# 后端服务端口(与 application.yml 中 server.port 一致)
EXPOSE 8801
# 清理 base image 的 entrypoint,确保 CMD 被正确执行
ENTRYPOINT []
CMD ["/bin/sh", "/opt/app/seatunnel-web/bin/seatunnel-backend-foreground.sh"]
3.2 引擎镜像 Dockerfile
文件:docker/Dockerfile.engine(构建上下文 = docker/ 目录)。
设计要点:引擎发行包在构建机原生下载解压 到
docker/seatunnel-engine-dist/,Dockerfile 通过
COPY拷入。原因:docker-container 驱动 + QEMU 模拟 amd64 时,
tar -xzf会报Function not implemented(ENOSYS),而COPY由 buildkit 直接处理、不经 QEMU。
dockerfile
# ============================================================
# SeaTunnel Zeta Engine 2.3.11 Dockerfile
# 构建上下文: docker/ 目录
# 前置条件: 构建机原生下载解压引擎包到 seatunnel-engine-dist/
# /opt/seatunnel-shared 为运行时的共享目录,由 docker-compose 绑定挂载到宿主机
# ============================================================
FROM eclipse-temurin:8-jre
ARG SEATUNNEL_VERSION=2.3.11
# 引擎安装目录
ENV SEATUNNEL_HOME=/opt/seatunnel
# 引擎共享目录(通过 volume 暴露给后端容器,用于访问客户端 JAR)
ENV SEATUNNEL_SHARED_DIR=/opt/seatunnel-shared
ENV TZ=Asia/Shanghai
# 从构建机预置目录拷入引擎(context=docker/,路径相对 docker/)
COPY seatunnel-engine-dist/apache-seatunnel-${SEATUNNEL_VERSION}/ ${SEATUNNEL_HOME}/
WORKDIR ${SEATUNNEL_HOME}
# 复制启动入口脚本
COPY seatunnel-engine-entrypoint.sh ${SEATUNNEL_HOME}/entrypoint.sh
RUN chmod +x ${SEATUNNEL_HOME}/entrypoint.sh
# 暴露 Hazelcast 集群通信端口
EXPOSE 5801
# 启动入口
ENTRYPOINT ["/opt/seatunnel/entrypoint.sh"]
3.3 引擎启动脚本(entrypoint)
文件:docker/seatunnel-engine-entrypoint.sh。
职责 :容器启动时将 lib / connectors / plugins 三个目录复制到共享目录(供后端只读挂载),随后前台启动 Zeta 集群节点。
关键坑(务必保留
plugins) :后端TableSchemaServiceImpl构造时会遍历$SEATUNNEL_HOME/plugins,若该目录缺失(即使为空),后端会抛
java.nio.file.NoSuchFileException: /opt/seatunnel/plugins导致启动失败。官方发行包
plugins/目录默认只有 README.md,但目录必须存在。
bash
#!/bin/sh
set -e
# 共享目录(会被 docker volume 挂载覆盖,所以需要在运行时复制)
mkdir -p ${SEATUNNEL_SHARED_DIR}
# 复制引擎 lib/connectors/plugins 到共享目录,供后端容器使用
# 注意: 后端容器挂载共享目录到 $SEATUNNEL_HOME 且为只读,若 plugins 目录缺失,
# TableSchemaServiceImpl 构造时 FileUtils.searchJarFiles(Common.pluginRootDir())
# 会抛 NoSuchFileException 导致整个应用启动失败,因此 plugins 必须一并复制
echo "Copying engine libraries to shared directory..."
for d in lib connectors plugins; do
mkdir -p ${SEATUNNEL_SHARED_DIR}/${d}
cp -r ${SEATUNNEL_HOME}/${d}/. ${SEATUNNEL_SHARED_DIR}/${d}/
done
# 启动 Zeta Engine 集群节点(前台运行)
echo "Starting SeaTunnel Zeta Engine..."
exec ${SEATUNNEL_HOME}/bin/seatunnel-cluster.sh
使用
cp -r ${SRC}/. ${DST}/而非cp -r ${SRC} ${DST},可避免目标目录已存在时嵌套出dst/src的重复层级。
3.4 后端启动脚本
文件:docker/seatunnel-backend-foreground.sh(容器主进程,前台运行):
bash
#!/bin/sh
set -e
WORKDIR=/opt/app/seatunnel-web/bin
LOGDIR=/opt/app/seatunnel-web/logs
# 检查 SEATUNNEL_HOME
if [ -z "$SEATUNNEL_HOME" ]; then
echo "SEATUNNEL_HOME is not set. Please check it."
exit 1
fi
echo "Load connectors from ${SEATUNNEL_HOME}"
# 创建日志目录
mkdir -p "$LOGDIR"
# JVM 参数
JAVA_OPTS="${JAVA_OPTS} -server -Xms512m -Xmx1g -Xmn512m"
JAVA_OPTS="${JAVA_OPTS} -XX:+PrintGCDetails -Xloggc:${LOGDIR}/gc.log"
JAVA_OPTS="${JAVA_OPTS} -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/app/seatunnel-web/dump.hprof"
JAVA_OPTS="${JAVA_OPTS} -Dseatunnel-web.logs.path=${LOGDIR}"
# Spring 配置参数
SPRING_OPTS="-Dspring.config.name=application.yml -Dspring.config.location=classpath:application.yml"
# CLASSPATH
CLASSPATH="$WORKDIR/../conf:$WORKDIR/../libs/*:$WORKDIR/../datasource/*"
# 前台启动,保持进程为容器主进程
exec $JAVA_HOME/bin/java $JAVA_OPTS \
-cp "$CLASSPATH" $SPRING_OPTS \
org.apache.seatunnel.app.SeatunnelApplication
3.5 构建命令(backend 与 engine)
bash
# ---------- 后端镜像(context = 项目根目录) ----------
cd /path/to/seatunnel-web
docker buildx build --platform linux/amd64 --provenance=false --sbom=false \
-t <REGISTRY_HOST>/<NAMESPACE>/seatunnel-web:1.0.3 \
-f docker/backend.dockerfile --push .
# ---------- 引擎镜像(context = docker/ 目录) ----------
# ① 准备引擎包:构建机原生下载解压(腾讯云镜像源,官方源国内极慢)
cd docker
mkdir -p seatunnel-engine-dist
curl -fSL "https://mirrors.cloud.tencent.com/apache/seatunnel/2.3.11/apache-seatunnel-2.3.11-bin.tar.gz" \
-o /tmp/st.tgz && tar -xzf /tmp/st.tgz -C seatunnel-engine-dist && rm /tmp/st.tgz
# ② 构建推送
docker buildx build --platform linux/amd64 --provenance=false --sbom=false \
-t <REGISTRY_HOST>/<NAMESPACE>/seatunnel-engine:2.3.11 \
-f Dockerfile.engine --push .
为什么必须
--provenance=false --sbom=false:buildx 默认生成 provenance/sbom attestation 清单(
application/vnd.oci.empty.v1+json),部分私有仓库(如 ACR 个人版)不支持,推送报
denied: unknown manifest class for application/vnd.oci.empty.v1+json。关闭后推送即成功。两个参数缺一不可。
四、镜像推送与验证
bash
# 验证远程镜像为单平台 OCI manifest(无 attestation)
docker buildx imagetools inspect <REGISTRY_HOST>/<NAMESPACE>/seatunnel-web:1.0.3
docker buildx imagetools inspect <REGISTRY_HOST>/<NAMESPACE>/seatunnel-engine:2.3.11
# 期望输出:MediaType: application/vnd.oci.image.manifest.v1+json(linux/amd64)
⚠️ 不要 在本地
docker build后直接docker push覆盖:本地默认构建的是 arm64 镜像,若本地也有同名 tag(如
.../seatunnel-web:1.0.3),直接 push 会覆盖仓库中刚推好的 AMD64 版本。始终用带
--platform的 buildx 命令,或给本地镜像用独立 tag(如1.0.3-arm64)。
五、生产部署
生产服务器只 pull 不构建,通过 Docker Compose 编排。
5.1 编排文件(Docker Compose)
文件:docker/docker-prod/docker-compose.yml(关键部分,完整见仓库):
yaml
services:
# ---------------- Zeta Engine 节点 ----------------
engine-1:
image: ${IMAGE_ENGINE}
container_name: seatunnel-engine-1
restart: unless-stopped
environment:
TZ: "Asia/Shanghai"
volumes:
# 覆盖引擎集群配置:tcp-ip 成员列表包含所有节点
- ./hazelcast-members.yaml:/opt/seatunnel/config/hazelcast.yaml:ro
# 共享目录:引擎将 lib/connectors/plugins 复制到此处,供后端挂载
- ./data/engine-shared:/opt/seatunnel-shared
networks:
- seatunnel-net
healthcheck:
test: ["CMD-SHELL", "bash -c '</dev/tcp/localhost/5801'"]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
engine-2:
image: ${IMAGE_ENGINE}
container_name: seatunnel-engine-2
restart: unless-stopped
environment:
TZ: "Asia/Shanghai"
volumes:
- ./hazelcast-members.yaml:/opt/seatunnel/config/hazelcast.yaml:ro
- ./data/engine-shared:/opt/seatunnel-shared
networks:
- seatunnel-net
healthcheck:
test: ["CMD-SHELL", "bash -c '</dev/tcp/localhost/5801'"]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
# ---------------- Web 后端 ----------------
web-backend:
image: ${IMAGE_BACKEND}
container_name: seatunnel-web-backend
restart: unless-stopped
environment:
SEATUNNEL_HOME: "/opt/seatunnel"
JAVA_HOME: "/opt/java/openjdk"
# 独立 MySQL 实例(生产库,最小权限用户)
SPRING_DATASOURCE_URL: "jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/seatunnel?useSSL=${MYSQL_USE_SSL}&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai"
SPRING_DATASOURCE_USERNAME: "${MYSQL_USERNAME}"
SPRING_DATASOURCE_PASSWORD: "${MYSQL_PASSWORD}"
# SeaTunnel 自身 ConfigProvider 的引擎成员列表(逗号分隔)
ST_DOCKER_MEMBER_LIST: "${ENGINE_MEMBERS}"
# Spring Boot Hazelcast 自动配置使用的外部 client 配置
SPRING_HAZELCAST_CONFIG: "file:/opt/app/seatunnel-web/conf/hazelcast-client.yaml"
# JWT 密钥与会话有效期(生产必须覆盖默认值)
JWT_SECRETKEY: "${JWT_SECRET_KEY}"
JWT_EXPIRETIME: "${JWT_EXPIRE_TIME:-86400}"
# 日志路径
SEATUNNEL_WEB_LOGS_PATH: "/opt/app/seatunnel-web/logs"
TZ: "Asia/Shanghai"
volumes:
# 挂载引擎客户端 JAR(由 engine-1/engine-2 写入,只读)
- ./data/engine-shared:/opt/seatunnel:ro
# 持久化后端日志
- ./data/backend-logs:/opt/app/seatunnel-web/logs
# 覆盖 Hazelcast 客户端配置(指向引擎集群)
- ./hazelcast-client.yaml:/opt/app/seatunnel-web/conf/hazelcast-client.yaml:ro
depends_on:
engine-1:
condition: service_healthy
networks:
- seatunnel-net
# ---------------- Nginx:唯一对外入口 ----------------
nginx:
image: nginx:1.24-alpine
container_name: seatunnel-nginx
restart: unless-stopped
ports:
- "25280:80" # 示例映射,按实际调整
#- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ui-dist:/usr/share/nginx/html/ui:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- web-backend
networks:
- seatunnel-net
networks:
seatunnel-net:
driver: bridge
name: seatunnel-network
5.2 环境变量模板(.env.example)
.env含敏感信息,权限设为 600,严禁提交仓库。以下为脱敏后的模板:
bash
# Docker Compose 项目名(影响容器与网络命名前缀)
COMPOSE_PROJECT_NAME=seatunnel-prod
# ---------------- 镜像(生产从私有仓库拉取)----------------
IMAGE_BACKEND=registry.example.com/seatunnel/web-backend:1.0.3
IMAGE_ENGINE=registry.example.com/seatunnel/engine:2.3.11
# ---------------- 数据库(独立 MySQL 实例,禁止使用 root)----------------
MYSQL_HOST=db.internal.example.com
MYSQL_PORT=3306
MYSQL_USERNAME=seatunnel
MYSQL_PASSWORD=CHANGE_ME_STRONG_PASSWORD
# 数据库连接是否启用 SSL(生产建议 true,需 MySQL 端开启 TLS)
MYSQL_USE_SSL=true
# ---------------- JWT 安全配置 ----------------
# 密钥生成: openssl rand -base64 48(需 >= 32 字节),
# 更换后所有已签发 token 失效,用户需重新登录
JWT_SECRET_KEY=CHANGE_ME_openssl_rand_base64_48
# Token 有效期(秒),默认 86400 = 24 小时
JWT_EXPIRE_TIME=86400
# ---------------- 引擎集群成员(与服务内 Hazelcast 配置保持一致)----------------
# 格式: 服务名:端口
ENGINE_MEMBERS=engine-1:5801,engine-2:5801
5.3 引擎集群配置(Hazelcast)
文件:docker/docker-prod/hazelcast-members.yaml(挂载覆盖引擎内 config/hazelcast.yaml):
yaml
hazelcast:
cluster-name: seatunnel
network:
rest-api:
enabled: false
endpoint-groups:
CLUSTER_WRITE:
enabled: true
DATA:
enabled: true
join:
tcp-ip:
enabled: true
member-list:
- engine-1
- engine-2
port:
auto-increment: false
port: 5801
properties:
hazelcast.invocation.max.retry.count: 20
hazelcast.tcp.join.port.try.count: 30
hazelcast.logging.type: log4j2
hazelcast.operation.generic.thread.count: 50
hazelcast.heartbeat.failuredetector.type: phi-accrual
hazelcast.heartbeat.interval.seconds: 2
hazelcast.max.no.heartbeat.seconds: 180
hazelcast.heartbeat.phiaccrual.failuredetector.threshold: 10
hazelcast.heartbeat.phiaccrual.failuredetector.sample.size: 200
hazelcast.heartbeat.phiaccrual.failuredetector.min.std.dev.millis: 100
文件:docker/docker-prod/hazelcast-client.yaml(后端通过 SPRING_HAZELCAST_CONFIG 加载):
yaml
hazelcast-client:
cluster-name: seatunnel
network:
cluster-members:
- engine-1:5801
- engine-2:5801
connection-strategy:
connection-retry:
cluster-connect-timeout-millis: 60000
集群成员使用 Compose 服务名 (自定义网络内 DNS 稳定解析),勿用容器名(container_name),
容器重建/改名后易失配。新增引擎节点时需同步更新以上两处 +
.env的ENGINE_MEMBERS。
5.4 Nginx 配置
文件:docker/docker-prod/nginx.conf(关键部分):
nginx
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
server {
listen 80;
server_name localhost;
# 前端静态资源(Vue 生产构建 base 路径为 /ui/)
location /ui {
alias /usr/share/nginx/html/ui;
index index.html;
try_files $uri $uri/ /ui/index.html;
}
# 根路径重定向到前端页面
# 必须用 $http_host(含端口)而非相对路径,否则经端口转发访问时
# 302 会丢端口跳回默认端口,导致打不开
location = / {
return 302 $scheme://$http_host/ui/;
}
# 后端 API 反向代理
# 用 Docker Compose 服务名 web-backend(自定义网络内 DNS 稳定解析;
# 勿用 container_name,容器重启/改名后易失配)
location /seatunnel/api/v1 {
proxy_pass http://web-backend:8801;
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;
proxy_connect_timeout 60s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
}
location /nginx-health {
return 200 'ok';
add_header Content-Type text/plain;
}
}
}
5.5 数据库初始化
在独立 MySQL 实例上执行:
sql
CREATE DATABASE IF NOT EXISTS `seatunnel` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER IF NOT EXISTS 'seatunnel'@'%' IDENTIFIED BY '<强密码>'; -- 与 .env 一致
GRANT ALL PRIVILEGES ON `seatunnel`.* TO 'seatunnel'@'%';
FLUSH PRIVILEGES;
导入表结构与初始数据(默认管理员 admin / admin,上线后请立即改密):
bash
mysql -h <DB_HOST> -u seatunnel -p seatunnel < init.sql
5.6 启动服务
bash
cd /opt/seatunnel/prod-docker # 按实际部署目录
cp .env.example .env && chmod 600 .env # 填写真实值
docker compose up -d
docker compose ps # 期望:engine-1/engine-2 healthy,backend/nginx Up
六、部署后验证与日常运维
6.1 验证
bash
# 前端
curl -sI http://<SERVER_IP>:<PORT>/ui/ # 200
# 登录
curl -X POST http://<SERVER_IP>:<PORT>/seatunnel/api/v1/user/login \
-H 'Content-Type: application/json' -d '{"username":"admin","password":"admin"}'
# 受保护接口(header 名是 token)
TOKEN=<上一步返回的 token>
curl "http://<SERVER_IP>:<PORT>/seatunnel/api/v1/user?pageNo=1&pageSize=5" -H "token: $TOKEN"
6.2 日常运维速查
| 场景 | 命令 |
|---|---|
| 查看日志 | docker logs -f seatunnel-web-backend / seatunnel-engine-1 / seatunnel-nginx |
| 后端日志文件 | tail -f data/backend-logs/*.log |
| 重启某服务 | docker compose restart web-backend |
| 升级后端 | 推送新镜像 → 更新 .env 的 IMAGE_BACKEND → docker compose up -d web-backend |
| 升级前端 | 重新构建 ui-dist/ → docker compose restart nginx |
| 回滚 | 改回旧镜像 tag → docker compose up -d --force-recreate web-backend |
| 备份数据库 | 每日 mysqldump --single-transaction --routines --triggers seatunnel > bk_$(date +%F).sql |
七、典型问题与排障
问题 1:生产报镜像平台不匹配
The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64/v3)
原因 :构建机本地 docker build 产物是 ARM64。
解决 :统一使用 buildx build --platform linux/amd64 交叉构建并 --push;不要本地直接 push 覆盖。
问题 2:私有仓库拒绝推送
denied: unknown manifest class for application/vnd.oci.empty.v1+json
原因 :Buildx 默认附加 provenance/sbom attestation 清单,部分仓库(ACR 个人版)不支持。
解决 :构建命令加 --provenance=false --sbom=false。
问题 3:QEMU 交叉构建内 tar 解压失败
tar: Cannot open: Function not implemented
原因 :docker-container 驱动 + QEMU 模拟 amd64 时,tar 部分系统调用不被支持。
解决 :构建机原生下载解压引擎包,Dockerfile 用 COPY 拷入(不经 QEMU)。
问题 4:外网访问打不开 / 跳回默认端口
- 现象 :
http://<SERVER_IP>:<PORT>/打不开,/ui/正常。 - 根因 :根路径
return 302 /ui/为相对路径,经端口转发访问时 Location 丢端口。 - 解决 :改为
return 302 $scheme://$http_host/ui/;($http_host含请求端口)。
问题 5:后端 API 502
-
根因 A :nginx
proxy_pass使用了 container_name → 改为 Docker Compose 服务名web-backend。 -
根因 B(更常见) :后端容器根本没起来。查看:
bashdocker logs seatunnel-web-backend --tail 50 docker logs seatunnel-engine-1 --tail 50 docker logs seatunnel-nginx --tail 100
问题 6:后端启动失败 NoSuchFileException: /opt/seatunnel/plugins
Caused by: java.nio.file.NoSuchFileException: /opt/seatunnel/plugins
at org.apache.seatunnel.common.utils.FileUtils.searchJarFiles(FileUtils.java:48)
at org.apache.seatunnel.app.service.impl.TableSchemaServiceImpl.<init>
- 原因 :后端构造时遍历
$SEATUNNEL_HOME/plugins;该目录由引擎入口脚本写入共享目录,
若脚本漏复制plugins(即使为空目录),后端只读挂载下目录不存在即抛异常。 - 解决 :引擎入口脚本统一复制
lib / connectors / plugins(见 3.3),
修复后重新构建推送引擎镜像。
通用排障顺序:先看容器日志定位表象,再沿依赖链(Nginx → backend → engine → MySQL)逐层排查根因。
八、附录:目录结构与关键文件
seatunnel-web/
├── docker/
│ ├── backend.dockerfile # 后端镜像 Dockerfile(context=项目根)
│ ├── Dockerfile.engine # 引擎镜像 Dockerfile(context=docker/)
│ ├── seatunnel-backend-foreground.sh # 后端前台启动脚本
│ ├── seatunnel-engine-entrypoint.sh # 引擎入口脚本(写共享目录 + 启动集群)
│ ├── buildkitd.toml # buildx builder 镜像加速配置
│ ├── hazelcast-client.yaml # 后端连接引擎集群的 client 配置(模板)
│ ├── init.sql # 数据库表结构与初始数据
│ └── docker-prod/
│ ├── docker-compose.yml # 生产编排(双引擎、独立 DB、仅 nginx 对外)
│ ├── Nginx 配置 # 生产 Nginx 配置
│ ├── Hazelcast members 配置(服务名) # 引擎集群成员列表(服务名)
│ ├── hazelcast-client.yaml # 后端 client 配置(生产版)
│ ├── .env.example # 环境变量模板
│ ├── ui-dist/ # 前端构建产物(自行放置)
│ ├── certs/ # TLS 证书(自行准备)
│ └── data/
│ ├── engine-shared/ # 引擎写入、后端只读的共享目录
│ └── backend-logs/ # 后端运行日志
└── seatunnel-engine-dist/ # 构建机预置引擎包(原生解压,.gitignore 忽略)