a1f6701bac
- gRPC service with MusicBrainz provider - PostgreSQL schema with migrations - Service layer with database-first caching - Repository pattern for data access - YAML configuration support - Research documentation for 17 music metadata projects
1040 lines
23 KiB
Markdown
1040 lines
23 KiB
Markdown
# Bedrock-API Deployment
|
|
|
|
## Containerization
|
|
|
|
### Dockerfile
|
|
|
|
**File**: `Dockerfile`
|
|
**Strategy**: Multi-stage build (builder + runtime)
|
|
|
|
```dockerfile
|
|
# Builder stage
|
|
FROM golang:1.23-alpine AS builder
|
|
|
|
WORKDIR /app
|
|
|
|
# Install git (required for submodules)
|
|
RUN apk add --no-cache git
|
|
|
|
# Copy go mod files
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
# Copy source code
|
|
COPY . .
|
|
|
|
# Initialize submodules
|
|
RUN git submodule update --init --recursive
|
|
|
|
# Build binary
|
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o bedrock-server ./bedrock_server
|
|
|
|
# Runtime stage
|
|
FROM alpine:latest
|
|
|
|
# Install ca-certificates (required for HTTPS requests to provider APIs)
|
|
RUN apk --no-cache add ca-certificates
|
|
|
|
WORKDIR /root/
|
|
|
|
# Copy binary from builder
|
|
COPY --from=builder /app/bedrock-server .
|
|
|
|
# Copy migrations (if needed)
|
|
COPY --from=builder /app/db/migrations ./db/migrations
|
|
|
|
# Expose ports
|
|
EXPOSE 50052 8080
|
|
|
|
# Run server
|
|
CMD ["./bedrock-server"]
|
|
```
|
|
|
|
**Build Stages**:
|
|
|
|
1. **Builder** (`golang:1.23-alpine`):
|
|
- Installs git for submodule support
|
|
- Downloads Go dependencies
|
|
- Initializes spotapi-go submodule
|
|
- Compiles binary with optimizations (`-ldflags="-w -s"`)
|
|
- CGO disabled for static binary
|
|
|
|
2. **Runtime** (`alpine:latest`):
|
|
- Minimal image (~5 MB base)
|
|
- Installs ca-certificates for HTTPS
|
|
- Copies binary from builder
|
|
- Exposes gRPC (50052) and HTTP (8080) ports
|
|
|
|
**Image Size**: ~20 MB (builder stage discarded)
|
|
|
|
**Version Mismatch**: Dockerfile uses Go 1.23, but `go.mod` specifies 1.25
|
|
|
|
**Fix**:
|
|
```dockerfile
|
|
FROM golang:1.25-alpine AS builder
|
|
```
|
|
|
|
### Docker Build
|
|
|
|
**Build Command**:
|
|
```bash
|
|
docker build -t bedrock-api:latest .
|
|
```
|
|
|
|
**Build Arguments** (not implemented):
|
|
```dockerfile
|
|
ARG GO_VERSION=1.25
|
|
FROM golang:${GO_VERSION}-alpine AS builder
|
|
```
|
|
|
|
**Build Time**: ~2-3 minutes (first build), ~30 seconds (cached)
|
|
|
|
### Docker Run
|
|
|
|
**Run Command**:
|
|
```bash
|
|
docker run -d \
|
|
--name bedrock-api \
|
|
-p 50052:50052 \
|
|
-p 8080:8080 \
|
|
-e DATABASE_URL=postgresql://user:pass@host:5432/bedrock \
|
|
-e JWT_SECRET=your-secret \
|
|
-e SPOTIFY_CLIENT_ID=your-id \
|
|
-e SPOTIFY_CLIENT_SECRET=your-secret \
|
|
-e SOUNDCLOUD_CLIENT_IDS=id1,id2,id3 \
|
|
-e GENIUS_ACCESS_TOKEN=your-token \
|
|
bedrock-api:latest
|
|
```
|
|
|
|
**Environment Variables**: Passed via `-e` flags (no `.env` file in container)
|
|
|
|
**Port Mapping**:
|
|
- `50052:50052` - gRPC server
|
|
- `8080:8080` - HTTP proxy
|
|
|
|
**No Volume Mounts**: Binary is stateless (no local file storage)
|
|
|
|
## Docker Compose
|
|
|
|
### Compose File
|
|
|
|
**File**: `docker-compose.yml`
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:15-alpine
|
|
container_name: bedrock-postgres
|
|
environment:
|
|
POSTGRES_USER: bedrock
|
|
POSTGRES_PASSWORD: bedrock
|
|
POSTGRES_DB: bedrock
|
|
ports:
|
|
- "5432:5432"
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U bedrock"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
networks:
|
|
- bedrock-network
|
|
|
|
volumes:
|
|
postgres_data:
|
|
driver: local
|
|
|
|
networks:
|
|
bedrock-network:
|
|
driver: bridge
|
|
```
|
|
|
|
**Services**: PostgreSQL only (application not included)
|
|
|
|
**Missing Services**:
|
|
- No application service (must be added or run separately)
|
|
- No Redis (planned for caching)
|
|
- No reverse proxy (nginx, Caddy)
|
|
- No monitoring (Prometheus, Grafana)
|
|
|
|
### Complete Compose File (Recommended)
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:15-alpine
|
|
container_name: bedrock-postgres
|
|
environment:
|
|
POSTGRES_USER: bedrock
|
|
POSTGRES_PASSWORD: bedrock
|
|
POSTGRES_DB: bedrock
|
|
ports:
|
|
- "5432:5432"
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
- ./db/migrations:/docker-entrypoint-initdb.d
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U bedrock"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
networks:
|
|
- bedrock-network
|
|
|
|
bedrock-api:
|
|
build: .
|
|
container_name: bedrock-api
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
environment:
|
|
DATABASE_URL: postgresql://bedrock:bedrock@postgres:5432/bedrock?sslmode=disable
|
|
JWT_SECRET: ${JWT_SECRET}
|
|
SPOTIFY_CLIENT_ID: ${SPOTIFY_CLIENT_ID}
|
|
SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET}
|
|
SOUNDCLOUD_CLIENT_IDS: ${SOUNDCLOUD_CLIENT_IDS}
|
|
GENIUS_ACCESS_TOKEN: ${GENIUS_ACCESS_TOKEN}
|
|
YOUTUBE_COOKIES: ${YOUTUBE_COOKIES}
|
|
ports:
|
|
- "50052:50052"
|
|
- "8080:8080"
|
|
networks:
|
|
- bedrock-network
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
postgres_data:
|
|
|
|
networks:
|
|
bedrock-network:
|
|
```
|
|
|
|
**Improvements**:
|
|
- Application service added
|
|
- Health check dependency (waits for PostgreSQL)
|
|
- Environment variables from `.env` file
|
|
- Automatic restart policy
|
|
- Migration initialization via volume mount
|
|
|
|
### Compose Commands
|
|
|
|
**Start Services**:
|
|
```bash
|
|
docker-compose up -d
|
|
```
|
|
|
|
**View Logs**:
|
|
```bash
|
|
docker-compose logs -f bedrock-api
|
|
```
|
|
|
|
**Stop Services**:
|
|
```bash
|
|
docker-compose down
|
|
```
|
|
|
|
**Rebuild**:
|
|
```bash
|
|
docker-compose up -d --build
|
|
```
|
|
|
|
**Clean Volumes**:
|
|
```bash
|
|
docker-compose down -v
|
|
```
|
|
|
|
## Local Development
|
|
|
|
### Prerequisites
|
|
|
|
- Go 1.25+
|
|
- PostgreSQL 15+
|
|
- Git (for submodules)
|
|
|
|
### Setup Steps
|
|
|
|
**1. Clone Repository**:
|
|
```bash
|
|
git clone https://github.com/feralbureau/bedrock-api
|
|
cd bedrock-api
|
|
```
|
|
|
|
**2. Initialize Submodules**:
|
|
```bash
|
|
git submodule update --init --recursive
|
|
```
|
|
|
|
**3. Install Dependencies**:
|
|
```bash
|
|
go mod download
|
|
```
|
|
|
|
**4. Setup Database**:
|
|
```bash
|
|
# Start PostgreSQL (Docker)
|
|
docker run -d \
|
|
--name bedrock-postgres \
|
|
-e POSTGRES_USER=bedrock \
|
|
-e POSTGRES_PASSWORD=bedrock \
|
|
-e POSTGRES_DB=bedrock \
|
|
-p 5432:5432 \
|
|
postgres:15-alpine
|
|
|
|
# Run migrations
|
|
psql postgresql://bedrock:bedrock@localhost:5432/bedrock -f db/migrations/001_create_users_table.up.sql
|
|
```
|
|
|
|
**5. Configure Environment**:
|
|
```bash
|
|
cp .env.example .env
|
|
# Edit .env with your credentials
|
|
```
|
|
|
|
**Example `.env`**:
|
|
```
|
|
DATABASE_URL=postgresql://bedrock:bedrock@localhost:5432/bedrock?sslmode=disable
|
|
JWT_SECRET=your-secret-key-change-this-in-production
|
|
|
|
SPOTIFY_CLIENT_ID=your_spotify_client_id
|
|
SPOTIFY_CLIENT_SECRET=your_spotify_client_secret
|
|
|
|
SOUNDCLOUD_CLIENT_IDS=client_id_1,client_id_2,client_id_3
|
|
|
|
DEEZER_APP_ID=your_deezer_app_id
|
|
|
|
YOUTUBE_COOKIES=your_youtube_cookies
|
|
|
|
GENIUS_ACCESS_TOKEN=your_genius_access_token
|
|
```
|
|
|
|
**6. Run Server**:
|
|
```bash
|
|
go run ./bedrock_server
|
|
```
|
|
|
|
**7. Verify**:
|
|
```bash
|
|
# gRPC health check (requires grpcurl)
|
|
grpcurl -plaintext localhost:50052 bedrock.BedrockService/GetServiceStatus
|
|
|
|
# HTTP proxy check
|
|
curl http://localhost:8080/stream/soundcloud/1234567890
|
|
```
|
|
|
|
### Development Workflow
|
|
|
|
**Hot Reload** (not configured):
|
|
```bash
|
|
# Install air
|
|
go install github.com/cosmtrek/air@latest
|
|
|
|
# Run with hot reload
|
|
air
|
|
```
|
|
|
|
**Example `.air.toml`**:
|
|
```toml
|
|
root = "."
|
|
tmp_dir = "tmp"
|
|
|
|
[build]
|
|
cmd = "go build -o ./tmp/main ./bedrock_server"
|
|
bin = "tmp/main"
|
|
include_ext = ["go", "proto"]
|
|
exclude_dir = ["tmp", "vendor"]
|
|
delay = 1000
|
|
```
|
|
|
|
### Testing
|
|
|
|
**Run Tests**:
|
|
```bash
|
|
go test ./...
|
|
```
|
|
|
|
**Integration Tests** (requires provider credentials):
|
|
```bash
|
|
export SPOTIFY_CLIENT_ID=your_id
|
|
export SPOTIFY_CLIENT_SECRET=your_secret
|
|
export SOUNDCLOUD_CLIENT_IDS=your_ids
|
|
export GENIUS_ACCESS_TOKEN=your_token
|
|
export BEDROCK_TEST_ADDR=localhost:50052
|
|
|
|
go test -v ./tests/
|
|
```
|
|
|
|
**Test Coverage**:
|
|
```bash
|
|
go test -cover ./...
|
|
```
|
|
|
|
## CI/CD Pipeline
|
|
|
|
### GitHub Actions
|
|
|
|
**Workflows**:
|
|
- `test.yml` - Integration tests
|
|
- `lint.yml` - Code linting
|
|
|
|
### Test Workflow
|
|
|
|
**File**: `.github/workflows/test.yml`
|
|
|
|
```yaml
|
|
name: Tests
|
|
|
|
on:
|
|
push:
|
|
branches: [ main, develop ]
|
|
pull_request:
|
|
branches: [ main, develop ]
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:15-alpine
|
|
env:
|
|
POSTGRES_USER: bedrock
|
|
POSTGRES_PASSWORD: bedrock
|
|
POSTGRES_DB: bedrock
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd pg_isready
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v3
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v4
|
|
with:
|
|
go-version: '1.24'
|
|
|
|
- name: Download dependencies
|
|
run: go mod download
|
|
|
|
- name: Run migrations
|
|
run: |
|
|
psql postgresql://bedrock:bedrock@localhost:5432/bedrock -f db/migrations/001_create_users_table.up.sql
|
|
|
|
- name: Run tests
|
|
env:
|
|
DATABASE_URL: postgresql://bedrock:bedrock@localhost:5432/bedrock?sslmode=disable
|
|
JWT_SECRET: test-secret
|
|
SPOTIFY_CLIENT_ID: ${{ secrets.SPOTIFY_CLIENT_ID }}
|
|
SPOTIFY_CLIENT_SECRET: ${{ secrets.SPOTIFY_CLIENT_SECRET }}
|
|
SOUNDCLOUD_CLIENT_IDS: ${{ secrets.SOUNDCLOUD_CLIENT_IDS }}
|
|
GENIUS_ACCESS_TOKEN: ${{ secrets.GENIUS_ACCESS_TOKEN }}
|
|
YOUTUBE_COOKIES: ${{ secrets.YOUTUBE_COOKIES }}
|
|
run: go test -v -timeout 120s ./tests/
|
|
```
|
|
|
|
**Features**:
|
|
- PostgreSQL service container
|
|
- Submodule initialization
|
|
- Go 1.24 (should be 1.25 to match go.mod)
|
|
- Migration execution
|
|
- Integration tests with provider secrets
|
|
- 120 second timeout
|
|
|
|
**Required Secrets**:
|
|
- `SPOTIFY_CLIENT_ID`
|
|
- `SPOTIFY_CLIENT_SECRET`
|
|
- `SOUNDCLOUD_CLIENT_IDS`
|
|
- `GENIUS_ACCESS_TOKEN`
|
|
- `YOUTUBE_COOKIES`
|
|
|
|
### Lint Workflow
|
|
|
|
**File**: `.github/workflows/lint.yml`
|
|
|
|
```yaml
|
|
name: Lint
|
|
|
|
on:
|
|
push:
|
|
branches: [ main, develop ]
|
|
pull_request:
|
|
branches: [ main, develop ]
|
|
|
|
jobs:
|
|
golangci-lint:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v3
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v4
|
|
with:
|
|
go-version: '1.24'
|
|
|
|
- name: Run golangci-lint
|
|
uses: golangci/golangci-lint-action@v3
|
|
with:
|
|
version: latest
|
|
|
|
comment-lint:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v3
|
|
|
|
- name: Check for decorative comments
|
|
run: |
|
|
# Fail if decorative comments found (e.g., // ========)
|
|
if grep -r "^[[:space:]]*//[[:space:]]*[=\-*#]\{3,\}" --include="*.go" .; then
|
|
echo "Decorative comments found"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Check for uppercase-leading comments
|
|
run: |
|
|
# Fail if comments start with uppercase (except TODO, FIXME, NOTE)
|
|
if grep -r "^[[:space:]]*//[[:space:]]*[A-Z]" --include="*.go" . | grep -v "TODO\|FIXME\|NOTE"; then
|
|
echo "Uppercase-leading comments found"
|
|
exit 1
|
|
fi
|
|
```
|
|
|
|
**Linters**:
|
|
- `golangci-lint` - Standard Go linting (gofmt, govet, staticcheck, etc.)
|
|
- Custom comment linter - Enforces comment style (no decorative comments, no uppercase-leading)
|
|
|
|
**Comment Rules**:
|
|
- No decorative comments (`// ========`, `// --------`, etc.)
|
|
- No uppercase-leading comments (except `TODO`, `FIXME`, `NOTE`)
|
|
|
|
## Production Deployment
|
|
|
|
### Reverse Proxy (TLS Termination)
|
|
|
|
**No Built-in TLS**: Application must be deployed behind reverse proxy
|
|
|
|
**Nginx Example**:
|
|
```nginx
|
|
upstream bedrock_grpc {
|
|
server localhost:50052;
|
|
}
|
|
|
|
upstream bedrock_http {
|
|
server localhost:8080;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name api.example.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
|
|
|
|
# gRPC endpoint
|
|
location /bedrock.BedrockService/ {
|
|
grpc_pass grpc://bedrock_grpc;
|
|
grpc_set_header X-Real-IP $remote_addr;
|
|
grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
}
|
|
|
|
# HTTP proxy endpoints
|
|
location /stream/ {
|
|
proxy_pass http://bedrock_http;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_buffering off;
|
|
}
|
|
|
|
location /cover/ {
|
|
proxy_pass http://bedrock_http;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Caddy Example** (simpler):
|
|
```
|
|
api.example.com {
|
|
reverse_proxy /bedrock.BedrockService/* h2c://localhost:50052
|
|
reverse_proxy /stream/* localhost:8080
|
|
reverse_proxy /cover/* localhost:8080
|
|
}
|
|
```
|
|
|
|
### Systemd Service
|
|
|
|
**File**: `/etc/systemd/system/bedrock-api.service`
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=Bedrock API Server
|
|
After=network.target postgresql.service
|
|
Requires=postgresql.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=bedrock
|
|
Group=bedrock
|
|
WorkingDirectory=/opt/bedrock-api
|
|
EnvironmentFile=/opt/bedrock-api/.env
|
|
ExecStart=/opt/bedrock-api/bedrock-server
|
|
Restart=always
|
|
RestartSec=10
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=bedrock-api
|
|
|
|
# Security hardening
|
|
NoNewPrivileges=true
|
|
PrivateTmp=true
|
|
ProtectSystem=strict
|
|
ProtectHome=true
|
|
ReadWritePaths=/opt/bedrock-api/logs
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
**Commands**:
|
|
```bash
|
|
# Enable service
|
|
sudo systemctl enable bedrock-api
|
|
|
|
# Start service
|
|
sudo systemctl start bedrock-api
|
|
|
|
# Check status
|
|
sudo systemctl status bedrock-api
|
|
|
|
# View logs
|
|
sudo journalctl -u bedrock-api -f
|
|
```
|
|
|
|
### Environment Variables (Production)
|
|
|
|
**Secure Storage**: Use secrets management (not `.env` file)
|
|
|
|
**AWS Secrets Manager**:
|
|
```bash
|
|
aws secretsmanager get-secret-value --secret-id bedrock-api/production --query SecretString --output text > /tmp/secrets.env
|
|
source /tmp/secrets.env
|
|
rm /tmp/secrets.env
|
|
```
|
|
|
|
**HashiCorp Vault**:
|
|
```bash
|
|
vault kv get -format=json secret/bedrock-api/production | jq -r '.data.data | to_entries[] | "\(.key)=\(.value)"' > /tmp/secrets.env
|
|
source /tmp/secrets.env
|
|
rm /tmp/secrets.env
|
|
```
|
|
|
|
**Kubernetes Secrets**:
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: bedrock-api-secrets
|
|
type: Opaque
|
|
stringData:
|
|
DATABASE_URL: postgresql://user:pass@postgres:5432/bedrock
|
|
JWT_SECRET: your-secret
|
|
SPOTIFY_CLIENT_ID: your-id
|
|
SPOTIFY_CLIENT_SECRET: your-secret
|
|
SOUNDCLOUD_CLIENT_IDS: id1,id2,id3
|
|
GENIUS_ACCESS_TOKEN: your-token
|
|
```
|
|
|
|
### Database Migrations (Production)
|
|
|
|
**Manual Execution** (current):
|
|
```bash
|
|
psql $DATABASE_URL -f db/migrations/001_create_users_table.up.sql
|
|
```
|
|
|
|
**Automated with golang-migrate** (recommended):
|
|
```bash
|
|
# Install migrate
|
|
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.16.2/migrate.linux-amd64.tar.gz | tar xvz
|
|
sudo mv migrate /usr/local/bin/
|
|
|
|
# Run migrations
|
|
migrate -path db/migrations -database $DATABASE_URL up
|
|
|
|
# Rollback
|
|
migrate -path db/migrations -database $DATABASE_URL down 1
|
|
```
|
|
|
|
**Migration Tracking**:
|
|
```sql
|
|
-- golang-migrate creates this table automatically
|
|
SELECT * FROM schema_migrations;
|
|
```
|
|
|
|
### Monitoring (Not Implemented)
|
|
|
|
**Recommended Stack**:
|
|
- Prometheus (metrics collection)
|
|
- Grafana (visualization)
|
|
- Loki (log aggregation)
|
|
- Jaeger (distributed tracing)
|
|
|
|
**Prometheus Metrics** (to implement):
|
|
```go
|
|
import "github.com/prometheus/client_golang/prometheus"
|
|
|
|
var (
|
|
requestsTotal = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Name: "bedrock_requests_total",
|
|
Help: "Total number of requests",
|
|
},
|
|
[]string{"method", "status"},
|
|
)
|
|
|
|
requestDuration = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Name: "bedrock_request_duration_seconds",
|
|
Help: "Request duration in seconds",
|
|
},
|
|
[]string{"method"},
|
|
)
|
|
|
|
providerErrors = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Name: "bedrock_provider_errors_total",
|
|
Help: "Total provider errors",
|
|
},
|
|
[]string{"provider"},
|
|
)
|
|
)
|
|
```
|
|
|
|
**Grafana Dashboard** (example queries):
|
|
```promql
|
|
# Request rate
|
|
rate(bedrock_requests_total[5m])
|
|
|
|
# Error rate
|
|
rate(bedrock_requests_total{status="error"}[5m]) / rate(bedrock_requests_total[5m])
|
|
|
|
# P95 latency
|
|
histogram_quantile(0.95, rate(bedrock_request_duration_seconds_bucket[5m]))
|
|
|
|
# Provider error rate
|
|
rate(bedrock_provider_errors_total[5m])
|
|
```
|
|
|
|
### Logging (Production)
|
|
|
|
**Structured Logging** (to implement):
|
|
```go
|
|
import "go.uber.org/zap"
|
|
|
|
logger, _ := zap.NewProduction()
|
|
defer logger.Sync()
|
|
|
|
logger.Info("search request",
|
|
zap.String("query", query),
|
|
zap.Int32("limit", limit),
|
|
zap.String("user_id", userID),
|
|
)
|
|
|
|
logger.Error("provider failed",
|
|
zap.String("provider", "spotify"),
|
|
zap.Error(err),
|
|
)
|
|
```
|
|
|
|
**Log Aggregation** (Loki):
|
|
```yaml
|
|
# promtail config
|
|
clients:
|
|
- url: http://loki:3100/loki/api/v1/push
|
|
|
|
scrape_configs:
|
|
- job_name: bedrock-api
|
|
static_configs:
|
|
- targets:
|
|
- localhost
|
|
labels:
|
|
job: bedrock-api
|
|
__path__: /var/log/bedrock-api/*.log
|
|
```
|
|
|
|
### Backup Strategy
|
|
|
|
**PostgreSQL Backups**:
|
|
```bash
|
|
# Daily backup script
|
|
#!/bin/bash
|
|
BACKUP_DIR=/backups/bedrock-api
|
|
DATE=$(date +%Y%m%d_%H%M%S)
|
|
|
|
pg_dump $DATABASE_URL | gzip > $BACKUP_DIR/bedrock_$DATE.sql.gz
|
|
|
|
# Keep last 30 days
|
|
find $BACKUP_DIR -name "bedrock_*.sql.gz" -mtime +30 -delete
|
|
|
|
# Upload to S3
|
|
aws s3 cp $BACKUP_DIR/bedrock_$DATE.sql.gz s3://backups/bedrock-api/
|
|
```
|
|
|
|
**Cron Schedule**:
|
|
```cron
|
|
0 2 * * * /opt/bedrock-api/scripts/backup.sh
|
|
```
|
|
|
|
**Point-in-Time Recovery** (WAL archiving):
|
|
```sql
|
|
-- Enable WAL archiving in postgresql.conf
|
|
wal_level = replica
|
|
archive_mode = on
|
|
archive_command = 'aws s3 cp %p s3://backups/bedrock-api/wal/%f'
|
|
```
|
|
|
|
### Scaling Strategies
|
|
|
|
**Vertical Scaling**:
|
|
- Increase CPU/RAM for single instance
|
|
- Increase PostgreSQL resources
|
|
- Increase connection pool size
|
|
|
|
**Horizontal Scaling**:
|
|
- Run multiple application instances behind load balancer
|
|
- Use read replicas for PostgreSQL (if read-heavy)
|
|
- Add Redis for caching (reduce provider API calls)
|
|
|
|
**Load Balancer** (nginx):
|
|
```nginx
|
|
upstream bedrock_grpc {
|
|
server bedrock-api-1:50052;
|
|
server bedrock-api-2:50052;
|
|
server bedrock-api-3:50052;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
location /bedrock.BedrockService/ {
|
|
grpc_pass grpc://bedrock_grpc;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Kubernetes Deployment**:
|
|
```yaml
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: bedrock-api
|
|
spec:
|
|
replicas: 3
|
|
selector:
|
|
matchLabels:
|
|
app: bedrock-api
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: bedrock-api
|
|
spec:
|
|
containers:
|
|
- name: bedrock-api
|
|
image: bedrock-api:latest
|
|
ports:
|
|
- containerPort: 50052
|
|
name: grpc
|
|
- containerPort: 8080
|
|
name: http
|
|
env:
|
|
- name: DATABASE_URL
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: bedrock-api-secrets
|
|
key: DATABASE_URL
|
|
resources:
|
|
requests:
|
|
memory: "256Mi"
|
|
cpu: "250m"
|
|
limits:
|
|
memory: "512Mi"
|
|
cpu: "500m"
|
|
livenessProbe:
|
|
exec:
|
|
command:
|
|
- grpc_health_probe
|
|
- -addr=:50052
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 10
|
|
readinessProbe:
|
|
exec:
|
|
command:
|
|
- grpc_health_probe
|
|
- -addr=:50052
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 5
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: bedrock-api
|
|
spec:
|
|
selector:
|
|
app: bedrock-api
|
|
ports:
|
|
- name: grpc
|
|
port: 50052
|
|
targetPort: 50052
|
|
- name: http
|
|
port: 8080
|
|
targetPort: 8080
|
|
type: LoadBalancer
|
|
```
|
|
|
|
## Deployment Checklist
|
|
|
|
### Pre-Deployment
|
|
|
|
- [ ] Update Go version in Dockerfile to match go.mod (1.25)
|
|
- [ ] Configure environment variables (secrets management)
|
|
- [ ] Run database migrations
|
|
- [ ] Test provider credentials
|
|
- [ ] Configure reverse proxy (TLS)
|
|
- [ ] Set up monitoring (Prometheus, Grafana)
|
|
- [ ] Set up logging (structured logs, aggregation)
|
|
- [ ] Configure backups (PostgreSQL, WAL archiving)
|
|
- [ ] Load test (ensure performance under load)
|
|
- [ ] Security audit (JWT secret, database credentials, etc.)
|
|
|
|
### Post-Deployment
|
|
|
|
- [ ] Verify gRPC endpoint (grpcurl)
|
|
- [ ] Verify HTTP proxy endpoints (curl)
|
|
- [ ] Check logs for errors
|
|
- [ ] Monitor metrics (request rate, error rate, latency)
|
|
- [ ] Test authentication (register, login, refresh)
|
|
- [ ] Test search (all providers)
|
|
- [ ] Test streaming (SoundCloud, YouTube Music)
|
|
- [ ] Test lyrics (LrcLib, Genius)
|
|
- [ ] Verify database connection
|
|
- [ ] Test backup restoration
|
|
|
|
### Ongoing Maintenance
|
|
|
|
- [ ] Monitor provider API changes
|
|
- [ ] Rotate JWT secret periodically
|
|
- [ ] Update dependencies (go mod tidy)
|
|
- [ ] Review logs for errors
|
|
- [ ] Monitor disk usage (PostgreSQL, logs)
|
|
- [ ] Test backup restoration monthly
|
|
- [ ] Update TLS certificates (Let's Encrypt auto-renewal)
|
|
- [ ] Review security advisories (Go, dependencies)
|
|
|
|
## Deployment Environments
|
|
|
|
### Development
|
|
|
|
**Infrastructure**: Local machine or Docker Compose
|
|
**Database**: PostgreSQL in Docker
|
|
**Secrets**: `.env` file
|
|
**TLS**: No (HTTP only)
|
|
**Monitoring**: No
|
|
**Backups**: No
|
|
|
|
### Staging
|
|
|
|
**Infrastructure**: Single VM or Kubernetes cluster
|
|
**Database**: Managed PostgreSQL (AWS RDS, Google Cloud SQL)
|
|
**Secrets**: Secrets manager (AWS Secrets Manager, Vault)
|
|
**TLS**: Yes (Let's Encrypt)
|
|
**Monitoring**: Prometheus + Grafana
|
|
**Backups**: Daily automated backups
|
|
|
|
### Production
|
|
|
|
**Infrastructure**: Kubernetes cluster (multi-region)
|
|
**Database**: Managed PostgreSQL with read replicas
|
|
**Secrets**: Secrets manager with rotation
|
|
**TLS**: Yes (Let's Encrypt or commercial cert)
|
|
**Monitoring**: Full observability stack (Prometheus, Grafana, Loki, Jaeger)
|
|
**Backups**: Hourly backups + WAL archiving + point-in-time recovery
|
|
**Scaling**: Horizontal pod autoscaling (HPA)
|
|
**High Availability**: Multi-zone deployment, load balancing
|
|
|
|
## Cost Estimation (AWS)
|
|
|
|
### Small Deployment (1000 requests/day)
|
|
|
|
| Resource | Specification | Monthly Cost |
|
|
|----------|---------------|--------------|
|
|
| EC2 Instance | t3.small (2 vCPU, 2 GB RAM) | $15 |
|
|
| RDS PostgreSQL | db.t3.micro (1 vCPU, 1 GB RAM) | $15 |
|
|
| Load Balancer | Application Load Balancer | $20 |
|
|
| Data Transfer | 100 GB/month | $9 |
|
|
| **Total** | | **$59/month** |
|
|
|
|
### Medium Deployment (100k requests/day)
|
|
|
|
| Resource | Specification | Monthly Cost |
|
|
|----------|---------------|--------------|
|
|
| EC2 Instances | 3x t3.medium (2 vCPU, 4 GB RAM) | $90 |
|
|
| RDS PostgreSQL | db.t3.small (2 vCPU, 2 GB RAM) | $30 |
|
|
| ElastiCache Redis | cache.t3.micro (1 vCPU, 0.5 GB RAM) | $12 |
|
|
| Load Balancer | Application Load Balancer | $20 |
|
|
| Data Transfer | 1 TB/month | $90 |
|
|
| **Total** | | **$242/month** |
|
|
|
|
### Large Deployment (1M requests/day)
|
|
|
|
| Resource | Specification | Monthly Cost |
|
|
|----------|---------------|--------------|
|
|
| EKS Cluster | Control plane | $73 |
|
|
| EC2 Instances | 10x t3.large (2 vCPU, 8 GB RAM) | $600 |
|
|
| RDS PostgreSQL | db.r5.large (2 vCPU, 16 GB RAM) + read replica | $300 |
|
|
| ElastiCache Redis | cache.r5.large (2 vCPU, 13 GB RAM) | $150 |
|
|
| Load Balancer | Application Load Balancer | $20 |
|
|
| Data Transfer | 10 TB/month | $900 |
|
|
| **Total** | | **$2,043/month** |
|
|
|
|
**Note**: Costs exclude provider API fees (Spotify, Genius, etc.)
|
|
|
|
## Deployment Recommendations for Metadata Aggregator
|
|
|
|
### Adopt
|
|
|
|
- Multi-stage Docker build (minimal runtime image)
|
|
- Docker Compose for local development
|
|
- GitHub Actions for CI/CD
|
|
- Reverse proxy for TLS termination
|
|
- Systemd service for production
|
|
|
|
### Avoid
|
|
|
|
- Manual migrations (use golang-migrate)
|
|
- No monitoring (implement Prometheus)
|
|
- No structured logging (use zap or zerolog)
|
|
- Go version mismatch (keep Dockerfile and go.mod in sync)
|
|
|
|
### Enhance
|
|
|
|
- Add health check endpoint (implement GetServiceStatus properly)
|
|
- Add graceful shutdown (handle SIGTERM)
|
|
- Add readiness probe (check database connection)
|
|
- Add metrics endpoint (/metrics for Prometheus)
|
|
- Add Redis for caching
|
|
- Add backup automation
|
|
- Add deployment documentation
|