diff --git a/.gitignore b/.gitignore index f8be6f3f..9c16e8a9 100644 --- a/.gitignore +++ b/.gitignore @@ -142,7 +142,7 @@ yq **/.env-devel **/.stack.*.yml **/.stack.*.yaml -docker-compose.yml + stack.yml stack_with_prefix.yml docker-compose.simcore.yml diff --git a/scripts/common-services.Makefile b/scripts/common-services.Makefile new file mode 100644 index 00000000..cbd30273 --- /dev/null +++ b/scripts/common-services.Makefile @@ -0,0 +1,3 @@ +STACK_NAME = $(notdir $(shell pwd)) +TEMP_COMPOSE=.stack.${STACK_NAME}.yaml +REPO_BASE_DIR := $(shell git rev-parse --show-toplevel) diff --git a/services/metabase/.gitignore b/services/metabase/.gitignore new file mode 100644 index 00000000..a0120e09 --- /dev/null +++ b/services/metabase/.gitignore @@ -0,0 +1,2 @@ +configure_metabase.sql +docker-compose.yml diff --git a/services/metabase/Makefile b/services/metabase/Makefile new file mode 100644 index 00000000..60539799 --- /dev/null +++ b/services/metabase/Makefile @@ -0,0 +1,17 @@ +REPO_BASE_DIR := $(shell git rev-parse --show-toplevel) + +include ${REPO_BASE_DIR}/scripts/common.Makefile +include ${REPO_BASE_DIR}/scripts/common-services.Makefile + +.PHONY: up +up: ${TEMP_COMPOSE} ## Deploys metabase stack + +${TEMP_COMPOSE}: docker-compose.yml .env + @${REPO_BASE_DIR}/scripts/docker-stack-config.bash -e .env $< > $@ + +docker-compose.yml: docker-compose.yml.j2 .env .venv + @$(call jinja, $<, .env, $@) + +configure_metabase.sql: .env + @set -o allexport; source $<; set +o allexport; \ + envsubst < $@.template > $@ diff --git a/services/metabase/configure_metabase.sql.template b/services/metabase/configure_metabase.sql.template new file mode 100644 index 00000000..0446927e --- /dev/null +++ b/services/metabase/configure_metabase.sql.template @@ -0,0 +1,7 @@ +CREATE USER ${METABASE_POSTGRES_USER} WITH PASSWORD '${METABASE_POSTGRES_PASSWORD}'; + +-- relies on readonly role aldready existing in the database +GRANT ${POSTGRES_DB}_readonly TO ${METABASE_POSTGRES_USER}; + +CREATE DATABASE ${METABASE_POSTGRES_DB} + WITH OWNER ${METABASE_POSTGRES_USER}; diff --git a/services/metabase/docker-compose.yml.j2 b/services/metabase/docker-compose.yml.j2 new file mode 100644 index 00000000..2faaca2b --- /dev/null +++ b/services/metabase/docker-compose.yml.j2 @@ -0,0 +1,91 @@ +# https://www.metabase.com/docs/v0.54/installation-and-operation/running-metabase-on-docker + +# https://www.metabase.com/docs/v0.54/installation-and-operation/running-metabase-on-docker#example-docker-compose-yaml-file +services: + metabase: + image: metabase/metabase:v0.54.13.3 + networks: + - public + - monitored + environment: + - MB_HEALTH_CHECK_LOGGING_ENABLED=false + # Avoid: site URL basename "/" does not match the actual basename + # https://www.metabase.com/docs/latest/configuring-metabase/environment-variables#mb_site_url + - MB_SITE_URL=https://${ADMIN_DOMAIN}/metabase/ + # Metabase logs: https://www.metabase.com/docs/v0.54/configuring-metabase/log-configuration + - JAVA_OPTS=-Dlog4j.configurationFile=file:/etc/metabase/log4j2.xml + # https://www.metabase.com/docs/v0.54/installation-and-operation/configuring-application-database + - MB_DB_TYPE=postgres + - MB_DB_DBNAME=metabase + - MB_DB_HOST=${POSTGRES_HOST} + - MB_DB_PORT=${POSTGRES_PORT} + - MB_DB_USER=${METABASE_POSTGRES_USER} + - MB_DB_PASS=${METABASE_POSTGRES_PASSWORD} + # https://www.metabase.com/docs/v0.54/installation-and-operation/running-metabase-on-docker#setting-the-java-timezone + - JAVA_TIMEZONE=UTC + # https://www.metabase.com/docs/latest/installation-and-operation/observability-with-prometheus + - MB_PROMETHEUS_SERVER_PORT=9191 + deploy: + # https://www.metabase.com/learn/metabase-basics/administration/administration-and-operation/metabase-at-scale + replicas: ${METABASE_REPLICAS} + update_config: + parallelism: 1 + order: start-first + failure_action: rollback + delay: 30s + placement: + constraints: + - node.labels.ops==true + labels: + # prometheus only keeps jobs with `prometheus-job` label + - prometheus-job=metabase + # Set in `MB_PROMETHEUS_SERVER_PORT` environment variable + - prometheus-port=9191 + # TODO: add prometheus metrics + - traefik.enable=true + - traefik.docker.network=${PUBLIC_NETWORK} + # router + - traefik.http.routers.metabase.rule=Host(`${ADMIN_DOMAIN}`) && PathPrefix(`/metabase`) + - traefik.http.routers.metabase.entrypoints=https + - traefik.http.routers.metabase.tls=true + - traefik.http.middlewares.metabase_stripprefixregex.stripprefixregex.regex=^/metabase + - traefik.http.routers.metabase.middlewares=ops_whitelist_ips@swarm, ops_gzip@swarm, ops_auth@swarm, metabase_stripprefixregex + # service + - traefik.http.services.metabase.loadbalancer.server.port=3000 + - traefik.http.services.metabase.loadbalancer.healthcheck.path=/api/health + - traefik.http.services.metabase.loadbalancer.healthcheck.interval=10s + # GET method sometimes fails (`Request canceled before finishing`) log + # This does not happen with HEAD method. Official healthcheck documentation + # defines HEAD as well + - traefik.http.services.metabase.loadbalancer.healthcheck.method=HEAD + - traefik.http.services.metabase.loadbalancer.healthcheck.timeout=1s + + # https://www.metabase.com/learn/metabase-basics/administration/administration-and-operation/metabase-in-production + resources: + limits: + memory: 2G + cpus: "2.0" + reservations: + memory: 1G + cpus: "1.0" + healthcheck: + test: curl --fail -I http://localhost:3000/api/health || exit 1 + interval: 15s + timeout: 5s + retries: 5 + configs: + - source: logging_configuration + target: /etc/metabase/log4j2.xml + +configs: + logging_configuration: + file: ./logging_configuration.xml + name: {{ STACK_NAME }}_logging_configuration_{{ "./logging_configuration.xml" | sha256file | substring(0,10) }} + +networks: + public: + external: true + name: ${PUBLIC_NETWORK} + monitored: + name: ${MONITORED_NETWORK} + external: true diff --git a/services/metabase/logging_configuration.xml b/services/metabase/logging_configuration.xml new file mode 100644 index 00000000..fcc286b9 --- /dev/null +++ b/services/metabase/logging_configuration.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/metabase/template.env b/services/metabase/template.env new file mode 100644 index 00000000..02212e44 --- /dev/null +++ b/services/metabase/template.env @@ -0,0 +1,16 @@ +STACK_NAME=${STACK_NAME} + +METABASE_REPLICAS=${METABASE_REPLICAS} + +ADMIN_DOMAIN=${ADMIN_DOMAIN} + +PUBLIC_NETWORK=${PUBLIC_NETWORK} +MONITORED_NETWORK=${MONITORED_NETWORK} + +POSTGRES_HOST=${POSTGRES_HOST} +POSTGRES_PORT=${POSTGRES_PORT} +POSTGRES_DB=${POSTGRES_DB} + +METABASE_POSTGRES_USER=${METABASE_POSTGRES_USER} +METABASE_POSTGRES_PASSWORD=${METABASE_POSTGRES_PASSWORD} +METABASE_POSTGRES_DB=${METABASE_POSTGRES_DB}