Skip to content

Commit 301fd65

Browse files
Merge pull request #6 from patterncatalyst/feat/nodeport-tunnels-otel-instrumentation
feat: NodePort tunnels, OTel for all services, Istio 1.29.2
2 parents 76db86f + ebf7618 commit 301fd65

25 files changed

Lines changed: 492 additions & 53 deletions

File tree

‎demos.html‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -346,26 +346,29 @@ <h2 class="section-heading">Running a demo</h2>
346346
./demos/demo-order.sh</code></pre>
347347

348348
<h3>What to look at after running a demo</h3>
349+
<p>
350+
Bootstrap starts SSH tunnels to all UI services automatically
351+
via <code>tunnel-services.sh</code>. The URLs below are
352+
available as soon as the stack is up:
353+
</p>
349354
<ul>
350355
<li>
351356
<strong>Grafana</strong> — dashboards for request rate,
352-
canary weight, KEDA replicas:<br>
353-
<code>kubectl port-forward -n capstone svc/grafana 3000:80</code>
354-
&rarr; <code>http://localhost:3000</code>
357+
canary weight, KEDA replicas:
358+
<code>http://localhost:3000</code>
355359
</li>
356360
<li>
357-
<strong>Kiali</strong> — live mesh topology graph:<br>
358-
<code>kubectl port-forward -n istio-system svc/kiali 20001:20001</code>
359-
&rarr; <code>http://localhost:20001/kiali</code>
361+
<strong>Kiali</strong> — live mesh topology graph:
362+
<code>http://localhost:20001/kiali</code>
360363
</li>
361364
<li>
362365
<strong>Tempo</strong> — distributed traces: open Grafana
363366
&rarr; Explore &rarr; select the Tempo datasource
367+
(or direct: <code>http://localhost:3200</code>)
364368
</li>
365369
<li>
366-
<strong>OpenMetadata</strong> — catalog and lineage:<br>
367-
<code>kubectl port-forward -n capstone svc/openmetadata 8585:8585</code>
368-
&rarr; <code>http://localhost:8585</code>
370+
<strong>OpenMetadata</strong> — catalog and lineage:
371+
<code>http://localhost:8585</code>
369372
</li>
370373
</ul>
371374

‎examples/lgtm-datamesh/README.md‎

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,14 @@ The profile setup script checks both of these and prints the same fixes.
119119
| minikube | **1.36** | 1.35's registry addon pins a `kube-registry-proxy` image digest that no longer exists on gcr.io |
120120
| kubectl | (any recent) | |
121121
| helm | 3.x | |
122-
| istioctl | 1.26.x | needs the full Istio distribution, not just the binary — `setup-kiali.sh` applies `samples/addons/kiali.yaml` from it |
122+
| istioctl | 1.29.x | needs the full Istio distribution, not just the binary — `setup-kiali.sh` applies `samples/addons/kiali.yaml` from it |
123123

124124
**Installing the Istio distribution:**
125125

126126
```bash
127-
curl -fsSL https://github.com/istio/istio/releases/download/1.26.2/istio-1.26.2-linux-amd64.tar.gz \
127+
curl -fsSL https://github.com/istio/istio/releases/download/1.29.2/istio-1.29.2-linux-amd64.tar.gz \
128128
| tar xz -C ~/.local/share
129-
ln -sfn ~/.local/share/istio-1.26.2 ~/.local/share/istio-current
129+
ln -sfn ~/.local/share/istio-1.29.2 ~/.local/share/istio-current
130130
cp ~/.local/share/istio-current/bin/istioctl ~/.local/bin/
131131
```
132132

@@ -203,22 +203,23 @@ kubectl context set to `capstone`.
203203

204204
### Observing the results
205205

206-
Bootstrap and the walkthrough automatically start port-forwards for the
207-
four UI services via `scripts/port-forward-ui.sh`. They survive script
208-
exit and stay up until you stop them.
206+
Bootstrap and the walkthrough automatically start SSH tunnels to
207+
NodePort services via `scripts/tunnel-services.sh`. They survive script
208+
exit and stay up until you stop them — no more port-forward drops under
209+
load or idle timeouts.
209210

210211
| Tool | Local URL | Notes |
211212
|------|-----------|-------|
212213
| Grafana | `http://localhost:3000` | |
213214
| Prometheus | `http://localhost:9091` | Port 9091 avoids Fedora Cockpit on 9090 |
215+
| Tempo | `http://localhost:3200` | Trace query API; also accessible via Grafana Explore |
214216
| Kiali | `http://localhost:20001/kiali` | |
215-
| Tempo | (via Grafana → Explore → Tempo datasource) | No direct forward needed |
216217
| OpenMetadata | `http://localhost:8585` | |
217218

218219
```bash
219-
./scripts/port-forward-ui.sh --status # check which forwards are alive
220-
./scripts/port-forward-ui.sh --stop # tear them all down
221-
./scripts/port-forward-ui.sh # restart them
220+
./scripts/tunnel-services.sh --status # check which tunnels are alive
221+
./scripts/tunnel-services.sh --stop # tear them all down
222+
./scripts/tunnel-services.sh # restart them
222223
```
223224

224225
Default credentials:

‎examples/lgtm-datamesh/charts/capstone/charts/inventory-service/templates/deployment.yaml‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,26 @@ spec:
5252
secretKeyRef:
5353
name: {{ .Values.postgres.appSecret }}
5454
key: password
55+
{{- if .Values.tracing.enabled }}
56+
- name: OTEL_SERVICE_NAME
57+
value: {{ .Values.tracing.serviceName | quote }}
58+
- name: OTEL_EXPORTER_OTLP_ENDPOINT
59+
value: {{ .Values.tracing.otlpEndpoint | quote }}
60+
- name: OTEL_EXPORTER_OTLP_PROTOCOL
61+
value: {{ .Values.tracing.otlpProtocol | quote }}
62+
- name: OTEL_TRACES_EXPORTER
63+
value: "otlp"
64+
- name: OTEL_METRICS_EXPORTER
65+
value: "none"
66+
- name: OTEL_LOGS_EXPORTER
67+
value: "none"
68+
- name: OTEL_TRACES_SAMPLER
69+
value: "parentbased_traceidratio"
70+
- name: OTEL_TRACES_SAMPLER_ARG
71+
value: {{ .Values.tracing.sampleRatio | quote }}
72+
- name: OTEL_RESOURCE_ATTRIBUTES
73+
value: "service.namespace=capstone"
74+
{{- end }}
5575
resources:
5676
requests:
5777
cpu: {{ .Values.resources.requests.cpu }}

‎examples/lgtm-datamesh/charts/capstone/charts/inventory-service/values.yaml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ postgres:
2222
appSecret: capstone-postgres-app
2323
schema: inventory
2424

25+
tracing:
26+
enabled: true
27+
otlpEndpoint: http://tempo.observability.svc.cluster.local:4318
28+
otlpProtocol: http/protobuf
29+
serviceName: inventory-service
30+
sampleRatio: "1.0"
31+
2532
resources:
2633
requests:
2734
cpu: 100m

‎examples/lgtm-datamesh/charts/capstone/charts/notification-service/templates/deployment.yaml‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,26 @@ spec:
9393
secretKeyRef:
9494
name: {{ .Values.postgres.appSecret }}
9595
key: password
96+
{{- if .Values.tracing.enabled }}
97+
- name: OTEL_SERVICE_NAME
98+
value: {{ .Values.tracing.serviceName | quote }}
99+
- name: OTEL_EXPORTER_OTLP_ENDPOINT
100+
value: {{ .Values.tracing.otlpEndpoint | quote }}
101+
- name: OTEL_EXPORTER_OTLP_PROTOCOL
102+
value: {{ .Values.tracing.otlpProtocol | quote }}
103+
- name: OTEL_TRACES_EXPORTER
104+
value: "otlp"
105+
- name: OTEL_METRICS_EXPORTER
106+
value: "none"
107+
- name: OTEL_LOGS_EXPORTER
108+
value: "none"
109+
- name: OTEL_TRACES_SAMPLER
110+
value: "parentbased_traceidratio"
111+
- name: OTEL_TRACES_SAMPLER_ARG
112+
value: {{ .Values.tracing.sampleRatio | quote }}
113+
- name: OTEL_RESOURCE_ATTRIBUTES
114+
value: "service.namespace=capstone"
115+
{{- end }}
96116
resources:
97117
requests:
98118
cpu: {{ .Values.resources.requests.cpu }}

‎examples/lgtm-datamesh/charts/capstone/charts/notification-service/values.yaml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ kafka:
3434
apicurio:
3535
url: http://apicurio:8080
3636

37+
tracing:
38+
enabled: true
39+
otlpEndpoint: http://tempo.observability.svc.cluster.local:4318
40+
otlpProtocol: http/protobuf
41+
serviceName: notification-service
42+
sampleRatio: "1.0"
43+
3744
resources:
3845
requests:
3946
cpu: 100m

‎examples/lgtm-datamesh/charts/capstone/charts/order-service/templates/deployment.yaml‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,26 @@ spec:
8585
secretKeyRef:
8686
name: {{ .Values.postgres.appSecret }}
8787
key: password
88+
{{- if .Values.tracing.enabled }}
89+
- name: OTEL_SERVICE_NAME
90+
value: {{ .Values.tracing.serviceName | quote }}
91+
- name: OTEL_EXPORTER_OTLP_ENDPOINT
92+
value: {{ .Values.tracing.otlpEndpoint | quote }}
93+
- name: OTEL_EXPORTER_OTLP_PROTOCOL
94+
value: {{ .Values.tracing.otlpProtocol | quote }}
95+
- name: OTEL_TRACES_EXPORTER
96+
value: "otlp"
97+
- name: OTEL_METRICS_EXPORTER
98+
value: "none"
99+
- name: OTEL_LOGS_EXPORTER
100+
value: "none"
101+
- name: OTEL_TRACES_SAMPLER
102+
value: "parentbased_traceidratio"
103+
- name: OTEL_TRACES_SAMPLER_ARG
104+
value: {{ .Values.tracing.sampleRatio | quote }}
105+
- name: OTEL_RESOURCE_ATTRIBUTES
106+
value: "service.namespace=capstone"
107+
{{- end }}
88108
resources:
89109
requests:
90110
cpu: {{ .Values.resources.requests.cpu }}

‎examples/lgtm-datamesh/charts/capstone/charts/order-service/values.yaml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ postgres:
3535
appSecret: capstone-postgres-app
3636
schema: orders
3737

38+
tracing:
39+
enabled: true
40+
otlpEndpoint: http://tempo.observability.svc.cluster.local:4318
41+
otlpProtocol: http/protobuf
42+
serviceName: order-service
43+
sampleRatio: "1.0"
44+
3845
resources:
3946
requests:
4047
cpu: 100m
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#!/usr/bin/env bash
2+
#
3+
# verify-grafana.sh — automated test that all Grafana dashboards, datasources,
4+
# and panels are provisioned correctly.
5+
#
6+
# Run from examples/lgtm-datamesh/ with SSH tunnels active:
7+
# ./demos/verify-grafana.sh
8+
9+
set -uo pipefail
10+
11+
GRAFANA="http://localhost:3000"
12+
PASS="capstone"
13+
AUTH="admin:${PASS}"
14+
15+
step() { printf '\n==> %s\n' "$1"; }
16+
ok() { printf ' \xe2\x9c\x93 %s\n' "$1"; }
17+
fail() { printf ' \xe2\x9c\x97 %s\n' "$1" >&2; FAILURES=$((FAILURES + 1)); }
18+
19+
FAILURES=0
20+
21+
# ── 1. Grafana health ────────────────────────────────────────────────────────
22+
step "Grafana health"
23+
health="$(curl -4 -fsS --max-time 5 "${GRAFANA}/api/health" 2>/dev/null)"
24+
if echo "$health" | grep -q '"database"'; then
25+
ok "Grafana API healthy"
26+
else
27+
fail "Grafana API not healthy: $health"
28+
fi
29+
30+
# ── 2. Datasources ──────────────────────────────────────────────────────────
31+
step "Datasources"
32+
ds="$(curl -4 -fsS --max-time 5 -u "$AUTH" "${GRAFANA}/api/datasources" 2>/dev/null)"
33+
34+
if echo "$ds" | python3 -c 'import sys,json; ds=json.load(sys.stdin); sys.exit(0 if any(d["type"]=="prometheus" for d in ds) else 1)' 2>/dev/null; then
35+
ok "Prometheus datasource exists"
36+
else
37+
fail "Prometheus datasource missing"
38+
fi
39+
40+
prom_uid="$(echo "$ds" | python3 -c 'import sys,json; ds=json.load(sys.stdin); [print(d["uid"]) for d in ds if d["type"]=="prometheus"]' 2>/dev/null | head -1)"
41+
if [[ -n "$prom_uid" ]]; then
42+
prom_query="$(curl -4 -fsS --max-time 10 -u "$AUTH" \
43+
"${GRAFANA}/api/datasources/proxy/uid/${prom_uid}/api/v1/query?query=up" 2>/dev/null)"
44+
if echo "$prom_query" | grep -q '"success"'; then
45+
ok "Prometheus datasource reachable (query returns data)"
46+
else
47+
fail "Prometheus datasource not reachable"
48+
fi
49+
fi
50+
51+
if echo "$ds" | python3 -c 'import sys,json; ds=json.load(sys.stdin); sys.exit(0 if any(d["type"]=="tempo" for d in ds) else 1)' 2>/dev/null; then
52+
ok "Tempo datasource exists"
53+
else
54+
fail "Tempo datasource missing"
55+
fi
56+
57+
# ── 3. Dashboards ───────────────────────────────────────────────────────────
58+
step "Dashboards"
59+
dashboards="$(curl -4 -fsS --max-time 5 -u "$AUTH" "${GRAFANA}/api/search?type=dash-db" 2>/dev/null)"
60+
61+
dash_count="$(echo "$dashboards" | python3 -c 'import sys,json; print(len(json.load(sys.stdin)))' 2>/dev/null)"
62+
if [[ "$dash_count" -ge 1 ]]; then
63+
ok "$dash_count dashboard(s) found"
64+
else
65+
fail "No dashboards found"
66+
fi
67+
68+
if echo "$dashboards" | python3 -c 'import sys,json; ds=json.load(sys.stdin); sys.exit(0 if any("capstone" in d.get("title","").lower() or "scaling" in d.get("title","").lower() for d in ds) else 1)' 2>/dev/null; then
69+
ok "Capstone — Scaling & Traffic dashboard found"
70+
else
71+
fail "Capstone — Scaling & Traffic dashboard missing"
72+
fi
73+
74+
# ── 4. Panel verification ───────────────────────────────────────────────────
75+
step "Panel verification (Capstone — Scaling & Traffic)"
76+
77+
dash_detail="$(curl -4 -fsS --max-time 5 -u "$AUTH" "${GRAFANA}/api/dashboards/uid/capstone-scaling" 2>/dev/null)"
78+
79+
if [[ -z "$dash_detail" ]] || echo "$dash_detail" | grep -q '"message"'; then
80+
fail "Could not fetch dashboard capstone-scaling"
81+
else
82+
panel_count="$(echo "$dash_detail" | python3 -c 'import sys,json; d=json.load(sys.stdin); print(len(d.get("dashboard",{}).get("panels",[])))' 2>/dev/null)"
83+
if [[ "$panel_count" -ge 2 ]]; then
84+
ok "$panel_count panels in the dashboard"
85+
else
86+
fail "Expected at least 2 panels, found $panel_count"
87+
fi
88+
89+
echo "$dash_detail" | python3 -c '
90+
import sys, json
91+
d = json.load(sys.stdin)
92+
panels = d.get("dashboard", {}).get("panels", [])
93+
for p in panels:
94+
title = p.get("title", "(untitled)")
95+
ptype = p.get("type", "?")
96+
targets = len(p.get("targets", []))
97+
ds_uid = p.get("datasource", {}).get("uid", "?")
98+
print(f" panel: {title} (type={ptype}, targets={targets}, datasource={ds_uid})")
99+
' 2>/dev/null
100+
101+
ok "Panel datasource references verified"
102+
fi
103+
104+
# ── 5. Prometheus scrape targets ────────────────────────────────────────────
105+
step "Prometheus scrape verification"
106+
if [[ -n "${prom_uid:-}" ]]; then
107+
targets="$(curl -4 -fsS --max-time 10 -u "$AUTH" \
108+
"${GRAFANA}/api/datasources/proxy/uid/${prom_uid}/api/v1/query?query=up" 2>/dev/null)"
109+
if echo "$targets" | grep -q '"success"'; then
110+
target_count="$(echo "$targets" | python3 -c 'import sys,json; d=json.load(sys.stdin); print(len(d.get("data",{}).get("result",[])))' 2>/dev/null)"
111+
ok "Prometheus returning data ($target_count scrape targets)"
112+
else
113+
fail "Prometheus query failed"
114+
fi
115+
116+
kube_metrics="$(curl -4 -fsS --max-time 10 -u "$AUTH" \
117+
"${GRAFANA}/api/datasources/proxy/uid/${prom_uid}/api/v1/query?query=kube_deployment_spec_replicas" 2>/dev/null)"
118+
if echo "$kube_metrics" | python3 -c 'import sys,json; d=json.load(sys.stdin); sys.exit(0 if d.get("data",{}).get("result") else 1)' 2>/dev/null; then
119+
ok "kube_deployment_spec_replicas metric available (KEDA scaling panel)"
120+
else
121+
fail "kube_deployment_spec_replicas metric not found — kube-state-metrics may not be scraping"
122+
fi
123+
124+
istio_metrics="$(curl -4 -fsS --max-time 10 -u "$AUTH" \
125+
"${GRAFANA}/api/datasources/proxy/uid/${prom_uid}/api/v1/label/__name__/values" 2>/dev/null)"
126+
if echo "$istio_metrics" | grep -q 'istio_requests_total'; then
127+
ok "istio_requests_total metric available (traffic panel)"
128+
else
129+
ok "istio_requests_total not yet available (appears after traffic is generated)"
130+
fi
131+
fi
132+
133+
# ── Summary ─────────────────────────────────────────────────────────────────
134+
printf '\n'
135+
if (( FAILURES == 0 )); then
136+
printf '\xe2\x9c\x93 ALL GRAFANA CHECKS PASSED\n'
137+
else
138+
printf '\xe2\x9c\x97 %d CHECK(S) FAILED\n' "$FAILURES"
139+
fi
140+
exit $FAILURES

‎examples/lgtm-datamesh/demos/walkthrough.sh‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ run_act() {
142142

143143
# ─── Cleanup ─────────────────────────────────────────────────────────────────
144144
# The trace act starts a temporary port-forward to graphql-gateway; tear it
145-
# down on exit. Persistent UI forwards (Grafana, Kiali, etc.) are managed
146-
# by port-forward-ui.sh and survive this script.
145+
# down on exit. Persistent UI tunnels (Grafana, Kiali, etc.) are managed
146+
# by tunnel-services.sh and survive this script.
147147
TRACE_PF=""
148148
cleanup() {
149149
if [[ -n "$TRACE_PF" ]] && kill -0 "$TRACE_PF" 2>/dev/null; then
@@ -270,9 +270,9 @@ INTRO
270270

271271
(( PREFLIGHT == 1 )) && preflight
272272

273-
# Start persistent UI port-forwards (Grafana, Prometheus, Kiali, OpenMetadata)
274-
# so the dashboards are available throughout the walkthrough.
275-
./scripts/port-forward-ui.sh
273+
# Start persistent SSH tunnels to NodePort services (Grafana, Prometheus, Kiali,
274+
# OpenMetadata) so the dashboards are available throughout the walkthrough.
275+
./scripts/tunnel-services.sh
276276

277277
prompt_enter "press Enter to start"
278278

@@ -458,7 +458,7 @@ if want_act topology; then
458458
prompt_enter "press Enter to verify Kiali"
459459
run_act "topology (smoke)" ./demos/demo-kiali.sh || exit 1
460460

461-
narrate "Kiali is already port-forwarded (port-forward-ui.sh)"
461+
narrate "Kiali is already tunneled (tunnel-services.sh)"
462462
info " http://localhost:20001/kiali (Graph → namespace: capstone)"
463463

464464
narrate "to make EDGES appear in the graph, generate some traffic:"
@@ -473,5 +473,5 @@ printf '\n%s%s══════════════════════
473473
printf '%s%s walkthrough complete · %d / %d acts run%s\n' "$BOLD" "$GRN" "$ACT_NUM" "$ACT_TOTAL" "$RST"
474474
printf '%s%s═══════════════════════════════════════════════════════════════════════%s\n\n' "$BOLD" "$GRN" "$RST"
475475

476-
printf '%s UI port-forwards remain active (Grafana :3000, Prometheus :9091, Kiali :20001, OpenMetadata :8585)%s\n' "$DIM" "$RST"
477-
printf '%s stop them with: ./scripts/port-forward-ui.sh --stop%s\n' "$DIM" "$RST"
476+
printf '%s SSH tunnels remain active (Grafana :3000, Prometheus :9091, Tempo :3200, Kiali :20001, OpenMetadata :8585)%s\n' "$DIM" "$RST"
477+
printf '%s stop them with: ./scripts/tunnel-services.sh --stop%s\n' "$DIM" "$RST"

0 commit comments

Comments
 (0)