Petrolyst server infrastructure: Traefik, Trino and Superset
Traefik as the server-wide edge (TLS via Let's Encrypt, dashboard on traefik.petrolyst.com behind oauth2-proxy and Keycloak). The fuel-monitoring stack: Superset on atoma.petrolyst.com with Keycloak login and Petrolyst branding, Trino reading the TimescaleDB fuel-db as catalog "fuel". Customization lives in petrolyst-insights/, deployment in the other folders. Deploys run server-side (deploy/deploy.sh) from a Gitea workflow or manually; releases are immutable, Superset metadata is backed up before migrations, and deploy/rollback.sh restores it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
24327f9462
commit
d3800ac9dd
34 files changed
+2117
No files matched your search
@@ -0,0 +1,174 @@
|
||||
# Fuel-monitoring analytics: Superset on atoma.petrolyst.com, reading the
|
||||
# operational store (fuel-db) through Trino. Run only through deploy/deploy.sh
|
||||
# or deploy/rollback.sh: they export SUPERSET_IMAGE_TAG and TRINO_CONFIG_HASH
|
||||
# from the release's ARTIFACTS file, and the :? guards below stop Compose from
|
||||
# running with either unset.
|
||||
name: fuel-monitoring
|
||||
|
||||
x-superset-env: &superset-env
|
||||
KEYCLOAK_REALM_URL: ${KEYCLOAK_REALM_URL:-https://iam.thedigitalbridge.io/realms/thedigitalbridge}
|
||||
|
||||
x-superset: &superset
|
||||
image: fuel-superset:${SUPERSET_IMAGE_TAG:?SUPERSET_IMAGE_TAG unset - run through deploy/deploy.sh}
|
||||
pull_policy: never
|
||||
secrets:
|
||||
- superset_secret_key
|
||||
- superset_db_password
|
||||
- keycloak_petrolyst_client_secret
|
||||
|
||||
services:
|
||||
superset:
|
||||
<<: *superset
|
||||
build:
|
||||
context: ./superset
|
||||
additional_contexts:
|
||||
insights: ../../petrolyst-insights
|
||||
environment:
|
||||
<<: *superset-env
|
||||
SERVER_WORKER_AMOUNT: "2"
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
superset-db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- backend
|
||||
- proxy
|
||||
mem_limit: 1536m
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8088/health"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 60s
|
||||
labels:
|
||||
traefik.enable: "true"
|
||||
traefik.docker.network: proxy
|
||||
traefik.http.routers.fm-superset.rule: Host(`${ATOMA_HOST:-atoma.petrolyst.com}`)
|
||||
traefik.http.routers.fm-superset.entrypoints: websecure
|
||||
traefik.http.routers.fm-superset.tls.certresolver: le
|
||||
traefik.http.routers.fm-superset.middlewares: secure-headers@file
|
||||
traefik.http.services.fm-superset.loadbalancer.server.port: "8088"
|
||||
|
||||
# One-shot: never started by `up`; deploy.sh runs it with `compose run --rm`.
|
||||
superset-init:
|
||||
<<: *superset
|
||||
profiles: ["init"]
|
||||
environment: *superset-env
|
||||
command: ["/bin/bash", "/app/petrolyst-init.sh"]
|
||||
depends_on:
|
||||
superset-db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- backend
|
||||
restart: "no"
|
||||
|
||||
superset-db:
|
||||
image: postgres:17.11-alpine
|
||||
environment:
|
||||
POSTGRES_USER: superset
|
||||
POSTGRES_DB: superset
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/superset_db_password
|
||||
secrets:
|
||||
- superset_db_password
|
||||
volumes:
|
||||
- superset_db_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- backend
|
||||
restart: unless-stopped
|
||||
mem_limit: 512m
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U superset -d superset"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# Cache and rate-limit store only; nothing to persist.
|
||||
redis:
|
||||
image: redis:7.4.11-alpine
|
||||
command: ["redis-server", "--maxmemory", "128mb", "--maxmemory-policy", "allkeys-lru", "--save", "", "--appendonly", "no"]
|
||||
networks:
|
||||
- backend
|
||||
restart: unless-stopped
|
||||
mem_limit: 256m
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
trino:
|
||||
image: trinodb/trino:484
|
||||
env_file:
|
||||
- ${PETROLYST_ROOT:-/opt/petrolyst}/env/fuel-monitoring.env
|
||||
volumes:
|
||||
- ./trino/etc:/etc/trino:ro
|
||||
labels:
|
||||
# Compose cannot see content changes in a bind mount; this label changes
|
||||
# with trino/etc, so exactly a config change recreates the container.
|
||||
petrolyst.config-hash: ${TRINO_CONFIG_HASH:?TRINO_CONFIG_HASH unset - run through deploy/deploy.sh}
|
||||
depends_on:
|
||||
fuel-db:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- backend
|
||||
restart: unless-stopped
|
||||
mem_limit: 3g
|
||||
# The image's own healthcheck (/usr/lib/trino/bin/health-check) applies.
|
||||
|
||||
fuel-db:
|
||||
image: timescale/timescaledb:2.31.0-pg17
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: fuel
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/fuel_db_password
|
||||
TS_TUNE_NUM_CPUS: "2"
|
||||
secrets:
|
||||
- fuel_db_password
|
||||
- fuel_db_trino_password
|
||||
volumes:
|
||||
- fuel_db_data:/var/lib/postgresql/data
|
||||
# A single file, not the folder: the image's own TimescaleDB init scripts
|
||||
# live in the same directory and must stay visible.
|
||||
- ./fuel-db/init/01-roles.sh:/docker-entrypoint-initdb.d/010_petrolyst_roles.sh:ro
|
||||
networks:
|
||||
- backend
|
||||
- fuel-db-tunnel
|
||||
# Loopback only, for `ssh -L 5433:127.0.0.1:5433 petrolyst`.
|
||||
ports:
|
||||
- "127.0.0.1:5433:5432"
|
||||
restart: unless-stopped
|
||||
mem_limit: 1536m
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d fuel"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
networks:
|
||||
backend:
|
||||
internal: true
|
||||
proxy:
|
||||
external: true
|
||||
# A port cannot be published from an internal network; this one exists only
|
||||
# so fuel-db's loopback port works.
|
||||
fuel-db-tunnel: {}
|
||||
|
||||
volumes:
|
||||
superset_db_data:
|
||||
fuel_db_data:
|
||||
|
||||
secrets:
|
||||
superset_secret_key:
|
||||
file: ${PETROLYST_ROOT:-/opt/petrolyst}/secrets/fuel-monitoring/superset_secret_key
|
||||
superset_db_password:
|
||||
file: ${PETROLYST_ROOT:-/opt/petrolyst}/secrets/fuel-monitoring/superset_db_password
|
||||
fuel_db_password:
|
||||
file: ${PETROLYST_ROOT:-/opt/petrolyst}/secrets/fuel-monitoring/fuel_db_password
|
||||
fuel_db_trino_password:
|
||||
file: ${PETROLYST_ROOT:-/opt/petrolyst}/secrets/fuel-monitoring/fuel_db_trino_password
|
||||
keycloak_petrolyst_client_secret:
|
||||
file: ${PETROLYST_ROOT:-/opt/petrolyst}/secrets/shared/keycloak_petrolyst_client_secret
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs once, when the fuel-db volume is first initialised (mounted after the
|
||||
# image's own TimescaleDB scripts, as 010_petrolyst_roles.sh).
|
||||
# fuel_owner owns database "fuel", its public schema and every table in it.
|
||||
# Loaders log in as their own role but act as fuel_owner (README
|
||||
# "Data loaders"): the default privileges below cover only what
|
||||
# fuel_owner itself creates, not what its members create.
|
||||
# trino_ro what Trino connects as: read-only, also on tables created later.
|
||||
set -euo pipefail
|
||||
|
||||
trino_password=$(cat /run/secrets/fuel_db_trino_password)
|
||||
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" \
|
||||
-v trino_password="$trino_password" <<'SQL'
|
||||
CREATE ROLE fuel_owner NOLOGIN;
|
||||
ALTER DATABASE fuel OWNER TO fuel_owner;
|
||||
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
|
||||
|
||||
CREATE ROLE trino_ro LOGIN PASSWORD :'trino_password';
|
||||
GRANT CONNECT ON DATABASE fuel TO trino_ro;
|
||||
GRANT USAGE ON SCHEMA public TO trino_ro;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA public TO trino_ro;
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE fuel_owner IN SCHEMA public
|
||||
GRANT SELECT ON TABLES TO trino_ro;
|
||||
SQL
|
||||
@@ -0,0 +1,28 @@
|
||||
# Petrolyst Superset image. Deployment only: every customization is copied from
|
||||
# petrolyst-insights/, which compose.yaml passes as the named build context
|
||||
# "insights". deploy/deploy.sh tags the result with a hash of exactly these
|
||||
# inputs (this file plus what is copied below), so the tag changes only when
|
||||
# the image would.
|
||||
FROM apache/superset:6.1.0@sha256:16b50bbef6648912a79e3293d418fefa743ce555d36c5af6b85d859119ed7f88
|
||||
|
||||
USER root
|
||||
# The 6.1.0 image ships neither an OAuth client nor the Postgres and Trino
|
||||
# drivers.
|
||||
RUN uv pip install --no-cache --python /app/.venv/bin/python \
|
||||
authlib==1.9.1 psycopg2-binary==2.9.13 "trino[sqlalchemy]==0.340.0"
|
||||
|
||||
COPY --from=insights --chown=superset:superset superset_config.py petrolyst_security.py /app/pythonpath/
|
||||
COPY --from=insights --chown=superset:superset assets/branding/ /app/superset/static/assets/branding/
|
||||
COPY --from=insights patches/ /app/patches/petrolyst/
|
||||
# Stop at the first patch or verify that fails, so a later success cannot hide
|
||||
# an earlier failure.
|
||||
RUN for p in /app/patches/petrolyst/fix_*.py; do \
|
||||
[ -e "$p" ] || continue; \
|
||||
{ python3 "$p" && python3 "$p" --verify; } || exit 1; \
|
||||
done
|
||||
|
||||
# Deployment, not customization: in the image (and so in the tag) rather than
|
||||
# bind-mounted, so a change to it makes deploy.sh run superset-init again.
|
||||
COPY init.sh /app/petrolyst-init.sh
|
||||
|
||||
USER superset
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# superset-init: migrate the metadata database, sync roles and permissions, and
|
||||
# register the Trino connection. It is copied into the Superset image, so a
|
||||
# change here changes the image tag; deploy/deploy.sh runs it on a fresh install
|
||||
# and whenever that tag changes. Every step is idempotent. Unlike the
|
||||
# upstream docker-init.sh it never creates a local admin: users come from
|
||||
# Keycloak.
|
||||
set -euo pipefail
|
||||
|
||||
superset db upgrade
|
||||
superset init
|
||||
superset set-database-uri \
|
||||
--database_name "Fuel (Trino)" \
|
||||
--uri "trino://superset@trino:8080/fuel"
|
||||
@@ -0,0 +1,3 @@
|
||||
# Analytics never writes through Trino. fuel-db's trino_ro role is read-only as
|
||||
# well; this is the second lock.
|
||||
access-control.name=read-only
|
||||
@@ -0,0 +1,5 @@
|
||||
# The fuel-monitoring operational store (service fuel-db), read-only.
|
||||
connector.name=postgresql
|
||||
connection-url=jdbc:postgresql://fuel-db:5432/fuel
|
||||
connection-user=trino_ro
|
||||
connection-password=${ENV:FUEL_DB_TRINO_PASSWORD}
|
||||
@@ -0,0 +1,14 @@
|
||||
# Single node: coordinator and worker in one process. Reachable only on the
|
||||
# backend network (Superset); no port is published and Traefik has no route.
|
||||
coordinator=true
|
||||
node-scheduler.include-coordinator=true
|
||||
http-server.http.port=8080
|
||||
discovery.uri=http://localhost:8080
|
||||
catalog.management=static
|
||||
|
||||
# Memory bounds. The container is capped at 3g (compose.yaml) and jvm.config
|
||||
# gives the heap 75 % of it (~2.25 GB); 1 GB for queries plus 512 MB headroom
|
||||
# leaves room for the rest of the heap.
|
||||
query.max-memory=1GB
|
||||
query.max-memory-per-node=1GB
|
||||
memory.heap-headroom-per-node=512MB
|
||||
@@ -0,0 +1,14 @@
|
||||
-server
|
||||
-agentpath:/usr/lib/trino/bin/libjvmkill.so
|
||||
-XX:InitialRAMPercentage=75
|
||||
-XX:MaxRAMPercentage=75
|
||||
-XX:G1HeapRegionSize=32M
|
||||
-XX:+ExplicitGCInvokesConcurrent
|
||||
-XX:+HeapDumpOnOutOfMemoryError
|
||||
-XX:+ExitOnOutOfMemoryError
|
||||
-XX:-OmitStackTraceInFastThrow
|
||||
-XX:ReservedCodeCacheSize=256M
|
||||
-XX:PerMethodRecompilationCutoff=10000
|
||||
-XX:PerBytecodeRecompilationCutoff=10000
|
||||
-Djdk.attach.allowAttachSelf=true
|
||||
-Djdk.nio.maxCachedBufferSize=2000000
|
||||
@@ -0,0 +1 @@
|
||||
io.trino=INFO
|
||||
@@ -0,0 +1,3 @@
|
||||
node.environment=petrolyst
|
||||
node.data-dir=/data/trino
|
||||
node.id=${ENV:HOSTNAME}
|
||||
Reference in new issue
Block a user