S3 storage — tile cache and rasters¶
Goal: move everything bulky or shared off the pods' disks — the GeoWebCache tile cache and the rasters — to S3 (or compatible) object storage, configured by code and shared by all replicas.
flowchart LR
C[WMTS / WMS client] --> P1[GeoServer pod A]
C --> P2[GeoServer pod B]
P1 -- "GWC: tile? MISS → render → PUT" --> S3T[(gwc-tiles bucket)]
P2 -- "GWC: GET → HIT" --> S3T
P1 -- "COG: range reads (Range)" --> S3R[(rasters bucket<br/>demo/demo-temperature.tif)]
P2 -- "COG" --> S3R
SEC[(s3-credentials Secret)] -.-> P1 & P2
DB[(Database: S3 blobstore, tile layers,<br/>COG store in JDBCStore/JDBCConfig)] -.-> P1 & P2
Decision: ADR-0009. Artifacts:
poc/04-s3/ plus evolutions of poc/02-geoserver-image/ (COG bundle, plugin,
bootstrap).
Prerequisites¶
- Sub-projects 1 to 3 deployed.
- An S3-compatible object store reachable from the pods, two buckets (tiles,
rasters) and one key pair. Test bench: SeaweedFS (Apache 2.0),
k8s/test-seaweedfs.yaml— not a reference deployment. s3-credentialsSecret (templatek8s/secret-s3.example.yaml):
| Key | Role |
|---|---|
S3_ENDPOINT, S3_REGION |
Endpoint (http/https) and region |
S3_ACCESS_KEY, S3_SECRET_KEY |
Credentials |
S3_PATH_STYLE |
true for most S3-compatible stores (SeaweedFS, MinIO, Ceph) |
S3_GWC_BUCKET |
Tile cache bucket |
S3_RASTER_BUCKET, COG_DEMO_KEY |
Rasters bucket and key of the demonstration COG |
The Deployment references this Secret with optional: true: without S3,
GeoServer runs with a local, ephemeral tile cache.
Tile cache: default S3 blobstore¶
gwc-s3extension (in the image since sub-project 2).- The
geoserver-initplugin creates or updates at every startup anS3BlobStoreInfo(s3-tiles):bucket,endpoint,useHTTPSderived from the URL, credentials,maxConnections,default=true— throughBlobStoreAggregator(org.geowebcache.storage), never through REST/UI (ADR-0006). With a custom endpoint, the client automatically switches to path-style. - The GWC configuration (
gwc/blobstores.xml,gwc-layers/*.xml) lives in the database via JDBCStore: it survives pod replacement and is common to all replicas. - The tile layers are created explicitly by the plugin
(
GWC.add(GeoServerTileLayer)) in aContextLoadedEventlistener — GeoWebCache is not yet initialized when theGeoServerInitializers run, and auto-creation upon layer addition did not happen at the prototype stage.
Rasters: COGs read directly from S3¶
- Community modules
gs-cog-core+gs-cog-s3(bundle resolved by Maven, ADR-0007): HTTP range requests against a Cloud Optimized GeoTIFF — no local copy, no volume. - GeoTIFF store created by the plugin: URL
cog://s3://<bucket>/<key>, metadataCogSettings.Key→CogSettingsStore(S3 RangeReader, credentials encrypted in the database). - Endpoint, region and path-style of the S3 RangeReader through
environment variables exported by the bootstrap:
IIO_S3_AWS_ENDPOINT,IIO_S3_AWS_REGION,IIO_S3_AWS_FORCE_PATH_STYLE(without it, the client triesbucket.endpoint: "Unable to execute HTTP request" — hit at the prototype stage),IIO_S3_AWS_USER/IIO_S3_AWS_PASSWORDas a fallback. - This is the mode required for the ImageMosaic on S3 of sub-project 5
(
Cog=true,CogRangeReader=…S3RangeReaderinindexer.properties).
The demonstration COG (data/make-cog.sh, GDAL in a container) is a
synthetic temperature in kelvin (north-south gradient), 256×256, EPSG:4326,
tiled 128, DEFLATE — metadata units=K, param_key=air_temperature
(ADR-0003).
Pre-check and failure modes¶
The bootstrap requires S3_ENDPOINT to answer over HTTP (any status code)
before starting Tomcat; otherwise explicit stop with exit code 5
(measured: 3 s). A missing bucket or wrong credentials show up in the
GeoServer log (blobstore or COG store as a warning, service intact).
Steps¶
cd poc/04-s3
kubectl apply -f k8s/test-seaweedfs.yaml # test bench only
./data/make-cog.sh && kubectl -n meteo-gis create configmap demo-cog --from-file=demo-temperature.tif=data/demo-temperature.tif
kubectl apply -f k8s/secret-s3.example.yaml -f k8s/job-seed-rasters.yaml # buckets + COG
IMAGE=<registry>/meteo-gis/geoserver:3.0.1-poc4 ./deploy.sh
Verification (tests in poc/04-s3/tests/)¶
Buckets and COG seeded (seed-s3 Job); startup: S3 blobstore verified, COG
store and layer, tile layers:
01 First tile MISS (rendered then written to S3), second HIT;
objects in the bucket:
02 Same tile on another pod: HIT (no rendering):
03 Pod replaced: blobstore still present and still the default, tile HIT:
04 COG layer on S3, GetMap from two pods:
05 Scale 3 → 2 → 3: all three pods serve the tile as a HIT:
06 S3 endpoint unreachable: explicit stop (exit code 5):
UI: blobstores and cached layers:
Deviations and pitfalls observed¶
BlobStoreAggregatorlives inorg.geowebcache.storage(GWC 2.0.1) and theS3BlobStoreInfosetters takeStrings.GWC.get()isnullwhile theGeoServerInitializers run → tile layers created onContextLoadedEvent; do not look upCatalog.classby type (several beans) but keep theGeoServerreference frominitialize().- A listener that throws an exception prevents the webapp from starting
("One or more listeners failed to start", details in the container's
logs/localhost.*.log, not on stdout). kubectl applyrejects a ConfigMap with more than 256 KB of annotation: usekubectl createfor the COG.- Same image tag republished:
imagePullPolicy: Always+rollout restart.










