Logferry is a high-performance log shipping system designed to transport Avro-formatted log files from GeoDNS servers to Kafka. It consists of two components: a client that watches for log files and uploads them, and an API server that receives uploads and forwards them to Kafka.
[GeoDNS] → [Avro Files] → [logferry client] → [HTTPS/TLS] → [logferry-api] → [Kafka]
The system is designed for the NTP Pool project and integrates with GeoDNS to ship DNS query logs for analysis and monitoring.
- Watches a directory for new
.avrofiles created by GeoDNS - Uploads files to the API server using mutual TLS authentication
- Automatically cleans up files after successful upload
- Provides Prometheus metrics on port 9097
- Runs as a native binary on Linux and FreeBSD
- HTTP API server that receives log uploads from clients
- Forwards received data to Kafka topic
geodns-avro - Runs in Kubernetes (requires TLS passthrough, not termination)
- Provides health checks and Prometheus metrics via Echo framework
- Uses mutual TLS (mTLS) for client authentication
The system has been tested by the NTP Pool project handling over 1 million log entries per second.
- Download or build the
logferrybinary for your platform - Install as a system service (example systemd service file in
scripts/logferry.service)
Deploy in Kubernetes with appropriate TLS certificates and Kafka connectivity.
Configure GeoDNS to output Avro logs:
[avrolog]
path = log/avro/
maxsize = 500000
maxtime = 5sThis creates Avro files in the log/avro/ directory that logferry will monitor and upload.
Run logferry with required parameters:
./logferry \
-cert /path/to/client.crt \
-key /path/to/client.key \
-path /path/to/log/avro \
-upstream https://logferry-api.example.com-cert: Path to client TLS certificate-key: Path to client TLS private key-path: Directory containing Avro log files (must match GeoDNS avrolog.path)-upstream: URL of the logferry-api server
-jsondump: Dump specified Avro files to JSON format (for debugging)-version: Show version information
The API server is configured via environment variables:
TLS_CERT: Path to server TLS certificateTLS_KEY: Path to server TLS private key
KAFKA_BROKERS: Comma-separated list of Kafka broker addressesKAFKA_TLS_CERT: Path to Kafka client certificateKAFKA_TLS_KEY: Path to Kafka client private keyKAFKA_TLS_CA: Path to Kafka CA certificate
Logs are published to the Kafka topic geodns-avro (currently not configurable).
Logferry uses mutual TLS (mTLS) for authentication. Both the client and server present certificates during the TLS handshake, and the server extracts the client's identity from its certificate.
- Client presents its certificate during TLS handshake
- Server verifies the certificate chain against its CA pool
- Server extracts the DNS name from the client certificate
- Server validates the DNS name ends with
.mon.ntppool.devor.ntppool.net - Server uses this hostname as the client identity for logging and Kafka records
If certificate validation fails or no valid hostname is found, the server returns HTTP 401 Unauthorized.
| Component | Minimum TLS Version |
|---|---|
| Client | TLS 1.3 |
| Server | TLS 1.2 |
- Client certificates: Must have a DNS name (SAN) ending in
.mon.ntppool.devor.ntppool.net - Server certificates: Standard TLS server certificate
- Kafka certificates: Separate certificates for Kafka broker authentication
Certificate management is handled externally (e.g., using HashiCorp Vault, cert-manager, or manual certificate deployment).
Important: The API server is NOT compatible with ingress controllers that terminate TLS.
The server requires direct access to the client's TLS certificate to:
- Verify the certificate chain
- Extract the DNS hostname for client identity
- Pass the hostname to Kafka records
If an ingress terminates TLS, the client certificate information is lost and authentication will fail.
-
TLS Passthrough (recommended for ingress)
Configure your ingress for SSL passthrough so encrypted traffic routes directly to the pod:
# NGINX Ingress example apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: logferry-api annotations: nginx.ingress.kubernetes.io/ssl-passthrough: "true" nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" spec: rules: - host: logferry-api.example.com http: paths: - path: / pathType: Prefix backend: service: name: logferry-api port: number: 8080
-
LoadBalancer Service (bypasses ingress)
Expose the service directly via a cloud load balancer with TCP passthrough:
apiVersion: v1 kind: Service metadata: name: logferry-api spec: type: LoadBalancer ports: - port: 443 targetPort: 8080 protocol: TCP selector: app: logferry-api
- Metrics available on port 9097
- Includes process metrics, upload timing, and file processing statistics
- Metrics integrated with Echo framework endpoints
- Includes HTTP request metrics, Kafka publishing metrics, and health status
The API server provides health check endpoints for Kubernetes probes.
- GeoDNS writes Avro log files to the configured directory
- logferry detects new
.avrofiles via filesystem watching - Files are uploaded to the API server using HTTPS with mutual TLS
- API server validates and forwards data to Kafka
- Upon successful Kafka publishing, logferry deletes the local Avro file
- Failed uploads are retried with exponential backoff
- Go 1.23.5 or later
- Access to go.ntppool.org/common (private module)
# Build client
go build -v -race
# Build API server
cd logferry-api/
go build -v -race# Run client in development mode
./run
# Run API server in development mode
./logferry-api/run
# Format code (required before commits)
gofumpt -w .
# Run tests
go test ./...[Unit]
Description=Logferry log shipper
After=network.target
[Service]
Type=simple
User=logferry
ExecStart=/usr/local/bin/logferry \
-cert /etc/logferry/client.crt \
-key /etc/logferry/client.key \
-path /var/log/geodns/avro \
-upstream https://logferry-api.ntppool.org
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetapiVersion: apps/v1
kind: Deployment
metadata:
name: logferry-api
spec:
replicas: 3
selector:
matchLabels:
app: logferry-api
template:
metadata:
labels:
app: logferry-api
spec:
containers:
- name: logferry-api
image: logferry-api:latest
ports:
- containerPort: 8080
env:
- name: TLS_CERT
value: /etc/tls/tls.crt
- name: TLS_KEY
value: /etc/tls/tls.key
- name: KAFKA_BROKERS
value: kafka-broker-1:9092,kafka-broker-2:9092
volumeMounts:
- name: tls-certs
mountPath: /etc/tls
- name: kafka-certs
mountPath: /etc/kafka-tls
volumes:
- name: tls-certs
secret:
secretName: logferry-api-tls
- name: kafka-certs
secret:
secretName: kafka-client-certs- Certificate errors: Verify certificate paths and permissions
- Connection refused: Check network connectivity and firewall rules
- Kafka publishing failures: Verify Kafka broker connectivity and authentication
- File permission errors: Ensure logferry user has read access to Avro directory
# Test Avro file parsing
./logferry -jsondump /path/to/file.avro
# Check version
./logferry -version
# View detailed logs
journalctl -u logferry -fThis project is part of the NTP Pool infrastructure.