使用 Docker 构建自定义 SeaTunnel Web 服务镜像

使用 Docker 构建自定义 SeaTunnel Web 服务镜像

面向外部技术人员的技术分享文档。基于实际生产环境踩坑与调优后的最终方案编写。

声明:本文中本地环境是MacBook(Apple ARM),模拟生产环境部署是Ubuntu(Linux AMDx64),Docker仓库使用了阿里云镜像容器个人版(有免费额度),目的是为了验证自建打包私有Docker仓库实现生产环境部署的流程。

版本基线:SeaTunnel Web 1.0.3 / SeaTunnel Zeta Engine 2.3.11 / Java 8


目录

  1. 背景说明
  2. 整体思路与架构
  3. 镜像构建:后端与引擎
  4. 镜像推送与验证
  5. 生产部署
  6. 部署后验证与日常运维
  7. 典型问题与排障
  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 目标

  1. 在 arm64 构建机上产出 linux/amd64 平台 的后端与引擎镜像,推送到私有仓库;
  2. 生产服务器通过 docker compose 一键拉起 前端(nginx)+ 后端 + 双引擎集群 + 独立数据库 的完整环境;
  3. 整个过程可复现、可文档化,后续版本升级、集群扩容可套用同一套流程。

1.3 整体思路

复制代码
构建机 (arm64 Mac)                        私有仓库                     生产服务器 (amd64 Linux)
┌────────────────────┐                ┌──────────────┐              ┌─────────────────────────┐
│ mvn/npm 打包产物    │   buildx --push │              │   docker pull │  docker compose up -d    │
│ buildx 交叉构建     │ ─────────────▶ │  amd64 镜像   │ ────────────▶ │  nginx + backend + 引擎集群 │
│ (--platform amd64) │                └──────────────┘              └─────────────────────────┘
└────────────────────┘

三条关键设计决策:

  1. 交叉构建 :统一使用 docker buildx build --platform linux/amd64,确保镜像平台与生产一致;
  2. 双镜像分离backend(Web 后端)+ engine(Zeta 引擎)独立镜像、独立 Dockerfile,职责单一;
  3. 共享目录协作 :引擎启动时将 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),

容器重建/改名后易失配。新增引擎节点时需同步更新以上两处 + .envENGINE_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
升级后端 推送新镜像 → 更新 .envIMAGE_BACKENDdocker 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(更常见) :后端容器根本没起来。查看:

    bash 复制代码
    docker 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 忽略)

相关推荐
山荷枝1 小时前
05-Vue
前端·javascript·vue.js
可爱的秋秋啊1 小时前
vue调用腾讯人脸组件封装+接口请求后端调用
前端·javascript·vue.js
程序员爱钓鱼1 小时前
Rust 生命周期常见错误详解:看懂编译器报错并正确修复
前端·后端·rust
zhanghaha13141 小时前
Python进阶教程:5_XML 解析 —— 新手完全指南
java·前端·数据库
绝世唐门三哥2 小时前
CSS 虚线下划线用法指南:text-decoration 完整解析
前端·javascript·css
程序员爱钓鱼2 小时前
Go if 判断详解
前端·后端·go
程序员黑豆2 小时前
Java字符串拼接全解析:6种方式性能对比与实战指南
java·前端·ai编程
万少7 小时前
DeepSeek 昨晚刚开源了 Harness:附万少的2 万字保姆级教程
前端·后端·架构
程序员黑豆8 小时前
Java字符串详解
java·前端·ai编程