All checks were successful
continuous-integration/drone/push Build is passing
- Complete unit, integration, and E2E test coverage (189 test cases) - Enhanced CI/CD pipeline with race detection and quality checks - Comprehensive godoc documentation for all packages - Updated README with API docs, examples, and deployment guides
338 lines
8.1 KiB
Markdown
338 lines
8.1 KiB
Markdown
# GeoIP API
|
|
|
|
A high-performance HTTP service that provides MaxMind GeoIP data for IP geolocation lookups. Designed to run as a lightweight daemon within Kubernetes clusters to serve geolocation data to other services.
|
|
|
|
## Features
|
|
|
|
- **Fast HTTP API** for IP geolocation lookups
|
|
- **Multiple response formats**: country codes and full JSON data
|
|
- **OpenTelemetry tracing** with standard Traceparent headers
|
|
- **Automatic database discovery** in standard system paths
|
|
- **Health check endpoint** with actual database verification
|
|
- **Go client library** for easy integration
|
|
- **Comprehensive test coverage** with unit, integration, and E2E tests
|
|
|
|
## API Endpoints
|
|
|
|
### Country Lookup
|
|
```
|
|
GET /api/country?ip=192.0.2.1
|
|
```
|
|
Returns the lowercase ISO country code (e.g., `us`, `gb`)
|
|
|
|
### Full JSON Data
|
|
```
|
|
GET /api/json?ip=192.0.2.1
|
|
```
|
|
Returns complete MaxMind GeoIP data in JSON format including:
|
|
- Country, city, and region information
|
|
- Latitude/longitude coordinates
|
|
- ISP and organization data (if available)
|
|
- Time zone information
|
|
|
|
### Health Check
|
|
```
|
|
GET /healthz
|
|
```
|
|
Performs an actual GeoIP lookup to verify database connectivity and returns the country code for a test IP.
|
|
|
|
## Installation
|
|
|
|
### From Source
|
|
```bash
|
|
go build -o geoipapi
|
|
./geoipapi
|
|
```
|
|
|
|
### Using Docker
|
|
```bash
|
|
docker build -t geoipapi .
|
|
docker run -p 8009:8009 geoipapi
|
|
```
|
|
|
|
## Database Setup
|
|
|
|
The service automatically searches for MaxMind databases in standard locations:
|
|
|
|
- `/usr/share/GeoIP/` (Linux default)
|
|
- `/usr/local/share/GeoIP/` (FreeBSD)
|
|
- `/opt/local/share/GeoIP/` (MacPorts)
|
|
- `/opt/homebrew/var/GeoIP/` (Homebrew)
|
|
|
|
### Supported Database Files
|
|
|
|
- **Country databases**: `GeoIP2-Country.mmdb`, `GeoLite2-Country.mmdb`
|
|
- **City databases**: `GeoIP2-City.mmdb`, `GeoLite2-City.mmdb`
|
|
- **ISP databases**: `GeoIP2-ISP.mmdb`
|
|
|
|
### Installing GeoLite2 Databases (Free)
|
|
|
|
1. Create a free MaxMind account at https://www.maxmind.com/en/geolite2/signup
|
|
2. Download the databases manually, or
|
|
3. Use the built-in MaxMind package for automatic downloads:
|
|
|
|
```go
|
|
import "go.ntppool.org/geoipapi/maxmind"
|
|
|
|
maxmind.LicenseKey = "your_license_key_here"
|
|
maxmind.EditionIDs = "GeoLite2-City,GeoLite2-Country"
|
|
maxmind.Path = "/usr/share/GeoIP/"
|
|
|
|
err := maxmind.DownloadGeoLite2DB()
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables
|
|
|
|
- **OpenTelemetry**: Standard OTel environment variables are supported
|
|
- `OTEL_EXPORTER_OTLP_ENDPOINT`
|
|
- `OTEL_SERVICE_NAME`
|
|
- `OTEL_RESOURCE_ATTRIBUTES`
|
|
|
|
### Server Configuration
|
|
|
|
The server runs on port 8009 by default with the following timeouts:
|
|
- **Read timeout**: 1 second
|
|
- **Write timeout**: 10 seconds
|
|
|
|
## OpenTelemetry Support
|
|
|
|
The service includes comprehensive OpenTelemetry instrumentation:
|
|
|
|
- **HTTP requests** are automatically traced
|
|
- **Database lookups** are instrumented with spans
|
|
- **Health checks** are filtered from tracing to reduce noise
|
|
- **Custom attributes** include IP addresses and operation details
|
|
|
|
Tracing works seamlessly with the OpenTelemetry Collector and common observability platforms.
|
|
|
|
## Go Client Library
|
|
|
|
Use the provided Go client for easy integration:
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/netip"
|
|
"os"
|
|
|
|
"go.ntppool.org/geoipapi/client/geoipapi"
|
|
)
|
|
|
|
func main() {
|
|
// Set the service endpoint
|
|
os.Setenv("geoip_service", "geoip-service:8009")
|
|
|
|
ctx := context.Background()
|
|
ip := netip.MustParseAddr("8.8.8.8")
|
|
|
|
city, err := geoipapi.GetCity(ctx, ip)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
fmt.Printf("IP: %s\n", ip)
|
|
fmt.Printf("Country: %s\n", city.Country.IsoCode)
|
|
fmt.Printf("City: %s\n", city.City.Names["en"])
|
|
fmt.Printf("Location: %f, %f\n", city.Location.Latitude, city.Location.Longitude)
|
|
}
|
|
```
|
|
|
|
### Client Configuration
|
|
|
|
Set the `geoip_service` environment variable to point to your GeoIP API service:
|
|
|
|
```bash
|
|
export geoip_service="geoip-service.default.svc.cluster.local:8009"
|
|
```
|
|
|
|
## Command Line Usage
|
|
|
|
The service can also be used as a command-line tool for IP lookups:
|
|
|
|
```bash
|
|
./geoipapi 8.8.8.8 1.1.1.1 192.168.1.1
|
|
```
|
|
|
|
Output:
|
|
```
|
|
8.8.8.8: us
|
|
1.1.1.1: us
|
|
192.168.1.1:
|
|
```
|
|
|
|
## Development
|
|
|
|
### Running Tests
|
|
|
|
The project includes comprehensive test coverage:
|
|
|
|
```bash
|
|
# Run all tests
|
|
go test ./...
|
|
|
|
# Run tests with race detection
|
|
go test -race ./...
|
|
|
|
# Run only fast tests
|
|
go test -short ./...
|
|
|
|
# Run with coverage
|
|
go test -cover ./...
|
|
```
|
|
|
|
### Test Types
|
|
|
|
- **Unit tests**: Test individual functions and components
|
|
- **Integration tests**: Test HTTP API endpoints with a running server
|
|
- **End-to-end tests**: Test complete client-server workflows
|
|
- **Race condition tests**: Verify thread safety under concurrent load
|
|
|
|
### Code Quality
|
|
|
|
```bash
|
|
# Format code
|
|
gofumpt -w .
|
|
|
|
# Lint code
|
|
go vet ./...
|
|
|
|
# Verify dependencies
|
|
go mod verify
|
|
```
|
|
|
|
## Architecture
|
|
|
|
### Core Components
|
|
|
|
1. **HTTP Server** (`geoipapi.go`): Main API server with endpoint handlers
|
|
2. **MaxMind Package** (`maxmind/`): Database download and management utilities
|
|
3. **Client Library** (`client/geoipapi/`): Go client for consuming the HTTP API
|
|
|
|
### Database Discovery
|
|
|
|
The service automatically discovers MaxMind databases by:
|
|
|
|
1. Searching standard system paths
|
|
2. Looking for supported database filenames
|
|
3. Opening the first available database for each type
|
|
4. Gracefully handling missing databases
|
|
|
|
### Error Handling
|
|
|
|
- **Invalid IP addresses** return HTTP 500 with "data error"
|
|
- **Missing databases** are detected during health checks
|
|
- **Network errors** in the client include proper context
|
|
- **Tracing errors** are recorded in spans for debugging
|
|
|
|
## Deployment
|
|
|
|
### Kubernetes
|
|
|
|
Example Kubernetes deployment:
|
|
|
|
```yaml
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: geoipapi
|
|
spec:
|
|
replicas: 3
|
|
selector:
|
|
matchLabels:
|
|
app: geoipapi
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: geoipapi
|
|
spec:
|
|
containers:
|
|
- name: geoipapi
|
|
image: your-registry/geoipapi:latest
|
|
ports:
|
|
- containerPort: 8009
|
|
env:
|
|
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
|
value: "http://otel-collector:4317"
|
|
- name: OTEL_SERVICE_NAME
|
|
value: "geoipapi"
|
|
volumeMounts:
|
|
- name: geoip-data
|
|
mountPath: /usr/share/GeoIP
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /healthz
|
|
port: 8009
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 10
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /healthz
|
|
port: 8009
|
|
initialDelaySeconds: 15
|
|
periodSeconds: 20
|
|
volumes:
|
|
- name: geoip-data
|
|
configMap:
|
|
name: geoip-databases
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: geoipapi
|
|
spec:
|
|
selector:
|
|
app: geoipapi
|
|
ports:
|
|
- port: 8009
|
|
targetPort: 8009
|
|
```
|
|
|
|
### Performance Considerations
|
|
|
|
- **Database caching**: MaxMind databases are loaded once on startup
|
|
- **Connection pooling**: HTTP client uses connection pooling for better performance
|
|
- **Concurrent requests**: Server handles multiple concurrent requests efficiently
|
|
- **Memory usage**: Minimal memory footprint suitable for container environments
|
|
|
|
## Contributing
|
|
|
|
1. Fork the repository
|
|
2. Create a feature branch
|
|
3. Make your changes with tests
|
|
4. Run the full test suite: `go test ./...`
|
|
5. Format code: `gofumpt -w .`
|
|
6. Submit a pull request
|
|
|
|
### CI/CD
|
|
|
|
The project uses Drone CI with the following pipeline:
|
|
|
|
1. **Dependencies**: Download Go modules
|
|
2. **Testing**: Run unit, integration, and race tests
|
|
3. **Code Quality**: Run `go vet`, `gofumpt`, and `go mod verify`
|
|
4. **Build**: Compile the binary
|
|
5. **Docker**: Build and push container image
|
|
|
|
## License
|
|
|
|
This project is licensed under the terms specified in the LICENSE file.
|
|
|
|
## Support
|
|
|
|
For issues and questions:
|
|
|
|
- **Bug reports**: Create an issue in the GitHub repository
|
|
- **Feature requests**: Submit a feature request with use case details
|
|
- **Documentation**: Check the Go docs: `go doc go.ntppool.org/geoipapi`
|
|
|
|
## Related Projects
|
|
|
|
- **MaxMind GeoIP2**: https://www.maxmind.com/en/geoip2-services-and-databases
|
|
- **OpenTelemetry Go**: https://github.com/open-telemetry/opentelemetry-go
|
|
- **Kubernetes ingress-nginx**: https://github.com/kubernetes/ingress-nginx (inspiration for MaxMind handling) |