Skip to content

Service Registration

Servicekit provides automatic service registration with an orchestrator for service discovery in Docker Compose and Kubernetes environments.

Quick Start

Basic Registration

The simplest approach with auto-detected hostname:

from servicekit.api import BaseServiceBuilder, ServiceInfo

app = (
    BaseServiceBuilder(info=ServiceInfo(display_name="My Service"))
    .with_registration()  # Reads SERVICEKIT_ORCHESTRATOR_URL from environment
    .build()
)

Set the environment variable:

export SERVICEKIT_ORCHESTRATOR_URL=http://orchestrator:9000/services/$register
fastapi run your_file.py

Docker Compose

The recommended approach for multi-service deployments:

services:
  orchestrator:
    image: your-orchestrator:latest
    ports:
      - "9000:9000"

  my-service:
    image: your-service:latest
    environment:
      SERVICEKIT_ORCHESTRATOR_URL: http://orchestrator:9000/services/$register
      # Hostname auto-detected from container name
    depends_on:
      - orchestrator

Custom ServiceInfo

For services with additional metadata:

from servicekit.api import ServiceInfo

class CustomServiceInfo(ServiceInfo):
    """Extended service info with custom fields."""
    deployment_env: str = "production"
    team: str = "platform"
    capabilities: list[str] = []

app = (
    BaseServiceBuilder(
        info=CustomServiceInfo(
            display_name="My Service",
            version="1.0.0",
            deployment_env="staging",
            team="data-science",
            capabilities=["ml-inference", "analytics"],
        )
    )
    .with_registration()
    .build()
)

Configuration Options

The .with_registration() method accepts these parameters:

.with_registration(
    orchestrator_url=None,              # Direct value or env var
    host=None,                          # Direct value, auto-detect, or env var
    port=None,                          # Direct value, env var, or 8000
    orchestrator_url_env="SERVICEKIT_ORCHESTRATOR_URL",
    host_env="SERVICEKIT_HOST",
    port_env="SERVICEKIT_PORT",
    max_retries=5,                      # Number of registration attempts
    retry_delay=2.0,                    # Seconds between retries
    fail_on_error=False,                # Abort startup on failure
    timeout=10.0,                       # HTTP request timeout
)

Parameters

  • orchestrator_url (str | None): Orchestrator registration endpoint URL. If None, reads from environment variable.
  • host (str | None): Service hostname. If None, auto-detects via socket.gethostname() or reads from environment variable.
  • port (int | None): Service port. If None, reads from environment variable or defaults to 8000.
  • orchestrator_url_env (str): Environment variable name for orchestrator URL. Default: SERVICEKIT_ORCHESTRATOR_URL.
  • host_env (str): Environment variable name for hostname override. Default: SERVICEKIT_HOST.
  • port_env (str): Environment variable name for port override. Default: SERVICEKIT_PORT.
  • max_retries (int): Maximum number of registration attempts. Default: 5.
  • retry_delay (float): Delay in seconds between retry attempts. Default: 2.0.
  • fail_on_error (bool): If True, raise exception and abort startup on registration failure. If False, log warning and continue. Default: False.
  • timeout (float): HTTP request timeout in seconds. Default: 10.0.

How It Works

Registration Flow

  1. Service Starts: FastAPI application initializes during lifespan startup
  2. Hostname Resolution: Determines service hostname (see resolution order below)
  3. Port Resolution: Determines service port (see resolution order below)
  4. URL Construction: Builds service URL as http://<hostname>:<port>
  5. Payload Creation: Serializes ServiceInfo to JSON (supports custom subclasses)
  6. Registration Request: Sends POST to orchestrator endpoint
  7. Retry on Failure: Retries with delay if request fails
  8. Logging: Logs all attempts and final outcome

Registration Payload

The service sends this payload to the orchestrator:

{
  "url": "http://my-service:8000",
  "info": {
    "display_name": "My Service",
    "version": "1.0.0",
    "summary": "Service description",
    ...
  }
}

For custom ServiceInfo subclasses:

{
  "url": "http://ml-service:8000",
  "info": {
    "display_name": "ML Service",
    "version": "2.0.0",
    "deployment_env": "production",
    "team": "data-science",
    "capabilities": ["ml-inference", "feature-extraction"],
    "priority": 5
  }
}

Hostname Resolution

Priority order:

  1. Direct Parameter: host="my-service" in .with_registration()
  2. Auto-Detection: socket.gethostname() (returns Docker container name or hostname)
  3. Environment Variable: Value of SERVICEKIT_HOST (or custom env var)
  4. Error: Raises exception if fail_on_error=True, otherwise logs warning

Docker Behavior: In Docker Compose, socket.gethostname() returns the service name or container ID, making auto-detection work seamlessly.

Port Resolution

Priority order:

  1. Direct Parameter: port=8080 in .with_registration()
  2. Environment Variable: Value of SERVICEKIT_PORT (or custom env var)
  3. Default: 8000

Important: SERVICEKIT_PORT should match the container's internal port, not the host-mapped port.


Examples

Environment Variables (Production)

from servicekit.api import BaseServiceBuilder, ServiceInfo

app = (
    BaseServiceBuilder(info=ServiceInfo(display_name="Production Service"))
    .with_logging()
    .with_health()
    .with_registration()  # Reads from environment
    .build()
)

docker-compose.yml:

services:
  my-service:
    image: my-service:latest
    environment:
      SERVICEKIT_ORCHESTRATOR_URL: http://orchestrator:9000/services/$register
      # SERVICEKIT_HOST auto-detected
      # SERVICEKIT_PORT defaults to 8000

Direct Configuration (Testing)

app = (
    BaseServiceBuilder(info=ServiceInfo(display_name="Test Service"))
    .with_registration(
        orchestrator_url="http://localhost:9000/services/$register",
        host="test-service",
        port=8080,
    )
    .build()
)

Custom Environment Variable Names

app = (
    BaseServiceBuilder(info=ServiceInfo(display_name="My Service"))
    .with_registration(
        orchestrator_url_env="MY_APP_ORCHESTRATOR_URL",
        host_env="MY_APP_HOST",
        port_env="MY_APP_PORT",
    )
    .build()
)

Environment:

export MY_APP_ORCHESTRATOR_URL=http://orchestrator:9000/services/$register
export MY_APP_HOST=my-service
export MY_APP_PORT=8000

Fail-Fast Mode

For critical services that must register:

app = (
    BaseServiceBuilder(info=ServiceInfo(display_name="Critical Service"))
    .with_registration(
        fail_on_error=True,  # Abort startup if registration fails
        max_retries=10,
        retry_delay=1.0,
    )
    .build()
)

Custom Retry Strategy

app = (
    BaseServiceBuilder(info=ServiceInfo(display_name="My Service"))
    .with_registration(
        max_retries=10,      # More attempts
        retry_delay=1.0,     # Faster retries
        timeout=5.0,         # Shorter timeout
    )
    .build()
)

Docker Compose

Basic Setup

services:
  orchestrator:
    image: orchestrator:latest
    ports:
      - "9000:9000"

  service-a:
    image: my-service:latest
    environment:
      SERVICEKIT_ORCHESTRATOR_URL: http://orchestrator:9000/services/$register
    depends_on:
      - orchestrator

With Health Checks

Wait for orchestrator to be healthy before starting services:

services:
  orchestrator:
    image: orchestrator:latest
    ports:
      - "9000:9000"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/health"]
      interval: 10s
      timeout: 5s
      retries: 3

  service-a:
    image: my-service:latest
    environment:
      SERVICEKIT_ORCHESTRATOR_URL: http://orchestrator:9000/services/$register
    depends_on:
      orchestrator:
        condition: service_healthy  # Wait for healthy status

Custom Port Mapping

When container port differs from host port:

services:
  service-a:
    image: my-service:latest
    ports:
      - "8001:8000"  # Host:Container
    environment:
      SERVICEKIT_ORCHESTRATOR_URL: http://orchestrator:9000/services/$register
      SERVICEKIT_PORT: "8000"  # Use container port, not host port

Why: Other services connect using the internal Docker network, so they use the container port (8000), not the host-mapped port (8001).

Multiple Services

services:
  orchestrator:
    image: orchestrator:latest
    ports:
      - "9000:9000"

  service-a:
    image: my-service:latest
    ports:
      - "8000:8000"
    environment:
      SERVICEKIT_ORCHESTRATOR_URL: http://orchestrator:9000/services/$register

  service-b:
    image: my-service:latest
    ports:
      - "8001:8000"
    environment:
      SERVICEKIT_ORCHESTRATOR_URL: http://orchestrator:9000/services/$register

Both services register with different hostnames (service-a, service-b) but same internal port (8000).


Kubernetes

ConfigMap for Orchestrator URL

apiVersion: v1
kind: ConfigMap
metadata:
  name: registration-config
data:
  orchestrator-url: "http://orchestrator-service:9000/services/$register"

Deployment with Registration

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-service
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: my-service
    spec:
      containers:
        - name: my-service
          image: my-service:latest
          env:
            - name: SERVICEKIT_ORCHESTRATOR_URL
              valueFrom:
                configMapKeyRef:
                  name: registration-config
                  key: orchestrator-url
            - name: SERVICEKIT_HOST
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name  # Pod name

Service Discovery

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-service
  ports:
    - port: 8000
      targetPort: 8000

Error Handling

Retry Behavior

By default, registration retries 5 times with 2-second delays:

.with_registration(
    max_retries=5,      # Total attempts
    retry_delay=2.0,    # Seconds between attempts
)

Example timeline: - Attempt 1: Immediate - Attempt 2: After 2 seconds - Attempt 3: After 4 seconds (2s + 2s) - Attempt 4: After 6 seconds - Attempt 5: After 8 seconds

Fail on Error

Default (fail_on_error=False): Service starts even if registration fails

.with_registration(fail_on_error=False)  # Log warning, continue

Fail-Fast (fail_on_error=True): Service aborts startup if registration fails

.with_registration(fail_on_error=True)  # Raise exception, abort

When to use fail-fast: - Critical services that require orchestrator awareness - Production environments with strict registration requirements - Services that cannot function without being registered

Structured Logging

All registration events are logged with structured data:

{
  "event": "registration.starting",
  "orchestrator_url": "http://orchestrator:9000/services/$register",
  "service_url": "http://my-service:8000",
  "host_source": "auto-detected",
  "port_source": "default",
  "max_retries": 5
}

Success:

{
  "event": "registration.success",
  "service_url": "http://my-service:8000",
  "attempt": 1,
  "status_code": 200
}

Failure:

{
  "event": "registration.attempt_failed",
  "service_url": "http://my-service:8000",
  "attempt": 1,
  "max_retries": 5,
  "error": "Connection refused",
  "error_type": "ConnectError"
}


Troubleshooting

Registration Fails

Check orchestrator is reachable:

# From service container
docker compose exec my-service curl http://orchestrator:9000/health

Check environment variables:

docker compose exec my-service env | grep SERVICEKIT

View registration logs:

docker compose logs my-service | grep registration

Hostname Auto-Detection Issues

Problem: Auto-detection fails or returns unexpected value

Solution: Override with environment variable

environment:
  SERVICEKIT_HOST: my-custom-hostname

Debug hostname detection:

docker compose exec my-service hostname
docker compose exec my-service python -c "import socket; print(socket.gethostname())"

Port Mismatch

Problem: Orchestrator cannot reach service at registered URL

Common mistake: Using host-mapped port instead of container port

# WRONG
ports:
  - "8001:8000"
environment:
  SERVICEKIT_PORT: "8001"  # ❌ Host port

# CORRECT
ports:
  - "8001:8000"
environment:
  SERVICEKIT_PORT: "8000"  # ✅ Container port

Why: Services communicate via Docker's internal network using container ports, not host-mapped ports.

Orchestrator URL Missing

Problem: No orchestrator URL configured

Error: registration.missing_orchestrator_url

Solution: Set environment variable

export SERVICEKIT_ORCHESTRATOR_URL=http://orchestrator:9000/services/$register

Or use direct configuration:

.with_registration(orchestrator_url="http://orchestrator:9000/services/$register")

Service Not Appearing in Registry

Check orchestrator logs:

docker compose logs orchestrator

Check service startup logs:

docker compose logs my-service | grep registration

Verify orchestrator endpoint:

curl http://localhost:9000/services

Production Considerations

High Availability

Use multiple orchestrator replicas:

services:
  orchestrator:
    image: orchestrator:latest
    deploy:
      replicas: 3

  service-a:
    environment:
      SERVICEKIT_ORCHESTRATOR_URL: http://orchestrator:9000/services/$register

Retry Strategy

Adjust retries for production reliability:

.with_registration(
    max_retries=10,       # More attempts
    retry_delay=1.0,      # Faster retries
    timeout=30.0,         # Longer timeout
    fail_on_error=True,   # Fail fast in production
)

Security

Authentication: Add API keys or tokens to registration requests (requires custom implementation)

TLS: Use HTTPS for orchestrator communication

.with_registration(
    orchestrator_url="https://orchestrator:9443/services/$register"
)

Monitoring

Monitor registration health: - Track registration success/failure rates - Alert on repeated registration failures - Monitor orchestrator availability - Log all registration attempts for audit


  • examples/registration/ - Complete registration demo with orchestrator
  • core_api/ - Basic CRUD service
  • monitoring/ - Prometheus metrics
  • auth_envvar/ - Environment-based authentication

See Also