☰
Superset 4.1.1 中文版 Docker 离线部署与内网 BI 避坑指南
2026/10/29 2:45:30 网站建设 项目流程

简介:面向企业数据团队与开发者的 Superset 4.1.1 中文版离线部署包,聚焦无外网环境下的数据探索与可视化平台搭建,适合具备一定 Docker 基础的中高级运维、数据工程人员参考。包内共 6 个文件,以 3 个 tar 镜像归档为主,另含 1 个 yml 编排定义、1 个 py 配置脚本和 1 个 env 环境变量文件,压缩包约 524.2MB,覆盖镜像、服务组织与运行参数等关键组成;各类型文件相互配套,便于按镜像、编排与配置模块分别查阅和维护。已有 370 人学习,说明该方案在离线部署场景中具有一定参考价值。读者可从中获得可复用的容器化环境基础,理解多容器服务的组织方式与配置分层,并借助中文界面降低使用门槛;其配置思路也可为数据源接入、仪表盘制作、图表配置与权限设置等常见分析场景提供参考,对后续版本更新、安全维护及数据分析实践亦有辅助意义。

1. 从机房那台连不上外网的服务器说起

上个月帮一个业务团队在内网机房落地 BI,机器是台干净的 CentOS,ping任何外网地址都不通,连 yum 源都是本地镜像。业务方的诉求很朴素:能连上内部几张报表库,做几个看板,界面是中文,导出 PDF 别出方块字。这种场景下 Superset 4.1.1 中文版 Docker 离线部署这套东西就是答案——它把汉化资源、常用数据库驱动、中文字体这些在线安装时最折腾的部分提前固化进镜像,再配合docker save/docker load把整条链路搬到无网环境。适合手里有内网服务器、又不想为了一套 BI 去申请外网白名单的运维和数据分析工程师。下面是我从头拆到尾的完整路径,包括几个让我重装过的坑。

2. 镜像分层拆解:中文版到底改了哪几层

拿到一个「中文版」镜像,第一件事不是急着docker run,而是先搞清楚它和官方镜像的差异在哪一层。搞不清楚差异,后面出问题就只能靠猜,那是纯玄学。我的习惯是先docker history看一眼分层,再进容器翻文件,把变更点定位到具体目录,这样离线包里该带什么、不该带什么才有依据。

2.1 基础镜像与 Python 依赖栈

Superset 4.1.1 的运行时骨架是 Python 3 + Flask + Flask-AppBuilder + SQLAlchemy + Pandas,前端是预编译好的静态资源直接塞进superset/static/assets。也就是说,容器启动时不需要 Node.js 构建,前端产物在镜像里就是成品。这一点对离线部署极其友好——你不需要在内网准备 npm 私服。

进容器后先确认三件事:Python 版本、关键包版本、以及/app目录结构。

# 进容器看运行时骨架,确认版本与工作目录 docker run --rm -it apache/superset:4.1.1 /bin/bash -c " python -V && pip list | grep -Ei 'superset|flask|sqlalchemy|pandas|pillow' && ls -al /app "

这段命令里--rm保证不留垃圾容器,-it是为了看到实时输出。pip list | grep -Ei用-E开扩展正则、-i忽略大小写,因为不同包的写法大小写不一致。重点看 Flask-AppBuilder 的版本,Superset 4.x 对 FAB 版本卡得比较死,如果中文版为了改汉化去动了 FAB,升级路径会变窄,这一点得心里有数。

/app下有superset/、superset-frontend/、pythonpath/三个关键目录。pythonpath是配置挂载点,官方镜像已经把/app/pythonpath加进PYTHONPATH,所以把superset_config.py丢进去就能生效,不需要改任何启动脚本。这是后面配置外挂的基础。

还有一个容易被忽略的点:镜像的ENTRYPOINT。官方镜像默认走/usr/bin/entrypoint.sh加/usr/bin/run-server.sh,脚本里会做一些环境检查。离线环境里这些检查如果依赖外网(比如某些版本会尝试探测更新),启动就会卡住几十秒。我一般直接在自己的 compose 里覆写command,跳过这层不可控逻辑,启动时间从四十多秒降到五秒内。

2.2 汉化的三个落点:locale 配置、前端语言包、后端 .mo

「中文版」这三个字在 Superset 上有明确的物理含义,它分布在三个不同位置,任何一个没覆盖到,界面上就会出现中英混杂的尴尬局面。

第一层是运行时 locale 配置。Superset 用 Flask-Babel 做国际化,核心开关是superset_config.py里的BABEL_DEFAULT_LOCALE和LANGUAGES字典。如果只改了环境变量LANG=zh_CN.UTF-8,那只是操作系统层面,Superset 自己的翻译表不会加载,界面还是英文。很多人第一次装完发现「怎么还是英文」,九成栽在这里。

第二层是前端语言包。Superset 的前端翻译走的是superset-frontend/src/locales/下编译出来的 JSON,构建时打包进static/assets。官方镜像自带 zh 语言包,但覆盖率和汉化版有差别,尤其是 SQL Lab 的提示语、告警配置页这些边缘模块。中文版一般会把这些补全后重新构建前端产物。

第三层是后端.mo文件。superset/translations/zh/LC_MESSAGES/messages.mo是编译后的二进制翻译表,管的是后端抛出的报错信息、API 返回的提示文案。.po是源码,.mo才是运行时真正加载的。汉化版如果把.po改了但忘了pybabel compile,界面上就看不到变化,这是很典型的翻车点。

验证方法很直接,不需要猜:

# 检查三个汉化落点是否真的到位 docker run --rm apache/superset:4.1.1 /bin/bash -c " echo '--- locale ---' && locale echo '--- 前端中文包 ---' && ls -lh /app/superset/static/assets/*.json 2>/dev/null | head echo '--- 后端 mo 文件 ---' && ls -lh /app/superset/translations/zh/LC_MESSAGES/ "

locale输出里要看LANG和LC_ALL是否都是 UTF-8 变体。前端包看文件是否存在、修改时间是否晚于镜像构建时间。.mo文件大小是个粗略指标,正常的中文.mo应该有几十 KB,如果只有几百字节,基本可以断定是空壳或者编译失败。

我自己踩过一次:镜像里.mo文件在,但LANGUAGES字典里没注册zh,结果前端语言选择器里根本看不到中文选项。所以这三层是「与」的关系,缺一不可。

2.3 中文版多出来的东西:驱动与字体

这部分才是中文版真正的价值所在,也是离线部署最容易漏带的。官方镜像只内置了 SQLite 和 PostgreSQL 的驱动,实际国内环境里 MySQL、ClickHouse、Doris、StarRocks 都可能要用。这些驱动如果在内网现场pip install,没有 wheel 包就得现场编译,而编译需要 gcc、python-dev、libffi-dev 一整串构建依赖,内网大概率没有。

# 盘点镜像里已经预装了哪些数据库驱动 docker run --rm apache/superset:4.1.1 /bin/bash -c " pip list 2>/dev/null | grep -Ei 'mysql|pymysql|clickhouse|psycopg|sqlalchemy|pyhive|trino|doris|starrocks' "

-Ei的组合前面讲过,这里额外注意pymysql和mysqlclient的区别:前者是纯 Python 实现,装完即用;后者是 C 扩展,性能好但要编译。离线环境优先选pymysql,连接串写成mysql+pymysql://。ClickHouse 用clickhouse-driver或clickhouse-connect,后者对 Superset 的 SQLAlchemy 方言支持更完整,连接串是clickhousedb+connect://。

第二个多出来的是中文字体。这个坑极其隐蔽——网页上中文显示正常,因为浏览器用本机字体渲染;但导出 PDF、导出 PNG 图表时,是容器内用 Pillow 和 WeasyPrint 渲染,容器里没有中文字体就直接出方块。检查方法:

# 检查容器内是否有可用的中文字体 docker run --rm apache/superset:4.1.1 /bin/bash -c " fc-list :lang=zh 2>/dev/null | head -5 || echo 'fontconfig 未安装' "

如果输出为空或者命令不存在,说明字体链是断的。成熟的汉化镜像一般会预装fonts-wqy-zenhei或 Noto Sans CJK,并且在superset_config.py里把WEBDRIVER_BASEURL相关的截图字体也指过去。这一点在选包时就该确认,别等到业务方要导出季度报告时才发现。

3. 有网侧备料:一次 docker save 要打包哪些东西

离线部署的核心逻辑是「在线侧固化、离线侧还原」。这一章的所有操作都在一台能访问外网的构建机上完成,产出的东西打成一个压缩包,用移动介质送进内网。关键原则是:凡是内网会用到的东西,一律提前落下,宁可多带。

3.1 镜像侧:钉版本、钉架构、钉 tag

三个「钉」字,每一个都对应一类事故。

钉版本是因为latest在离线环境里是灾难——你永远不知道三个月前 save 的那份包里到底是什么。钉架构是因为现在的开发机大量是 ARM(各种 M 系列芯片),而内网服务器基本是 x86_64,直接docker save过去的镜像docker load之后启动会报exec format error,报错信息还很晦涩,第一次见容易懵。

# 在有外网的构建机上执行,三个镜像一次拉齐 export SUPERSET_VER=4.1.1 export PLATFORM=linux/amd64 docker pull --platform ${PLATFORM} apache/superset:${SUPERSET_VER} docker pull --platform ${PLATFORM} postgres:15 docker pull --platform ${PLATFORM} redis:7 # 确认架构没拉错 docker inspect --format '{{.Os}}/{{.Architecture}}' apache/superset:${SUPERSET_VER}

--platform是显式声明目标架构,即使构建机本身就是 x86 也建议写上,避免以后换机器时忘记。docker inspect --format用的是 Go template 语法,.Os和.Architecture两个字段直接给出镜像的真实架构,输出必须是linux/amd64才算过关。

钉 tag 指的是 save 之后到内网 load 出来可能变成<none>。原因通常是 save 时用了镜像 ID 而不是repo:tag形式,或者中间做过docker tag又删了原 tag。稳妥做法是 save 前先确认docker images里 tag 完整,save 时严格写仓库名:版本号。

mkdir -p offline/images for img in "apache/superset:${SUPERSET_VER}" "postgres:15" "redis:7"; do # 文件名里的斜杠和冒号换成下划线,避免跨平台解压出问题 name=$(echo "${img}" | tr '/:' '__') docker save "${img}" -o "offline/images/${name}.tar" gzip -9 "offline/images/${name}.tar" done ls -lh offline/images/

循环里用tr '/:' '__'把apache/superset:4.1.1变成apache_superset_4.1.1,这是为了避开 Windows 解压工具对冒号的处理差异。gzip -9是最高压缩级别,Superset 镜像压缩比通常能到 3:1 左右,对移动介质容量紧张的场景很有用。压缩会多花一两分钟,但省下的传输时间更值。

提示:docker save会把镜像的所有层完整导出,包括历史层。如果镜像做过多次RUN叠加,体积会偏大。这不是问题,不要在离线包上做docker squash之类的操作,会破坏镜像摘要,反而增加内网校验的麻烦。

3.2 依赖侧:pip download 的 wheel 陷阱

即使中文版预装了大量驱动,实际项目里总会遇到需要补装的情况——比如接一个特定版本的 Oracle,或者某个小众数据库的方言包。这时候要在有网侧把 wheel 包下全。

pip download有几个参数必须显式给,否则下出来的东西在内网装不上。

# 把需要补充的驱动列进 requirements-local.txt cat > requirements-local.txt <<'EOF' pymysql==1.1.0 clickhouse-connect==0.7.16 psycopg2-binary==2.9.9 EOF mkdir -p offline/wheels pip download \ -r requirements-local.txt \ -d offline/wheels \ --platform manylinux2014_x86_64 \ --python-version 311 \ --implementation cp \ --only-binary=:all:

参数逐个说清楚:--platform manylinux2014_x86_64指定目标平台,和前面钉的镜像架构对齐;--python-version 311要和镜像内的 Python 版本一致,版本对不上装的时候会直接拒绝;--implementation cp表示 CPython;--only-binary=:all:是最关键的一条,强制只下二进制 wheel,不允许下源码包。

为什么要卡死--only-binary?因为源码包(.tar.gz)在内网安装时要现场编译,而编译需要 gcc、make、python3-dev、libssl-dev 一整串东西,内网机器基本没有。与其到现场才发现编译失败,不如在有网侧就让 pip 报错——报错了说明这个包没有预编译版本,需要换方案,比如换纯 Python 实现的替代库。

psycopg2-binary就是典型的替代方案。原版psycopg2需要pg_config和编译工具链,-binary版本自带编译好的 libpq,装上就能用。生产环境对-binary有些争议,但在离线内网这种「能跑起来优先」的场景,它是性价比最高的选择。

如果某个包确实没有 manylinux wheel,比如某些厂商的私有驱动,那就得换思路:在有网侧用相同基础镜像起一个容器,在容器里编译好,再docker commit成派生镜像。这样编译环境天然对齐,比在宿主机上折腾交叉编译靠谱得多。

3.3 打包清单与完整性校验

备料完成后,把所有产物整理成固定结构,并生成校验清单。这一步看起来啰嗦,但内网现场没有校验清单的话,一旦某个文件传输损坏,排查方向会完全跑偏。

# 生成 SHA256 校验清单 cd offline find . -type f \( -name '*.tar.gz' -o -name '*.whl' \) -print0 \ | sort -z \ | xargs -0 sha256sum > SHA256SUMS # 内网侧还原前先校验 sha256sum -c SHA256SUMS

find -print0配xargs -0是为了处理文件名里的特殊字符,虽然这里文件名都规整,但养成习惯没坏处。sort -z保证顺序稳定,这样两次生成的清单可以直接 diff 对比。

离线包建议的目录结构:

目录内容说明
images/三个.tar.gzsuperset、postgres、redis
wheels/补充驱动的.whl纯 Python 包可省略
config/superset_config.py、.env模板不含真实密码
scripts/init.sh、backup.sh初始化与备份脚本
SHA256SUMS校验清单还原前必跑

配置文件里千万不要写死真实密码,.env只放占位符,让内网侧现场生成。SECRET_KEY更是如此,它一旦泄露,攻击者可以伪造会话,也能反向解出存在元数据库里的数据源密码。

4. 内网侧落地:从 load 到能登录

介质送进内网之后,接下来所有操作都在目标服务器上。这一章按「能登录」这个目标推进,每一步都给出可验证的中间态,避免走到最后一步才发现前面某环断了。

4.1 load 之后第一件事:给镜像补 tag

# 解包并校验 tar -xzf superset-offline-4.1.1.tar.gz -C /opt/ cd /opt/offline && sha256sum -c SHA256SUMS # 逐个 load,注意 gunzip 是流式解压,不占额外磁盘 for f in images/*.tar.gz; do gunzip -c "${f}" | docker load done # 检查 tag 是否完整 docker images | grep -E 'superset|postgres|redis'

gunzip -c把解压结果送到标准输出,直接管道给docker load,避免了「先解压出几十 G 的 tar 再 load」的磁盘峰值。内网服务器的磁盘配额往往不宽裕,这个细节很实际。

docker images的输出里如果看到<none>:<none>,说明 save 时用的不是repo:tag形式,需要手动补:

# tag 丢了就按镜像 ID 补回来 docker tag <IMAGE_ID> apache/superset:4.1.1

补 tag 之后最好再docker inspect核对一下RepoDigests,确保和在线侧的摘要是同一个。摘要对不上说明传输过程中镜像层损坏了,别犹豫,重新传。

4.2 superset_config.py:不改这几项等于白装

Superset 的配置优先级是「环境变量 >superset_config.py> 默认config.py」。离线部署里,我把所有业务相关的配置都集中在这一个文件,用环境变量只传密码类敏感值,这样配置可以进版本库,密码不会。

# config/superset_config.py import os # 元数据库:必须换成 PostgreSQL/MySQL,SQLite 只适合试用 SQLALCHEMY_DATABASE_URI = ( "postgresql+psycopg2://superset:{pwd}@superset-db:5432/superset".format( pwd=os.environ["DB_PASSWORD"] ) ) # SECRET_KEY 必须固定,见第 5 章的坑 SECRET_KEY = os.environ["SUPERSET_SECRET_KEY"] # 缓存:查询结果缓存走 Redis,避免大表重复扫 REDIS_HOST = "superset-redis" REDIS_PORT = 6379 CACHE_CONFIG = { "CACHE_TYPE": "RedisCache", "CACHE_DEFAULT_TIMEOUT": 300, "CACHE_KEY_PREFIX": "superset_meta_", "CACHE_REDIS_HOST": REDIS_HOST, "CACHE_REDIS_PORT": REDIS_PORT, "CACHE_REDIS_DB": 0, } DATA_CACHE_CONFIG = { "CACHE_TYPE": "RedisCache", "CACHE_DEFAULT_TIMEOUT": 3600, "CACHE_KEY_PREFIX": "superset_data_", "CACHE_REDIS_HOST": REDIS_HOST, "CACHE_REDIS_PORT": REDIS_PORT, "CACHE_REDIS_DB": 1, } # 异步任务:SQL Lab 长查询和告警报表都依赖 Celery CELERY_BROKER_URL = f"redis://{REDIS_HOST}:{REDIS_PORT}/2" CELERY_RESULT_BACKEND = f"redis://{REDIS_HOST}:{REDIS_PORT}/3" # 汉化与时区,这两组是中文场景的必改项 BABEL_DEFAULT_LOCALE = "zh" BABEL_DEFAULT_TIMEZONE = "Asia/Shanghai" DEFAULT_TIMEZONE = "Asia/Shanghai" LANGUAGES = { "en": {"flag": "us", "name": "English"}, "zh": {"flag": "cn", "name": "Chinese"}, } # 查询与超时,按内网数据量调 ROW_LIMIT = 5000 DEFAULT_SQLLAB_LIMIT = 1000 SQLLAB_TIMEOUT = 300 SUPERSET_WEBSERVER_TIMEOUT = 300

几个参数值得展开。CACHE_REDIS_DB把元数据缓存和数据缓存分到 0 和 1 两个库,是为了让FLUSHDB的时候可以只清数据缓存,不动到看板元信息。CELERY_BROKER_URL用 2 号库、结果后端用 3 号库,同理。这种分库习惯在单机部署里不显眼,等你要排查「清了缓存为什么看板配置丢了」的时候会感谢自己。

ROW_LIMIT是全局行数上限,设太大内存吃不消,设太小业务方抱怨导不全。5000 是我在十几个内网项目里试出来的折中值,具体看单行宽度。

LANGUAGES字典里必须包含zh键,否则前端语言选择器里就没有中文选项,BABEL_DEFAULT_LOCALE设了也没用。这是第 2 章提到的那条「与」关系的具体体现。

4.3 compose 编排:三个容器和依赖顺序

Superset 单容器能跑,但生产环境至少要三个:应用、元数据库、缓存。编排的关键在启动顺序和健康检查,depends_on不加condition的话,容器起来了不代表数据库能连上。

# docker-compose.yml services: superset-db: image: postgres:15 restart: always environment: POSTGRES_DB: superset POSTGRES_USER: superset POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - ./data/pg:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U superset -d superset"] interval: 10s timeout: 5s retries: 6 superset-redis: image: redis:7 restart: always command: ["redis-server", "--appendonly", "yes"] volumes: - ./data/redis:/data superset: image: apache/superset:4.1.1 restart: always depends_on: superset-db: condition: service_healthy superset-redis: condition: service_started environment: SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY} SUPERSET_CONFIG_PATH: /app/pythonpath/superset_config.py DB_PASSWORD: ${DB_PASSWORD} LANG: C.UTF-8 LC_ALL: C.UTF-8 TZ: Asia/Shanghai volumes: - ./config/superset_config.py:/app/pythonpath/superset_config.py:ro - ./data/uploads:/app/superset/static/assets/uploads ports: - "8088:8088" command: > /bin/sh -c "superset db upgrade && superset init && gunicorn --bind 0.0.0.0:8088 --workers 4 --timeout 300 --access-logfile - 'superset.app:create_app()'" superset-worker: image: apache/superset:4.1.1 restart: always depends_on: - superset environment: SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY} SUPERSET_CONFIG_PATH: /app/pythonpath/superset_config.py DB_PASSWORD: ${DB_PASSWORD} volumes: - ./config/superset_config.py:/app/pythonpath/superset_config.py:ro command: > celery --app=superset.tasks.celery_app:app worker --pool=prefork -O fair -c 4 --loglevel=INFO

healthcheck里pg_isready是 PostgreSQL 官方镜像自带的工具,比nc -z判断更准,因为它会真的做一次连接握手。retries: 6配interval: 10s意味着最多等 60 秒,够数据库首次初始化用了。

command里把db upgrade和init串在启动链上,是个便利做法但有个副作用:每次重启容器都会执行一次迁移检查。迁移本身是幂等的,多执行不报错,但会拖慢启动。更严谨的做法是拆成一个一次性的init服务,superset服务只跑 gunicorn,用depends_on的service_completed_successfully串起来。我在这类内网项目里倾向后者,因为重启频率不低。

gunicorn的--workers 4按 CPU 核数的一半到一倍取,--timeout 300必须和SUPERSET_WEBSERVER_TIMEOUT对齐,否则会出现「Superset 认为还没超时、gunicorn 已经把 worker 杀了」的诡异现象,前端表现是页面转圈然后 502。

.env文件单独放,权限设成600:

# 在内网服务器上现场生成,不要从有网侧带过来 cat > .env <<EOF DB_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=' | head -c 24) SUPERSET_SECRET_KEY=$(openssl rand -base64 42) EOF chmod 600 .env

openssl rand -base64 42是 Superset 官方推荐的SECRET_KEY长度。tr -d '/+='去掉 base64 里的特殊字符,防止密码出现在连接串里时被 URL 编码搞坏。

4.4 初始化四连与首个管理员

容器起来之后还有四步初始化,缺一步都可能登录不了。

# 1. 迁移元数据库 docker compose exec superset superset db upgrade # 2. 创建管理员,密码走交互,别写在命令行 docker compose exec superset superset fab create-admin \ --username admin \ --firstname 系统 \ --lastname 管理员 \ --email admin@example.internal # 3. 初始化角色与权限 docker compose exec superset superset init # 4. 按需加载示例数据,生产环境建议跳过 # docker compose exec superset superset load_examples

第 2 步不写--password是有意为之。写在命令行里会进 shell 历史、进ps输出,内网多人共用的跳板机上等于公开。省略该参数后命令会交互式提示输入,安全得多。

superset init这一步做的是同步权限点到角色,如果跳过了,管理员登录进去会发现菜单大面积缺失——不是汉化问题,是权限没初始化,这个现象很容易误判。

验证登录:

curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8088/login/ # 期望输出 200

返回 200 说明 gunicorn 正常、配置加载成功、元数据库能连上。如果返回 502 或者连接被拒,问题在容器层;如果返回 500,去看docker compose logs superset里的 traceback,八成是superset_config.py有语法错误或者某个依赖没装。

5. 避坑与排查:五个让我重装过的现场

这一章全是血泪经验。每一条我都真实遇到过,也见过别人重复踩。

5.1 起不来的那几种

现象一:容器启动即退出,日志只有exec format error。

原因:镜像架构和宿主机不匹配。在有 ARM 芯片的机器上docker pull没加--platform,save 出来的镜像架构是linux/arm64,灌到 x86 服务器上内核直接拒绝执行。

解决:回到第 3 章,pull 时显式加--platform linux/amd64,save 前用docker inspect --format '{{.Architecture}}'确认。已经传进去的包不用重做全部,只重做架构不对的那一个镜像即可。

现象二:docker load成功,但docker images里是<none>,compose 报image not found。

原因:save 时传的是镜像 ID 而不是repo:tag,或者中途docker rmi把 tag 删了但层还在。

解决:docker tag <ID> apache/superset:4.1.1补回来。根本性的预防是在 save 脚本里用docker inspect --format '{{.RepoTags}}'先断言 tag 非空,为空就退出。

现象三:superset db upgrade卡在某条ALTER TABLE上,或者报OperationalError: database is locked。

原因:元数据库还是默认的 SQLite。SQLite 的写锁是库级锁,迁移过程中任何并发查询都会阻塞,而且 Superset 的迁移脚本里有大量索引重建,SQLite 上跑得极慢。

解决:这是配置问题不是环境问题,直接换 PostgreSQL。在superset_config.py里把SQLALCHEMY_DATABASE_URI改成postgresql+psycopg2://,重新db upgrade。SQLite 只适合本机试玩,任何多人使用的场景都不该用它。

5.2 起来了但登录不了

现象四:重启容器后,所有人都被踢下线,而且数据源连接报「无法解密密码」。

原因:SECRET_KEY没固定。Superset 用它加密存在元数据库里的数据源密码、CSRF token、会话签名。如果没显式配置,每次启动会随机生成一个,重启后旧密文全部解不开。

解决:在.env里固化SUPERSET_SECRET_KEY,生成一次就不再改。已经踩了的,只能把所有数据源连接重新录一遍密码。这个坑没有后悔药,我现在的做法是初始化脚本里第一件事就是检查SUPERSET_SECRET_KEY是否为空,为空直接exit 1,逼着自己先配。

现象五:中文界面下时间全部差 8 小时,看板按天聚合的数据对不上。

原因:BABEL_DEFAULT_TIMEZONE和DEFAULT_TIMEZONE只配了一个,或者容器TZ环境变量没设。Superset 的时间处理链里,容器时区影响日志和datetime.now(),配置项影响 SQL 生成时的时区转换,两边不一致就会出现「页面显示昨天、数据库里是今天」的情况。

解决:三处同时配——容器环境变量TZ=Asia/Shanghai、DEFAULT_TIMEZONE = "Asia/Shanghai"、BABEL_DEFAULT_TIMEZONE = "Asia/Shanghai"。改完重启,然后在 SQL Lab 里跑SELECT now()对照宿主机时间验证。

5.3 用起来才发现的

现象六:看板导出 PDF 时中文全变方块,英文正常。

原因:容器内没有中文字体。PDF 渲染走 WeasyPrint + Pillow,用的是容器内的 fontconfig 字体列表,和浏览器渲染完全两套机制。网页看着正常给了人错觉。

解决:apt-get install fonts-wqy-zenhei或者在镜像里预装。离线环境没 apt 源,所以必须在有网侧就把字体打进派生镜像:

# 有网侧构建派生镜像,把字体固化进去 FROM apache/superset:4.1.1 USER root RUN apt-get update \ && apt-get install -y --no-install-recommends fonts-wqy-zenhei fontconfig \ && fc-cache -fv \ && rm -rf /var/lib/apt/lists/* USER superset

--no-install-recommends避免拖进一堆推荐包把镜像撑大。fc-cache -fv刷新字体缓存,不做这步新装的字体不会立即生效。USER superset切回非 root,官方镜像默认用这个用户跑应用。

现象七:大报表导出 CSV,用 Excel 打开中文乱码。

原因:UTF-8 不带 BOM,Excel 在中文 Windows 下默认按 GBK 解码。

解决:Superset 的 CSV 导出可以在superset_config.py里控制编码,也可以在前端导出时选 Excel 格式(.xlsx)绕开。更彻底的做法是在 Nginx 反代层不改动响应体,让业务方改用「导出为 Excel」。这个问题严格说不算 Superset 的 bug,是国内办公软件的编码习惯问题。

现象八:SQL Lab 跑超过 60 秒的查询,页面报 504。

原因:Nginx 反代的proxy_read_timeout默认 60 秒,比 Superset 自己的SQLLAB_TIMEOUT短,请求被反代层掐断。

解决:反代配置里把超时拉长,并且和gunicorn --timeout、SQLLAB_TIMEOUT三者对齐:

location / { proxy_pass http://127.0.0.1:8088; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 300s; # 与 SQLLAB_TIMEOUT 对齐 proxy_send_timeout 300s; client_max_body_size 100m; # 大 CSV 上传 }

proxy_read_timeout管的是「后端多久没吐数据就断开」,不是总时长。但 Superset 在长查询期间是阻塞的,没有心跳输出,所以效果上等同于总时长限制。三个超时值建议按Nginx >= gunicorn >= SQLLAB_TIMEOUT的顺序设置,留出缓冲。

6. 上线后必补的两件事:汉化覆盖率校验与元数据备份

前面五章讲的是「怎么让它能跑」,这一章讲「怎么让它跑得住」。两件事,都是上线之后才想起来、但又不能不做。

6.1 汉化覆盖率怎么验

汉化是不是完整,不能靠肉眼扫一遍界面。Superset 的翻译条目有几千条,靠人眼只能看到最常用的那几十个。我的做法是用脚本扫.mo文件的实际条目数,对照源码里的.po看编译率。

# scripts/check_i18n.py import gettext # 加载镜像里编译好的中文翻译表 with open("/app/superset/translations/zh/LC_MESSAGES/messages.mo", "rb") as f: trans = gettext.GNUTranslations(f) catalog = trans._catalog total = len([k for k in catalog if isinstance(k, str)]) empty = len([k for k, v in catalog.items() if isinstance(k, str) and not v]) print(f"条目总数: {total}") print(f"空翻译(回落英文): {empty}") print(f"覆盖率: {(total - empty) / total * 100:.1f}%")

GNUTranslations加载.mo后,_catalog是个字典,键是原文,值是译文。值为空字符串的条目在运行时会被 gettext 回落到原文,也就是英文。这个脚本给出的是后端覆盖率,前端语言包是 JSON,用jq数一下键值对里空值的比例,逻辑一样。

覆盖率低于 85% 的时候,常见页面上就能明显感觉到中英混杂。这时候要么补翻译、重新pybabel compile,要么干脆接受现状,在内部文档里说明哪些模块是英文的,省得业务方反复来问。

注意:补翻译后必须重新编译.mo,pybabel compile -d superset/translations -l zh。只改.po不编译等于没改,容器里加载的永远是旧的.mo。

6.2 元数据备份:pg_dump 的正确姿势

Superset 的所有资产——看板、图表、数据集定义、用户、权限、数据源连接——全在元数据库里。丢了这个库,等于所有配置重来一遍。而它偏偏是最容易被忽略的:应用有镜像、有 compose,看起来「部署完成了」,实际上单点。

# scripts/backup.sh,建议挂到 crontab 每天凌晨跑 #!/bin/bash set -euo pipefail BACKUP_DIR=/opt/superset-backup STAMP=$(date +%Y%m%d_%H%M) KEEP_DAYS=14 mkdir -p "${BACKUP_DIR}" # -Fc 是自定义格式,压缩率高且支持 pg_restore 选择性恢复 docker compose exec -T superset-db \ pg_dump -U superset -d superset -Fc \ > "${BACKUP_DIR}/superset_${STAMP}.dump" # 只留最近 14 天 find "${BACKUP_DIR}" -name 'superset_*.dump' -mtime +${KEEP_DAYS} -delete echo "[$(date)] backup ok: superset_${STAMP}.dump"

-Fc自定义格式比纯 SQL 文本小得多,而且恢复时可以只挑某张表,灵活性高。exec -T关掉伪终端分配,否则输出流里会混进控制字符,备份文件直接损坏——这个我吃过一次亏,恢复的时候pg_restore报格式错误,翻了半天才发现是少了-T。

恢复流程反过来:

# 恢复前先停应用,避免写入冲突 docker compose stop superset superset-worker docker compose exec -T superset-db \ pg_restore -U superset -d superset --clean --if-exists \ < /opt/superset-backup/superset_20250101_0300.dump docker compose start superset superset-worker

--clean --if-exists会先删除同名对象再重建,适合恢复到同一个库。如果是恢复到新库,去掉这两个参数。

最后说个习惯。我现在的初始化脚本里,superset init之后一定会跑一次备份,哪怕库是空的。因为那一刻的库是「已知良好状态」,出问题时可以退回到这个基线。从那以后我每次部署新环境,都会强制走一遍「初始化 → 立即备份 → 再交给业务方」这个流程,多花两分钟,省掉的是半夜被叫起来重配几十个数据源的时间。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询