L3 - Nền tảng giám sát máy chủ - Collector - Ingest API
Quy ước tên: L3 - <P&L> - <Hệ thống L2> - <Thành phần>. P&L: chưa chỉ định. Hệ thống L2: Collector. Thành phần: Ingest API (giao diện HTTP dành cho agent, internal/ingest).
Quy ước đánh dấu: "Đã làm" là có mã và kiểm thử. "Chỉ thiết kế" là chỉ có trong docs/. "Đề xuất" là ý của tác giả bản nháp. Đường dẫn tương đối với gốc repo access-hub-collector. Khi tài liệu thiết kế và mã khác nhau, mã thắng.
0. Front matter
Thông tin tài liệu đầy đủ
| Trạng thái | BẢN NHÁP, phiên bản 0.1, 2026-09-30. Người phê duyệt: chưa chỉ định |
| Thành phần (Component) | Ingest API: các điểm cuối /agent/v1/*, kiểm tra header, xác thực Bearer, giải nén, kiểm tra và làm sạch số liệu, giới hạn tốc độ, đẩy lô lên bus, chuyển tiếp kiểm kê |
| Truy vết L2 | L2 SAD Collector: thành phần "Ingest API" (mục 2.3, 6.1), FR 1, 3, 6, 7 (mục 3), NFR 1, 2, 8, 9 (mục 4), rủi ro AR-004 (mục 16). Mục tiêu L1: G1, G4, G5 (qua L2) |
| Tài liệu cha | L2 SAD Collector |
| Tài liệu anh em | Registry, Enroll, Bus, Worker, TSDB |
| Tier | Đề xuất Cấp 3 (kế thừa L2, chưa xác nhận) |
| Phân loại dữ liệu (Data classification) | Nội bộ (Internal). Bearer token là bí mật: không lưu rõ, không log |
| Blast radius | Ingest chết: agent không gửi được, tự đệm WAL trên đĩa và gửi bù. Một node chết: LB chuyển sang node còn lại. Lỗi kiểm tra sai có thể làm mất mẫu của mọi tenant trên node đó |
| Sign-off gate | Chưa chỉ định. Không có ai đã sign-off |
Truy vết mục L3 đến thành phần L2:
| Mục L3 | Thành phần hoặc mục L2 |
|---|---|
| 1 Phạm vi | 2.3 Các thành phần chính (Ingest API) |
| 2 Yêu cầu | 3 Functional, 4 NFR |
| 3 Kiến trúc | 6.1 Component table |
| 5 Hợp đồng API | 6.2 Integration table (Agent đến Ingest) |
| 7 Thuật toán | 8.1 Business flow (enroll, ingest) |
| 8, 9 Lỗi và suy thoái | 6.3 Resilience, 12.2 Reliability |
| 11 Bảo mật | 9 Security |
| 12 Cấu hình | 10 Deployment |
| 13 Telemetry | 13 Observability |
| 14, 15 Kiểm thử, lộ trình | 15 Testing |
1. Scope and Non-Goals
1.1 Vai trò
Ingest API là điểm vào duy nhất của agent. Nó không tin nội dung agent: danh tính (công ty, máy chủ) lấy từ registry theo băm token, số liệu phải nằm trong danh mục và nhãn cho phép. Nó không ghi TSDB (việc của worker) và không quyết định trạng thái agent (việc của Access Hub).
1.2 Context diagram
flowchart LR
classDef bc fill:#1f3a5f,stroke:#4a90d9,color:#fff;
classDef owned fill:#2d4a3e,stroke:#5fb37a,color:#fff;
classDef entity fill:#3a3320,stroke:#d9b84a,color:#fff;
classDef datastore fill:#3a2d4a,stroke:#a06fd9,color:#fff;
AG(["Agent (ext)"]):::entity
HUB(["Access Hub (ext)"]):::entity
ING["Ingest API"]:::bc
RG["Registry"]:::owned
PS["Presence"]:::owned
BUS[["Bus"]]:::datastore
AG -->|"đẩy số liệu, enroll"| ING
ING -->|"tra danh tính"| RG
ING -->|"chuyển tiếp enroll, kiểm kê"| HUB
ING -->|"cập nhật lần thấy cuối"| PS
ING -.->|"lô mẫu theo shard"| BUS| Chiều | Bên | Nội dung |
|---|---|---|
| Vào | Agent | GET /agent/v1/ping, POST /agent/v1/enroll, POST /agent/v1/metrics, GET /agent/v1/config, POST /agent/v1/inventory, POST /agent/v1/credentials/renew (stub) |
| Ra | Registry, Resolver | Tra danh tính theo băm token, ghi agent sau enroll (L3 Registry) |
| Ra | Access Hub | Chuyển tiếp enroll (timeout 20 giây), lô kiểm kê |
| Ra | Presence | Touch sau khi publish, gọi OnAgentUp khi agent vừa hết down |
| Ra | Bus | Publish lô đã chuẩn hóa (L3 Bus) |
1.3 Trong phạm vi
- Kiểm tra
X-AH-Proto,X-AH-Agent-Version, Content-Type, Content-Encoding, kích thước. - Xác thực Bearer, ánh xạ lỗi xác thực sang mã HTTP.
- Kiểm tra số liệu theo danh mục (
internal/catalog), làm sạch nhãn, giới hạn series, cửa sổ thời gian. - Giới hạn tốc độ theo agent và theo IP.
- Cấu hình mặc định và ETag cho agent, kiểm kê có khử trùng, chuyển tiếp lô kiểm kê.
1.4 Ngoài phạm vi
| Nội dung | Không thuộc BC | Thuộc về |
|---|---|---|
| Ghi TSDB, gom lô | Có | L3 Bus, Worker, TSDB |
| Lưu và đồng bộ danh tính agent, tra ngược Access Hub | Có | L3 Registry, Enroll |
| Xác thực License, tạo agent, ràng buộc máy chủ | Có | Access Hub (HUB-*) |
| Phát hiện mất tín hiệu, sự kiện | Có | L3 Alerting, Outbox |
Cấu hình agent theo máy, ConfigFor thực tế | Có | Chưa làm (luật ngưỡng đã xong ở COL-8, nằm trong worker) |
Xoay token (credentials/renew) | Có | Chưa làm, hiện stub 503 |
| Kết thúc TLS, cân tải | Có | Load balancer (nginx ở dev) |
2. Requirements
2.1 Functional Requirements
| # | Trách nhiệm | Giải thích | Hiện thực ở |
|---|---|---|---|
| FR-ING-01 | Ping | Trả server_time_ms, proto_min, proto_max (đều 1), không cần xác thực, không kiểm tra header | internal/ingest/handlers.go, TestPingNeedsNoAuthOrHeaders |
| FR-ING-02 | Kiểm tra giao thức | X-AH-Proto phải là "1" (thiếu 400, khác 426 upgrade_required); X-AH-Agent-Version khớp ^[0-9A-Za-z][0-9A-Za-z.+_-]{0,63}$ | internal/ingest/http.go, TestHeaderChecks |
| FR-ING-03 | Xác thực Bearer | Token tối đa 256 ký tự, băm SHA-256, tra registry; ánh xạ lỗi (mục 5.3) | internal/ingest/http.go, TestAuth, TestRevokedAgentGets403, TestHubDownGives503NotUnauthorized |
| FR-ING-04 | Nhận số liệu | Giải nén, kiểm tra, làm sạch, gắn danh tính từ registry, publish bus, trả ack | internal/ingest/handlers.go, TestMetricsAcceptedAndTenantIdentityFromRegistry |
| FR-ING-05 | Chống giả mạo danh tính | Bỏ nhãn company_id, server_id, agent_id do agent gửi và đếm | TestReservedLabelsAreStripped |
| FR-ING-06 | Giới hạn series mỗi agent | Chấp nhận series mới đến max_series trong cửa sổ trượt | internal/ingest/series.go, TestSeriesLimitPerAgent, TestForgetAgentReleasesSeriesBudget |
| FR-ING-07 | Thống kê agent | Chuyển AgentStats thành series agent_*, không bị giới hạn series | internal/ingest/handlers.go |
| FR-ING-08 | Cấu hình agent | Trả mặc định, ETag, 304 với If-None-Match | internal/ingest/agentconfig.go, TestConfigETag |
| FR-ING-09 | Kiểm kê | Nhận, trả 202, khử trùng 1 giờ, chuyển tiếp lô | internal/ingest/inventory.go, TestInventoryForwardedAndDeduped |
| FR-ING-10 | Enroll proxy | Chuyển tiếp sang Access Hub, ánh xạ lỗi, lưu agent vào registry | internal/ingest/handlers.go, TestEnrollProxy, TestEnrollErrors (chi tiết ở L3 Registry) |
| FR-ING-11 | Giới hạn tốc độ | Theo agent (metrics, config, inventory) và theo IP (enroll) | internal/ratelimit, TestMetricsRateLimit, TestConfigRateLimit, TestEnrollRateLimitPerIP |
| FR-ING-12 | Cấp lại token | Stub: 503 unavailable, Retry-After: 300 | TestRenewIsAStub (chưa làm) |
2.2 Non-Functional Requirements
Allocated (phân bổ từ L2):
| NFR L2 | Target | Cách đáp ứng ở L3 |
|---|---|---|
| NFR-01 sức chứa | 10.000 agent, p99 không quá 250 ms | Xử lý không trạng thái, giới hạn giải nén, publish không chặn. Chưa đo |
| NFR-11 TLS | 1.2 trở lên | Khi bật TLS tự quản: MinVersion TLS 1.2 |
| NFR-12 cô lập tenant | Không lẫn tenant | Danh tính từ registry, TestTwoTenantsNeverMix |
| NFR-15 quan sát | Metric, log có request_id | Mục 13 |
Inherited (kế thừa nền chung, không định nghĩa lại ở đây): timeout máy chủ (ReadHeaderTimeout 10s, ReadTimeout 30s, WriteTimeout 30s, IdleTimeout 120s, MaxHeaderBytes 64 KiB) từ internal/httpx và internal/app.
Owned (do thành phần này sở hữu):
| ID | Target | Parent L2-NFR | Satisfied-by |
|---|---|---|---|
| NFR-ING-01 | Body nén tối đa 1 MiB (max_body_bytes), giải nén tối đa 8 MiB, tối đa 20.000 điểm và 500 series mỗi request | NFR-01, NFR-12 | internal/ingest/decode.go, TestBodyLimits |
| NFR-ING-02 | Từ chối có kiểm soát: bus đầy thì 503 kèm Retry-After: 5, Access Hub tạm mất thì 503 kèm Retry-After: 30 | NFR-08 | TestBusFullGives503WithRetryAfter, TestHubDownGives503NotUnauthorized |
| NFR-ING-03 | Handler panic được bắt và trả 500, không sập tiến trình | NFR-02 | internal/ingest/http.go |
| NFR-ING-04 | Không log token, Authorization | NFR-11 | Xem mục 11 |
2.3 Acceptance Criteria
| AC | Given / When / Then | Test ID |
|---|---|---|
| AC-ING-01 | Given agent hợp lệ, When gửi metrics đúng danh mục, Then 200 kèm ack và mẫu đến bus với company_id, server_id từ registry | TestMetricsAcceptedAndTenantIdentityFromRegistry |
| AC-ING-02 | Given agent gửi nhãn company_id, When nhận, Then nhãn bị bỏ và đếm | TestReservedLabelsAreStripped |
| AC-ING-03 | Given token đã thu hồi, When gửi metrics, Then 403 agent_revoked | TestRevokedAgentGets403 |
| AC-ING-04 | Given Access Hub không truy cập được và token chưa cache, When gửi metrics, Then 503 chứ không 401 | TestHubDownGives503NotUnauthorized |
| AC-ING-05 | Given bus đầy, When gửi metrics, Then 503 Retry-After: 5 | TestBusFullGives503WithRetryAfter |
| AC-ING-06 | Given body vượt giới hạn nén hoặc giải nén, When gửi, Then 413 too_large | TestBodyLimits |
| AC-ING-07 | Given hai tenant, When cả hai gửi, Then dữ liệu không lẫn | TestTwoTenantsNeverMix |
| AC-ING-08 | Given X-AH-Proto khác 1, When gọi, Then 426 upgrade_required | TestHeaderChecks |
| AC-ING-09 | Given If-None-Match trùng ETag, When lấy config, Then 304 | TestConfigETag |
2.4 Quality Attribute Scenarios
| Nguồn | Kích thích | Môi trường | Phản hồi | Thước đo |
|---|---|---|---|---|
| Agent | Gửi lô 30 giây một lần | Tải bình thường | Trả ack, mẫu vào bus | p99 không quá 250 ms (mục tiêu NFR-01, chưa đo) |
| Agent bị hỏng | Gửi nhãn company_id giả | Bất kỳ | Bỏ nhãn, danh tính giữ nguyên | 0 mẫu sai tenant |
| Bus đầy | Nhiều agent gửi | TSDB chậm | 503 kèm Retry-After: 5, không mất mẫu đã ack | Không có mẫu ack rồi mất do ingest |
| Access Hub tạm mất | Token mới chưa cache | Sự cố Access Hub | 503 kèm Retry-After: 30, agent tự thử lại | 0 phản hồi 401 sai |
| Kẻ tấn công | Bom giải nén (gzip, zstd) | Bất kỳ | 413 sau khi chạm trần giải nén | Bộ nhớ cấp phát không quá 8 MiB mỗi request |
3. Application Architecture
3.1 Kiến trúc thời chạy (C&C)
flowchart LR
classDef bc fill:#1f3a5f,stroke:#4a90d9,color:#fff;
classDef owned fill:#2d4a3e,stroke:#5fb37a,color:#fff;
classDef datastore fill:#3a2d4a,stroke:#a06fd9,color:#fff;
RT["Router và middleware"]:::bc
AU["Xác thực Bearer"]:::bc
DC["Giải nén và kiểm tra"]:::bc
SL["Bộ giới hạn series"]:::bc
RL["Bộ giới hạn tốc độ"]:::owned
RG["Resolver"]:::owned
PS["Presence"]:::owned
BUS[["Bus"]]:::datastore
INV["Forwarder kiểm kê"]:::bc
RT --> AU
AU --> RG
RT --> RL
RT --> DC
DC --> SL
DC --> PS
DC -.-> BUS
RT --> INVMũi tên chỉ chiều khởi tạo lời gọi. Ingest là bên gọi Registry, Presence và Bus.
| Thành phần | Trách nhiệm | Vòng đời |
|---|---|---|
| Router và middleware | Định tuyến /agent/v1, header, X-Request-Id, bắt panic, Cache-Control: no-store, X-Content-Type-Options: nosniff | Theo tiến trình |
| Xác thực Bearer | Cắt token, băm, gọi Resolver, ánh xạ lỗi | Theo request |
| Giải nén và kiểm tra | Chọn decoder (identity, gzip, zstd, có pool), giới hạn kích thước, kiểm tra danh mục | Theo request |
| Bộ giới hạn series | Đếm series mới mỗi agent trong cửa sổ trượt, khóa FNV-64, dọn mỗi 5 phút | Theo tiến trình, trong bộ nhớ |
| Bộ giới hạn tốc độ | Token bucket 16 shard, tối đa 65.536 khóa mỗi shard, dọn khóa rảnh 10 phút, quét 1 phút | Theo tiến trình, trong bộ nhớ |
| Forwarder kiểm kê | Giữ báo cáo mới nhất mỗi agent, gửi lô 100, tick 2 giây | Theo tiến trình |
| Kết nối | Kiểu | Chi tiết |
|---|---|---|
| Ingest đến Resolver | Đồng bộ, trong tiến trình | Lookup(hash) trả AgentRecord hoặc lỗi |
| Ingest đến Bus | Bất đồng bộ (không chặn) | Publish(shard, batch); đầy hoặc đóng thì ErrFull rồi 503 |
| Ingest đến Access Hub | Đồng bộ | Chỉ enroll (20 giây) và forwarder kiểm kê |
| Ingest đến Presence | Đồng bộ | Touch sau publish thành công |
3.2 Module view
flowchart TB
classDef bc fill:#1f3a5f,stroke:#4a90d9,color:#fff;
classDef owned fill:#2d4a3e,stroke:#5fb37a,color:#fff;
APP["internal/app (wiring)"]:::bc
ING["internal/ingest"]:::bc
CAT["internal/catalog"]:::owned
RGS["internal/registry"]:::owned
PRE["internal/presence"]:::owned
BUSP["internal/bus"]:::owned
RLP["internal/ratelimit"]:::owned
MET["internal/metrics"]:::owned
HX["internal/httpx"]:::owned
HC["internal/hubclient"]:::owned
APP --> ING
ING --> CAT
ING --> RGS
ING --> PRE
ING --> BUSP
ING --> RLP
ING --> MET
ING --> HX
ING --> HCTệp trong internal/ingest: http.go (router, xác thực, header), handlers.go (ping, enroll, metrics, config, renew), decode.go (giải nén), series.go (giới hạn series), agentconfig.go (cấu hình mặc định, ETag), inventory.go (khử trùng, forwarder), ingest.go (tùy chọn và khởi tạo).
4. Domain Model
classDiagram
class MetricsBatch {
<<wire>>
seq
sent_at_ms
series
stats
}
class Series {
<<wire>>
name
labels
points
}
class Point {
<<wire>>
ts_ms
value
}
class AgentStats {
<<wire>>
cpu_percent
rss_bytes
}
class SampleBatch {
<<internal>>
company_id
server_id
agent_id
received_ms
series
}
class AgentRecord {
<<registry>>
agent_id
company_id
server_id
state
}
MetricsBatch "1" --> "*" Series
Series "1" --> "*" Point
MetricsBatch "1" --> "0..1" AgentStats
AgentRecord "1" --> "*" SampleBatch : gắn danh tính
MetricsBatch ..> SampleBatch : chuẩn hóaCác thông điệp wire khớp docs/03-protocol.md. Tệp proto/accesshub/agent/v1/agent.proto là bản nháp (TD-009 của L2). SampleBatch là kiểu nội bộ của internal/bus; định dạng dây Redis: c (company), s (server), a (agent), r (received), d[n,l,p] (tên, nhãn, điểm).
5. API Contract
5.1 Operations
Tiền tố /agent/v1. Mọi điểm cuối trừ ping yêu cầu X-AH-Proto: 1 và X-AH-Agent-Version.
| Method | Path | Xác thực | Mô tả | Thành công |
|---|---|---|---|---|
| GET | /ping | Không | Kiểm tra sống và lệch đồng hồ | 200 |
| POST | /enroll | Không (License trong body) | Đổi License lấy agent token | 200 |
| POST | /metrics | Bearer | Gửi lô số liệu | 200 kèm ack |
| GET | /config | Bearer | Lấy cấu hình, hỗ trợ ETag | 200 hoặc 304 |
| POST | /inventory | Bearer | Gửi báo cáo kiểm kê | 202 |
| POST | /credentials/renew | Bearer | Cấp lại token (chưa làm) | 503 unavailable, Retry-After: 300 |
Content-Type: application/x-protobuf. application/json chỉ được chấp nhận khi ingest.accept_json bật. Content-Encoding: identity, gzip hoặc zstd.
5.2 Request và Response schema
Ký hiệu: ! bắt buộc, ? tùy chọn.
POST /metrics
Request (MetricsBatch):
seq!: uint64
sent_at_ms!: int64
series![]: { name!: string, labels?: map<string,string>, points![]: { ts_ms!: int64, value!: double } }
stats?: AgentStats
Response 200 (MetricsAck):
server_time_ms!: int64
ack_seq!: uint64
config_etag!: string
accepted_points!: uint32
dropped_points!: uint32
warnings?: string[] # tối đa 10 mục, mỗi mục tối đa 160 ký tự
Errors: 400 bad_request, 401 unauthorized, 403 agent_revoked, 413 too_large,
426 upgrade_required, 429 rate_limited, 503 unavailablePOST /enroll
Request (EnrollRequest):
license!: string (tối đa 256)
hostname!: string (tối đa 253)
machine_id!: string (tối đa 128, băm SHA-256)
ip_addresses?: string[] (tối đa 32)
os?: { family, name, version, kernel, arch } (mỗi trường tối đa 128)
agent_version?: string
Response 200 (EnrollResponse):
agent_id!, agent_token!, collector_url!, config!, server_time_ms!
Errors: 400 bad_request, 401 unauthorized, 409 already_enrolled | token_used,
422 binding_failed, 429 rate_limited (Retry-After 60), 503 unavailableGET /config: If-None-Match? gửi ETag. Response 200 (AgentConfig) hoặc 304. ETag là 8 byte đầu của SHA-256 trên bản mã hóa protobuf xác định, dạng hex.
POST /inventory (InventoryFacts): 202, không nội dung. Trùng nội dung trong 1 giờ vẫn trả 202 nhưng không chuyển tiếp lại.
Định dạng lỗi (mọi điểm cuối): đối tượng ErrorResponse { code, message, retry_after_seconds }, kèm header Retry-After khi có.
5.3 Error codes
| Mã | HTTP | Ý nghĩa | Ghi chú |
|---|---|---|---|
bad_request | 400 | Header, body, trường sai | X-AH-Proto thiếu cũng 400 |
unauthorized | 401 | Token không tìm thấy, hết hạn, ở trạng thái pending | Agent phải enroll lại |
agent_revoked | 403 | Token thuộc agent đã thu hồi | Agent dừng gửi |
already_enrolled | 409 | Máy đã enroll | Từ Access Hub |
token_used | 409 | License đã dùng | Từ Access Hub |
too_large | 413 | Vượt max_body_bytes, max_decoded_bytes, max_points hoặc max_series | |
binding_failed | 422 | Ràng buộc máy chủ thất bại | Từ Access Hub |
upgrade_required | 426 | X-AH-Proto khác "1" | |
rate_limited | 429 | Vượt tốc độ | Kèm Retry-After |
server_error | 500, 503 | Lỗi nội bộ hoặc lỗi không phân loại từ registry | Handler panic thành 500 |
unavailable | 503 | Bus đầy hoặc đóng, Access Hub tạm mất, renew stub | Retry-After 5, 30 hoặc 300 giây |
not_found | 404 | Đường dẫn không có | Định dạng JSON |
method_not_allowed | 405 | Sai method | Định dạng JSON |
Kiểm thử: TestRoutingErrorsAreJSON.
5.4 Versioning
Phiên bản giao thức nằm ở header X-AH-Proto. Hiện chỉ có phiên bản 1 (proto_min 1, proto_max 1 từ ping). Mục tiêu N và N-1 (NFR-14) chưa có cơ chế thực tế vì chưa có N+1. Quy tắc đề xuất: thêm trường protobuf là tương thích ngược, đổi nghĩa hoặc xóa trường thì tăng X-AH-Proto. Cấu hình mẫu buf breaking chưa có (TD-012 của L2).
5.5 Authz
| Điểm cuối | Cơ chế | Vai |
|---|---|---|
ping, enroll | Không Bearer. enroll bị giới hạn theo IP | Ẩn danh |
metrics, config, inventory, credentials/renew | Bearer token agent | Agent, danh tính từ registry |
| Chủ thể | ping | enroll | metrics | config | inventory | renew |
|---|---|---|---|---|---|---|
| Ẩn danh | Có | Có (giới hạn IP) | Không | Không | Không | Không |
| Agent hợp lệ | Có | Có | Có | Có | Có | Stub |
| Agent đã thu hồi | Có | Có | 403 | 403 | 403 | 403 |
Ghi chú: agent đã thu hồi vẫn gọi được ping và enroll (enroll bị Access Hub từ chối nếu token không hợp lệ).
6. Physical Data Schema
6.1 Mapping
Ingest không có lược đồ dữ liệu bền vững. Trạng thái trong bộ nhớ của tiến trình:
| Cấu trúc | Khóa | Giá trị | Giới hạn |
|---|---|---|---|
| Bộ giới hạn series | agent id, băm FNV-64 của khóa series | Thời điểm thấy | max_series (500) trong cửa sổ series_window (1 giờ), dọn mỗi 5 phút |
| Bộ giới hạn tốc độ | (endpoint, agent hoặc IP) | Token bucket | 16 shard, 65.536 khóa mỗi shard, dọn khóa rảnh 10 phút |
| Bộ khử trùng kiểm kê | agent id | SHA-256 của protobuf xác định, thời điểm | 1 giờ |
| Hàng chờ kiểm kê | agent id | Báo cáo mới nhất | Chỉ giữ một mỗi agent |
Toàn bộ mất khi tiến trình khởi động lại. Hệ quả vô hại: series limiter cho phép lại series, bucket đầy lại, kiểm kê có thể chuyển tiếp trùng (Access Hub xử lý idempotent, xem ADR 0011).
6.2 Phân loại và lưu giữ
| Dữ liệu | Phân loại | Lưu giữ |
|---|---|---|
Băm token (trong bộ nhớ, từ Authorization) | Nhạy cảm | Chỉ trong request |
| Báo cáo kiểm kê chờ gửi | Nội bộ (hostname, IP, cấu hình) | Đến khi gửi hoặc bị thay bằng báo cáo mới hơn |
| Bộ đếm series, bucket | Không nhạy cảm | Theo tiến trình |
7. Algorithms
7.1 Sequences (đường thành công)
sequenceDiagram
participant AG as Agent (ext)
participant RT as Router
participant AU as Xác thực
participant DC as Kiểm tra số liệu
participant BS as Bus
participant PS as Presence
AG->>RT: POST /agent/v1/metrics
RT->>RT: kiểm tra header, tốc độ theo agent
RT->>AU: băm token, tra registry
AU-->>RT: agent, công ty, máy chủ
RT->>DC: giải nén, kiểm tra danh mục, nhãn, cửa sổ, series
DC--)BS: publish theo shard của server_id
RT->>PS: Touch(agent)
RT-->>AG: 200 ack (ack_seq, config_etag, accepted, dropped)7.2 Sequences (đường lỗi)
sequenceDiagram
participant AG as Agent (ext)
participant RT as Router
participant AU as Xác thực
participant RG as Resolver
participant HB as Access Hub (ext)
participant BS as Bus
AG->>RT: POST /agent/v1/metrics
RT->>AU: xác thực
AU->>RG: tra băm token
alt token thu hồi
RG-->>AU: ErrRevoked
AU-->>AG: 403 agent_revoked
else Access Hub tạm mất
RG->>HB: LookupAgent
HB--)RG: lỗi hoặc quá tải
RG-->>AU: ErrHubUnavailable
AU-->>AG: 503 Retry-After 30
else bus đầy
RT--)BS: publish
BS--)RT: ErrFull
RT-->>AG: 503 Retry-After 5
end7.3 State machines
Không áp dụng: Ingest không giữ trạng thái bền. Trạng thái agent (pending, active, revoked) thuộc L3 Registry.
7.4 Core algorithms
| Vấn đề | Giải pháp | Trade-off |
|---|---|---|
| Bom giải nén | Đặt trần giải nén max_decoded_bytes (8 MiB); decoder gzip và zstd có pool | Chi phí CPU giải nén; tăng trần làm tăng bộ nhớ mỗi request |
| Agent giả nhãn tenant | Bỏ nhãn dành riêng, danh tính lấy từ registry, tenant do worker gắn cuối cùng (internal/tsdb/vm.go) | Không cho agent tự đặt nhãn agent_id |
| Bùng nổ cardinality | Danh mục 61 chỉ số, danh sách nhãn cho phép theo chỉ số, giá trị nhãn cắt 128 byte, giới hạn 500 series mỗi agent theo cửa sổ 1 giờ | Series hợp lệ vượt giới hạn bị bỏ (đếm ahc_ingest_series_limit_dropped_total) |
| Mẫu sai thời gian | Bỏ điểm cũ hơn max_point_age (24 giờ), tương lai hơn max_point_future (5 phút), NaN, Inf | Agent lệch đồng hồ lớn mất mẫu; clock_skew_seconds trong AgentStats để chẩn đoán |
| Tra Access Hub khi miss | Cache âm 60 giây, giới hạn toàn cục 50/s, single-flight (L3 Registry) | Token mới có thể chờ tối đa vài giây |
| Lấy IP client sau proxy | Lấy mục bên phải nhất của X-Forwarded-For không thuộc trusted_proxies, chỉ khi peer tin cậy | Cần cấu hình trusted_proxies đúng (dev: 127.0.0.0/8) |
| Kiểm kê trùng | Băm SHA-256 của protobuf xác định, bỏ trong 1 giờ; chỉ giữ báo cáo mới nhất | Có thể mất báo cáo trung gian (chấp nhận, kiểm kê là trạng thái mới nhất) |
| Cấu hình đổi | ETag là 8 byte đầu SHA-256, agent hỏi If-None-Match | Va chạm 64 bit không đáng kể |
| Enroll spam | Giới hạn 6/phút mỗi IP, burst 10 | IP chung sau NAT có thể bị chặn nhầm |
8. Error Handling
8.1 Bảng lỗi theo bước
| Bước lỗi | Nguyên nhân | Cơ chế xử lý | Trạng thái cuối |
|---|---|---|---|
| Header | X-AH-Proto thiếu, sai; version sai định dạng | 400 hoặc 426 | Không xử lý tiếp |
| Xác thực | Token sai, hết hạn, pending | 401 | Agent phải enroll lại |
| Xác thực | Token thu hồi | 403 agent_revoked | Agent dừng |
| Xác thực | Access Hub tạm mất, bị giới hạn, lỗi khác | 503 Retry-After: 30 (lỗi không phân loại: 503 server_error) | Agent thử lại |
| Giới hạn tốc độ | Vượt bucket | 429 Retry-After | Agent chờ |
| Giải nén | Encoding không hợp lệ, body sai, vượt trần | 400 hoặc 413 | Bỏ request |
| Kiểm tra | Tên chỉ số, nhãn ngoài danh mục; điểm ngoài cửa sổ; series vượt giới hạn | Bỏ mẫu, đếm, thêm warnings | Ack vẫn 200 với dropped_points |
| Publish | Bus đầy hoặc đóng | 503 Retry-After: 5, ahc_ingest_publish_failures_total | Agent giữ WAL |
| Presence | Touch lỗi | Ghi log, không làm hỏng ack (đề xuất: xác nhận từ mã) | Lần thấy cuối trễ |
| Panic | Lỗi lập trình | Bắt, trả 500 | Tiến trình tiếp tục |
8.2 Fail-fast
- Cấu hình sai (giới hạn,
trusted_proxieskhông phải CIDR) làm tiến trình không khởi động (thoát mã 3). XemTestNewValidatesOptions. - Ingest role không có registry hoặc bus hợp lệ: từ chối khi khởi động (
internal/app).
8.3 Race conditions
| Tình huống | Xử lý |
|---|---|
| Hai request cùng token miss cùng lúc | Single-flight ở Resolver, một lần gọi Access Hub (TestConcurrentMissesCollapse) |
| Agent gửi hai lô trùng | Bus at-least-once, mẫu idempotent theo timestamp nên ghi lại vô hại |
OnAgentUp chạy song song hai node | Chỉ node thắng SADD ở presence sở hữu chuyển trạng thái (L3 Alerting) |
9. Degradation
Nguyên tắc: ingest thà từ chối có kiểm soát (503 kèm Retry-After) còn hơn nhận rồi mất; không bao giờ trả 401 khi nguyên nhân là hạ tầng.
9.1 Dependency matrix
| Dependency lỗi | Hành vi | Kiểu suy thoái | Hệ quả |
|---|---|---|---|
| Access Hub | Token đã cache vẫn dùng được. Miss thì 503 Retry-After: 30. Enroll trả 503 | Giảm chức năng | Agent mới không enroll được, agent cũ vẫn gửi |
| Redis (registry) | Lỗi đọc thành ErrNotFound, hỏi Access Hub có giới hạn; cache nút còn cache_ttl 15 giây | Giảm chức năng | Tăng tải lên Access Hub |
| Redis (bus) | Publish lỗi thì 503 | Từ chối | Agent đệm WAL |
| Bus đầy | 503 Retry-After: 5 | Từ chối | Agent đệm WAL |
| Presence | Đề xuất xác nhận: lỗi ghi presence chỉ ghi log | Giảm chức năng | Có thể báo down nhầm khi Redis mất lâu |
| TSDB | Không ảnh hưởng ingest trực tiếp; đệm bus đầy dần | Gián tiếp | 503 khi bus đầy |
9.2 Backup và recovery
Ingest không có dữ liệu cần sao lưu. RTO khởi động lại tiến trình: chưa đo, đề xuất vài giây (nhờ không trạng thái). RPO: mẫu đã ack đã nằm trong bus (bền theo AOF Redis, xem L3 Bus); mẫu chưa ack agent còn giữ trong WAL.
10. Concurrency
10.1 Ranh giới giao dịch
Không có giao dịch. Mỗi request độc lập. Thứ tự: publish bus rồi Presence.Touch rồi trả ack. Ack đã trả nghĩa là mẫu đã vào bus.
10.2 Cơ chế
| Cơ chế | Mô tả | Mối nguy |
|---|---|---|
| Token bucket theo khóa, 16 shard | Mỗi shard một khóa (mutex) | Tranh chấp khóa thấp nhờ 16 shard |
| Bộ giới hạn series | Khóa mutex theo agent hoặc shard, dọn 5 phút | Dọn chậm khi nhiều agent |
| Pool decoder gzip, zstd | sync.Pool | Rò bộ nhớ nếu không trả lại decoder (đã có kiểm thử giới hạn) |
| Single-flight tra Access Hub | Gộp lời gọi trùng | Một lỗi làm hỏng mọi người chờ, chấp nhận |
| Forwarder kiểm kê | Một goroutine, map có khóa | Mất báo cáo chờ khi chết (chấp nhận) |
11. Security
11.1 Ba lớp
| Lớp | Biện pháp |
|---|---|
| Truyền thông | TLS 1.2 trở lên khi Collector tự kết thúc TLS. Dev: TLS ở nginx, ingest lắng nghe HTTP nội bộ. trusted_proxies chỉ tin X-Forwarded-For từ proxy khai báo |
| Mã | Danh mục và nhãn cho phép, giới hạn kích thước, bỏ nhãn dành riêng, làm sạch giá trị nhãn UTF-8 và cắt 128 byte, nosniff, no-store, bắt panic, X-Request-Id chỉ nhận mẫu ^[A-Za-z0-9._-]{1,64}$ |
| Dữ liệu | Token chỉ ở dạng băm SHA-256; không log token; danh tính tenant do registry cấp |
11.2 Pipeline phân quyền năm bước
- Kiểm tra header giao thức và phiên bản agent.
- Trích Bearer (tối đa 256 ký tự) và băm SHA-256.
Resolver.Lookup(registry, rồi cache âm, rồi giới hạn, rồi Access Hub).- Kiểm tra trạng thái:
activevà còn hạn; ngược lại 401 hoặc 403. - Gắn
company_id,server_id,agent_idtừ bản ghi registry vào lô; nội dung agent không thể ghi đè.
11.3 Chỉ mục neo (nguyên tắc L2 đến biện pháp)
| Nguyên tắc L2 | Biện pháp nội bộ |
|---|---|
| P1 danh tính từ registry | Bước 5 của pipeline, TestReservedLabelsAreStripped |
| P2 nhãn tenant gắn sau cùng | internal/tsdb/vm.go (xem L3 Bus, Worker, TSDB) |
| P5 từ chối có kiểm soát | 503 kèm Retry-After |
| 9.4 kiểm tra đầu vào | Mục 7.4 |
12. Configuration
12.1 Tunables
Khóa YAML dưới ingest:. Biến môi trường chỉ có cho vài khóa (AHC_INGEST_LISTEN, AHC_INGEST_TLS_CERT_FILE, AHC_INGEST_TLS_KEY_FILE, AHC_INGEST_PUBLIC_URL); phần còn lại chỉ YAML.
| Tham số | Default | Ý nghĩa và tác động | Mục liên quan |
|---|---|---|---|
ingest.listen | :8443 | Địa chỉ lắng nghe | 3.1 |
ingest.public_url | rỗng | collector_url trả cho agent khi enroll | 5.2 |
ingest.tls_cert_file, tls_key_file | rỗng | Đặt cùng nhau, bật TLS tự quản | 11.1 |
ingest.trusted_proxies | rỗng | Danh sách CIDR proxy tin cậy | 7.4 |
ingest.accept_json | false | Cho phép application/json | 5.1 |
ingest.limits.max_body_bytes | 1 MiB | Trần body nén; vượt thì 413 | 7.4 |
ingest.limits.max_decoded_bytes | 8 MiB | Trần sau giải nén | 7.4 |
ingest.limits.max_points | 20.000 | Số điểm mỗi request | 7.4 |
ingest.limits.max_series | 500 | Series mỗi agent trong cửa sổ | 7.4 |
ingest.limits.series_window | 1 giờ | Cửa sổ trượt của series | 7.4 |
ingest.limits.max_point_age | 24 giờ | Điểm cũ hơn bị bỏ | 7.4 |
ingest.limits.max_point_future | 5 phút | Điểm tương lai hơn bị bỏ | 7.4 |
ingest.limits.metrics_per_minute, metrics_burst | 4, 10 | Giới hạn metrics mỗi agent | 7.4 |
ingest.limits.config_per_minute, config_burst | 2, 2 | Giới hạn config mỗi agent | 7.4 |
ingest.limits.inventory_per_minute, inventory_burst | 2, 2 | Giới hạn inventory mỗi agent | 7.4 |
ingest.limits.enroll_per_minute, enroll_burst | 6, 10 | Giới hạn enroll mỗi IP client | 7.4 |
hub.lookup_per_second | 50 | Trần tra Access Hub toàn nút | 7.4 |
Timeout máy chủ HTTP cố định trong mã: đọc header 10 giây, đọc 30 giây, ghi 30 giây, rảnh 120 giây, header tối đa 64 KiB.
Tên khóa lấy từ internal/config/config.go.
12.2 Feature flags
Không có feature flag. Cờ tương đương: ingest.accept_json (mặc định tắt), TLS tự quản (bật khi đặt cả cert và key).
13. Telemetry
13.1 Metrics
| Tên | Kiểu | Nhãn | Ngữ nghĩa |
|---|---|---|---|
ahc_ingest_requests_total | counter | endpoint, code | Số request |
ahc_ingest_request_duration_seconds | histogram | endpoint | Thời gian xử lý |
ahc_ingest_auth_failures_total | counter | reason | Xác thực thất bại |
ahc_ingest_rate_limited_total | counter | endpoint | Bị giới hạn tốc độ |
ahc_ingest_points_accepted_total | counter | Điểm nhận | |
ahc_ingest_points_dropped_total | counter | reason | Điểm bỏ |
ahc_ingest_series_limit_dropped_total | counter | Bỏ do vượt series | |
ahc_ingest_reserved_labels_total | counter | Nhãn dành riêng bị bỏ | |
ahc_ingest_publish_failures_total | counter | Publish bus lỗi | |
ahc_ingest_enroll_total | counter | result | Kết quả enroll |
ahc_ingest_bus_depth | gauge | Độ sâu bus lấy mẫu | |
ahc_ingest_inventory_pending | gauge | Báo cáo kiểm kê chờ | |
ahc_inventory_forwarded_total | counter | result | Đã chuyển tiếp |
ahc_inventory_dropped_total | counter | reason | Không chuyển tiếp |
Nhãn lấy từ khai báo trong internal/ingest/ingest.go. ahc_inventory_forwarded_total có nhãn result, ahc_inventory_dropped_total có nhãn reason. Cổng ops còn phơi bày ahc_build_info{version,commit}, ahc_uptime_seconds, ahc_goroutines. Tài liệu docs/10 dùng tên khác (ví dụ ahc_ingest_duration_seconds, ahc_samples_accepted_total, ahc_auth_failures_total): mã thắng.
13.2 Log schema
JSON một dòng (khi log.format: json), trường tối thiểu: time, level, msg, request_id, collector_id. Không có Authorization. Ví dụ:
{"time":"2026-09-30T00:00:00Z","level":"WARN","msg":"ingest publish failed","request_id":"abc123","collector_id":"dev-ingest-1"}13.3 Cảnh báo đến runbook
Bộ luật cảnh báo chưa có trong repo (L2 mục 13). Đề xuất: cảnh báo khi ahc_ingest_publish_failures_total tăng, ahc_ingest_auth_failures_total tăng đột biến, ahc_ingest_bus_depth cao kéo dài. Runbook: docs/09 mục 6.
13.4 Probes
| Probe | Điểm cuối | Ý nghĩa |
|---|---|---|
| Startup | Tiến trình nghe cổng ops | Cấu hình hợp lệ, ExecStartPre chạy collector -check |
| Liveness | /healthz (cổng ops) | Tiến trình sống |
| Readiness | /readyz (cổng ops), kiểm tra "redis" và "tsdb" | 503 khi đang tắt êm hoặc phụ thuộc lỗi |
13.5 Trace propagation
Chỉ X-Request-Id (sinh nếu thiếu, nhận nếu khớp mẫu). Chưa có trace phân tán.
14. Test Plan
| Loại | Kiểm thử | Vị trí |
|---|---|---|
| Đơn vị | TestPingNeedsNoAuthOrHeaders, TestHeaderChecks, TestAuth, TestRevokedAgentGets403, TestHubDownGives503NotUnauthorized | internal/ingest/ingest_test.go |
| Đơn vị | TestMetricsAcceptedAndTenantIdentityFromRegistry, TestEmptyBatchIsAHeartbeat, TestReservedLabelsAreStripped, TestSeriesValidation, TestPointTimeWindowAndValues, TestSeriesLimitPerAgent, TestForgetAgentReleasesSeriesBudget | như trên |
| Đơn vị | TestBodyLimits, TestCompressedBodies, TestJSONOnlyWhenEnabled, TestMetricsRateLimit, TestBusFullGives503WithRetryAfter, TestAgentUpCallback | như trên |
| Đơn vị | TestConfigETag, TestConfigRateLimit, TestInventoryForwardedAndDeduped, TestRenewIsAStub, TestEnrollProxy, TestEnrollErrors, TestEnrollRateLimitPerIP, TestEnrollWithoutHub, TestClientIPHonoursTrustedProxiesOnly, TestRoutingErrorsAreJSON, TestNewValidatesOptions | như trên |
| Hai tenant | TestTwoTenantsNeverMix | internal/ingest/ingest_test.go |
| Tích hợp | TestTwoTenantIsolationThroughIngestAndAdminAPI, TestIngestToTSDBPipelineKeepsTenantsApart, TestIngestIsServedOnTheIngestListener | internal/app |
| Chưa có | Fuzz giải mã protobuf, tải (agentsim), golden protobuf, buf breaking | Kế hoạch ở docs/12 |
Lệnh chạy có thể dùng (không đụng Redis 6380 hay VictoriaMetrics thật): go test ./internal/ingest/.... Bản nháp này không chạy kiểm thử.
15. Implementation Sequence
15.1 Milestone matrix
| Milestone | Nội dung | Phụ thuộc | Đóng góp nghiệm thu |
|---|---|---|---|
| COL-1 | Khung dự án Go, cấu hình, log, /healthz /readyz /metrics, tắt êm | Không | NFR-15 |
| COL-3 | Ingest: xác thực token, registry (Redis và Access Hub), giới hạn, lần thấy cuối | COL-1 | AC-ING-01, 02, 03, 04, 06, 08 |
| COL-4 | Enroll proxy, đồng bộ registry, thu hồi (renew là stub 503) | COL-3 | AC-ING-03 |
| COL-5 | Bus, worker, ghi TSDB | COL-3 | AC-ING-05 |
| COL-7 | API admin | COL-4 | Không thuộc Ingest |
| COL-R | Tách tiến trình qua Redis | COL-3, COL-5 | AC-ING-07 |
| COL-8 | Luật ngưỡng (worker). ConfigFor theo máy vẫn chưa làm | COL-R | Không thuộc Ingest |
| Renew (chưa làm) | credentials/renew thật (cần endpoint Access Hub) | COL-4 | FR-ING-12 |
Ánh xạ COL-x theo docs/11-roadmap.md (kiểm kê và cấu hình mặc định thuộc phạm vi COL-3 và COL-4 theo mã, roadmap không có dòng riêng).
15.2 Dependency flowchart
flowchart LR
C1["COL-1 khung dự án"] --> C3["COL-3 ingest, registry"]
C3 --> C4["COL-4 enroll, đồng bộ"]
C3 --> C5["COL-5 bus, worker, TSDB"]
C4 --> C7["COL-7 admin"]
C3 --> CR["COL-R tách vai trò"]
C5 --> CR
CR --> C8["COL-8 luật (xong, ConfigFor chưa)"]
C4 --> RN["renew (chưa làm)"]Appendix A. Open Questions
| # | Câu hỏi | Hành vi tạm thời | Owner | Mã theo dõi |
|---|---|---|---|---|
| 1 | Presence Touch lỗi thì có nên trả lỗi hay chỉ ghi log? | Đề xuất ghi log (cần xác nhận trong mã) | Chưa chỉ định | OQ-ING-1 |
| 2 | ConfigFor theo máy lấy từ đâu (chưa thuộc COL-8 phần luật)? | Trả mặc định | Chưa chỉ định | OQ-ING-2 |
| 3 | Khi có protocol phiên bản 2, cơ chế N và N-1 thế nào? | Chỉ hỗ trợ 1 | Chưa chỉ định | OQ-ING-3 |
| 4 | Có nên giới hạn tốc độ dùng chung qua Redis? | Chỉ theo từng node | Chưa chỉ định | OQ-ING-4 |
| 5 | Bộ luật cảnh báo cho các metric ahc_ingest_* | Chưa có | Chưa chỉ định | OQ-ING-5 |
Appendix B. ADR nội bộ
| Mã ADR | Quyết định | Trạng thái | Driver |
|---|---|---|---|
| ADR 0002 | HTTPS + protobuf, không gRPC | Chấp nhận | Qua proxy |
| ADR 0007 | Token mờ, tra registry, tra ngược Access Hub | Chấp nhận | Thu hồi nhanh |
| ADR 0011 | Kiểm kê không ghi vào Server | Chấp nhận | An toàn dữ liệu |
| ADR 0012 (đề xuất) | Bus Redis Streams | Đề xuất | COL-R |
| ADR nội bộ (đề xuất) | Ack chỉ sau khi publish bus thành công | Đề xuất, đang là hành vi mã | Không mất mẫu đã ack |
Appendix C. Section Profile
| Phân mục | Hồ sơ | Trạng thái điền | Giải trình |
|---|---|---|---|
| 0 đến 3 | Bắt buộc | Đã điền | |
| 4 Domain model | Bắt buộc | Đã điền | |
| 5 API contract | Bắt buộc | Đã điền | |
| 6 Physical data | Tùy chọn | Điền tối thiểu | Không có lưu trữ bền |
| 7.1, 7.2 | Bắt buộc | Đã điền | |
| 7.3 State machine | Tùy chọn | Không áp dụng | Không giữ trạng thái bền |
| 7.4 | Bắt buộc | Đã điền | |
| 8, 9, 10 | Bắt buộc | Đã điền | |
| 11 | Bắt buộc | Đã điền | |
| 12 | Bắt buộc | Đã điền | Tên khóa lấy từ internal/config/config.go |
| 13 | Bắt buộc | Đã điền | Nhãn lấy từ khai báo metric trong mã |
| 14 | Bắt buộc | Đã điền | Kiểm thử chưa chạy trong bản nháp |
| 15 | Bắt buộc | Đã điền | Tên COL-x theo docs/11-roadmap.md |