239 lines
8.6 KiB
Markdown
239 lines
8.6 KiB
Markdown
# Arsitektur asepharyana-hub
|
|
|
|
## Topologi Fisik
|
|
|
|
Dua node terhubung via **Tailscale** overlay network:
|
|
|
|
```
|
|
┌──────────────────────────────┐ ┌──────────────────────────────┐
|
|
│ orangevps (VPS) │ │ imrnes (Bare-metal) │
|
|
│ IP: 45.127.35.244 │ │ Tailscale: 100.121.180.82 │
|
|
│ Tailscale: 100.x.x.x │◄──────┤ │
|
|
│ │ │ Layanan: │
|
|
│ Layanan: │ │ ├─ PostgreSQL (port 6432) │
|
|
│ ├─ Caddy (port 80/443) │ │ └─ Redis (port 6379) │
|
|
│ ├─ NATS + JetStream │ │ │
|
|
│ ├─ Dapr Placement │ └──────────────────────────────┘
|
|
│ ├─ Redis (cache, Dapr) │
|
|
│ ├─ Scraper API + Dapr │
|
|
│ └─ Hub (Next.js SPA) │
|
|
└──────────────────────────────┘
|
|
```
|
|
|
|
### Konektivitas Container ke Tailscale
|
|
|
|
Container di `orangevps` tidak bisa langsung mencapai IP Tailscale (`100.x.x.x`). Route Tailscale harus ditambahkan ke tabel routing utama (`main`) via `tailscale-routes.service` agar traffic dari container bisa melewati host ke Tailscale.
|
|
|
|
## Alur Request HTTP (External)
|
|
|
|
```
|
|
Internet
|
|
│
|
|
▼ Port 443
|
|
Caddy 2.11.4 (auto-TLS LE, HTTP/3)
|
|
├─ TLS termination (sertifikat dari volume mount)
|
|
├─ Middleware chain: secure-headers → compress → retry → rate-limit → buffer
|
|
├─ Plugin: real-ip (Cloudflare), block-sensitive-paths
|
|
│
|
|
▼ Router matching
|
|
Host(`asepharyana.my.id`) || Host(`www.asepharyana.my.id`) → hub
|
|
host(`hub.asepharyana.my.id`) → hub (SPA + dashboard)
|
|
Host(`scraper.asepharyana.my.id`) || Host(`api.asepharyana.my.id`) → scraper-api
|
|
│
|
|
├─ hub (Next.js, port 4003)
|
|
│ ├─ / — Portfolio SPA
|
|
│ ├─ /dashboard — Ops dashboard (client-side, auto-refresh 15s)
|
|
│ ├─ /api/dashboard — JSON: systemd services, Jaeger traces, Prometheus metrics
|
|
│ └─ Metrics via node-exporter + app endpoints
|
|
│
|
|
▼ Service load balancer
|
|
http://scraper-api:4091
|
|
│
|
|
▼
|
|
Scraper API (Rust / Axum)
|
|
├─ Health check: GET /, respon 200
|
|
├─ REST endpoints
|
|
├─ Database via `DATABASE_URL` (Tailscale → PostgreSQL di imrnes)
|
|
├─ Cache via `REDIS_URL` (Redis lokal di container)
|
|
└─ Pub/sub via Dapr sidecar (localhost:3500)
|
|
```
|
|
|
|
## Infrastruktur Internal
|
|
|
|
### Docker Compose Project
|
|
|
|
Semua service berjalan dalam satu Docker Compose project bernama `compose` dan bergabung di network `app-shared-net`:
|
|
|
|
| File | Service | Peran |
|
|
|------|---------|-------|
|
|
| `traefik.yml` | `traefik` | Reverse proxy + TLS + metrics Prometheus |
|
|
| `shared.yml` | `redis` | Cache, session store, backend Dapr pub/sub & state |
|
|
| `nats.yml` | `nats` | Message broker + JetStream persistent streaming |
|
|
| `dapr.yml` | `dapr-placement` | Koordinasi actor placement untuk sidecar Dapr |
|
|
| `scraper.yml` | `scraper-api` + `scraper-api-dapr` | Aplikasi Rust + sidecar Dapr |
|
|
| systemd hub | `hub` | Next.js SPA portfolio + dashboard |
|
|
| `observability.yml` | `otel-collector`, `jaeger`, `prometheus`, `node-exporter` | Tracing, metrics, observability |
|
|
|
|
### Dapr Sidecar Pattern
|
|
|
|
Setiap aplikasi yang menggunakan Dapr mendapat sidecar container `daprd`:
|
|
|
|
```
|
|
┌─────────────────────┐
|
|
│ scraper-api │
|
|
│ (app port 4091) │
|
|
└────────┬────────────┘
|
|
│ localhost:3500 (HTTP)
|
|
│ localhost:50001 (gRPC)
|
|
┌────────▼────────────┐
|
|
│ scraper-api-dapr │
|
|
│ (daprd sidecar) │
|
|
│ │
|
|
│ Dapr components: │
|
|
│ ├─ pubsub.redis │
|
|
│ └─ state.redis │
|
|
└─────────────────────┘
|
|
```
|
|
|
|
Komponen Dapr:
|
|
|
|
| Komponen | Tipe | Backend |
|
|
|----------|------|---------|
|
|
| `pubsub` | `pubsub.redis` | `redis:6379` |
|
|
| `statestore` | `state.redis` | `redis:6379` (prefix `dapr`) |
|
|
|
|
### Monitoring & Auto-Discovery
|
|
|
|
#### Prometheus Docker Auto-Discovery
|
|
|
|
Prometheus menggunakan `docker_sd_configs` untuk auto-detect container yang perlu di-scrape. Cukup tambah label pada container:
|
|
|
|
```yaml
|
|
labels:
|
|
- 'prometheus.io/scrape=true'
|
|
- 'prometheus.io/port=8080' # port metrics endpoint
|
|
- 'prometheus.io/path=/metrics' # optional, default /metrics
|
|
```
|
|
|
|
Prometheus akan auto-detect dan mulai scrape container dalam 15 detik.
|
|
|
|
#### Traefik Metrics
|
|
|
|
Traefik mengekspos metrics Prometheus di port 8080 (`--metrics.prometheus=true`). Metrics yang tersedia:
|
|
|
|
| Metric | Query untuk dashboard |
|
|
|--------|----------------------|
|
|
| Request rate | `sum(rate(traefik_service_requests_total[1m]))` |
|
|
| Latency | `avg(traefik_service_request_duration_seconds_sum / traefik_service_request_duration_seconds_count) * 1000` |
|
|
| Error rate | `sum(rate(traefik_service_requests_total{code=~"5.."}[1m]))` |
|
|
|
|
Dashboard di `/api/dashboard` returns node metrics + Traefik range data untuk 4 sparkline charts (RPS, latency, errors, trace volume).
|
|
|
|
#### Docker Socket Access
|
|
|
|
Container yang perlu akses Docker socket (`/var/run/docker.sock`) harus punya group docker (GID 988):
|
|
|
|
```yaml
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
group_add:
|
|
- '988'
|
|
```di compose atau `--group-add 988` via CLI. Berlaku untuk `hub` (container list) dan `prometheus` (Docker SD).
|
|
|
|
### NATS + JetStream
|
|
|
|
NATS berjalan dengan flag `-js` untuk mengaktifkan JetStream. Persistent stream disimpan di volume `nats_data`. Dapr pub/sub routing:
|
|
|
|
```
|
|
Service → Dapr sidecar (pubsub.redis) → Redis streams
|
|
```
|
|
|
|
> **Catatan:** Saat ini Dapr pub/sub menggunakan Redis, bukan NATS. Jika ingin migrasi ke NATS untuk pub/sub, komponen Dapr perlu diganti dengan `pubsub.nats`.
|
|
|
|
## Arsitektur CI/CD
|
|
|
|
```
|
|
Push ke main (apps/**, infra/**)
|
|
│
|
|
▼
|
|
docker-build-push.yml
|
|
├─ Phase 1: Detect changed services
|
|
├─ Phase 2: Build & Push image ke GHCR
|
|
└─ Phase 3: Update compose manifest + submodule pointer
|
|
│
|
|
▼ (workflow_run trigger)
|
|
deploy-docker.yml
|
|
├─ SSH ke orangevps
|
|
├─ Git sync, pull images
|
|
├─ Remove stale containers
|
|
└─ Selective restart service
|
|
```
|
|
|
|
Submodule update dari remote repo via `repository_dispatch`:
|
|
|
|
```
|
|
Push ke asepharyana-hub-scraper
|
|
│
|
|
▼ (repository_dispatch)
|
|
update-submodule.yml
|
|
├─ Update submodule pointer
|
|
└─ Commit & push ke hub repo
|
|
│
|
|
▼ (repository_dispatch trigger)
|
|
docker-build-push.yml
|
|
└─ Build, push, deploy
|
|
```
|
|
|
|
## Image Tagging Strategy
|
|
|
|
| Tag | Contoh | Penggunaan |
|
|
|-----|--------|------------|
|
|
| `sha-<short>` | `sha-a3c5d74` | Immutable, deterministic rollback |
|
|
| `latest` | `latest` | Mutable, convenience |
|
|
| `buildcache` | `sha-a3c5d74-buildcache` | Registry-based build cache (internal) |
|
|
|
|
## Networking
|
|
|
|
### Port Map
|
|
|
|
| Port | Service | Deskripsi |
|
|
|------|---------|-----------|
|
|
| 443 | Traefik | HTTPS eksternal |
|
|
| 80 | Traefik | Redirect ke HTTPS |
|
|
| 4222 | NATS | Client connections |
|
|
| 8222 | NATS | HTTP monitor / health |
|
|
| 6379 | Redis | Internal container network |
|
|
| 3500 | Dapr sidecar | Dapr HTTP API (per service) |
|
|
| 50001 | Dapr sidecar | Dapr gRPC API (per service) |
|
|
| 50005 | Dapr placement | Actor placement |
|
|
| 4091 | Scraper API | Aplikasi HTTP |
|
|
|
|
## Event Topics Convention
|
|
|
|
Semua event menggunakan prefix `hub.`:
|
|
|
|
| Topic | Payload | Deskripsi |
|
|
|-------|---------|-----------|
|
|
| `hub.image.cached` | `{original_url, cdn_url, source}` | Image selesai di-cache |
|
|
| `hub.image.repaired` | `{old_url, new_url}` | CNAME image diperbaiki |
|
|
| `hub.scrape.anime.done` | `{source, slug, duration}` | Scrape anime selesai |
|
|
| `hub.system.alert` | `{service, level, message}` | Error/alert dari service |
|
|
|
|
## Service Registry (Traefik)
|
|
|
|
Domain routing:
|
|
|
|
| Subdomain | Service | URL Backend |
|
|
|-----------|---------|-------------|
|
|
| `asepharyana.my.id` (root) | Hub SPA + dashboard | `http://hub:3000` |
|
|
| `www.*` | Hub (alias) | `http://hub:3000` |
|
|
| `hub.*` | Hub (alias) | `http://hub:3000` |
|
|
| `scraper.*` | Scraper API | `http://scraper-api:4091` |
|
|
| `api.*` | Scraper API (alias) | `http://scraper-api:4091` |
|
|
| `traefik.*` | Traefik Dashboard | `api@internal` |
|
|
| `jaeger.*` | Jaeger UI | `http://jaeger:16686` |
|
|
|
|
Semua domain tersedia di:
|
|
- `<service>.asepharyana.my.id`
|
|
- `<service>.asepharyana.web.id`
|