Apache Superset をソースコードから起動してカスタマイズする方法

Apache Superset をソースコードから起動してカスタマイズする方法

Apache Superset をソースコードから起動してカスタマイズする方法の画面

なぜ Apache Superset?

BI(ビジネスインテリジェンス)ツールを選定する時、いくつかの条件があると思います。
その中では「機能性」が用途に応えられるのかが選定条件の一つになると思います。
数多のBIツールから一つ一つ調査することになります。

最近の流行りだと
・Tableau ← 非常に優秀だが、ライセンス料金がそれなりにかかる
・Looker Studio ← 無料で機能性も充実だが、Google アカウントや Google Cloud との連携を前提とした使い方になることが多いなど。
・Kibana + Elastic Search ← 高速で機能性も充実だが、ドキュメントベースでリレーショナルの DB と違って、 ESQL の勉強などが必要
・その他、色々

幾つかのツールを使ってみて感じたのは、どのツールも必ず一長一短があります。

であれば、足りない機能を自分たちで実装できる BI ツールがいいのでは?
それが Apache Superset です。

Apache Supersetをソースコードから開発モードで起動したターミナル

superset のコードを改変し、ソースから起動する

Superset の良いところ、気になるところ

Superset の良いところ、気になるところの画面

superset のコミュニティはかなり大きい

良いところ
・無料
・コミュニティが大きい
・Opensource なのでいくらでも改変できる
・エンジニアなら自由度が高い

気になるところ
・グラフの種類がわりと少ない
・グラフの Layer 機能がない
・改変はできるが Python と React の知識が必要
・Kibana + Elastic Search など高速設計エンジンに比べると遅い

上記の理由から、本記事では Superset をソースコードから起動する手順を整理して紹介します。公式ドキュメントにも開発手順はありますが、設定や構成が複雑だったりして、Docker に不慣れだと動作確認に悩む場面があると思います。そこで、本記事では実際に試した際につまずいたり補足が必要だと感じたポイントも含めて解説します。

Superset をソースコードから起動する

選定条件

インストールするために Git, Python (Backend), Npm (Frontend) とデータベースが必要です。
開発環境汚染を防止するため、docker を使います。

ソースコードを取得する

Superset のソースコードは Github で公開しています。
https://github.com/apache/superset

まずは github から clone してソースコードを取得します。

Plain text
git clone https://github.com/apache/superset.git

すると、superset のフォルダができますので、下記のコマンドで出来たフォルダに入ります。

Plain text
cd superset

そのままだと main ブランチの古いコードになるので、新しいバージョンに切り替えます。
今回は 6.0.0rc2 を使用します。テーマや細かい権限管理できるバージョンなのでおすすめです。(あくまでローカル開発・検証環境の構築を対象としています)

Plain text
git checkout -b 6.0.0rc2 6.0.0rc2

ソースコードを取得する画面

6.0.0rc2 を checkout した結果

カスタム Docker コンテナ作成

git から clone したディレクトリにも docker-compose.yml がありますが、不要な設定も多いため、
今回は使いません。

下記は docker-compose.yml です。
仮の設定が多いため、Production などでは使わないでください。

Plain text
services:
  postgres-db:
    image: postgres:17
    restart: always
    container_name: postgres-db
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: postgres
      POSTGRES_HOST: postgres-db
    ports:
      - 5432:5432
    networks:
      - superset_network
  superset:
    build: .
    container_name: superset
    tty: true
    volumes:
      - .:/app
    ports:
      - 8088:8088
      - 9000:9000
    networks:
      - superset_network
    environment:
      PYTHONUNBUFFERED: 1
      ENABLE_PLAYWRIGHT: false
      PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
      BUILD_SUPERSET_FRONTEND_IN_DOCKER: true

      SECRET_KEY: 029348uhfdaspf8uya
      SUPERSET_SECRET_KEY: 029348uhfdaspf8uya
      FLASK_APP: superset

      COMPOSE_PROJECT_NAME: superset
      DEV_MODE: true

      DATABASE_DB: superset
      DATABASE_HOST: postgres-db
      DATABASE_PASSWORD: postgres
      DATABASE_USER: postgres

      EXAMPLES_DB: superset
      EXAMPLES_HOST: postgres-db
      EXAMPLES_USER: postgres
      EXAMPLES_PASSWORD: postgres
      EXAMPLES_PORT: 5432

      DATABASE_PORT: 5432
      DATABASE_DIALECT: postgresql
      POSTGRES_DB: superset
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres

      PYTHONPATH: /app/pythonpath:/app/docker/pythonpath_dev
      REDIS_HOST: redis
      REDIS_PORT: 6379

      FLASK_DEBUG: true
      SUPERSET_LOG_LEVEL: info

      SUPERSET_APP_ROOT: /
      SUPERSET_ENV: development
      SUPERSET_LOAD_EXAMPLES: no
      SUPERSET_PORT: 8088
      CYPRESS_CONFIG: false
      MAPBOX_API_KEY: ''
networks:
  superset_network:
    name: "superset_network"
    driver: bridge

そして Dockerfile です。

Plain text
FROM debian:12-slim

RUN apt update
RUN apt upgrade
RUN apt install -y curl build-essential gnupg ca-certificates python3.11 python3.11-dev python3.11-venv libssl-dev libffi-dev libsasl2-dev libldap2-dev default-libmysqlclient-dev
RUN apt install -y zstd

ARG NODE_MAJOR=20

RUN mkdir -p /etc/apt/keyrings && \
    curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
    echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list

# Update APT and install Node.js
RUN apt-get update && \
    apt-get install -y nodejs && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

上記の docker-compose.yml と Dockerfile を一緒に superset のルートディレクトリにおいてください。
既存の docker-compose.yml と Dockerfile があるので、上書きか、消してください。

次は docker compose でコンテナを作成します。

Plain text
sudo docker compose up -d

これで下準備のデータベースとソース開発用のコンテナができています。

Superset 用のデータベース作成

下記のコマンドで postgres-db のコンテナ内に入ります。

Plain text
docker exec -it -u postgres postgres-db /bin/bash
ls

psql コマンドで postgresql に接続します。

Plain text
psql

Superset 用のデータベースを作成しておきます。

Plain text
create database superset;

その後 exit を 2 回入力して postgresql、コンテナから出ます。

Superset 用のデータベース作成の画面

\l コマンドでデータベースが作成されたことを確認できます。

バックエンドサーバのセットアップ

バックエンドサーバ(Python)をセットアップします。
下記のコマンドで python3 を使いますが、システムによっては python だったりします。

まずは Python の Dependencies をインストールしますが、
今回は mysql ではなく、 postgresql を使いますので、 
requirements/development.txt をちょっと編集します。
452 行目のところに mysqlclient を消すか、コメントアウトします。

また、authlib==1.6.5 をつけることで各認証機能を有効できます。

Plain text
authlib==1.6.5# mysqlclient==2.2.6 #コメントアウトすることで、ライブラリをインストールしなくなります。# # via apache-superset

次にコンテナ内で操作するため、 docker exec でコンテナ内に入ります。

Plain text
docker exec -it superset /bin/bash
ls

バックエンドサーバのセットアップの画面(1)

コンテナ内のファイルがちゃんとマウントされているところの確認

また、python の仮想環境から作ることをおすすめします。下記のコマンドは python 仮想環境から作成します。

Plain text
python3 -m venv venv
source venv/bin/activate

pip で python の必要ライブラリパッケージをインストールします。

Plain text
pip install -r requirements/development.txt
pip install -e .

バックエンドサーバのセットアップの画面(2)

pip でライブラリパッケージインストール完了

superset のコマンドが使えるようになるので、superset コマンドで db 作成します。

Plain text
superset db upgrade

バックエンドサーバのセットアップの画面(3)

admin ユーザを作成します。

Plain text
superset fab create-admin

バックエンドサーバのセットアップの画面(4)

任意ですが、サンプルのデータ入れます。

Plain text
superset load_examples

superset を初期化します。

Plain text
superset init

バックエンドサーバのセットアップの画面(5)

最後に、superset のバックエンドサーバを起動します。
下記の例はポート 8088 に起動します。

Plain text
superset run -p 8088 -h 0.0.0.0 --with-threads --reload --debugger --debug

これでバックエンドサーバのセットアップは完了です。

ただ、フロントエンドサーバは今ないので、このまま localhost:8088 に接続すると、
404エラーになります。

フロントエンドサーバのセットアップ

フロントエンドサーバのソースコードは clone したレポジトリーの superset-frontend フォルダにあります。
バックエンドの terminal 起動したままで、別の terminal 開いてまたコンテナに入り直します。

Plain text
docker exec -it superset /bin/bash
ls

フロントエンドサーバのフォルダに入ります。

Plain text
cd superset-frontend

フロントエンドサーバは React で、 node バージョン 20.18.1 以上と npm バージョン 10.8.1 以上が必要です。
node と npm のインストールは Dockerfile で既に行なっているので問題ありません。

初期ライブラリをインストールします。

Plain text
npm install

フロントエンドサーバをビルドします。
初回ビルドなので、時間かかります。

Plain text
npm run build

これでフロントエンドも起動できるようになりましたので、下記のコマンドで起動します。

Plain text
npm run dev

フロントエンドサーバのセットアップの画面

しばらくビルドしたら起動完了します。

コンフィグを編集する

Superset は Redis を通して WebSocket を使っているので、
今回は使わないため、コンフィグを編集して無効にします。

docker/pythonpath_dev/superset_config.py を開いて編集します。

Plain text
import logging
import os
import sys

from celery.schedules import crontab
from flask_caching.backends.filesystemcache import FileSystemCache

logger = logging.getLogger()

DATABASE_DIALECT = os.getenv("DATABASE_DIALECT")
DATABASE_USER = os.getenv("DATABASE_USER")
DATABASE_PASSWORD = os.getenv("DATABASE_PASSWORD")
DATABASE_HOST = os.getenv("DATABASE_HOST")
DATABASE_PORT = os.getenv("DATABASE_PORT")
DATABASE_DB = os.getenv("DATABASE_DB")

EXAMPLES_USER = os.getenv("EXAMPLES_USER")
EXAMPLES_PASSWORD = os.getenv("EXAMPLES_PASSWORD")
EXAMPLES_HOST = os.getenv("EXAMPLES_HOST")
EXAMPLES_PORT = os.getenv("EXAMPLES_PORT")
EXAMPLES_DB = os.getenv("EXAMPLES_DB")

# The SQLAlchemy connection string.
SQLALCHEMY_DATABASE_URI = (
    f"{DATABASE_DIALECT}://"
    f"{DATABASE_USER}:{DATABASE_PASSWORD}@"
    f"{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_DB}"
)

SQLALCHEMY_EXAMPLES_URI = (
    f"{DATABASE_DIALECT}://"
    f"{EXAMPLES_USER}:{EXAMPLES_PASSWORD}@"
    f"{EXAMPLES_HOST}:{EXAMPLES_PORT}/{EXAMPLES_DB}"
)

# ---------- WebSocket を無効します ----------

WEBSOCKET_ENABLED = False

# ---------- キャッシュの種類を変更を無効します ----------

CACHE_CONFIG = {
    "CACHE_TYPE": "SimpleCache",
    "CACHE_DEFAULT_TIMEOUT": 86400,
    "CACHE_THRESHOLD": 10000,
}
QUERY_SEARCH_CACHE_CONFIG = {
        'CACHE_TYPE': 'SimpleCache',
        'CACHE_DEFAULT_TIMEOUT': 86400,
        'CACHE_THRESHOLD': 10000,
}
FILTER_STATE_CACHE_CONFIG = {
        'CACHE_TYPE': 'SimpleCache',
        'CACHE_DEFAULT_TIMEOUT': 86400,
        'CACHE_THRESHOLD': 10000,
}

# ---------- Redis 設定をコメントアウトして、無効にします ----------

# REDIS_HOST = os.getenv("REDIS_HOST", "redis")
# REDIS_PORT = os.getenv("REDIS_PORT", "6379")
# REDIS_CELERY_DB = os.getenv("REDIS_CELERY_DB", "0")
# REDIS_RESULTS_DB = os.getenv("REDIS_RESULTS_DB", "1")
#
# RESULTS_BACKEND = FileSystemCache("/app/superset_home/sqllab")
#
# CACHE_CONFIG = {
#     "CACHE_TYPE": "RedisCache",
#     "CACHE_DEFAULT_TIMEOUT": 300,
#     "CACHE_KEY_PREFIX": "superset_",
#     "CACHE_REDIS_HOST": REDIS_HOST,
#     "CACHE_REDIS_PORT": REDIS_PORT,
#     "CACHE_REDIS_DB": REDIS_RESULTS_DB,
# }
# DATA_CACHE_CONFIG = CACHE_CONFIG
# THUMBNAIL_CACHE_CONFIG = CACHE_CONFIG
#
#
# class CeleryConfig:
#     broker_url = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_CELERY_DB}"
#     imports = (
#         "superset.sql_lab",
#         "superset.tasks.scheduler",
#         "superset.tasks.thumbnails",
#         "superset.tasks.cache",
#     )
#     result_backend = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_RESULTS_DB}"
#     worker_prefetch_multiplier = 1
#     task_acks_late = False
#     beat_schedule = {
#         "reports.scheduler": {
#             "task": "reports.scheduler",
#             "schedule": crontab(minute="*", hour="*"),
#         },
#         "reports.prune_log": {
#             "task": "reports.prune_log",
#             "schedule": crontab(minute=10, hour=0),
#         },
#     }
#
#
# CELERY_CONFIG = CeleryConfig

FEATURE_FLAGS = {"ALERT_REPORTS": True}
ALERT_REPORTS_NOTIFICATION_DRY_RUN = True
WEBDRIVER_BASEURL = f"http://superset_app{os.environ.get('SUPERSET_APP_ROOT', '/')}/"  # When using docker compose baseurl should be http://superset_nginx{ENV{BASEPATH}}/  # noqa: E501
# The base URL for the email report hyperlinks.
WEBDRIVER_BASEURL_USER_FRIENDLY = (
    f"http://localhost:8888/{os.environ.get('SUPERSET_APP_ROOT', '/')}/"
)
SQLLAB_CTAS_NO_LIMIT = True

log_level_text = os.getenv("SUPERSET_LOG_LEVEL", "INFO")
LOG_LEVEL = getattr(logging, log_level_text.upper(), logging.INFO)

if os.getenv("CYPRESS_CONFIG") == "true":
    # When running the service as a cypress backend, we need to import the config
    # located @ tests/integration_tests/superset_test_config.py
    base_dir = os.path.dirname(__file__)
    module_folder = os.path.abspath(
        os.path.join(base_dir, "../../tests/integration_tests/")
    )
    sys.path.insert(0, module_folder)
    from superset_test_config import *  # noqa

    sys.path.pop(0)

#
# Optionally import superset_config_docker.py (which will have been included on
# the PYTHONPATH) in order to allow for local settings to be overridden
#
try:
    import superset_config_docker
    from superset_config_docker import *  # noqa: F403

    logger.info(
        f"Loaded your Docker configuration at [{superset_config_docker.__file__}]"
    )
except ImportError:
    logger.info("Using default Docker config...")

バックエンドサーバを起動したままなので、 Hot Reload 機能でコンフィグが編集された時すぐに変更が反映します。

Superset を試す

Webブラウザを開いて http://localhost:8088/ に入ると下記の画面がでれば設定がうまくいってます。
superset fab create-admin で作った user/pass を入力すれば入ります。

Superset を試すの画面(1)

ログイン画面が出ていれば設定に問題なくできました。

Superset を試すの画面(2)

ログイン後の画面

Superset をカスタマイズしてみる

やっとソースコードから起動できましたので、利点であるコードの改変に試しましょう。

まずは簡単なものから紹介します。

フロントエンドのコードを変更してみる

Superset のフロントエンドのソースコードは root フォルダから superset-frontend フォルダーにあります。

Supersetのフロントエンドソースコードがあるsuperset-frontendフォルダー

今回は試しに**「テーマを強制的に Dark Theme にする」**のソース変更をしてみましょう。
superset-frontend/src/theme/ThemeController.ts の 147行あたりに下記のように編集します。

Plain text
    // Initialize theme and mode
    this.currentMode = this.determineInitialMode();
    const initialTheme =
      this.getThemeForMode(ThemeMode.DARK) || this.defaultTheme;
      //this.getThemeForMode(this.currentMode) || this.defaultTheme;

this.currentMode → ThemeMode.DARK に変更することで、システム設定関係なく強制的に Dark Mode にすることができます。

変更が自動的に反映されます。

Supersetのフロントエンドコード変更後に自動コンパイルが完了したログ

コード編集すると自動でコンパイルしてくれます

ブラウザを更新するとテーマが Dark Mode に変更されたことが確認できます。

フロントエンドコードの変更によってダークモードになったSuperset

Dark Mode に変更されました

Superset グラフの種類を増やしてみる

Superset のグラフの種類は他の有名 BI ツールと比べると少なく、
増やしたい気持ちが強いです。
本来は使用目的に伴って色々コード改変、もしくは作成しますが、
今回は単純に増やすことをしてみます。

Superset のグラフのソースコードは superset-frontend/plugins フォルダにあります。

Superset グラフの種類を増やしてみる(1)

今回は下記の画像の Histogramと同じものを増やしてみます。
上のメニュの Charts → +New Chart でこのメニューをアクセスできます。

Superset グラフの種類を増やしてみる(2)

Superset グラフの種類を増やしてみる(3)

Histogram を複製します

superset-frontend/plugins/plugin-chart-echarts/Histogram
のフォルダを同じ階層で複製して、 フォルダ名を Histogram_New にします。

Superset グラフの種類を増やしてみる(4)

superset-frontend/plugins/plugin-chart-echarts/src/Histogram_New/index.ts
を編集します。
30行目の EchartsHistogramChartPlugin を EchartsHistogramNewChartPlugin にします。

Plain text
// TODO: Implement cross filtering
// export default class EchartsHistogramChartPlugin extends ChartPlugin<
export default class EchartsHistogramNewChartPlugin extends ChartPlugin<
  HistogramFormData,
  HistogramChartProps
> {

新しいグラフクラスができているので、index に追加します。
superset-frontend/plugins/plugin-chart-echarts/src/index.ts の31行あたりに追加します。

Plain text
export { default as EchartsGraphChartPlugin } from './Graph';
export { default as EchartsGaugeChartPlugin } from './Gauge';
export { default as EchartsHistogramChartPlugin } from './Histogram';
// HistogramNew を追加
export { default as EchartsHistogramNewChartPlugin } from './Histogram_New';
export { default as EchartsRadarChartPlugin } from './Radar';
export { default as EchartsFunnelChartPlugin } from './Funnel';

superset-frontend/packages/superset-ui-core/src/chart/types/VizType.ts の 39行目あたりに新しいタイプを増やす

Plain text
Handlebars = 'handlebars',
Heatmap = 'heatmap_v2',
Histogram = 'histogram_v2',
HistogramNew = 'histogram_New', // 追加
Horizon = 'horizon',
LegacyBubble = 'bubble',

最後に superset-frontend/src/visualizations/presets/MainPreset.js で 169行目あたりに新しいグラフを追加します。

Plain text
new EchartsHeatmapChartPlugin().configure({ key: VizType.Heatmap }),
new EchartsHistogramChartPlugin().configure({ key: VizType.Histogram }),

//追加
new EchartsHistogramNewChartPlugin().configure({ key: VizType.HistogramNew }), 
new SelectFilterPlugin().configure({ key: FilterPlugins.Select }),

上記の編集が終わったらもう一度グラフのリスト画面に入ると追加したグラフが増えていることを確認できます。

Superset グラフの種類を増やしてみる(5)

Histogram New が増えました

最後に

今回は時間の都合でフロントエンドの簡単な改変しか紹介できませんでしたが、
また時間ができたらバックエンドの機能改変方法や、全く新しいグラフ作製方法なども紹介したいと思います。

では、また次回に!