SVG1: Single server with green checkmark (slide 4)
SVG2: Laptop fan-out to 10 servers via SSH lines (slide 5)
SVG3: Chaotic grid of ~60 servers with drift indicators —
warning triangles, snowflakes, mixed status dots (slide 6)
All use OVHcloud Design System colors, sized for 1280×720 canvas.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
714 lines
39 KiB
HTML
714 lines
39 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Configuration as Code — Puppet vs Ansible vs Terraform</title>
|
||
|
||
<!-- Reveal.js (vendored) -->
|
||
<link rel="stylesheet" href="vendor/reveal.js/dist/reset.css">
|
||
<link rel="stylesheet" href="vendor/reveal.js/dist/reveal.css">
|
||
<link rel="stylesheet" href="vendor/reveal.js/dist/white.css">
|
||
|
||
<!-- Highlight.js for code blocks -->
|
||
<link rel="stylesheet" href="vendor/reveal.js/plugin/highlight/github.css">
|
||
|
||
<style>
|
||
:root {
|
||
/* OVHcloud Design System palette (ovh/design-system) */
|
||
--ods-blue-500: #0050d7;
|
||
--ods-blue-800: #00185e;
|
||
--ods-text: #4d5592;
|
||
--ods-neutral-600: #666666;
|
||
|
||
/* Tool brand colors */
|
||
--puppet-color: #A06010;
|
||
--ansible-color: #CC0000;
|
||
--terraform-color: #7B42BC;
|
||
|
||
--strip-color: var(--ods-blue-500);
|
||
}
|
||
|
||
/* Blue accent strip — fixed outside Reveal.js, always visible */
|
||
.top-strip {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 6px;
|
||
background: var(--strip-color);
|
||
z-index: 50;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.reveal .slides section {
|
||
text-align: left;
|
||
}
|
||
|
||
.reveal h1, .reveal h2 { text-align: left; }
|
||
|
||
.title-slide, .title-slide h1 { text-align: center !important; }
|
||
.title-slide .subtitle { color: var(--ods-text); }
|
||
.title-slide .meta { font-size: 0.6em; color: var(--ods-text); margin-top: 1em; }
|
||
.title-slide .meta a { color: var(--strip-color); }
|
||
|
||
.puppet-col { color: var(--puppet-color); }
|
||
.ansible-col { color: var(--ansible-color); }
|
||
.tf-col { color: var(--terraform-color); }
|
||
|
||
.filename {
|
||
font-family: monospace;
|
||
font-size: 0.65em;
|
||
color: var(--ods-text);
|
||
margin-bottom: 0.2em;
|
||
}
|
||
|
||
/* Code blocks: fill the slide without scrollbars */
|
||
.reveal pre {
|
||
width: 100%;
|
||
max-height: none;
|
||
overflow: visible;
|
||
margin: 0;
|
||
}
|
||
.reveal pre code {
|
||
font-size: 0.65em;
|
||
line-height: 1.6;
|
||
max-height: none;
|
||
overflow: visible;
|
||
padding: 1em;
|
||
}
|
||
.filename { margin-top: 0; margin-bottom: 0.1em; }
|
||
|
||
/* Tool logos — fixed like OVHcloud logo, toggled by JS per slide */
|
||
.tool-logo-global {
|
||
position: fixed;
|
||
top: 16px;
|
||
left: 16px;
|
||
width: 40px;
|
||
height: 40px;
|
||
opacity: 0.8;
|
||
z-index: 50;
|
||
pointer-events: none;
|
||
display: none;
|
||
}
|
||
|
||
/* Persistent logo — outside Reveal container, fixed to viewport */
|
||
.ovh-logo-global {
|
||
position: fixed;
|
||
bottom: 16px;
|
||
left: 16px;
|
||
height: 28px;
|
||
width: auto;
|
||
opacity: 0.7;
|
||
z-index: 50;
|
||
pointer-events: none;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="top-strip"></div>
|
||
<img src="vendor/ovhcloud-logo.svg" alt="OVHcloud" class="ovh-logo-global">
|
||
<div class="reveal">
|
||
<div class="slides">
|
||
|
||
<!-- ─── SLIDE 1 : Title ─────────────────────────────────────────── -->
|
||
<section class="title-slide">
|
||
<h1>Configuration as Code</h1>
|
||
<p class="subtitle">
|
||
<img src="https://cdn.simpleicons.org/puppet/C17F00" alt="Puppet" style="height:0.9em; vertical-align:middle;"> Puppet ·
|
||
<img src="https://cdn.simpleicons.org/ansible" alt="Ansible" style="height:0.9em; vertical-align:middle;"> Ansible ·
|
||
<img src="https://cdn.simpleicons.org/terraform" alt="Terraform" style="height:0.9em; vertical-align:middle;"> Terraform
|
||
<br>— What's the difference and when to use what? —</p>
|
||
<p class="meta">FinistDevs · 2026</p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 2 : Speaker intro ────────────────────────────────── -->
|
||
<section>
|
||
<h2>Arnaud Prémel-Cabic</h2>
|
||
<p>Tech Lead @ OVHCloud</p>
|
||
<p><small style="color:var(--ods-text);">arnaud.premel-cabic@ovhcloud.com</small></p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 3 ──────────────────────────────────────────────────── -->
|
||
<section>
|
||
<h2>"It works on my server."</h2>
|
||
<p><em>Or: why doesn't it work here, when it works everywhere else?</em></p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 4 ──────────────────────────────────────────────────── -->
|
||
<section>
|
||
<h2>You have a server. It works.</h2>
|
||
<p><em>Great.</em></p>
|
||
<!-- SVG1 — single server, green check -->
|
||
<svg width="700" height="250" viewBox="0 0 700 250" style="margin-top:0.5em;" xmlns="http://www.w3.org/2000/svg">
|
||
<!-- Server body -->
|
||
<rect x="270" y="30" width="160" height="140" rx="12" ry="12" fill="#f0f2f8" stroke="#00185e" stroke-width="2.5"/>
|
||
<!-- Rack lines -->
|
||
<line x1="290" y1="65" x2="410" y2="65" stroke="#4d5592" stroke-width="1.5" stroke-linecap="round"/>
|
||
<line x1="290" y1="100" x2="410" y2="100" stroke="#4d5592" stroke-width="1.5" stroke-linecap="round"/>
|
||
<line x1="290" y1="135" x2="410" y2="135" stroke="#4d5592" stroke-width="1.5" stroke-linecap="round"/>
|
||
<!-- Drive bays / LEDs -->
|
||
<circle cx="298" cy="50" r="4" fill="#0050d7"/>
|
||
<circle cx="298" cy="85" r="4" fill="#0050d7"/>
|
||
<circle cx="298" cy="120" r="4" fill="#0050d7"/>
|
||
<!-- Power button -->
|
||
<circle cx="402" cy="155" r="5" fill="#00185e"/>
|
||
<!-- Server legs -->
|
||
<rect x="290" y="170" width="14" height="10" rx="2" fill="#666666"/>
|
||
<rect x="396" y="170" width="14" height="10" rx="2" fill="#666666"/>
|
||
<!-- Green checkmark circle -->
|
||
<circle cx="350" cy="215" r="22" fill="#2ecc71"/>
|
||
<polyline points="339,215 347,224 363,207" fill="none" stroke="#fff" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 5 ──────────────────────────────────────────────────── -->
|
||
<section>
|
||
<h2>You have 10 servers.</h2>
|
||
<p><em>Still manageable… but every small change means 10 SSH sessions, 10 vim edits, 10 chances to make a mistake.</em></p>
|
||
<!-- SVG2 — laptop + 10 servers fan-out -->
|
||
<svg width="720" height="260" viewBox="0 0 720 260" style="margin-top:0.5em;" xmlns="http://www.w3.org/2000/svg">
|
||
<!-- Laptop body -->
|
||
<rect x="20" y="60" width="120" height="80" rx="8" ry="8" fill="#f0f2f8" stroke="#00185e" stroke-width="2"/>
|
||
<!-- Screen inner -->
|
||
<rect x="30" y="68" width="100" height="56" rx="3" fill="#00185e"/>
|
||
<!-- Terminal prompt on screen -->
|
||
<text x="38" y="88" font-family="monospace" font-size="10" fill="#2ecc71">$</text>
|
||
<text x="48" y="88" font-family="monospace" font-size="9" fill="#aab4d5">ssh root@…</text>
|
||
<text x="38" y="103" font-family="monospace" font-size="10" fill="#2ecc71">$</text>
|
||
<text x="48" y="103" font-family="monospace" font-size="9" fill="#aab4d5">vim /etc/…</text>
|
||
<!-- Laptop base -->
|
||
<path d="M10,140 L150,140 L140,155 L20,155 Z" fill="#d8dce8" stroke="#00185e" stroke-width="1.5"/>
|
||
<!-- Keyboard hinge -->
|
||
<rect x="40" y="140" width="80" height="3" rx="1" fill="#666666"/>
|
||
<!-- 10 server icons on right, 2 columns of 5 -->
|
||
<!-- Dashed lines from laptop to each server -->
|
||
<!-- Column 1 (x=460) -->
|
||
<line x1="150" y1="100" x2="460" y2="18" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
|
||
<line x1="150" y1="100" x2="460" y2="66" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
|
||
<line x1="150" y1="100" x2="460" y2="114" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
|
||
<line x1="150" y1="100" x2="460" y2="162" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
|
||
<line x1="150" y1="100" x2="460" y2="210" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
|
||
<!-- Column 2 (x=590) -->
|
||
<line x1="150" y1="100" x2="590" y2="18" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
|
||
<line x1="150" y1="100" x2="590" y2="66" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
|
||
<line x1="150" y1="100" x2="590" y2="114" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
|
||
<line x1="150" y1="100" x2="590" y2="162" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
|
||
<line x1="150" y1="100" x2="590" y2="210" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
|
||
<!-- Arrow tips (small triangles) on server end -->
|
||
<polygon points="458,16 458,20 463,18" fill="#0050d7" opacity="0.7"/>
|
||
<polygon points="458,64 458,68 463,66" fill="#0050d7" opacity="0.7"/>
|
||
<polygon points="458,112 458,116 463,114" fill="#0050d7" opacity="0.7"/>
|
||
<polygon points="458,160 458,164 463,162" fill="#0050d7" opacity="0.7"/>
|
||
<polygon points="458,208 458,212 463,210" fill="#0050d7" opacity="0.7"/>
|
||
<polygon points="588,16 588,20 593,18" fill="#0050d7" opacity="0.7"/>
|
||
<polygon points="588,64 588,68 593,66" fill="#0050d7" opacity="0.7"/>
|
||
<polygon points="588,112 588,116 593,114" fill="#0050d7" opacity="0.7"/>
|
||
<polygon points="588,160 588,164 593,162" fill="#0050d7" opacity="0.7"/>
|
||
<polygon points="588,208 588,212 593,210" fill="#0050d7" opacity="0.7"/>
|
||
<!-- Column 1 servers -->
|
||
<rect x="465" y="5" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
|
||
<rect x="465" y="53" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
|
||
<rect x="465" y="101" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
|
||
<rect x="465" y="149" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
|
||
<rect x="465" y="197" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
|
||
<!-- Column 1 rack lines -->
|
||
<line x1="475" y1="18" x2="555" y2="18" stroke="#4d5592" stroke-width="1"/>
|
||
<line x1="475" y1="66" x2="555" y2="66" stroke="#4d5592" stroke-width="1"/>
|
||
<line x1="475" y1="114" x2="555" y2="114" stroke="#4d5592" stroke-width="1"/>
|
||
<line x1="475" y1="162" x2="555" y2="162" stroke="#4d5592" stroke-width="1"/>
|
||
<line x1="475" y1="210" x2="555" y2="210" stroke="#4d5592" stroke-width="1"/>
|
||
<!-- Column 1 LEDs -->
|
||
<circle cx="473" cy="12" r="2.5" fill="#0050d7"/>
|
||
<circle cx="473" cy="60" r="2.5" fill="#0050d7"/>
|
||
<circle cx="473" cy="108" r="2.5" fill="#0050d7"/>
|
||
<circle cx="473" cy="156" r="2.5" fill="#0050d7"/>
|
||
<circle cx="473" cy="204" r="2.5" fill="#0050d7"/>
|
||
<!-- Column 2 servers -->
|
||
<rect x="595" y="5" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
|
||
<rect x="595" y="53" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
|
||
<rect x="595" y="101" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
|
||
<rect x="595" y="149" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
|
||
<rect x="595" y="197" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
|
||
<!-- Column 2 rack lines -->
|
||
<line x1="605" y1="18" x2="685" y2="18" stroke="#4d5592" stroke-width="1"/>
|
||
<line x1="605" y1="66" x2="685" y2="66" stroke="#4d5592" stroke-width="1"/>
|
||
<line x1="605" y1="114" x2="685" y2="114" stroke="#4d5592" stroke-width="1"/>
|
||
<line x1="605" y1="162" x2="685" y2="162" stroke="#4d5592" stroke-width="1"/>
|
||
<line x1="605" y1="210" x2="685" y2="210" stroke="#4d5592" stroke-width="1"/>
|
||
<!-- Column 2 LEDs -->
|
||
<circle cx="603" cy="12" r="2.5" fill="#0050d7"/>
|
||
<circle cx="603" cy="60" r="2.5" fill="#0050d7"/>
|
||
<circle cx="603" cy="108" r="2.5" fill="#0050d7"/>
|
||
<circle cx="603" cy="156" r="2.5" fill="#0050d7"/>
|
||
<circle cx="603" cy="204" r="2.5" fill="#0050d7"/>
|
||
<!-- "ssh" labels floating near lines -->
|
||
<text x="260" y="65" font-family="monospace" font-size="9" fill="#0050d7" opacity="0.7" transform="rotate(-8,260,65)">ssh</text>
|
||
<text x="280" y="115" font-family="monospace" font-size="9" fill="#0050d7" opacity="0.7">ssh</text>
|
||
<text x="260" y="160" font-family="monospace" font-size="9" fill="#0050d7" opacity="0.7" transform="rotate(6,260,160)">ssh</text>
|
||
</svg>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 6 ──────────────────────────────────────────────────── -->
|
||
<section>
|
||
<h2>Now you have 100 servers.</h2>
|
||
<p><em>Some 2 years old. Some a couple of months. Some freshly provisioned.<br>None of them are exactly alike.</em></p>
|
||
<!-- SVG3 — 100-server chaos grid with drift indicators -->
|
||
<svg width="700" height="260" viewBox="0 0 700 260" style="margin-top:0.5em;" xmlns="http://www.w3.org/2000/svg">
|
||
<defs>
|
||
<!-- Warning triangle symbol -->
|
||
<symbol id="warn" viewBox="0 0 16 16">
|
||
<polygon points="8,1 15,15 1,15" fill="#e00034" stroke="#fff" stroke-width="0.5"/>
|
||
<text x="8" y="13" text-anchor="middle" font-size="10" font-weight="bold" fill="#fff">!</text>
|
||
</symbol>
|
||
<!-- Snowflake symbol -->
|
||
<symbol id="snow" viewBox="0 0 16 16">
|
||
<text x="8" y="14" text-anchor="middle" font-size="14" fill="#0050d7">❄</text>
|
||
</symbol>
|
||
</defs>
|
||
<!-- Generate a 10×6 grid of servers (60 shown, suggests ~100) -->
|
||
<!-- Row 0 -->
|
||
<rect x="10" y="5" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="78" y="5" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="146" y="5" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
|
||
<rect x="214" y="5" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="282" y="5" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
|
||
<rect x="350" y="5" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="418" y="5" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
|
||
<rect x="486" y="5" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
|
||
<rect x="554" y="5" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="622" y="5" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
|
||
<!-- Row 1 -->
|
||
<rect x="10" y="45" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
|
||
<rect x="78" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="146" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="214" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
|
||
<rect x="282" y="45" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
|
||
<rect x="350" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="418" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="486" y="45" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
|
||
<rect x="554" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="622" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
|
||
<!-- Row 2 -->
|
||
<rect x="10" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="78" y="85" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
|
||
<rect x="146" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
|
||
<rect x="214" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="282" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="350" y="85" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
|
||
<rect x="418" y="85" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
|
||
<rect x="486" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="554" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="622" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
|
||
<!-- Row 3 -->
|
||
<rect x="10" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
|
||
<rect x="78" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="146" y="125" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
|
||
<rect x="214" y="125" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
|
||
<rect x="282" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="350" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="418" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="486" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
|
||
<rect x="554" y="125" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
|
||
<rect x="622" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<!-- Row 4 -->
|
||
<rect x="10" y="165" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="78" y="165" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
|
||
<rect x="146" y="165" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="214" y="165" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="282" y="165" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
|
||
<rect x="350" y="165" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
|
||
<rect x="418" y="165" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="486" y="165" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
|
||
<rect x="554" y="165" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="622" y="165" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
|
||
<!-- Row 5 (partial — fades into "...more") -->
|
||
<rect x="10" y="205" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
|
||
<rect x="78" y="205" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="146" y="205" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
|
||
<rect x="214" y="205" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="282" y="205" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<rect x="350" y="205" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
|
||
<rect x="418" y="205" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
|
||
<!-- Ellipsis to suggest more -->
|
||
<text x="510" y="226" font-family="sans-serif" font-size="22" fill="#666666" letter-spacing="4">· · ·</text>
|
||
<!-- Rack lines on every server (subtle) -->
|
||
<g stroke="#4d5592" stroke-width="0.6" opacity="0.4">
|
||
<!-- Just add a mid-line to each server for texture -->
|
||
<line x1="18" y1="20" x2="60" y2="20"/> <line x1="86" y1="20" x2="128" y2="20"/>
|
||
<line x1="18" y1="60" x2="60" y2="60"/> <line x1="86" y1="60" x2="128" y2="60"/>
|
||
<line x1="18" y1="100" x2="60" y2="100"/> <line x1="86" y1="100" x2="128" y2="100"/>
|
||
<line x1="18" y1="140" x2="60" y2="140"/> <line x1="86" y1="140" x2="128" y2="140"/>
|
||
<line x1="18" y1="180" x2="60" y2="180"/> <line x1="86" y1="180" x2="128" y2="180"/>
|
||
<line x1="18" y1="220" x2="60" y2="220"/> <line x1="86" y1="220" x2="128" y2="220"/>
|
||
</g>
|
||
<!-- Warning triangles on red-tinted servers -->
|
||
<use href="#warn" x="50" y="7" width="14" height="14"/>
|
||
<use href="#warn" x="528" y="7" width="14" height="14"/>
|
||
<use href="#warn" x="324" y="47" width="14" height="14"/>
|
||
<use href="#warn" x="120" y="87" width="14" height="14"/>
|
||
<use href="#warn" x="460" y="87" width="14" height="14"/>
|
||
<use href="#warn" x="256" y="127" width="14" height="14"/>
|
||
<use href="#warn" x="324" y="167" width="14" height="14"/>
|
||
<use href="#warn" x="528" y="167" width="14" height="14"/>
|
||
<use href="#warn" x="392" y="207" width="14" height="14"/>
|
||
<!-- Snowflakes on blue-tinted servers (drift / unique configs) -->
|
||
<use href="#snow" x="324" y="6" width="14" height="14"/>
|
||
<use href="#snow" x="664" y="6" width="14" height="14"/>
|
||
<use href="#snow" x="52" y="46" width="14" height="14"/>
|
||
<use href="#snow" x="528" y="46" width="14" height="14"/>
|
||
<use href="#snow" x="392" y="86" width="14" height="14"/>
|
||
<use href="#snow" x="188" y="126" width="14" height="14"/>
|
||
<use href="#snow" x="596" y="126" width="14" height="14"/>
|
||
<use href="#snow" x="120" y="166" width="14" height="14"/>
|
||
<use href="#snow" x="664" y="166" width="14" height="14"/>
|
||
<use href="#snow" x="52" y="206" width="14" height="14"/>
|
||
<!-- Small status dots (mixed: green OK, amber, red) scattered -->
|
||
<circle cx="18" cy="12" r="2.5" fill="#2ecc71"/> <circle cx="86" cy="12" r="2.5" fill="#2ecc71"/>
|
||
<circle cx="154" cy="12" r="2.5" fill="#e00034"/> <circle cx="222" cy="12" r="2.5" fill="#2ecc71"/>
|
||
<circle cx="494" cy="12" r="2.5" fill="#e00034"/> <circle cx="562" cy="12" r="2.5" fill="#2ecc71"/>
|
||
<circle cx="18" cy="52" r="2.5" fill="#f39c12"/> <circle cx="86" cy="52" r="2.5" fill="#2ecc71"/>
|
||
<circle cx="154" cy="52" r="2.5" fill="#2ecc71"/> <circle cx="290" cy="52" r="2.5" fill="#e00034"/>
|
||
<circle cx="358" cy="52" r="2.5" fill="#2ecc71"/> <circle cx="426" cy="52" r="2.5" fill="#2ecc71"/>
|
||
<circle cx="18" cy="92" r="2.5" fill="#2ecc71"/> <circle cx="86" cy="92" r="2.5" fill="#e00034"/>
|
||
<circle cx="222" cy="92" r="2.5" fill="#2ecc71"/> <circle cx="290" cy="92" r="2.5" fill="#f39c12"/>
|
||
<circle cx="426" cy="92" r="2.5" fill="#e00034"/> <circle cx="494" cy="92" r="2.5" fill="#2ecc71"/>
|
||
<circle cx="18" cy="132" r="2.5" fill="#f39c12"/> <circle cx="86" cy="132" r="2.5" fill="#2ecc71"/>
|
||
<circle cx="222" cy="132" r="2.5" fill="#e00034"/> <circle cx="290" cy="132" r="2.5" fill="#2ecc71"/>
|
||
<circle cx="494" cy="132" r="2.5" fill="#f39c12"/>
|
||
<circle cx="18" cy="172" r="2.5" fill="#2ecc71"/> <circle cx="290" cy="172" r="2.5" fill="#e00034"/>
|
||
<circle cx="358" cy="172" r="2.5" fill="#f39c12"/> <circle cx="494" cy="172" r="2.5" fill="#e00034"/>
|
||
<circle cx="562" cy="172" r="2.5" fill="#2ecc71"/>
|
||
<circle cx="86" cy="212" r="2.5" fill="#2ecc71"/> <circle cx="222" cy="212" r="2.5" fill="#f39c12"/>
|
||
<circle cx="290" cy="212" r="2.5" fill="#2ecc71"/> <circle cx="358" cy="212" r="2.5" fill="#e00034"/>
|
||
</svg>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 7 ──────────────────────────────────────────────────── -->
|
||
<section>
|
||
<h2>Each one is unique. Unreproducible. Undocumented.</h2>
|
||
<p><em>Welcome to Snowflake Hell.</em></p>
|
||
<img src="https://media.giphy.com/media/QMHoU66sBXqqLqYvGO/giphy.gif"
|
||
alt="This is fine"
|
||
style="height:220px; margin-top:0.5em; border-radius:6px;">
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 8 ──────────────────────────────────────────────────── -->
|
||
<section>
|
||
<h2>Configuration drift is silent… until it isn't.</h2>
|
||
<p><em>Prod breaks on a Tuesday. You can't reproduce the bug locally. You can't scale reliably. You can't onboard a new server without fear.</em></p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 9 ──────────────────────────────────────────────────── -->
|
||
<section>
|
||
<h2>What if your infrastructure was just… code?</h2>
|
||
<p><em>Yes, even your AI assistant can write it. But you still need to understand what it deploys.</em></p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 10 ──────────────────────────────────────────────────── -->
|
||
<section>
|
||
<h2>Configuration as Code</h2>
|
||
<p>Managing infrastructure through machine-readable files, stored in version control.</p>
|
||
<p><small style="color:var(--ods-neutral-600);">Reproducible · Versionable · Auditable</small></p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 11 ──────────────────────────────────────────────────── -->
|
||
<section>
|
||
<h2>Meet the three musketeers of infrastructure.</h2>
|
||
<p><span class="puppet-col">Puppet</span> · <span class="ansible-col">Ansible</span> · <span class="tf-col">Terraform</span> — each fights a different battle.</p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 12 : Terraform intro ──────────────────────────────── -->
|
||
<section class="s-tf">
|
||
<h2><span class="tf-col">Terraform</span></h2>
|
||
<p>Start here. Before you configure a server, you need to have one.</p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 13 : Terraform concepts ───────────────────────────── -->
|
||
<section class="s-tf">
|
||
<h2>HCL: HashiCorp Configuration Language.</h2>
|
||
<p>Declarative, human-readable — and pure JSON works too.<br>
|
||
<code>terraform plan</code> previews · <code>terraform apply</code> creates · <code>terraform destroy</code> removes.</p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 14 : Terraform state ──────────────────────────────── -->
|
||
<section class="s-tf">
|
||
<h2>Terraform remembers what it built.</h2>
|
||
<p>The <code>.tfstate</code> file maps code to real-world resources. Store it remotely.<br>
|
||
Depending on what you manage, it can contain plaintext sensitive values — credentials, tokens, secrets.<br>
|
||
<em>Handle it with care. Don't feed it to your LLM.</em></p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 15 : Terraform providers ──────────────────────────── -->
|
||
<section class="s-tf">
|
||
<h2>One tool. Every API.</h2>
|
||
<p>1000+ providers: AWS, GCP, Azure, Cloudflare, GitHub, Kubernetes…<br>
|
||
Not just cloud — manage GitHub teams, Datadog monitors, PagerDuty schedules, DNS records.<br>
|
||
<em>If it has an API, there's a Terraform provider for it.</em></p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 16 : Terraform code ───────────────────────────────── -->
|
||
<section class="s-tf">
|
||
<p class="filename"># main.tf</p>
|
||
<pre><code class="language-hcl" data-trim>
|
||
terraform {
|
||
required_providers {
|
||
openstack = { source = "terraform-provider-openstack/openstack", version = "~> 3.0" }
|
||
ovh = { source = "ovh/ovh", version = "~> 1.0" }
|
||
}
|
||
}
|
||
|
||
resource "openstack_compute_instance_v2" "web" {
|
||
name = "finistdevs-web"
|
||
image_name = "Debian 12"
|
||
flavor_name = "b3-8"
|
||
network { name = "Ext-Net" }
|
||
}
|
||
|
||
resource "ovh_domain_zone_record" "web" {
|
||
zone = "example.com"
|
||
subdomain = "finistdevs"
|
||
fieldtype = "A"
|
||
target = openstack_compute_instance_v2.web.access_ip_v4
|
||
}
|
||
</code></pre>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 17 : OpenTofu ──────────────────────────────────────── -->
|
||
<section class="s-tf">
|
||
<h2>In 2023, HashiCorp changed Terraform's license.</h2>
|
||
<p>BSL instead of MPL — no longer truly open-source.<br>
|
||
The community responded: <strong>OpenTofu</strong>, by the OpenTF Foundation, is the open-source fork.<br>
|
||
<em>Drop-in replacement. Fully compatible. Community-driven.</em></p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 18 : Terraform platforms ──────────────────────────── -->
|
||
<section class="s-tf">
|
||
<h2>Terraform at scale needs a platform.</h2>
|
||
<p>
|
||
<strong>Terraform Enterprise / HCP Terraform</strong> — HashiCorp's commercial offering: remote state, RBAC, audit logs<br>
|
||
<strong>Spacelift</strong> — GitOps-first CI/CD for Terraform (and OpenTofu)<br>
|
||
<strong>Atlantis</strong> — open-source: plan & apply triggered by pull requests<br>
|
||
<strong>env0, Scalr</strong> — SaaS alternatives with policy & cost management
|
||
</p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 19 : Ansible intro ─────────────────────────────────── -->
|
||
<section class="s-ansible">
|
||
<h2><span class="ansible-col">Ansible</span></h2>
|
||
<p>Your servers are provisioned. Now make them do something.</p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 20 : Ansible concepts ──────────────────────────────── -->
|
||
<section class="s-ansible">
|
||
<h2>Push, not pull. SSH, not agents.</h2>
|
||
<p>YAML playbooks run tasks in order, across any number of hosts.<br>
|
||
No daemon. No certificate authority. Just Python + SSH.<br>
|
||
<em>Idempotent modules ensure the same playbook can run safely again and again.</em></p>
|
||
<img src="https://media.giphy.com/media/3oEjI6SIIHBdRxXI40/giphy.gif"
|
||
alt="It just works"
|
||
style="height:180px; margin-top:0.5em; border-radius:6px;">
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 21 : Ansible code ──────────────────────────────────── -->
|
||
<section class="s-ansible">
|
||
<p class="filename"># playbook/webserver.yml</p>
|
||
<pre><code class="language-yaml" data-trim>
|
||
- name: Configure web server
|
||
hosts: webservers
|
||
become: true
|
||
|
||
tasks:
|
||
- name: Install nginx
|
||
ansible.builtin.package:
|
||
name: nginx
|
||
state: present
|
||
|
||
- name: Deploy nginx config
|
||
ansible.builtin.template:
|
||
src: nginx.conf.j2
|
||
dest: /etc/nginx/nginx.conf
|
||
notify: Restart nginx
|
||
|
||
handlers:
|
||
- name: Restart nginx
|
||
ansible.builtin.service:
|
||
name: nginx
|
||
state: restarted
|
||
</code></pre>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 22 : Ansible operations ───────────────────────────── -->
|
||
<section class="s-ansible">
|
||
<h2>Not just configuration. Operations.</h2>
|
||
<p>Patch 200 servers tonight. Roll out a kernel upgrade with a canary strategy. Run a compliance audit across your whole fleet.<br>
|
||
<em>Ansible is the tool you reach for when you need to do something — once, or every week.</em></p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 23 : Ansible Galaxy ────────────────────────────────── -->
|
||
<section class="s-ansible">
|
||
<h2>The community does the heavy lifting.</h2>
|
||
<p><strong>Ansible Galaxy</strong> — 10,000+ ready-made roles and collections.<br>
|
||
Don't write a playbook to install Docker from scratch. Someone already did.<br>
|
||
<em>Just <code>ansible-galaxy install geerlingguy.docker</code>.</em></p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 24 : Ansible platforms ────────────────────────────── -->
|
||
<section class="s-ansible">
|
||
<h2>Ansible at scale: open-source vs enterprise.</h2>
|
||
<p>
|
||
<strong>AWX</strong> — open-source web UI, API, and scheduler for Ansible<br>
|
||
<strong>Ansible Automation Platform</strong> (Red Hat) — enterprise version of AWX, with support & integrations<br>
|
||
<strong>Semaphore</strong> — lightweight open-source alternative to AWX<br>
|
||
<em>The core Ansible engine remains Apache 2.0 — truly open-source.</em>
|
||
</p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 25 : Puppet intro ─────────────────────────────────── -->
|
||
<section class="s-puppet">
|
||
<h2><span class="puppet-col">Puppet</span></h2>
|
||
<p>Your servers are configured. Now keep them that way.</p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 26 : Puppet concepts ─────────────────────────────── -->
|
||
<section class="s-puppet">
|
||
<h2>Pull, not push. Agents, not SSH.</h2>
|
||
<p>Every 30 minutes, each puppet-agent polls the Puppet Server, compiles a catalog, and enforces it.<br>
|
||
<em>Drift is corrected automatically — without anyone lifting a finger.</em></p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 27 : Puppet code ──────────────────────────────────── -->
|
||
<section class="s-puppet">
|
||
<p class="filename"># manifests/webserver.pp</p>
|
||
<pre><code class="language-puppet" data-trim>
|
||
class webserver {
|
||
package { 'nginx':
|
||
ensure => installed,
|
||
}
|
||
|
||
file { '/etc/nginx/nginx.conf':
|
||
ensure => file,
|
||
content => template('webserver/nginx.conf.erb'),
|
||
notify => Service['nginx'],
|
||
}
|
||
|
||
service { 'nginx':
|
||
ensure => running,
|
||
enable => true,
|
||
}
|
||
}
|
||
</code></pre>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 28 : Puppet drift detection ──────────────────────── -->
|
||
<section class="s-puppet">
|
||
<h2>Someone SSH'd in and changed something.</h2>
|
||
<p>Puppet noticed. Puppet fixed it.<br>
|
||
<em>Continuous compliance — not just at deploy time. Every. 30. Minutes.</em><br>
|
||
No more gardening your servers by hand.</p>
|
||
<img src="https://media.giphy.com/media/l3q2K5jinAlChoCLS/giphy.gif"
|
||
alt="Automated maintenance"
|
||
style="height:180px; margin-top:0.5em; border-radius:6px;">
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 29 : Puppet platforms ────────────────────────────── -->
|
||
<section class="s-puppet">
|
||
<h2>Puppet is for large fleets that can't afford drift.</h2>
|
||
<p>Continuous compliance, auditability, and guaranteed state — at scale.<br>
|
||
Best suited for enterprises with hundreds or thousands of long-lived servers.<br>
|
||
<em>Fewer friendly SaaS options than Terraform or Ansible.<br>
|
||
Puppet Enterprise and Foreman are self-hosted. No managed cloud offering.</em></p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 30 : Puppet community ────────────────────────────── -->
|
||
<section class="s-puppet">
|
||
<h2>The ecosystem outlives the company.</h2>
|
||
<p><strong>Vox Pupuli</strong> — 100+ open-source Puppet modules, community-maintained.<br>
|
||
<strong>OpenVox</strong> — an emerging open-source fork of the Puppet core.<br>
|
||
<em>The community is strong, with or without Puppet Inc.</em></p>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 31 : They're complementary ───────────────────────── -->
|
||
<section>
|
||
<h2>They're not competing. They're complementary.</h2>
|
||
<p>Each one solves a different layer of the same problem.</p>
|
||
<img src="https://media.giphy.com/media/j2pWZpr5RlpCodOB0d/giphy.gif"
|
||
alt="Assembling"
|
||
style="height:200px; margin-top:0.5em; border-radius:6px;">
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 32 : Real-world stack ─────────────────────────────── -->
|
||
<section>
|
||
<h2>A common production setup:</h2>
|
||
<ol>
|
||
<li class="fragment"><span class="tf-col">Terraform</span> provisions the VM</li>
|
||
<li class="fragment"><span class="ansible-col">Ansible</span> configures it and deploys the app</li>
|
||
<li class="fragment"><span class="puppet-col">Puppet</span> continuously enforces compliance</li>
|
||
</ol>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 33 : Closing ──────────────────────────────────────── -->
|
||
<section class="title-slide">
|
||
<h1>Questions?</h1>
|
||
<p class="subtitle">Thank you!</p>
|
||
<p class="meta" style="margin-top:2em;">
|
||
Arnaud Prémel-Cabic · arnaud.premel-cabic@ovhcloud.com<br>
|
||
Slides: <a href="https://ministicraft.pages.git.cloud.arnaud-pc.fr/finistdev-configuration-as-code/" target="_blank">ministicraft.pages.git.cloud.arnaud-pc.fr/finistdev-configuration-as-code/</a>
|
||
</p>
|
||
</section>
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<script src="vendor/reveal.js/dist/reveal.js"></script>
|
||
<script src="vendor/reveal.js/plugin/notes/notes.js"></script>
|
||
<script src="vendor/reveal.js/plugin/highlight/highlight.js"></script>
|
||
<script>
|
||
const toolLogos = {
|
||
's-tf': 'https://cdn.simpleicons.org/terraform',
|
||
's-ansible': 'https://cdn.simpleicons.org/ansible',
|
||
's-puppet': 'https://cdn.simpleicons.org/puppet/C17F00'
|
||
};
|
||
const altNames = {'s-tf': 'Terraform', 's-ansible': 'Ansible', 's-puppet': 'Puppet'};
|
||
|
||
// Create fixed logo elements outside Reveal (like OVHcloud logo)
|
||
const logoElements = {};
|
||
Object.entries(toolLogos).forEach(([cls, src]) => {
|
||
const img = document.createElement('img');
|
||
img.src = src;
|
||
img.alt = altNames[cls];
|
||
img.className = 'tool-logo-global';
|
||
document.body.appendChild(img);
|
||
logoElements[cls] = img;
|
||
});
|
||
|
||
// Show/hide the correct tool logo based on current slide's class
|
||
function updateToolLogo() {
|
||
const slide = Reveal.getCurrentSlide();
|
||
Object.entries(logoElements).forEach(([cls, img]) => {
|
||
img.style.display = slide && slide.classList.contains(cls) ? 'block' : 'none';
|
||
});
|
||
}
|
||
|
||
Reveal.initialize({
|
||
width: 1280,
|
||
height: 720,
|
||
margin: 0.04,
|
||
minScale: 0.2,
|
||
maxScale: 2.0,
|
||
hash: true,
|
||
slideNumber: 'c/t',
|
||
transition: 'slide',
|
||
backgroundTransition: 'fade',
|
||
controls: true,
|
||
progress: true,
|
||
center: true,
|
||
plugins: [ RevealNotes, RevealHighlight ]
|
||
}).then(updateToolLogo);
|
||
|
||
Reveal.on('slidechanged', updateToolLogo);
|
||
</script>
|
||
</body>
|
||
</html>
|