Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
environment: hetzner
env:
ACME_EMAIL: ${{ vars.ACME_EMAIL }}
COMPOSE_FILE: docker-compose.yml:docker-compose.mock-oidc.yml:infrastructure/docker-compose.hetzner.yml
COMPOSE_FILE: docker-compose.yml:docker-compose.mock-oidc.yml:docker-compose.metrics.yml:infrastructure/docker-compose.hetzner.yml
DEPLOY_PATH: /home/user/deploy
DEPLOY_USER: user
EOAPI_DOMAIN: ${{ vars.EOAPI_DOMAIN }}
Expand Down Expand Up @@ -146,6 +146,7 @@ jobs:
"cd '${DEPLOY_PATH}' && \
export COMPOSE_FILE='${COMPOSE_FILE}' && \
export EOAPI_DOMAIN='${EOAPI_DOMAIN}' && \
export EOAPI_GRAFANA_ROOT_URL='https://${EOAPI_DOMAIN}/monitoring/' && \
export ACME_EMAIL='${ACME_EMAIL}' && \
docker compose down && \
bash .github/workflows/ingest.sh --wipe-pgdata-only && \
Expand All @@ -155,6 +156,17 @@ jobs:
docker compose restart vector && \
docker compose ps"

- name: Install demo refresh cron
run: |
ssh $SSH_OPTS "${DEPLOY_USER}@${EOAPI_DOMAIN}" \
"cd '${DEPLOY_PATH}' && \
export DEPLOY_PATH='${DEPLOY_PATH}' && \
export EOAPI_DOMAIN='${EOAPI_DOMAIN}' && \
export EOAPI_GRAFANA_ROOT_URL='https://${EOAPI_DOMAIN}/monitoring/' && \
export ACME_EMAIL='${ACME_EMAIL}' && \
export COMPOSE_FILE='${COMPOSE_FILE}' && \
bash scripts/install_demo_cron.sh"

- name: Wait for services
run: |
wait_for_service() {
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests/test_raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def test_raster_api():

def test_mosaic_api():
"""test mosaic."""
query = {"collections": ["noaa-emergency-response"], "filter-lang": "cql-json"}
query = {"collections": ["noaa-emergency-response"], "filter-lang": "cql2-json"}
resp = httpx.post(f"{raster_endpoint}/searches/register", json=query)
assert resp.headers["content-type"] == "application/json"
assert resp.status_code == 200
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,39 @@ Item map viewers are available at `/raster/collections/{collection_id}/items/{it

If you've added a vector dataset to the `public` schema in the Postgres database, they will be available through the **Vector** service at [http://localhost:8080/vector](http://localhost:8080/vector).

### Monitoring

Optional Prometheus + Grafana stack for service metrics. Start with the metrics overlay (combine with mock OIDC for Grafana login):

```
docker compose -f docker-compose.yml -f docker-compose.mock-oidc.yml -f docker-compose.metrics.yml up --watch
```

| Endpoint | Description |
|----------|-------------|
| [http://localhost:8080/monitoring](http://localhost:8080/monitoring) | Grafana dashboards (via Traefik); start with **eoAPI Overview** |
| [http://127.0.0.1:9090](http://127.0.0.1:9090) | Prometheus UI (debugging) |
| `stac:8081/_mgmt/metrics` | STAC API metrics (internal) |
| `raster:8082/metrics` | Raster service metrics (internal) |
| `vector:8083/metrics` | Vector service metrics (internal) |

Provisioned dashboards live in the **eoAPI** folder:

- **eoAPI Overview** — service health, cross-service comparison, and database saturation (connections, transactions, cache hit ratio)
- **eoAPI — STAC Auth Proxy**, **STAC**, **Raster**, **Vector** — per-service detail (errors, latency, traffic by operation)

All use low-cardinality `operation` labels from the runtimes. STAC maps STAC API endpoints; raster and vector map coarse route groups (`tiles`, `register_search`, `list_items`, etc.). The Kubernetes infrastructure dashboard in [eoapi-k8s](https://github.com/developmentseed/eoapi-k8s) (`charts/eoapi/data/dashboards/eoAPI-Dashboard.json`) targets pod CPU/memory and nginx ingress metrics and is not included in this Docker Compose stack.

**Grafana login:** When using `docker-compose.mock-oidc.yml`, click **Sign in with Mock OIDC** and enter any username on the mock login form. Grafana maps the username to `{username}@localhost` when the provider does not return an email claim. Username/password login is disabled.

If Grafana is accessed on a different host or port, set `EOAPI_GRAFANA_ROOT_URL` (for example `https://eoapi.example.com/monitoring/`) so OAuth callbacks use the Traefik URL rather than Grafana's internal port.

To populate Grafana with sample traffic, run the [ingest profile](#local-testing) first for richer STAC and raster traffic, then generate load with:

```
uv run scripts/simulate_traffic.py --duration 3m --users 6
```

## Deployment

### Requirements
Expand Down
95 changes: 95 additions & 0 deletions docker-compose.metrics.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Optional overlay: Prometheus + Grafana monitoring for eoAPI runtimes.
#
# docker compose -f docker-compose.yml -f docker-compose.mock-oidc.yml -f docker-compose.metrics.yml up
#
# Grafana is served at http://localhost:8080/monitoring (via Traefik).
# Prometheus UI is at http://127.0.0.1:9090 for debugging.
#
# Grafana login uses the mock OIDC server when docker-compose.mock-oidc.yml is
# also applied — any username works. Username/password login is disabled.

services:
traefik:
depends_on:
- grafana

prometheus:
image: prom/prometheus:latest
volumes:
- ./dockerfiles/metrics/prometheus.yml:/etc/prometheus/prometheus.yml:ro
ports:
- "${MY_DOCKER_IP:-127.0.0.1}:9090:9090"
depends_on:
- stac
- stac-auth-proxy
- raster
- vector
- postgres-exporter
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --web.enable-lifecycle

grafana:
image: grafana/grafana:latest
environment:
# Static creds are inert while GF_AUTH_DISABLE_LOGIN_FORM/GF_AUTH_BASIC_ENABLED
# below keep the basic-auth login form off; don't rely on this if that changes.
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: admin
GF_USERS_ALLOW_SIGN_UP: "true"
GF_USERS_AUTO_ASSIGN_ORG_ROLE: Editor
# Must match the Traefik URL, not Grafana's internal :3000 port.
GF_SERVER_ROOT_URL: "${EOAPI_GRAFANA_ROOT_URL:-http://localhost:8080/monitoring/}"
GF_SERVER_SERVE_FROM_SUB_PATH: "true"
GF_AUTH_DISABLE_LOGIN_FORM: "true"
GF_AUTH_BASIC_ENABLED: "false"
GF_AUTH_GENERIC_OAUTH_ENABLED: "true"
GF_AUTH_GENERIC_OAUTH_NAME: Mock OIDC
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: grafana
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: ""
GF_AUTH_GENERIC_OAUTH_SCOPES: openid profile
GF_AUTH_GENERIC_OAUTH_AUTH_URL: http://localhost:8085/authorize
GF_AUTH_GENERIC_OAUTH_TOKEN_URL: http://mock-oidc:8888/token
GF_AUTH_GENERIC_OAUTH_USE_PKCE: "true"
GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP: "true"
# mock-oidc only returns `sub` unless custom claims are provided.
GF_AUTH_GENERIC_OAUTH_LOGIN_ATTRIBUTE_PATH: sub
GF_AUTH_GENERIC_OAUTH_NAME_ATTRIBUTE_PATH: sub
GF_AUTH_GENERIC_OAUTH_EMAIL_ATTRIBUTE_PATH: "email || join('', [sub, '@localhost'])"
GF_AUTH_GENERIC_OAUTH_SKIP_ORG_ROLE_SYNC: "true"
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: "Editor"
volumes:
- ./dockerfiles/metrics/grafana/provisioning:/etc/grafana/provisioning:ro
- ./dockerfiles/metrics/grafana/dashboards:/etc/grafana/dashboards:ro
depends_on:
- prometheus
labels:
- traefik.enable=true
- traefik.http.routers.grafana.rule=PathPrefix(`/monitoring`)
- traefik.http.routers.grafana.entrypoints=web
- traefik.http.services.grafana.loadbalancer.server.port=3000

postgres-exporter:
image: quay.io/prometheuscommunity/postgres-exporter:latest
environment:
DATA_SOURCE_NAME: postgresql://username:password@database:5432/postgis?sslmode=disable
depends_on:
database:
condition: service_healthy

stac:
environment:
- PROMETHEUS_DISABLE_CREATED_SERIES=True

raster:
environment:
- PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus
- PROMETHEUS_DISABLE_CREATED_SERIES=True
command: bash -c "mkdir -p /tmp/prometheus && rm -rf /tmp/prometheus/* && bash /tmp/scripts/wait-for-it.sh -t 120 -h database -p 5432 && uvicorn eoapi.raster.app:app --host 0.0.0.0 --port 8082 --workers 10"

vector:
environment:
- PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus
- PROMETHEUS_DISABLE_CREATED_SERIES=True
command: bash -c "mkdir -p /tmp/prometheus && rm -rf /tmp/prometheus/* && bash /tmp/scripts/wait-for-it.sh -t 120 -h database -p 5432 && uvicorn eoapi.vector.app:app --host 0.0.0.0 --port 8083 --workers 10"
10 changes: 5 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,11 @@ services:
environment:
- PYTHONUNBUFFERED=1
# Application
- POSTGRES_USER=username
- POSTGRES_PASS=password
- POSTGRES_DBNAME=postgis
- POSTGRES_HOST=database
- POSTGRES_PORT=5432
- PGUSER=username
- PGPASSWORD=password
- PGDATABASE=postgis
- PGHOST=database
- PGPORT=5432
- DB_MIN_CONN_SIZE=1
- DB_MAX_CONN_SIZE=10
- CPL_TMPDIR=/tmp
Expand Down
10 changes: 6 additions & 4 deletions dockerfiles/Dockerfile.browser
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
# Requires stac-browser >=5.0.0, which lets SB_pathPrefix (and other config)
# be set at container startup via DYNAMIC_CONFIG (radiantearth/stac-browser#930).
ARG STAC_BROWSER_REPO=https://github.com/radiantearth/stac-browser.git
ARG STAC_BROWSER_REF=v5.0.0

FROM node:lts-alpine AS build-step
ARG DYNAMIC_CONFIG=true
ARG historyMode="history"
ARG SB_CONFIG=""
ARG STAC_BROWSER_REPO
ARG STAC_BROWSER_REF
ENV DYNAMIC_CONFIG=true
ENV DYNAMIC_CONFIG="${DYNAMIC_CONFIG}"
ENV SB_historyMode="${historyMode}"
ENV SB_CONFIG="${SB_CONFIG}"

WORKDIR /app

RUN apk add --no-cache git
RUN git clone "${STAC_BROWSER_REPO}" . && \
git checkout "${STAC_BROWSER_REF}"
RUN git clone --branch "${STAC_BROWSER_REF}" --depth 1 "${STAC_BROWSER_REPO}" .

RUN npm install
# Uncomment the <base>/runtime-config.js tags in index.html, which Vite (via
# DYNAMIC_CONFIG) otherwise leaves commented out for builds with a fixed pathPrefix.
RUN sed -i 's/<!--RC//;s/RC-->//' index.html
RUN if [ "${DYNAMIC_CONFIG}" = "true" ]; then sed -i 's/<!--RC//;s/RC-->//' index.html; fi
RUN npm run build


Expand Down
1 change: 1 addition & 0 deletions dockerfiles/landing/img/grafana_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 14 additions & 1 deletion dockerfiles/landing/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,19 @@ <h5>tipg — spatial objects</h5>
</div>
</article>
</li>
<li>
<article class="feature-item">
<img src="img/grafana_logo.svg" alt="Grafana logo" loading="lazy">
<div class="feature-item__description md-typeset">
<h3>Monitoring</h3>
<h5>Grafana — service metrics</h5>
<p>Prometheus-backed observability dashboards for eoAPI services.</p>
<ul class="service-links">
<li><a href="/monitoring">Open dashboards</a></li>
</ul>
</div>
</article>
</li>
</ul>
</div>
</div>
Expand Down Expand Up @@ -121,7 +134,7 @@ <h2>Learn more</h2>
<section class="md-content gradient-section">
<div class="md-grid">
<div class="md-content__inner text-centered">
<p class="md-typeset">Made by</p>
<p class="md-typeset">Brought to you by</p>
<a href="https://developmentseed.org" target="_blank" rel="noopener noreferrer"
style="display: inline-block; margin-top: 1.5rem;" title="Development Seed">
<img src="img/devseed_logo.svg" height="64" alt="Development Seed logo" loading="lazy">
Expand Down
Loading