Skip to content

Commit d5ae725

Browse files
docs(platform): add telemetry data verification guide (#150)
Signed-off-by: Arnob kumar saha <arnob@appscode.com> Signed-off-by: Rokibul Hasan <mdrokibulhasan@appscode.com> Co-authored-by: Rokibul Hasan <mdrokibulhasan@appscode.com>
1 parent 033402b commit d5ae725

1 file changed

Lines changed: 307 additions & 0 deletions

File tree

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
---
2+
layout: docs
3+
menu:
4+
docsplatform_{{.version}}:
5+
identifier: cluster-management-telemetry-data-check
6+
name: Verifying Telemetry Data
7+
parent: cluster-management
8+
weight: 100
9+
menu_name: docsplatform_{{.version}}
10+
section_menu_id: guides
11+
---
12+
13+
# Verifying Telemetry Data (Logs & Metrics)
14+
15+
This guide shows how to confirm your logs and metrics are actually being stored — not just
16+
collected — in the monitoring stack: **ClickHouse** for logs, **Thanos + S3** for metrics.
17+
See [OpenTelemetry Monitoring](otel-monitoring.md) for how the stack is set up.
18+
19+
## Basics
20+
21+
- Logs and metrics are stored in the **same S3 bucket**, under different prefixes:
22+
- Logs → `<bucket>/logs/`
23+
- Metrics → `<bucket>/metrics/`
24+
- Bucket, endpoint, and prefixes are defined in your `TelemetryStack` resource:
25+
```bash
26+
kubectl get telemetrystack -n monitoring -o yaml
27+
```
28+
Look under `spec.logs.clickhouse.s3` and `spec.metrics.thanos.s3`.
29+
- ClickHouse database name is your tenant ID, not a fixed name like `otel` or `default`.
30+
Find it with `SHOW DATABASES` (see below) — the table inside is `otel_logs`.
31+
- All checks below run from inside the cluster via `kubectl exec`, so no port-forwarding
32+
or extra credentials are required except for the optional S3 listing step.
33+
34+
---
35+
36+
## 1. Check logs are landing in ClickHouse
37+
38+
Find the ClickHouse pod:
39+
40+
```bash
41+
kubectl get pods -n monitoring -l app.kubernetes.io/name=clickhouse
42+
```
43+
44+
List databases and confirm the `otel_logs` table exists:
45+
46+
```bash
47+
kubectl exec -n monitoring <clickhouse-pod> -- clickhouse-client --query "SHOW DATABASES"
48+
kubectl exec -n monitoring <clickhouse-pod> -- clickhouse-client --query "SHOW TABLES FROM <your-tenant-db>"
49+
```
50+
51+
Check row count and freshness:
52+
53+
```bash
54+
kubectl exec -n monitoring <clickhouse-pod> -- clickhouse-client --query \
55+
"SELECT count(), min(Timestamp), max(Timestamp), now() FROM <your-tenant-db>.otel_logs"
56+
57+
kubectl exec -n monitoring <clickhouse-pod> -- clickhouse-client --query \
58+
"SELECT count() FROM <your-tenant-db>.otel_logs WHERE Timestamp > now() - INTERVAL 5 MINUTE"
59+
```
60+
61+
**Healthy result:** `max(Timestamp)` is close to `now()`, and the 5-minute count is greater
62+
than zero. If both queries return recent data, logs are flowing end-to-end.
63+
64+
---
65+
66+
## 2. Check metrics are landing in Thanos / S3
67+
68+
Find the Thanos query pod:
69+
70+
```bash
71+
kubectl get pods -n monitoring -l app.kubernetes.io/name=thanos-query
72+
```
73+
74+
Ask it which StoreAPIs it's talking to and what time range each one covers:
75+
76+
```bash
77+
kubectl exec -n monitoring <thanos-query-pod> -- \
78+
wget -qO- --header="THANOS-TENANT: <your-tenant>" "http://localhost:9090/api/v1/stores"
79+
```
80+
81+
Look at the `minTime`/`maxTime` fields (Unix epoch **milliseconds**) for each store:
82+
83+
```bash
84+
date -u -d @<maxTime-in-seconds> # drop the last 3 digits first to convert ms -> s
85+
```
86+
87+
**Healthy result:** `maxTime` should be within the last few minutes for the live/receive
88+
store, and within your compaction interval for the long-term S3-backed store.
89+
90+
For a second confirmation, check the ingester's own TSDB head timestamp:
91+
92+
```bash
93+
kubectl get pods -n monitoring -l app.kubernetes.io/name=thanos-receive-ingester
94+
kubectl exec -n monitoring <thanos-receive-ingester-pod> -- wget -qO- http://localhost:10902/metrics \
95+
| grep prometheus_tsdb_head_max_time
96+
```
97+
98+
If this timestamp is stale (not advancing over successive checks), metrics are not being
99+
ingested. Metrics are shipped from each connected cluster by the OpenTelemetry stack
100+
(`appscode-otel-stack`). See the [Troubleshooting](#troubleshooting-metrics-stale-but-logs-healthy)
101+
section below for how to trace that pipeline.
102+
103+
---
104+
105+
## 3. Check the S3 bucket directly (optional)
106+
107+
Read the bucket, endpoint, and credentials from the `TelemetryStack` spec first — the same
108+
values back both the ClickHouse and Thanos configs:
109+
110+
```bash
111+
kubectl get telemetrystack -n monitoring -o jsonpath='{.spec.metrics.thanos.s3}'
112+
```
113+
114+
Which client you use depends on where the bucket actually lives. The backend is
115+
S3-compatible in every case, so pick the matching variant below.
116+
117+
### a. MinIO (in-cluster or S3-compatible with a custom endpoint)
118+
119+
Use the MinIO client (`mc`) with the endpoint and access credentials from the spec.
120+
The `--insecure` flag is only needed when the endpoint serves a self-signed certificate:
121+
122+
```bash
123+
kubectl run mc-check -n monitoring --rm -it --restart=Never --image=minio/mc:latest -- sh
124+
mc alias set myminio <endpoint> <accessKey> <secretKey> --insecure
125+
mc ls myminio/<bucket>/logs/ --insecure --recursive | tail
126+
mc ls myminio/<bucket>/metrics/ --insecure --recursive | tail
127+
```
128+
129+
### b. Real AWS S3
130+
131+
When the bucket is a genuine AWS S3 bucket, there is no custom endpoint — just pass the
132+
AWS access key, secret, and region. Drop `--endpoint-url` entirely so the CLI talks to
133+
the regional AWS endpoint:
134+
135+
```bash
136+
kubectl run aws-check -n monitoring --rm -it --restart=Never --image=amazon/aws-cli:latest \
137+
--env AWS_ACCESS_KEY_ID=<accessKey> \
138+
--env AWS_SECRET_ACCESS_KEY=<secretKey> \
139+
--env AWS_DEFAULT_REGION=<region> \
140+
-- s3 ls s3://<bucket>/logs/ --recursive | tail
141+
# repeat for the metrics prefix
142+
kubectl run aws-check -n monitoring --rm -it --restart=Never --image=amazon/aws-cli:latest \
143+
--env AWS_ACCESS_KEY_ID=<accessKey> \
144+
--env AWS_SECRET_ACCESS_KEY=<secretKey> \
145+
--env AWS_DEFAULT_REGION=<region> \
146+
-- s3 ls s3://<bucket>/metrics/ --recursive | tail
147+
```
148+
149+
### c. s3grid (S3-compatible service with a custom endpoint)
150+
151+
s3grid is S3-compatible but reached through its own endpoint, so use the AWS CLI with
152+
`--endpoint-url` pointing at the s3grid endpoint from the spec:
153+
154+
```bash
155+
kubectl run s3grid-check -n monitoring --rm -it --restart=Never --image=amazon/aws-cli:latest \
156+
--env AWS_ACCESS_KEY_ID=<accessKey> \
157+
--env AWS_SECRET_ACCESS_KEY=<secretKey> \
158+
--env AWS_DEFAULT_REGION=<region> \
159+
-- s3 ls s3://<bucket>/logs/ --endpoint-url <s3grid-endpoint> --recursive | tail
160+
# repeat for the metrics prefix
161+
kubectl run s3grid-check -n monitoring --rm -it --restart=Never --image=amazon/aws-cli:latest \
162+
--env AWS_ACCESS_KEY_ID=<accessKey> \
163+
--env AWS_SECRET_ACCESS_KEY=<secretKey> \
164+
--env AWS_DEFAULT_REGION=<region> \
165+
-- s3 ls s3://<bucket>/metrics/ --endpoint-url <s3grid-endpoint> --recursive | tail
166+
```
167+
168+
> The same `mc`-based flow from (a) works against s3grid too — `mc alias set` accepts any
169+
> S3-compatible endpoint. Use whichever client you already have handy.
170+
171+
**Healthy result (any variant):** recent objects under both prefixes, with timestamps
172+
matching your retention/compaction schedule.
173+
174+
---
175+
176+
## Summary
177+
178+
| Pipeline | Where to look | Healthy signal |
179+
|---|---|---|
180+
| Logs | ClickHouse `<tenant-db>.otel_logs` | `max(Timestamp)` ≈ now, non-zero rows in last 5 min |
181+
| Metrics | Thanos Query `/api/v1/stores`, receive ingester `/metrics` | `maxTime`/head timestamp advancing, close to now |
182+
| S3 | `<bucket>/logs/` and `<bucket>/metrics/` prefixes | Recent objects under both prefixes |
183+
184+
---
185+
186+
## Troubleshooting: metrics stale but logs healthy
187+
188+
There is no Prometheus doing remote-write in this setup. Telemetry is shipped from each
189+
connected cluster by the **`appscode-otel-stack`** chart (built on `opentelemetry-kube-stack`),
190+
which runs two OpenTelemetry collectors in the `monitoring` namespace of the *connected*
191+
cluster:
192+
193+
- **Daemonset (agent) collector**: runs on every node. Its `prometheus` receiver scrapes
194+
targets assigned by the **Target Allocator** (which discovers `ServiceMonitor`/`PodMonitor`
195+
CRs), and its `filelog` receiver collects container logs. Everything is forwarded over
196+
OTLP to the gateway collector.
197+
- **Gateway collector** (deployment): receives OTLP and exports to the monitoring cluster
198+
through `prom-label-proxy`:
199+
- **Metrics**`prometheusremotewrite` exporter → `https://<ingest-endpoint>:10001/api/v1/receive`
200+
→ Thanos receive. The `THANOS-TENANT` header is injected per batch by the
201+
`headers_setter` extension.
202+
- **Logs**`clickhouse` exporter → the same `prom-label-proxy` endpoint → ClickHouse
203+
(`otel_logs` table).
204+
205+
The `<ingest-endpoint>` depends on how the platform is deployed:
206+
207+
- **DNS mode**: the exporters point at the monitoring cluster's domain name.
208+
- **IP mode**: the exporters use the fixed hostname `prom-label-proxy.monitoring.svc.cluster.local`,
209+
which is mapped to the monitoring cluster's IP via a `hostAliases` entry on the gateway
210+
collector pod (required for TLS certificate verification against a bare IP).
211+
212+
Confirm the endpoint actually in use from the gateway collector's config:
213+
214+
```bash
215+
kubectl get opentelemetrycollectors -n monitoring -o yaml | grep -B2 -A2 'endpoint:'
216+
```
217+
218+
Logs and metrics share the daemon → gateway OTLP hop and the mTLS client cert
219+
(`otel-client-cert`), but split into different exporters at the gateway. So if logs are
220+
healthy and metrics are stale, connectivity and certs are fine; the problem is specific
221+
to the metrics path: either **scraping** (daemon collector / Target Allocator) or the
222+
**remote-write export** (gateway → Thanos). Run these checks **on the connected cluster**:
223+
224+
### a. Collector pods are running
225+
226+
```bash
227+
kubectl get pods -n monitoring -l otel-collector-type=daemonset
228+
kubectl get pods -n monitoring -l otel-collector-type=deployment
229+
```
230+
231+
The daemonset collector must have one Running pod per node; a node without an agent pod
232+
silently drops that node's scrape targets (allocation is per-node).
233+
234+
### b. Gateway is exporting metrics without errors
235+
236+
Check the gateway collector's own telemetry for sent vs. failed metric points. The
237+
collector image is minimal (no shell or `wget` inside), so `kubectl exec` will not work;
238+
port-forward its internal telemetry port (8888) and query it from your machine instead:
239+
240+
```bash
241+
kubectl port-forward -n monitoring <gateway-collector-pod> 8888:8888 &
242+
sleep 2 # give the port-forward a moment to establish
243+
curl -s http://localhost:8888/metrics \
244+
| grep -E 'otelcol_exporter_(sent|send_failed|enqueue_failed).*metric_points'
245+
```
246+
247+
These counters are cumulative since the pod started, so a single reading proves nothing:
248+
a large `send_failed` value may just be leftover from a past incident (for example a
249+
monitoring-cluster restart) while everything is healthy now. Run the `curl` again after
250+
30 seconds or so and compare the two readings; only the counters that are still
251+
*advancing* matter. There are three possible outcomes:
252+
253+
1. **`sent_metric_points{exporter="prometheusremotewrite"}` is increasing and
254+
`send_failed`/`enqueue_failed` stay flat.** The gateway is exporting successfully, so
255+
this cluster's pipeline is healthy. The problem is downstream (prom-label-proxy or
256+
Thanos receive on the monitoring cluster) or a tenant mismatch; continue with step (d).
257+
2. **`send_failed_metric_points` is climbing.** Exports are failing. Read the gateway logs
258+
for the actual `prometheusremotewrite` error (HTTP status codes, TLS failures) against
259+
the `prom-label-proxy` endpoint:
260+
261+
```bash
262+
kubectl logs -n monitoring <gateway-collector-pod> --tail=100 | grep -iE "error|prometheusremotewrite"
263+
```
264+
265+
TLS or certificate errors point at the `otel-client-cert` secret (check it exists and
266+
is not expired). Connection refused or timeout points at a wrong or unreachable ingest
267+
endpoint. HTTP 4xx/5xx responses point at the prom-label-proxy / Thanos receive side
268+
on the monitoring cluster.
269+
3. **All counters are flat or zero.** The gateway has nothing to export, which means the
270+
daemon collectors are not scraping anything. Continue with step (c).
271+
272+
A growing `enqueue_failed_metric_points` means the exporter's sending queue is
273+
overflowing because sends are too slow or failing, and those points are dropped; treat
274+
it the same as outcome 2.
275+
276+
Note the exporter retries for a bounded time (`max_elapsed_time: 120s`) and then drops the
277+
batch, so persistent export errors mean permanent data loss, not delayed delivery.
278+
279+
When finished, stop the background port-forward with `kill %1`.
280+
281+
### c. Scrape targets exist and are being allocated
282+
283+
If the gateway shows nothing to export (`sent` flat, no errors), the daemon collectors
284+
aren't scraping anything. Check that the Target Allocator is running and that
285+
`ServiceMonitor`/`PodMonitor` resources exist for your workloads:
286+
287+
```bash
288+
kubectl get pods -n monitoring -l app.kubernetes.io/component=opentelemetry-targetallocator
289+
kubectl get servicemonitors,podmonitors -A
290+
```
291+
292+
The Target Allocator watches these CRs and distributes their scrape targets across the
293+
daemonset collectors. No matching monitors (or an unhealthy allocator) means the
294+
`prometheus` receiver has nothing to scrape: no data flows and no errors are logged
295+
anywhere, which is exactly the "silently stale" symptom.
296+
297+
### d. Data flowing under the wrong tenant
298+
299+
Metrics may be arriving in Thanos but under a different tenant than the one you're
300+
querying. The gateway derives the tenant from resource attributes: it defaults to
301+
`default`, and is set to the source namespace name only for namespaces labeled
302+
`ace.appscode.com/client-org: "true"`; batches with no tenant attribute fall back to
303+
`anonymous`. Re-run the store/query checks from section 2 with `THANOS-TENANT: default`
304+
(and `anonymous`) before concluding data is missing.
305+
306+
After fixing whichever stage was broken, re-run the `prometheus_tsdb_head_max_time` check
307+
from section 2 over a couple of minutes to confirm the timestamp starts advancing.

0 commit comments

Comments
 (0)