Skip to content

Commit a6ecf2d

Browse files
observ page
1 parent 6b949e8 commit a6ecf2d

File tree

3 files changed

+360
-0
lines changed

3 files changed

+360
-0
lines changed

apps/observability-app/Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM nginx:alpine
2+
3+
# Copy the HTML file to nginx html directory
4+
COPY public/index.html /usr/share/nginx/html/index.html
5+
6+
# Expose port 80
7+
EXPOSE 80
8+
9+
# Start nginx
10+
CMD ["nginx", "-g", "daemon off;"]
11+
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta name="color-scheme" content="light dark">
7+
<title>Observability experimentations</title>
8+
<link rel="stylesheet" href="https://roussev.com/styles.css">
9+
<style>
10+
.architecture-diagram {
11+
margin: 2rem 0;
12+
padding: 1.5rem;
13+
background: var(--bg-secondary, #f5f5f5);
14+
border-radius: 8px;
15+
overflow-x: auto;
16+
}
17+
.architecture-diagram h2 {
18+
margin-top: 0;
19+
margin-bottom: 1rem;
20+
color: var(--text-primary, #333);
21+
}
22+
.link-icon {
23+
width: 20px;
24+
height: 20px;
25+
vertical-align: middle;
26+
margin-right: 8px;
27+
display: inline-block;
28+
}
29+
.token-box {
30+
display: inline-flex;
31+
gap: 0.25rem;
32+
align-items: center;
33+
margin-left: 0.5rem;
34+
vertical-align: middle;
35+
}
36+
.token-input {
37+
width: 120px;
38+
padding: 0.25rem 0.5rem;
39+
font-family: monospace;
40+
font-size: 0.7rem;
41+
border: 1px solid var(--border-color, #ccc);
42+
border-radius: 4px;
43+
background: var(--bg-primary, white);
44+
color: var(--text-primary, #333);
45+
overflow: hidden;
46+
text-overflow: ellipsis;
47+
}
48+
.copy-btn {
49+
padding: 0.25rem 0.75rem;
50+
background: #0066cc;
51+
color: white;
52+
border: none;
53+
border-radius: 4px;
54+
cursor: pointer;
55+
font-size: 0.8rem;
56+
white-space: nowrap;
57+
transition: background 0.2s;
58+
}
59+
.copy-btn:hover {
60+
background: #0052a3;
61+
}
62+
.copy-btn:active {
63+
background: #003d7a;
64+
}
65+
.copy-btn.copied {
66+
background: #28a745;
67+
}
68+
@media (prefers-color-scheme: dark) {
69+
.architecture-diagram {
70+
background: var(--bg-secondary, #1a1a1a);
71+
}
72+
.token-input {
73+
background: var(--bg-primary, #2a2a2a);
74+
color: var(--text-primary, #e0e0e0);
75+
border-color: var(--border-color, #444);
76+
}
77+
}
78+
</style>
79+
</head>
80+
<body>
81+
<div class="profile-container">
82+
<div class="profile-header">
83+
<div class="profile-path">items-service/README.md</div>
84+
</div>
85+
86+
<article class="profile-content">
87+
<h1>Observability</h1>
88+
89+
<p>This is an Observability experiment using Bun, Postgre and OpenTelemetry showcasing cool technologies below:</p>
90+
91+
<p>
92+
<img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/grafana/grafana-original.svg" class="link-icon" alt="Grafana">
93+
<a href="/grafana/d/items-service-metrics/items-service-metrics" target="_blank">Grafana</a> - Metrics dashboard
94+
</p>
95+
<p>
96+
<img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/prometheus/prometheus-original.svg" class="link-icon" alt="Prometheus">
97+
<a href="/items/prometheus-queries" target="_blank">Prometheus queries</a>
98+
</p>
99+
<p>
100+
<img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/prometheus/prometheus-original.svg" class="link-icon" alt="Prometheus">
101+
<a href="/items/metrics" target="_blank">Prometheus raw metrics</a>
102+
</p>
103+
<p>
104+
<svg class="link-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
105+
<path d="M12 2L2 7L12 12L22 7L12 2Z" fill="#60D0E4"/>
106+
<path d="M2 17L12 22L22 17" stroke="#60D0E4" stroke-width="2"/>
107+
<path d="M2 12L12 17L22 12" stroke="#60D0E4" stroke-width="2"/>
108+
</svg>
109+
<a href="/jaeger" target="_blank">Jaeger</a> - Distributed tracing
110+
</p>
111+
<p>
112+
<svg class="link-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
113+
<rect x="2" y="4" width="20" height="3" fill="#F5A800"/>
114+
<rect x="2" y="10" width="20" height="3" fill="#F5A800"/>
115+
<rect x="2" y="16" width="20" height="3" fill="#F5A800"/>
116+
</svg>
117+
<a href="/grafana/explore?orgId=1&left=%7B%22datasource%22:%22loki%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22expr%22:%22%7Bapp%3D%5C%22items-service%5C%22%7D%22%7D%5D,%22range%22:%7B%22from%22:%22now-1h%22,%22to%22:%22now%22%7D%7D" target="_blank">Loki</a> - Log aggregation
118+
</p>
119+
120+
<p><strong>Kubernetes Dashboard:</strong></p>
121+
<p>
122+
<svg class="link-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
123+
<path d="M10.9 2.1l-7.2 4.2v8.4l7.2 4.2 7.2-4.2V6.3L10.9 2.1zm5.5 11.8l-5.5 3.2-5.5-3.2V7.5l5.5-3.2 5.5 3.2v6.4z" fill="#326CE5"/>
124+
<circle cx="10.9" cy="10.9" r="2.5" fill="#326CE5"/>
125+
</svg>
126+
<a href="https://kube.roussev.com" target="_blank">Headlamp</a> - k8s readonly UI (copy token to access)
127+
<span class="token-box">
128+
<input
129+
type="text"
130+
class="token-input"
131+
id="headlamp-token"
132+
value="eyJhbGciOiJSUzI1NiIsImtpZCI6IkYwNnVfdXZvVDdvbHhYZnVrTGhHLWg1ZEhZN1hoMHNCczEyWjBOUm5GU1EifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJoZWFkbGFtcC1yZWFkb25seSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJoZWFkbGFtcC1yZWFkb25seSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImE0ZDg0ZGU4LWRmNTctNDg2ZC1hZjJkLWYyZTlhYTkxMGFlNiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTpoZWFkbGFtcC1yZWFkb25seSJ9.Sr1CZT5gAXBtT6ERnbTEPsvuUxuhQ9HtqqykIHpnoWoSNSU363p4Hs4FJGQorEgen8ne0hH_cOOIhthy7djqFxN16ctHGEpzmGPmooOswK5fA0pSKBC6NWyfxiTjmvJVar6W7K7-unX7rIp6uPLI0ULZmPwqBx7ZgAbEbhBmNC0bxUFi2W7EqttRRhIeWcFIdfr9ww5M-1LJOLCb9Usnz6pPhW8QAIhZZ2looXEWT5zclRQc0JpkWpzWpoyG0pB2HRVMl-xlGytz1QMpD4kHuBC3gAcr5pZUco0DUIgpm8_7_yNuQ7mc9WWCJC8mEwaos1352a2cE_DwsOdhMdSIXQ"
133+
readonly
134+
/>
135+
<button class="copy-btn" onclick="copyToken()">Copy</button>
136+
</span>
137+
</p>
138+
139+
140+
<p><strong>Dummy Service :</strong></p>
141+
<p>
142+
<img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/swagger/swagger-original.svg" class="link-icon" alt="Swagger">
143+
<a href="/items/docs" target="_blank">Open API / Swagger Docs</a>
144+
</p>
145+
146+
<div class="architecture-diagram">
147+
<h2>Architecture</h2>
148+
<pre class="mermaid">
149+
graph TB
150+
%% Client
151+
subgraph ClientLayer["Client Layer"]
152+
Client[HTTP Client]
153+
end
154+
155+
%% Application
156+
subgraph AppLayer["Application Layer"]
157+
App[items-service]
158+
ItemsEndpoint[/items endpoint/]
159+
MetricsEndpoint[/metrics endpoint/]
160+
OTel[OpenTelemetry SDK]
161+
Logs[Application Logs]
162+
end
163+
164+
%% Observability
165+
subgraph ObsLayer["Observability Stack"]
166+
Prometheus[Prometheus Metrics]
167+
Jaeger[Jaeger Tracing]
168+
Grafana[Grafana Dashboards]
169+
170+
subgraph LogPipeline["Logs Pipeline"]
171+
Promtail[Promtail / FluentBit Log Agent]
172+
LogStore[Loki / OpenSearch / Elastic]
173+
end
174+
end
175+
176+
%% Data Layer (bottom)
177+
subgraph DataLayer["Data Layer"]
178+
DB[(PostgreSQL Database)]
179+
end
180+
181+
%% Request Flow
182+
Client -->|HTTP requests| App
183+
App --> ItemsEndpoint
184+
ItemsEndpoint -->|SQL queries| DB
185+
186+
%% Metrics Flow
187+
App --> MetricsEndpoint
188+
Prometheus -.->|scrapes metrics| MetricsEndpoint
189+
Grafana -.->|reads metrics| Prometheus
190+
191+
%% Tracing Flow
192+
App -->|trace spans| OTel
193+
OTel -->|OTLP| Jaeger
194+
Grafana -.->|trace links| Jaeger
195+
196+
%% Logs Flow
197+
App -->|stdout logs| Logs
198+
Promtail -.->|reads logs| Logs
199+
Promtail -->|pushes logs| LogStore
200+
Grafana -.->|logs query| LogStore
201+
202+
%% Styling
203+
style App fill:#e85d04,stroke:#dc2f02,stroke-width:3px,color:#fff
204+
style ItemsEndpoint fill:#ffba08,stroke:#faa307,stroke-width:2px,color:#000
205+
style MetricsEndpoint fill:#ffba08,stroke:#faa307,stroke-width:2px,color:#000
206+
style OTel fill:#f5a800,stroke:#d89000,stroke-width:2px,color:#000
207+
style Logs fill:#6c757d,stroke:#495057,stroke-width:2px,color:#fff
208+
209+
style DB fill:#336791,stroke:#2d5a7b,stroke-width:2px,color:#fff
210+
211+
style Prometheus fill:#e6522c,stroke:#c93a1f,stroke-width:2px,color:#fff
212+
style Jaeger fill:#60d0e4,stroke:#4db8ca,stroke-width:2px,color:#000
213+
style Grafana fill:#f46800,stroke:#d85600,stroke-width:2px,color:#fff
214+
215+
style Promtail fill:#1f78b4,stroke:#145684,stroke-width:2px,color:#fff
216+
style LogStore fill:#7cb342,stroke:#558b2f,stroke-width:2px,color:#fff
217+
style LogPipeline fill:#ffffff,stroke:#999,stroke-width:1px,color:#000
218+
219+
</pre>
220+
</div>
221+
</article>
222+
</div>
223+
224+
<script type="module">
225+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
226+
mermaid.initialize({
227+
startOnLoad: true,
228+
theme: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'default'
229+
});
230+
</script>
231+
232+
<script>
233+
function copyToken() {
234+
const tokenInput = document.getElementById('headlamp-token');
235+
const copyBtn = event.target;
236+
237+
// Select and copy the token
238+
tokenInput.select();
239+
tokenInput.setSelectionRange(0, 99999); // For mobile devices
240+
241+
navigator.clipboard.writeText(tokenInput.value).then(() => {
242+
// Change button text and style
243+
const originalText = copyBtn.textContent;
244+
copyBtn.textContent = 'Copied!';
245+
copyBtn.classList.add('copied');
246+
247+
// Reset after 2 seconds
248+
setTimeout(() => {
249+
copyBtn.textContent = originalText;
250+
copyBtn.classList.remove('copied');
251+
}, 2000);
252+
}).catch(err => {
253+
console.error('Failed to copy:', err);
254+
alert('Failed to copy token. Please copy it manually.');
255+
});
256+
}
257+
</script>
258+
</body>
259+
</html>
260+
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: observability-app
5+
namespace: default
6+
labels:
7+
app: observability-app
8+
spec:
9+
replicas: 1
10+
selector:
11+
matchLabels:
12+
app: observability-app
13+
template:
14+
metadata:
15+
labels:
16+
app: observability-app
17+
spec:
18+
containers:
19+
- name: observability-app
20+
image: ghcr.io/nextinterfaces/observability-app:latest
21+
imagePullPolicy: IfNotPresent
22+
ports:
23+
- containerPort: 80
24+
name: http
25+
resources:
26+
requests:
27+
memory: "32Mi"
28+
cpu: "50m"
29+
limits:
30+
memory: "64Mi"
31+
cpu: "100m"
32+
livenessProbe:
33+
httpGet:
34+
path: /
35+
port: 80
36+
initialDelaySeconds: 10
37+
periodSeconds: 10
38+
readinessProbe:
39+
httpGet:
40+
path: /
41+
port: 80
42+
initialDelaySeconds: 5
43+
periodSeconds: 5
44+
45+
---
46+
apiVersion: v1
47+
kind: Service
48+
metadata:
49+
name: observability-app
50+
namespace: default
51+
labels:
52+
app: observability-app
53+
spec:
54+
type: ClusterIP
55+
ports:
56+
- port: 80
57+
targetPort: 80
58+
protocol: TCP
59+
name: http
60+
selector:
61+
app: observability-app
62+
63+
---
64+
apiVersion: networking.k8s.io/v1
65+
kind: Ingress
66+
metadata:
67+
name: observability-app
68+
namespace: default
69+
annotations:
70+
nginx.ingress.kubernetes.io/ssl-redirect: "true"
71+
cert-manager.io/cluster-issuer: "letsencrypt-prod"
72+
spec:
73+
ingressClassName: nginx
74+
tls:
75+
- hosts:
76+
- app.roussev.com
77+
secretName: items-service-tls
78+
rules:
79+
- host: app.roussev.com
80+
http:
81+
paths:
82+
- path: /
83+
pathType: Prefix
84+
backend:
85+
service:
86+
name: observability-app
87+
port:
88+
number: 80
89+

0 commit comments

Comments
 (0)