Small Go HTTP service that exposes MaxMind GeoLite2 City and ASN lookups as a flat JSON REST API. Built for Kubernetes on EKS where an init container delivers .mmdb files to a shared volume; this service only reads them. Reloads happen via fsnotify with an atomic pointer swap, so the hot lookup path is lock-free and the process never restarts when databases update.
| Path | Description |
|---|---|
GET /lookup/{ip} |
Combined City + ASN lookup, flat JSON |
GET /healthz |
Liveness; always 200 once started |
GET /readyz |
200 only when the City reader is loaded |
GET /metrics |
Prometheus (separate listener) |
$ curl -s localhost:8080/lookup/2.125.160.216 | jq
{
"ip": "2.125.160.216",
"city": {
"country_iso_code": "GB",
"country_name": "United Kingdom",
"city_name": "Boxford",
"subdivision_iso_code": "WBK",
"subdivision_name": "West Berkshire",
"postal_code": "OX1",
"latitude": 51.75,
"longitude": -1.25,
"accuracy_radius": 100,
"time_zone": "Europe/London"
},
"asn": {
"number": 1221,
"organization": "Telstra Pty Ltd"
}
}
$ curl -s -o /dev/null -w '%{http_code}\n' localhost:8080/lookup/not-an-ip
400
$ curl -s localhost:8080/lookup/192.168.1.1
{"ip":"192.168.1.1"}city and asn are omitted entirely when no record is found in the corresponding database (or that database is not loaded). 400 is returned only for syntactically invalid IPs.
| Var | Default | Purpose |
|---|---|---|
GEOIP_DB_DIR |
/var/lib/geoip |
Directory with GeoLite2-City.mmdb and GeoLite2-ASN.mmdb |
GEOIP_LISTEN_ADDR |
:8080 |
Main API listener |
GEOIP_METRICS_ADDR |
:9090 |
Metrics listener (separate so NetworkPolicy can restrict it) |
GEOIP_LOG_LEVEL |
info |
debug, info, warn, error |
GEOIP_RELOAD_DEBOUNCE |
1s |
fsnotify debounce window |
GEOIP_SHUTDOWN_GRACE |
5s |
Graceful HTTP drain and old-reader close delay |
| Metric | Type | Labels | Notes |
|---|---|---|---|
geoip_lookups_total |
counter | result (ok, not_found, bad_request, error) |
per-request outcome |
geoip_lookup_duration_seconds |
histogram | — | [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05] |
geoip_db_build_timestamp_seconds |
gauge | database (city, asn) |
from mmdb BuildEpoch |
geoip_db_reload_total |
counter | database, result (ok, error) |
each reload attempt |
geoip_db_loaded |
gauge | database |
0 / 1 |
make build
GEOIP_DB_DIR=./testdata ./bin/geoip-api
# in another terminal
curl -s localhost:8080/lookup/2.125.160.216 | jq
curl -s localhost:9090/metrics | grep geoip_Tests use MaxMind's official test data, downloaded on demand into testdata/ (pinned by commit SHA + verified by testdata/SHA256SUMS). The .mmdb files are gitignored.
make testdata # one-off fetch (also run automatically by `make test`)
make testTo update the pinned fixtures: bump MMDB_REF in the Makefile, delete testdata/*.mmdb, run make testdata (it will fail the checksum), and replace testdata/SHA256SUMS with the new sums (shasum -a 256 testdata/*.mmdb).
- An init container pulls
GeoLite2-City.mmdbandGeoLite2-ASN.mmdbfrom S3 into anemptyDirvolume mounted at/var/lib/geoip. This service only reads. A sidecar (or a CronJob withkubectl rollout restart, or a periodic init re-run) is expected to refresh the volume; this service picks up changes automatically viafsnotify. - Expose
8080(serve) and9090(metrics) as separatecontainerPorts so a NetworkPolicy can restrict scraping to the Prometheus namespace. - Set
readinessProbeto/readyzon8080andlivenessProbeto/healthz. Readiness is gated on the City database being loaded; ASN is treated as optional. - The image is distroless
nonrootand the binary is fully static — no shell, no package manager.