ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Superset 4.1.1离线部署指南:从镜像到内网BI看板

Superset 4.1.1离线部署指南:从镜像到内网BI看板

简介:面向需要在隔离网络、内网或离线环境中部署数据可视化平台的运维与开发人员,Superset 4.1.1 中文版 Docker 离线部署包提供了一套开箱即用的完整方案。压缩包共6个文件,包含3个预打包的Docker镜像tar文件(涵盖Superset应用、PostgreSQL数据库、Redis缓存等核心组件)、1个docker-compose.yml编排文件、1个superset_config.py配置文件和1个.env环境变量文件,整体约524.2MB,用户无需外网即可导入镜像并一键启动服务。目前已有368人学习下载,适合具备基础Docker操作能力、希望快速搭建企业内网数据探索平台的中高级工程师。下载后可获得整合好的中文版Superset环境及配套配置,借助Compose命令完成多容器编排,同时保留数据库连接、安全认证等关键配置示例,大幅降低部署门槛,提升数据分析交付效率。该包尤其适合需要内网交付、数据私有化部署的企业用户。

1. Superset 4.1.1 离线部署:内网环境的一次完整交付

某公司的数据团队要搭一套内部 BI 看板,上游是几个业务库的只读账号。环境在隔离内网,没有外网,docker pull 和 pip install 全部不可用。选型落到 Superset 4.1.1,开源、能连多类数据库、看板功能够用。原本以为标准 Docker 部署拉起来就行,真正落地时才发现,离线场景的坑全部藏在镜像导出、compose 编排、初始化顺序和中文化里。这篇把整套流程拆成可复现步骤,从镜像搬运、元数据库持久化、管理员创建,到数据库驱动插件安装和中文界面切换,适合要给内网交付 BI 工具的从业者,也适合第一次接触 Superset 的开发者照着完整跑一遍。

2. 镜像收集与导出:把依赖镜像完整搬到隔离网络

2.1 先盘清镜像依赖关系

Superset 4.1.1 不是孤立容器,官方 compose 部署至少包含三个服务:superset 主应用、postgres(保存报表元数据)和 redis(缓存与异步任务)。离线部署第一步不是 docker pull,而是先列清单:哪些镜像必须导出、每个镜像的 tag 是什么、宿主机架构是 amd64 还是 arm64。常见的坑是有人只导出主应用镜像,遗漏 redis 和 postgres,到内网 docker compose up 时直接报 pull access denied。另一个更隐蔽的坑是 tag 含含糊糊,比如导出时用了 latest,load 到内网后又换了内容,回滚都没得回滚。

我一般会在联网机器上先写一个版本清单文件,记录镜像名、tag、镜像 ID 和用途,再批量拉取。这一步只花几分钟,却能让后面节省大量时间。还有个细节:arm64 和 amd64 的镜像 ID 完全不同,导出前用 docker images --format 确认平台,避免把 amd64 的包灌进 arm 机器,否则容器能 load 但启动时可能直接 segfault。

注意:离线部署前先确认所有镜像都已经在联网机器上docker run过一次,避免遗漏启动时才拉取的辅助镜像。

2.2 拉取并导出 tar 包

批量拉取命令很简单:

docker pull apache/superset:4.1.1 docker pull postgres:16-alpine docker pull redis:7-alpine

如果联网机器有镜像仓库代理,可以先把代理配置好再拉,但这里重点是拉下来之后的导出。导出的核心命令是 docker save:

mkdir -p /data/offline-images docker save -o /data/offline-images/superset-4.1.1.tar apache/superset:4.1.1 docker save -o /data/offline-images/postgres-16.tar postgres:16-alpine docker save -o /data/offline-images/redis-7.tar redis:7-alpine

-o 参数指定输出文件;镜像名必须带完整 tag,不建议用 IMAGE ID,因为用 ID 导出的包在内网 load 后容易产生同名镜像 ID 冲突,排查起来很折腾。docker save 会把镜像的所有层连同元数据打成一个 tar 包,Superset 主镜像通常在 1.5GB 到 2GB 之间。如果通过 U 盘或内网共享搬运,建议先压缩:

gzip -k superset-4.1.1.tar

gzip 之后体积能压缩一半左右,内网端解压再 load。tar 包超过 2GB 时,用 split 切成多个分片:

split -b 2G -d superset-4.1.1.tar superset-part- cat superset-part-* > superset-4.1.1.tar

split 的 -b 2G 表示每个分片 2GB,-d 表示用数字后缀命名;合并时 cat 的顺序不能乱,所以分片文件名要按顺序保留。实际传输中我试过 U 盘、内网共享和移动硬盘三种方式,核心原则只有一个:传输后先做一致性校验,不要直接 load。

2.3 内网导入与校验

在内网机器上把 tar 传过去之后,导入不能直接 docker load 就完事,先做 md5 校验:

md5sum superset-4.1.1.tar > checksum.txt md5sum -c checksum.txt

md5 校验在离线场景里特别重要。tar 文件在传输过程中只要损坏一个字节,docker load 可能不报错,但容器启动到某一层时会出现莫名其妙的 "tar: Unexpected EOF" 或运行时崩溃。这类问题排起来非常耗时,校验是最便宜的后悔药。

校验通过后导入:

docker load -i superset-4.1.1.tar docker load -i postgres-16.tar docker load -i redis-7.tar

load 完成后用 docker images 检查。正常情况下应该看到三行 REPOSITORY 和 TAG,且镜像 ID 与联网机器一致。如果某个镜像 TAG 变成<none>,说明 save 时用了短 ID 或者中间层被打包,处理办法在第五章展开。

2.4 把数据库驱动插件打进镜像

官方镜像内置的数据库驱动只有 PostgreSQL、SQLite 等少数几种,实际业务常常要连 MySQL、ClickHouse 或 SQL Server。隔离内网里没法 pip install,最稳的做法是在联网机器上准备 Python wheel 包,写一个自定义镜像:

FROM apache/superset:4.1.1 USER root COPY ./wheels /tmp/wheels RUN pip install --no-index --find-links=/tmp/wheels \ clickhouse-driver==0.2.8 \ pymysql==1.1.1 USER superset

这段 Dockerfile 做了三件事:切换到 root 安装系统级依赖,用 --no-index 强制不走网络,--find-links 指定本地 wheel 目录;装完切回 superset 用户,避免容器以 root 运行产生权限风险。wheel 打包这一步在联网机器上执行:

pip download --only-binary=:all: --python-version 3.11 --platform manylinux2014_x86_64 \ --dest ./wheels clickhouse-driver pymysql

这里指定 Python 版本和平台是为了和 Superset 4.1.1 镜像内的 Python 环境匹配。镜像的 Python 版本可以用docker run --rm apache/superset:4.1.1 python --version查看,先记下来再决定 --python-version 参数。平台、版本、glibc 三个要素只要错一个,内网 pip install 时会直接报找不到版本,而且 --no-index 模式下错误信息更晦涩。

实际项目里见过有人图省事直接进容器 pip install,配好数据源后容器一重启所有驱动全丢。重新构建镜像虽然多花几分钟,但内网交付时只需要这一个自包含镜像,后面运维不用重复踩这个坑。

3. Compose 编排与持久化:让三个容器按要求启动

3.1 离线环境的 docker-compose.yml 怎么写

镜像准备好之后,内网部署的入口是 docker-compose.yml。为什么不用 docker run 一条条执行?三个容器之间有依赖关系,用 docker run 只能靠脚本控制启动顺序,出问题不好回滚。compose 把网络、环境变量、数据卷和健康检查都声明在一个文件里,离线环境同样适用,前提是镜像全部 load 到本机。

下面这份是排过坑的版本:

version: "3.8" services: superset-db: image: postgres:16-alpine restart: always environment: POSTGRES_USER: superset POSTGRES_PASSWORD: superset POSTGRES_DB: superset volumes: - ./pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U superset"] interval: 10s timeout: 5s retries: 5 superset-cache: image: redis:7-alpine restart: always superset: image: apache/superset:4.1.1 restart: always environment: SUPERSET_ADMIN_USERNAME: admin SUPERSET_ADMIN_PASSWORD: admin123 SUPERSET_ADMIN_EMAIL: admin@example.local SUPERSET_ADMIN_FIRSTNAME: Admin SUPERSET_ADMIN_LASTNAME: User volumes: - ./superset_config.py:/app/pythonpath/superset_config.py - ./superset_home:/app/superset_home ports: - "8088:8088" depends_on: superset-db: condition: service_healthy superset-cache: condition: service_started healthcheck: test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8088/health')\""] interval: 30s timeout: 10s retries: 3

这份 compose 的关键点:postgres 服务用 healthcheck 里的 pg_isready 告诉 superset 数据库已就绪;superset 的 depends_on 用了 condition 形式而不是简单启动顺序。如果没有 healthcheck,容器起来只代表进程活着,不代表端口能接受连接,superset 启动时 SQLAlchemy 连不上 postgres 就会抛 connection refused 并退出。

superset 镜像默认不是 root 用户,挂载./superset_config.py时要注意宿主机文件权限。我一般把配置文件放到 compose 同目录,并 chmod 644;superset_home 目录给当前用户可写权限,否则容器内写 /app/superset_home 会报 PermissionError。这类权限问题逻辑上不复杂,但排查时容易被归到玄学范畴,实际就是权限位没对齐。

3.2 superset_config.py:元数据库、缓存与安全密钥

配置文件是整个部署的核心,内容不长,每个参数都直接影响运行行为:

import os SECRET_KEY = "a-very-long-random-string-please-change-in-production" SQLALCHEMY_DATABASE_URI = os.environ.get( "SQLALCHEMY_DATABASE_URI", "postgresql://superset:superset@superset-db:5432/superset" ) REDIS_HOST = os.environ.get("REDIS_HOST", "superset-cache") REDIS_PORT = 6379 REDIS_CELERY_DB = 1 REDIS_CACHE_DB = 0 REDIS_CELERY_BROKER_URL = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_CELERY_DB}" REDIS_CELERY_RESULT_BACKEND = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_CELERY_DB}" BABEL_DEFAULT_LOCALE = "zh" LANGUAGES = { "en": {"flag": "us", "name": "English"}, "zh": {"flag": "cn", "name": "中文"}, }

SECRET_KEY 必须固定成一个随机长字符串,不要用默认值,否则容器重启后所有会话失效、CSRF 校验频繁报错。SQLALCHEMY_DATABASE_URI 里的主机名写成 compose 服务名 superset-db,容器在同一个 compose 网络里通过服务名互通,不能写 localhost。REDIS 配置把缓存和 Celery broker 分成两个 db,实际部署中经常有人省掉这两行,结果缓存和异步任务共用同一个 db,数据混乱后界面行为变得不可预期。

BABEL_DEFAULT_LOCALE 设为 zh,LANGUAGES 里保留 en 作为回退。Superset 的翻译文件如果不存在,界面会退回英文而不是报错,所以设置语言后要检查容器内翻译文件是否存在,这个放到第六章讲。配置文件通过 volume 挂载到 /app/pythonpath/superset_config.py,这是 Superset 读取配置的默认路径。改完配置后只需要重启容器:

docker compose restart superset

不需要重新 build,因为挂载的是文件,restart 后进程会重新读取。

3.3 数据卷与启动顺序

数据卷分三类:postgres 的 pgdata 目录保存元数据库,里面是所有报表、图表、仪表盘的定义,备份就看这个目录;superset_home 里存上传文件、导出的 CSV、暂存查询结果;配置文件单独挂载。三个目录都应该放在非系统盘,最好单独做定期备份策略。

启动顺序上,depends_on 加了 condition 之后,compose 会等 superset-db 的 healthcheck 通过再启动 superset。但 healthcheck 只是端口存活检测,postgres 刚启动但还没完成初始化时 pg_isready 会失败,重试 5 次、间隔 10 秒的设计就是为了覆盖这个窗口。redis 没有健康检查是因为它启动快且失败影响小,所以条件只设为 service_started。

第一次执行完整启动命令:

docker compose up -d

之后查看状态:

docker compose ps

注意,如果宿主机 8088 已被其他服务占用,superset 会起不来,端口映射改成18088:8088即可。端口冲突看起来小,团队共用服务器时很常见,先ss -lntp | grep 8088确认再改映射,能少走一段弯路。

4. 初始化 Superset:数据库迁移与管理员创建

4.1 先执行 db upgrade,别急着开浏览器

容器起来后直接访问 8088,最常见的画面是 500 报错或提示数据库表不存在。原因是 Superset 的元数据库仍然是空的,还没有任何业务表。容器启动只把进程拉起来,初始化工作要手动触发。离线环境下这一步没有额外下载成本,迁移脚本都封装在镜像内部,直接执行:

docker compose exec superset superset db upgrade

db upgrade 的本质是把内置迁移脚本逐个应用。执行过程会打印大量日志,结尾出现 OK 才代表迁移完成。整个流程通常几十秒,如果卡住不动,先怀疑是不是 postgres 连接出问题,回到 superset_config.py 检查 SQLALCHEMY_DATABASE_URI。

迁移完成后可以验证表结构:

docker compose exec superset-db psql -U superset -c "\dt" | wc -l

注意,psql 在 postgres 容器里执行,看到的行数包含标题行。核心表应该包括 ab_user、ab_permission、ab_role、tables、dashboards 这些。如果表数量明显偏少,几乎可以断定连错了数据库,常见错误是连到默认 postgres 库而不是 compose 里指定的 superset 库。

4.2 用环境变量或命令创建管理员

Superset 官方脚本支持通过环境变量自动创建管理员,compose 里配置的 SUPERSET_ADMIN_USERNAME 和 SUPERSET_ADMIN_PASSWORD 就是给这个机制用的。不过自动创建依赖入口脚本的版本行为,不同 tag 对这套变量的实现不完全一致,有的版本只在数据库为空时生效,有的版本会重复执行导致冲突。建议不要把命运交给黑匣子,手动创建更可控:

docker compose exec superset superset fab create-admin \ --username admin \ --password admin123 \ --firstname Admin \ --lastname User \ --email admin@example.local

参数说明:--username 是登录账号,--password 是登录密码,生产环境务必换成强密码;--email 会用在找回密码和邮件通知里。这条命令由 Flask-AppBuilder 框架提供,执行成功后会输出 "Admin User admin created" 之类的提示。

创建完管理员后,还有一步经常被漏掉:

docker compose exec superset superset init

init 负责初始化角色、权限点和菜单。漏掉它的问题不会立刻爆发,一般出现在登录之后:菜单缺项、SQL Lab 报权限不足、无法新建数据源。如果你在 compose 环境变量里已经配置了管理员账号,容器首次启动可能已经自动建过,手动 create-admin 会提示用户存在,不影响后续流程,直接执行 init 即可。

4.3 首次登录与基础验证

浏览器访问http://内网IP:8088/login,用 admin 登录。登录后首先要去的不是建图表,而是打开 Settings 确认账号信息和语言设置。4.1.1 默认可能带一个示例数据库,离线环境下没有初始化示例数据时这个列表为空,属于正常现象,不要以为部署失败。

第一次验证建议先连一个真实业务库。PostgreSQL 可以直接用,因为官方镜像内置 psycopg2;MySQL 如果报找不到 pymysql,说明镜像里没有对应驱动,这正是第五章要讲的插件问题。先不追求复杂图表,SQL Lab 里跑一条 select 1,能出结果,说明元数据库、缓存、数据库驱动三层链路都通了。

这个阶段完成后有一件很重要的事:马上给 postgres 打一个初始快照:

docker compose exec superset-db pg_dump -U superset superset > init_backup.sql

这个快照是整个离线部署的基准点。后边调配置、装插件、改权限,一旦改出问题,把数据清空后恢复这份备份,就能回到刚初始化完成的状态。离线环境里没有后悔药,手动备份就是唯一的后悔药。

5. 离线部署常见问题排查:五个实际翻车点

5.1 docker load 后镜像 TAG 变成<none>

现象:内网 load 完,docker images 看不到 apache/superset:4.1.1,只有一堆 REPOSITORY 为<none>的镜像。

原因:导出时用了 IMAGE ID 而不是完整 tag,或者 docker save 前手工改过 tag,tar 里记录的名称和期望不一致。

解决:在联网机器上先用docker tag把镜像名固定成标准格式再 save;load 后如果已出现<none>,用docker tag <IMAGE_ID> apache/superset:4.1.1重新打 tag。检查镜像 IMAGE ID 是否和源一致,这个对比最可靠,不要只看 REPOSITORY 列。

5.2 postgres 起来了但 Superset 一直连不上

现象:superset 容器日志反复出现psycopg2.OperationalError或Connection refused @ superset-db。

原因:常见是 postgres 还没初始化好,或 SQLALCHEMY_DATABASE_URI 的密码、库名不一致,甚至是 compose 网络被重建导致主机名解析失败。

解决:先docker compose logs superset-db | tail -20看 postgres 是否接受连接,再用docker compose exec superset python -c "import socket; socket.create_connection(('superset-db', 5432))"验证容器间连通性。确认 URI 中的用户、密码、库名与 compose 里 POSTGRES_USER、POSTGRES_PASSWORD、POSTGRES_DB 完全一致。我踩过一次:compose 里改了 postgres 密码,但 superset_config.py 里连接串还是旧密码,服务名和网络都对,唯独密码不一致,排查了快一小时才定位。

5.3 8088 端口能通但页面白屏

现象:curl 返回 200,但浏览器打开白屏或控制台报静态资源 404。

原因:前端静态资源路径在 4.1.1 里是 /static/assets,如果宿主机 8088 被反向代理接管,或者浏览器缓存了旧的 index.html,就会出现资源加载不全。

解决:先用无痕模式访问,排除缓存;再用docker compose logs superset看有没有 asset 相关报错。如果走了 Nginx 反向代理,Nginx 要把 /static、/health 等路径都转发到 superset 容器,不能只把 / 转发过去。离线部署一般直接暴露 8088 给内网使用,白屏的多数场景其实是浏览器缓存或代理配置,而不是 Superset 本身。

5.4 配置文件挂载了但没生效

现象:在 superset_config.py 里改了 BABEL_DEFAULT_LOCALE 或 SECRET_KEY,重启后界面语言没变化,日志里看不到任何配置加载痕迹。

原因:挂载文件权限不对,superset 进程读不到;或者挂载路径写错,默认镜像是 /app/pythonpath/superset_config.py,如果挂到 /app/superset_config.py 就完全不生效。

解决:先在宿主机上用ls -l superset_config.py确认文件权限,再进入容器验证:

docker compose exec superset python -c \ "from superset_config import BABEL_DEFAULT_LOCALE; print(BABEL_DEFAULT_LOCALE)"

这条命令会直接打印配置读取结果。如果报 ImportError,说明容器内读的根本不是宿主机挂载的文件,检查 volume 路径。配置生效问题是黑匣子高发区,用这一行命令验证,比重启十次容器都管用。

5.5 新建数据库连接时提示缺少驱动插件

现象:在 Data > Databases 里填 PyMySQL 连接串时测试连接,报ModuleNotFoundError: No module named 'pymysql'。

原因:官方镜像只打包了基础驱动,PyMySQL、pyodbc、clickhouse-driver 都属于额外插件,离线没装就没有。

解决:不要进容器现场 pip install,那样重启即丢。回到第二章,把 wheels 目录准备好,Dockerfile 里装好插件重新构建镜像,内网 load 后替换 superset 镜像 tag,再重新 compose up。如果已经用旧镜像起了容器,可以临时进容器装一次保持业务继续,随后立刻安排镜像重新构建,否则下一次 recreate 就会打回原形。

6. 汉化与维护收尾:中文界面与交付前检查

6.1 检查中文翻译并切换到完整中文界面

config 里设置 BABEL_DEFAULT_LOCALE = "zh" 只是第一步,真正的中文界面依赖翻译文件。先检查容器内是否存在:

docker compose exec superset ls /app/superset/translations/zh/LC_MESSAGES/

正常情况下能看到 messages.mo 文件。如果目录为空,界面语言会部分回退到英文。离线环境下有两个解决方向:一是确认镜像 tag 是否来自官方完整版,部分第三方精简镜像会删掉 translations 目录;二是从内部源码库拿到翻译文件,编译后挂载进容器。官方镜像 apache/superset:4.1.1 内置 zh 翻译,只要 tag 没选错,上面 ls 就能看到文件。

如果个别菜单仍是英文,到 Settings 里确认当前语言确实是 zh,再强制刷新浏览器。4.1.1 的中文翻译覆盖率一般在八成以上,剩下的通常是用户自定义菜单或第三方插件,属于正常现象。

6.2 交付前的三点检查习惯

每次离线交付,我最后会强制做三件事:docker compose config校验编排语法、md5sum 校验传输文件、pg_dump 备份初始化数据。这套动作已经成了固定习惯,从那以后省掉了很多半夜被叫起来处理的返工。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进