replace cadvisor for otelcollector docker_stats plugin, use docker socket proxy for otelcollector

This commit is contained in:
qpismont committed 2026-10-28 20:46:18 +00:00
1 parent 0747dda9e7
commit 887668dde9
7 files changed
+590 -508

No files matched your search

+271 -164
View File
@@ -5,38 +5,63 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Portfolio of Quentin Pismont, senior/lead platform engineer: distributed systems, observability, security and self-hosted CI/CD, tested in production.">
<title>Quentin Pismont — Senior/Lead Platform Engineer</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&display=swap" rel="stylesheet">
<!-- Open Graph -->
<meta property="og:type" content="website">
<meta property="og:title" content="Quentin Pismont — Senior/Lead Platform Engineer">
<meta property="og:description" content="Self-hosted infrastructure running in production, a security-first AI agent in Rust, and a distributed media pipeline. Source code available.">
<style>
:root {
--bg: #0d1117;
--bg-soft: #161b22;
--bg-card: #161b22;
--border: #21262d;
--border-hover: #30363d;
--text: #e6edf3;
--text-dim: #8b949e;
--text-faint: #6e7681;
--accent: #58a6ff;
--accent-soft: rgba(88, 166, 255, 0.1);
--green: #3fb950;
--mono: 'SF Mono', 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
--sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--bg: #0b0b14;
--bg-soft: #14142a;
--bg-card: #101024;
--border: #3a3a6a;
--text: #e8e8f0;
--text-dim: #bcbcd8;
--text-faint: #7a7aa4;
--accent: #3cbcfc;
--accent-soft: rgba(60, 188, 252, 0.12);
--accent-2: #f8b800;
--green: #00e436;
--pink: #ff2d78;
--mono: 'VT323', 'Courier New', monospace;
--pixel: 'Press Start 2P', 'Courier New', monospace;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
background: var(--bg);
background-color: var(--bg);
/* Scanlines + grid live in the background layer, never over the glyphs */
background-image:
repeating-linear-gradient(
to bottom,
rgba(0, 0, 0, 0.22) 0px,
rgba(0, 0, 0, 0.22) 1px,
transparent 1px,
transparent 4px
),
linear-gradient(rgba(60, 188, 252, 0.035) 1px, transparent 1px),
linear-gradient(90deg, rgba(60, 188, 252, 0.035) 1px, transparent 1px);
background-size: 100% 4px, 32px 32px, 32px 32px;
background-attachment: fixed, scroll, scroll;
color: var(--text);
font-family: var(--sans);
line-height: 1.65;
font-size: 16px;
-webkit-font-smoothing: antialiased;
font-family: var(--mono);
font-size: 21px;
line-height: 1.6;
}
/* CRT vignette — smooth gradient, does not cut through text */
body::after {
content: "";
position: fixed;
inset: 0;
z-index: 9999;
pointer-events: none;
background: radial-gradient(ellipse at center, transparent 72%, rgba(0, 0, 0, 0.28) 100%);
}
.wrap {
max-width: 860px;
max-width: 880px;
margin: 0 auto;
padding: 0 24px;
}
@@ -45,225 +70,279 @@
position: sticky;
top: 0;
z-index: 100;
background: rgba(13, 17, 23, 0.8);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border);
background: var(--bg);
border-bottom: 4px solid var(--border);
}
nav .wrap {
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
height: 64px;
}
nav .logo {
font-family: var(--mono);
font-weight: 600;
font-size: 15px;
font-family: var(--pixel);
font-weight: 400;
font-size: 10px;
color: var(--text);
text-transform: uppercase;
}
nav .logo span { color: var(--accent); }
nav .links { display: flex; gap: 28px; }
nav .logo span { color: var(--accent-2); }
nav .logo::after {
content: "_";
color: var(--accent);
animation: blink 1s steps(2, start) infinite;
}
nav .links { display: flex; gap: 24px; }
nav a {
color: var(--text-dim);
text-decoration: none;
font-size: 14px;
transition: color 0.2s;
font-family: var(--pixel);
font-size: 8px;
text-transform: uppercase;
}
nav a:hover {
color: var(--accent-2);
text-decoration: underline;
text-decoration-thickness: 2px;
text-underline-offset: 4px;
}
nav a:hover { color: var(--text); }
@media (max-width: 620px) { nav .links { display: none; } }
/* ---------- HERO ---------- */
header {
padding: 100px 0 80px;
border-bottom: 1px solid var(--border);
padding: 72px 0 64px;
border-bottom: 4px solid var(--border);
}
.badge {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: var(--mono);
font-size: 13px;
gap: 10px;
font-family: var(--pixel);
font-size: 8px;
text-transform: uppercase;
color: var(--green);
background: rgba(63, 185, 80, 0.1);
border: 1px solid rgba(63, 185, 80, 0.2);
padding: 5px 12px;
border-radius: 20px;
margin-bottom: 28px;
background: transparent;
border: 3px solid var(--green);
padding: 8px 12px;
margin-bottom: 32px;
box-shadow: 4px 4px 0 rgba(0, 228, 54, 0.25);
}
.badge .dot {
width: 7px; height: 7px;
border-radius: 50%;
width: 8px; height: 8px;
background: var(--green);
box-shadow: 0 0 8px var(--green);
animation: blink 1s steps(2, start) infinite;
}
header h1 {
font-size: clamp(2rem, 5vw, 3rem);
line-height: 1.15;
letter-spacing: -0.02em;
margin-bottom: 20px;
font-weight: 700;
font-family: var(--pixel);
font-size: clamp(0.85rem, 2.6vw, 1.45rem);
line-height: 1.9;
letter-spacing: 0;
margin-bottom: 28px;
font-weight: 400;
text-transform: uppercase;
text-shadow: 3px 3px 0 #000, 6px 6px 0 rgba(60, 188, 252, 0.25);
}
header h1 .grad {
background: linear-gradient(120deg, #58a6ff, #a371f7);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: var(--accent-2);
background: none;
-webkit-text-fill-color: currentColor;
}
header p.lead {
font-size: 1.15rem;
font-size: 23px;
line-height: 1.65;
color: var(--text-dim);
max-width: 640px;
max-width: 660px;
margin-bottom: 32px;
}
.cta {
display: flex;
gap: 14px;
gap: 16px;
flex-wrap: wrap;
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
border-radius: 8px;
padding: 12px 18px;
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
border: 1px solid transparent;
font-family: var(--pixel);
font-size: 8px;
text-transform: uppercase;
border: 3px solid var(--text);
background: var(--bg-soft);
color: var(--text);
box-shadow: 5px 5px 0 #000;
transition: none;
}
.btn-primary {
background: var(--accent);
color: #0d1117;
color: #0b0b14;
border-color: var(--accent);
}
.btn-primary:hover {
background: var(--accent-2);
border-color: var(--accent-2);
transform: translate(2px, 2px);
box-shadow: 3px 3px 0 #000;
}
.btn-primary:hover { background: #79b8ff; transform: translateY(-1px); }
.btn-ghost {
background: transparent;
background: var(--bg-soft);
color: var(--text);
border-color: var(--border-hover);
border-color: var(--border);
}
.btn-ghost:hover {
color: var(--accent-2);
border-color: var(--accent-2);
transform: translate(2px, 2px);
box-shadow: 3px 3px 0 #000;
}
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
/* ---------- SECTIONS ---------- */
section { padding: 80px 0; border-bottom: 1px solid var(--border); }
section { padding: 64px 0; border-bottom: 4px solid var(--border); }
.section-head {
display: flex;
align-items: baseline;
gap: 12px;
margin-bottom: 40px;
align-items: center;
gap: 14px;
margin-bottom: 36px;
}
.section-head .num {
font-family: var(--mono);
font-size: 14px;
color: var(--accent);
font-family: var(--pixel);
font-size: 10px;
color: #0b0b14;
background: var(--accent-2);
padding: 6px 8px;
box-shadow: 3px 3px 0 #000;
}
.section-head h2 {
font-size: 1.6rem;
letter-spacing: -0.01em;
font-weight: 600;
font-family: var(--pixel);
font-size: clamp(0.85rem, 2.2vw, 1.1rem);
letter-spacing: 0;
font-weight: 400;
text-transform: uppercase;
color: var(--text);
}
.section-intro {
color: var(--text-dim);
max-width: 640px;
margin: -24px 0 40px;
max-width: 660px;
margin: -20px 0 36px;
font-size: 22px;
line-height: 1.65;
}
/* ---------- ABOUT ---------- */
.about-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
gap: 36px;
}
@media (max-width: 620px) { .about-grid { grid-template-columns: 1fr; gap: 28px; } }
.about-grid p { color: var(--text-dim); margin-bottom: 16px; }
.about-grid p strong { color: var(--text); font-weight: 500; }
.about-grid p { color: var(--text-dim); margin-bottom: 18px; font-size: 22px; line-height: 1.65; }
.about-grid p strong { color: var(--accent-2); font-weight: 400; }
.principles { list-style: none; }
.principles li {
padding: 10px 0 10px 24px;
padding: 10px 0 10px 26px;
position: relative;
color: var(--text-dim);
border-bottom: 1px solid var(--border);
border-bottom: 2px dashed var(--border);
font-size: 21px;
line-height: 1.6;
}
.principles li:last-child { border-bottom: none; }
.principles li::before {
content: "→";
content: ">";
position: absolute;
left: 0;
color: var(--accent);
color: var(--green);
font-family: var(--pixel);
font-size: 9px;
top: 14px;
}
/* ---------- PROJECTS ---------- */
.project {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 14px;
padding: 32px;
margin-bottom: 24px;
transition: border-color 0.25s, transform 0.25s;
border: 3px solid var(--border);
border-radius: 0;
padding: 28px;
margin-bottom: 28px;
box-shadow: 6px 6px 0 #000;
transition: none;
}
.project:hover {
border-color: var(--border-hover);
transform: translateY(-2px);
border-color: var(--accent);
transform: none;
box-shadow: 6px 6px 0 rgba(60, 188, 252, 0.35);
}
.project-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
margin-bottom: 8px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.project h3 {
font-size: 1.3rem;
font-weight: 600;
font-family: var(--pixel);
font-size: 11px;
line-height: 1.8;
font-weight: 400;
text-transform: uppercase;
display: flex;
align-items: center;
gap: 10px;
gap: 12px;
flex-wrap: wrap;
color: var(--accent);
}
.project .tag {
font-family: var(--mono);
font-size: 11px;
padding: 3px 10px;
border-radius: 20px;
background: var(--accent-soft);
font-family: var(--pixel);
font-size: 7px;
padding: 6px 8px;
border-radius: 0;
background: transparent;
color: var(--accent);
border: 1px solid rgba(88,166,255,0.2);
border: 2px solid var(--accent);
white-space: nowrap;
text-transform: uppercase;
}
.tag.wip { background: rgba(210,153,34,0.1); color: #d29922; border-color: rgba(210,153,34,0.2); }
.tag.done { background: rgba(63,185,80,0.1); color: var(--green); border-color: rgba(63,185,80,0.2); }
.tag.wip { background: transparent; color: var(--accent-2); border-color: var(--accent-2); }
.tag.done { background: transparent; color: var(--green); border-color: var(--green); }
.project .subtitle {
color: var(--text-dim);
font-size: 0.98rem;
font-size: 22px;
line-height: 1.65;
margin-bottom: 24px;
}
.project .subtitle strong { color: var(--text); font-weight: 400; }
.block { margin-bottom: 20px; }
.block-label {
font-family: var(--mono);
font-size: 12px;
color: var(--text-faint);
font-family: var(--pixel);
font-size: 8px;
color: var(--accent-2);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 8px;
letter-spacing: 0;
margin-bottom: 10px;
}
.block p, .block ul { color: var(--text-dim); font-size: 0.95rem; }
.block-label::before { content: "■ "; color: var(--pink); }
.block p, .block ul { color: var(--text-dim); font-size: 21px; line-height: 1.65; }
.block ul { list-style: none; }
.block ul li {
padding: 4px 0 4px 20px;
padding: 6px 0 6px 22px;
position: relative;
}
.block ul li::before {
content: "▹";
content: ">";
position: absolute;
left: 0;
color: var(--accent);
font-size: 0.85em;
color: var(--green);
font-family: var(--pixel);
font-size: 8px;
top: 9px;
}
.block strong { color: var(--text); font-weight: 400; }
.block code {
font-family: var(--mono);
font-size: 0.85em;
background: var(--bg-soft);
padding: 2px 6px;
border-radius: 4px;
font-size: 0.95em;
background: #000;
padding: 1px 5px;
border-radius: 0;
color: var(--accent);
border: 1px solid var(--border);
border: 2px solid var(--border);
}
.stack {
display: flex;
@@ -271,73 +350,101 @@
gap: 8px;
margin-top: 24px;
padding-top: 20px;
border-top: 1px solid var(--border);
border-top: 2px dashed var(--border);
}
.stack span {
font-family: var(--mono);
font-size: 12px;
font-family: var(--pixel);
font-size: 7px;
color: var(--text-dim);
background: var(--bg-soft);
padding: 4px 10px;
border-radius: 6px;
border: 1px solid var(--border);
background: #000;
padding: 6px 8px;
border-radius: 0;
border: 2px solid var(--border);
text-transform: uppercase;
}
/* ---------- SKILLS ---------- */
.skills-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
}
.skill-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 22px;
border: 3px solid var(--border);
border-radius: 0;
padding: 20px;
box-shadow: 5px 5px 0 #000;
}
.skill-card:hover { border-color: var(--accent-2); }
.skill-card h4 {
font-size: 0.95rem;
font-family: var(--pixel);
font-size: 8px;
line-height: 1.8;
margin-bottom: 12px;
color: var(--text);
text-transform: uppercase;
display: flex;
align-items: center;
gap: 8px;
}
.skill-card h4 .ic { color: var(--accent); }
.skill-card p { color: var(--text-dim); font-size: 0.9rem; }
.skill-card h4 .ic { color: var(--pink); }
.skill-card p { color: var(--text-dim); font-size: 21px; line-height: 1.6; }
/* ---------- CONTACT ---------- */
#contact { text-align: center; border-bottom: none; }
#contact h2 { justify-content: center; }
#contact .section-head { justify-content: center; }
#contact p { color: var(--text-dim); max-width: 500px; margin: 0 auto 32px; }
.contact-links { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
#contact p { color: var(--text-dim); max-width: 560px; margin: 0 auto 32px; font-size: 22px; line-height: 1.65; }
.contact-links { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; }
/* ---------- FOOTER ---------- */
footer {
padding: 40px 0;
padding: 32px 0;
text-align: center;
color: var(--text-faint);
font-size: 13px;
font-size: 18px;
font-family: var(--mono);
border-top: 1px solid var(--border);
border-top: 4px solid var(--border);
}
/* Reveal animation */
footer .wrap::before {
content: "PRESS START TO CONTINUE";
display: block;
font-family: var(--pixel);
font-size: 7px;
color: var(--text-faint);
margin-bottom: 14px;
animation: blink 1.4s steps(2, start) infinite;
}
footer .wrap::after {
content: "1UP HI-SCORE 000000 © 2026";
display: block;
font-family: var(--pixel);
font-size: 7px;
color: var(--text-faint);
margin-top: 14px;
}
@keyframes blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; }
}
/* Reveal animation: hard step, no easing */
.reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s ease, transform 0.6s ease;
transform: translateY(12px);
transition: opacity 0.15s steps(2, end), transform 0.15s steps(2, end);
}
.reveal.visible { opacity: 1; transform: translateY(0); }
/* Accessibility: no animation if the user prefers reduced motion */
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
.reveal { opacity: 1; transform: none; transition: none; }
.project:hover, .btn-primary:hover { transform: none; }
.project:hover, .btn-primary:hover, .btn-ghost:hover { transform: none; }
.badge .dot, nav .logo::after, footer .wrap::before { animation: none; }
}
</style>
</head>
<body>
<nav>
<div class="wrap">
<div class="logo">~/<span>quentin_pismont</span></div>
<div class="logo">1UP <span>QUENTIN_PISMONT</span></div>
<div class="links">
<a href="#about">About</a>
<a href="#projects">Projects</a>
@@ -348,15 +455,15 @@
</nav>
<header>
<div class="wrap">
<div class="badge"><span class="dot"></span> Open to opportunities</div>
<h1>I design systems<br><span class="grad">end to end</span>, built to hold up in production.</h1>
<div class="badge"><span class="dot"></span> Player 1 ready — open to work</div>
<h1>I design systems<br><span class="grad">end to end</span>,<br>built to hold up<br>in production.</h1>
<p class="lead">
Platform / infrastructure engineer. From code to production, through CI/CD,
observability and security — an integrated ecosystem designed to be operated
over the long run.
</p>
<div class="cta">
<a href="#projects" class="btn btn-primary">View projects →</a>
<a href="#projects" class="btn btn-primary">Start game &gt;</a>
<a href="https://gitea.qpismont.fr/explore/repos" target="_blank" class="btn btn-ghost">Gitea / Source code</a>
</div>
</div>
@@ -416,9 +523,9 @@
<!-- PROJECT 1 : INFRA -->
<div class="project reveal">
<div class="project-top">
<h3>🏗️ Self-hosted infrastructure <span class="tag done">Complete &amp; tested</span></h3>
<h3>Self-hosted infrastructure <span class="tag done">Complete &amp; tested</span></h3>
</div>
<p class="subtitle">A complete ecosystem — code → CI → production → observability — on bare metal, validated by real failure tests.</p>
<p class="subtitle">A complete ecosystem — code -> CI -> production -> observability — on bare metal, validated by real failure tests.</p>
<div class="block">
<div class="block-label">The problem</div>
<p>Having a reliable personal platform to host and operate my own services, with the standards of real production: resilience, alerting, zero data loss.</p>
@@ -427,17 +534,17 @@
<div class="block-label">The architecture</div>
<ul>
<li><strong>Networking</strong> — WireGuard (the only entry point), Docker overlay, and <code>CoreDNS</code> for <code>*.internal</code> resolution with fallback to Docker's DNS.</li>
<li><strong>CI/CD</strong> — Gitea + Woodpecker: lint → tests → build → automatic push registry on tag/release → deploy on prod.</li>
<li><strong>Observability</strong> — OtelCollector (central hub), Prometheus (storage + alerting), Loki (logs) + Tempo (traces), Perses dashboards, Alertmanager → ntfy.</li>
<li><strong>CI/CD</strong> — Gitea + Woodpecker: lint -> tests -> build -> automatic push registry on tag/release -> deploy on prod.</li>
<li><strong>Observability</strong> — OtelCollector (central hub), Prometheus (storage + alerting), Loki (logs) + Tempo (traces), Perses dashboards, Alertmanager -> ntfy.</li>
<li><strong>Safety net</strong> — Blackbox scraped directly by Prometheus: monitoring survives a Collector failure.</li>
</ul>
</div>
<div class="block">
<div class="block-label">What sets it apart — validated resilience</div>
<ul>
<li>Gitea outage → ntfy alert received ✓</li>
<li>OtelCollector outage → alert received, monitoring maintained ✓</li>
<li>Multiple redeployments → zero data loss (persistent volumes) ✓</li>
<li>Gitea outage -> ntfy alert received [OK]</li>
<li>OtelCollector outage -> alert received, monitoring maintained [OK]</li>
<li>Multiple redeployments -> zero data loss (persistent volumes) [OK]</li>
</ul>
</div>
<div class="stack">
@@ -447,7 +554,7 @@
<!-- PROJECT 2 : HERALD -->
<div class="project reveal">
<div class="project-top">
<h3>🤖 Herald — AI agent for Gitea <span class="tag wip">In active development</span></h3>
<h3>Herald — AI agent for Gitea <span class="tag wip">In active development</span></h3>
</div>
<p class="subtitle">A code-review and AI-assistance bot for Gitea, written in Rust, with a <strong>security-first</strong> agentic architecture.</p>
<div class="block">
@@ -482,7 +589,7 @@
<!-- PROJECT 3 : PIPELINE -->
<div class="project reveal">
<div class="project-top">
<h3>⚡ Distributed media pipeline <span class="tag wip">Design / upcoming</span></h3>
<h3>Distributed media pipeline <span class="tag wip">Design / upcoming</span></h3>
</div>
<p class="subtitle">An extended imgproxy-style media processing pipeline, designed for horizontal scaling and streaming.</p>
<div class="block">
@@ -516,27 +623,27 @@
</div>
<div class="skills-grid">
<div class="skill-card reveal">
<h4><span class="ic">◆</span> Languages</h4>
<h4><span class="ic">■</span> Languages</h4>
<p>Rust (async / Tokio), TypeScript / Node. Looking for the right tool for the right problem.</p>
</div>
<div class="skill-card reveal">
<h4><span class="ic">◆</span> Infrastructure &amp; Ops</h4>
<h4><span class="ic">■</span> Infrastructure &amp; Ops</h4>
<p>Docker Swarm, bare metal, Fedora Bootc (immutable OS), CI/CD (Gitea, Woodpecker).</p>
</div>
<div class="skill-card reveal">
<h4><span class="ic">◆</span> Observability</h4>
<h4><span class="ic">■</span> Observability</h4>
<p>OpenTelemetry, Prometheus, Loki, Tempo, Perses, Alertmanager. Full stack, tested under failure.</p>
</div>
<div class="skill-card reveal">
<h4><span class="ic">◆</span> Networking</h4>
<h4><span class="ic">■</span> Networking</h4>
<p>WireGuard, CoreDNS, overlay networks, Caddy. Designing self-hosted private networks.</p>
</div>
<div class="skill-card reveal">
<h4><span class="ic">◆</span> Security &amp; Isolation</h4>
<h4><span class="ic">■</span> Security &amp; Isolation</h4>
<p>Container sandboxing, privilege separation (DBus / Polkit), threat modeling.</p>
</div>
<div class="skill-card reveal">
<h4><span class="ic">◆</span> Distributed &amp; Messaging</h4>
<h4><span class="ic">■</span> Distributed &amp; Messaging</h4>
<p>NATS, distributed architectures, claim-check patterns, horizontal scaling.</p>
</div>
</div>
@@ -562,7 +669,7 @@
</section>
<footer>
<div class="wrap">
© 2026
INSERT COIN
</div>
</footer>
<script>