Keep the slide bullet concise; detail the Nov 2024 Perforce binary lockdown, the developer/commercial EULA, and the Jan 2025 OpenVox 8.11 fork in the notes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1532 lines
89 KiB
HTML
1532 lines
89 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 .slides section svg,
|
||
.reveal .slides section > img {
|
||
display: block;
|
||
margin-left: auto;
|
||
margin-right: auto;
|
||
}
|
||
|
||
.reveal h1, .reveal h2 { text-align: left; }
|
||
.reveal h1 { font-size: 1.8em; }
|
||
.reveal h2 { font-size: 1.05em; }
|
||
|
||
/* Content bullet lists sit a touch smaller than the heading */
|
||
.reveal .slides section ul { font-size: 0.9em; }
|
||
|
||
/* Section-divider titles (Terraform / Ansible / Puppet) stay large */
|
||
.reveal h2 .tf-col,
|
||
.reveal h2 .ansible-col,
|
||
.reveal h2 .puppet-col { font-size: 2em; }
|
||
|
||
.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>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Welcome — quick intro to the topic: how we manage infrastructure as code.</li>
|
||
<li>Three tools compared: Puppet, Ansible, Terraform — what each is for, when to pick which.</li>
|
||
<li>Goal: not "which is best" but understanding their distinct roles.</li>
|
||
</ul>
|
||
</aside>
|
||
</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>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Tech Lead at OVHcloud — work with these tools daily.</li>
|
||
<li>Keep it short, get to the content.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 3 ─────────────────────────────────────────────────── -->
|
||
<section>
|
||
<h2>"It works on my server."</h2>
|
||
<p><em>Why doesn't it work here when it works everywhere else?</em></p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>The classic excuse — everyone's heard it (or said it).</li>
|
||
<li>Hook: the real problem isn't the code, it's the un-managed environment.</li>
|
||
<li>Sets up the whole talk: configuration we can't reproduce.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 4 ─────────────────────────────────────────────────── -->
|
||
<section>
|
||
<h2>You have a server. It works.</h2>
|
||
<p><em>Great.</em></p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>One server, configured by hand — totally fine at this scale.</li>
|
||
<li>No tooling needed yet. The pain starts when you grow.</li>
|
||
</ul>
|
||
</aside>
|
||
<!-- 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>
|
||
<ul>
|
||
<li>10 SSH sessions</li>
|
||
<li>10 manual edits</li>
|
||
<li>10 chances to make a mistake</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Manual edits don't scale — repetition breeds error.</li>
|
||
<li>Did you apply the change to all 10? Identically? You can't be sure.</li>
|
||
</ul>
|
||
</aside>
|
||
<!-- 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>
|
||
<ul>
|
||
<li>Some 2 years old. Some a few months. Some brand new.</li>
|
||
<li>None of them are exactly alike.</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Different ages, different patch levels, manual tweaks over time.</li>
|
||
<li>Point at the snowflakes/warnings in the diagram — each box is subtly different.</li>
|
||
<li>This is the reality of a hand-managed fleet.</li>
|
||
</ul>
|
||
</aside>
|
||
<!-- 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>Unique. Unreproducible. Undocumented.</h2>
|
||
<p><em>Welcome to configuration drift.</em></p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>"Snowflake servers" — unique, fragile, impossible to recreate.</li>
|
||
<li>The "this is fine" meme — we've all normalized this chaos.</li>
|
||
<li>Lighten the mood, then pivot to the consequences.</li>
|
||
</ul>
|
||
</aside>
|
||
<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…</h2>
|
||
<ul>
|
||
<li>Can't reproduce the bug locally</li>
|
||
<li>Can't scale reliably</li>
|
||
<li>Can't onboard a new server without fear</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Define drift: state slowly diverging from intent, T0 → T2 in the diagram.</li>
|
||
<li>It's silent — no alarm goes off until prod breaks.</li>
|
||
<li>These four pains are why we need Configuration as Code.</li>
|
||
</ul>
|
||
</aside>
|
||
<svg width="750" height="220" style="margin-top:0.5em;" viewBox="0 0 750 220" xmlns="http://www.w3.org/2000/svg">
|
||
<line x1="60" y1="180" x2="700" y2="180" stroke="#666666" stroke-width="2.5"/>
|
||
<polygon points="700,174 720,180 700,186" fill="#666666"/>
|
||
<text x="140" y="210" text-anchor="middle" font-family="system-ui,sans-serif" font-size="14" fill="#4d5592" font-weight="600">T0</text>
|
||
<text x="390" y="210" text-anchor="middle" font-family="system-ui,sans-serif" font-size="14" fill="#4d5592" font-weight="600">T1</text>
|
||
<text x="630" y="210" text-anchor="middle" font-family="system-ui,sans-serif" font-size="14" fill="#4d5592" font-weight="600">T2</text>
|
||
<line x1="140" y1="175" x2="140" y2="185" stroke="#666666" stroke-width="2"/>
|
||
<line x1="390" y1="175" x2="390" y2="185" stroke="#666666" stroke-width="2"/>
|
||
<line x1="630" y1="175" x2="630" y2="185" stroke="#666666" stroke-width="2"/>
|
||
<!-- T0: identical servers -->
|
||
<rect x="90" y="70" width="28" height="36" rx="4" fill="#0050d7" opacity="0.9"/>
|
||
<rect x="122" y="70" width="28" height="36" rx="4" fill="#0050d7" opacity="0.9"/>
|
||
<rect x="154" y="70" width="28" height="36" rx="4" fill="#0050d7" opacity="0.9"/>
|
||
<rect x="186" y="70" width="28" height="36" rx="4" fill="#0050d7" opacity="0.9"/>
|
||
<text x="140" y="55" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#00a344" font-weight="600">ALL IDENTICAL</text>
|
||
<!-- T1: drifting -->
|
||
<rect x="340" y="70" width="28" height="36" rx="4" fill="#0050d7" opacity="0.9"/>
|
||
<rect x="372" y="68" width="28" height="36" rx="4" fill="#0050d7" opacity="0.7"/>
|
||
<rect x="404" y="72" width="28" height="36" rx="4" fill="#4d5592" opacity="0.85"/>
|
||
<rect x="436" y="70" width="28" height="36" rx="4" fill="#0050d7" opacity="0.9"/>
|
||
<text x="390" y="55" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#4d5592" font-weight="600">DRIFTING…</text>
|
||
<!-- T2: chaos -->
|
||
<rect x="580" y="74" width="28" height="36" rx="4" fill="#e00034" opacity="0.85"/>
|
||
<rect x="612" y="66" width="28" height="36" rx="4" fill="#4d5592" opacity="0.8"/>
|
||
<rect x="644" y="78" width="28" height="36" rx="4" fill="#00185e" opacity="0.9"/>
|
||
<rect x="676" y="70" width="28" height="36" rx="4" fill="#e00034" opacity="0.7"/>
|
||
<polygon points="594,68 600,58 606,68" fill="#e00034" stroke="white" stroke-width="1"/>
|
||
<text x="600" y="67" text-anchor="middle" font-family="system-ui,sans-serif" font-size="8" fill="white" font-weight="bold">!</text>
|
||
<polygon points="654,72 660,62 666,72" fill="#e00034" stroke="white" stroke-width="1"/>
|
||
<text x="660" y="71" text-anchor="middle" font-family="system-ui,sans-serif" font-size="8" fill="white" font-weight="bold">!</text>
|
||
<polygon points="684,64 690,54 696,64" fill="#e00034" stroke="white" stroke-width="1"/>
|
||
<text x="690" y="63" text-anchor="middle" font-family="system-ui,sans-serif" font-size="8" fill="white" font-weight="bold">!</text>
|
||
<text x="630" y="48" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#e00034" font-weight="700">CHAOS</text>
|
||
</svg>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 9 ─────────────────────────────────────────────────── -->
|
||
<section style="height:100%;">
|
||
<h2>What if your infrastructure was just… code?</h2>
|
||
<p><em>Your AI assistant can write it*. You still need to understand what it deploys.</em></p>
|
||
<svg width="750" height="220" style="margin-top:0.5em;" viewBox="0 0 750 220" xmlns="http://www.w3.org/2000/svg">
|
||
<!-- Code file icon -->
|
||
<rect x="80" y="35" width="120" height="150" rx="6" fill="none" stroke="#0050d7" stroke-width="2.5"/>
|
||
<path d="M165,35 L200,35 L200,70 L165,70 Z" fill="white" stroke="white" stroke-width="3"/>
|
||
<path d="M165,35 L200,70" stroke="#0050d7" stroke-width="2.5" fill="none"/>
|
||
<line x1="165" y1="35" x2="165" y2="70" stroke="#0050d7" stroke-width="2.5"/>
|
||
<line x1="165" y1="70" x2="200" y2="70" stroke="#0050d7" stroke-width="2.5"/>
|
||
<text x="140" y="108" text-anchor="middle" font-family="monospace" font-size="30" fill="#0050d7" font-weight="700">{ }</text>
|
||
<line x1="105" y1="128" x2="175" y2="128" stroke="#4d5592" stroke-width="2" opacity="0.4"/>
|
||
<line x1="112" y1="140" x2="168" y2="140" stroke="#4d5592" stroke-width="2" opacity="0.3"/>
|
||
<line x1="108" y1="152" x2="172" y2="152" stroke="#4d5592" stroke-width="2" opacity="0.4"/>
|
||
<!-- Arrow -->
|
||
<line x1="260" y1="110" x2="420" y2="110" stroke="#0050d7" stroke-width="4" stroke-linecap="round"/>
|
||
<polygon points="420,98 448,110 420,122" fill="#0050d7"/>
|
||
<text x="340" y="95" text-anchor="middle" font-family="system-ui,sans-serif" font-size="13" fill="#4d5592" font-weight="600">deploy</text>
|
||
<!-- Cloud with servers -->
|
||
<path d="M530,160 Q470,160 480,120 Q470,80 510,75 Q525,45 570,50 Q610,40 630,65 Q680,60 685,95 Q710,105 700,135 Q705,165 670,160 Z" fill="none" stroke="#0050d7" stroke-width="2.5" opacity="0.8"/>
|
||
<rect x="520" y="90" width="36" height="44" rx="5" fill="#0050d7" opacity="0.15" stroke="#0050d7" stroke-width="1.5"/>
|
||
<circle cx="548" cy="124" r="3" fill="#00a344"/>
|
||
<rect x="572" y="90" width="36" height="44" rx="5" fill="#0050d7" opacity="0.15" stroke="#0050d7" stroke-width="1.5"/>
|
||
<circle cx="600" cy="124" r="3" fill="#00a344"/>
|
||
<rect x="624" y="90" width="36" height="44" rx="5" fill="#0050d7" opacity="0.15" stroke="#0050d7" stroke-width="1.5"/>
|
||
<circle cx="652" cy="124" r="3" fill="#00a344"/>
|
||
</svg>
|
||
<small style="position:absolute; bottom:40px; right:0; color:var(--ods-neutral-600); font-size:0.45em;">*Like this presentation 🤖</small>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>The pitch: describe infra in files, deploy reproducibly.</li>
|
||
<li>Aside on AI: it can write the code, but you must understand what it deploys — own the result.</li>
|
||
<li>Fun fact: this deck itself was built with AI assistance.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 10 ────────────────────────────────────────────────── -->
|
||
<section>
|
||
<h2>Configuration as Code</h2>
|
||
<p>Machine-readable files. Version-controlled. Automated.</p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Three core benefits: reproducible, versionable, auditable.</li>
|
||
<li>Same input → same result. Git history = change log. Know who changed what & when.</li>
|
||
<li>This is the foundation all three tools share.</li>
|
||
</ul>
|
||
</aside>
|
||
<svg width="750" height="220" style="margin-top:0.5em;" viewBox="0 0 750 220" xmlns="http://www.w3.org/2000/svg">
|
||
<!-- Reproducible -->
|
||
<g transform="translate(125,80)">
|
||
<path d="M0,-32 A32,32 0 1,1 -22,22" fill="none" stroke="#0050d7" stroke-width="3" stroke-linecap="round"/>
|
||
<polygon points="-28,14 -22,26 -14,16" fill="#0050d7"/>
|
||
<path d="M0,32 A32,32 0 1,1 22,-22" fill="none" stroke="#0050d7" stroke-width="3" stroke-linecap="round"/>
|
||
<polygon points="28,-14 22,-26 14,-16" fill="#0050d7"/>
|
||
</g>
|
||
<text x="125" y="145" text-anchor="middle" font-family="system-ui,sans-serif" font-size="16" fill="#00185e" font-weight="700">Reproducible</text>
|
||
<text x="125" y="165" text-anchor="middle" font-family="system-ui,sans-serif" font-size="12" fill="#4d5592">Same input → same result</text>
|
||
<line x1="250" y1="40" x2="250" y2="180" stroke="#4d5592" stroke-width="0.5" opacity="0.3"/>
|
||
<!-- Versionable -->
|
||
<g transform="translate(375,80)">
|
||
<line x1="0" y1="-35" x2="0" y2="35" stroke="#0050d7" stroke-width="3" stroke-linecap="round"/>
|
||
<path d="M0,-5 Q20,-5 25,-25" fill="none" stroke="#0050d7" stroke-width="3" stroke-linecap="round"/>
|
||
<circle cx="0" cy="-30" r="5" fill="#0050d7"/>
|
||
<circle cx="0" cy="-5" r="5" fill="#0050d7"/>
|
||
<circle cx="0" cy="20" r="5" fill="#0050d7"/>
|
||
<circle cx="25" cy="-25" r="5" fill="#0050d7" opacity="0.7"/>
|
||
<path d="M25,-25 Q30,-10 0,20" fill="none" stroke="#0050d7" stroke-width="2.5" stroke-linecap="round" stroke-dasharray="4,3"/>
|
||
</g>
|
||
<text x="375" y="145" text-anchor="middle" font-family="system-ui,sans-serif" font-size="16" fill="#00185e" font-weight="700">Versionable</text>
|
||
<text x="375" y="165" text-anchor="middle" font-family="system-ui,sans-serif" font-size="12" fill="#4d5592">Track every change</text>
|
||
<line x1="500" y1="40" x2="500" y2="180" stroke="#4d5592" stroke-width="0.5" opacity="0.3"/>
|
||
<!-- Auditable -->
|
||
<g transform="translate(625,72)">
|
||
<circle cx="-8" cy="-8" r="22" fill="none" stroke="#0050d7" stroke-width="3"/>
|
||
<line x1="8" y1="8" x2="24" y2="24" stroke="#0050d7" stroke-width="4" stroke-linecap="round"/>
|
||
<polyline points="-18,-14 -14,-10 -6,-18" fill="none" stroke="#0050d7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
<line x1="-2" y1="-14" x2="8" y2="-14" stroke="#0050d7" stroke-width="1.5" opacity="0.5"/>
|
||
<polyline points="-18,-2 -14,2 -6,-6" fill="none" stroke="#0050d7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
<line x1="-2" y1="-2" x2="8" y2="-2" stroke="#0050d7" stroke-width="1.5" opacity="0.5"/>
|
||
</g>
|
||
<text x="625" y="145" text-anchor="middle" font-family="system-ui,sans-serif" font-size="16" fill="#00185e" font-weight="700">Auditable</text>
|
||
<text x="625" y="165" text-anchor="middle" font-family="system-ui,sans-serif" font-size="12" fill="#4d5592">Who changed what & when</text>
|
||
</svg>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 11 ────────────────────────────────────────────────── -->
|
||
<section style="text-align:center;">
|
||
<h2 style="text-align:center;">Meet the three musketeers of infrastructure.</h2>
|
||
<p style="font-size:1.2em; margin-top:1em;">
|
||
<img src="https://cdn.simpleicons.org/puppet/C17F00" alt="Puppet" style="height:0.9em; vertical-align:middle;"> <span class="puppet-col">Puppet</span> ·
|
||
<img src="https://cdn.simpleicons.org/ansible" alt="Ansible" style="height:0.9em; vertical-align:middle;"> <span class="ansible-col">Ansible</span> ·
|
||
<img src="https://cdn.simpleicons.org/terraform" alt="Terraform" style="height:0.9em; vertical-align:middle;"> <span class="tf-col">Terraform</span>
|
||
</p>
|
||
<p><em>Each solves a different problem.</em></p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Introduce the three: Puppet, Ansible, Terraform.</li>
|
||
<li>Tease the punchline early: they're complementary, not rivals.</li>
|
||
<li>We'll go through them in deploy order: provision → configure → enforce.</li>
|
||
</ul>
|
||
</aside>
|
||
</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>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>First layer: provisioning. You can't configure what doesn't exist.</li>
|
||
<li>Terraform's job is creating the infrastructure itself.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
|
||
<!-- ─── SLIDE 13 : What is Terraform ────────────────────────────── -->
|
||
<section class="s-tf">
|
||
<h2>What is Terraform?</h2>
|
||
<ul>
|
||
<li><strong>Infrastructure as Code</strong> tool for provisioning cloud resources</li>
|
||
<li>Created by <strong>HashiCorp</strong> in 2014</li>
|
||
<li>Written in <strong>Go</strong></li>
|
||
<li>BUSL 1.1 license since 2023 (was MPL)</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>The IaC standard for cloud provisioning.</li>
|
||
<li>HashiCorp, 2014, Go. Flag the 2023 license change — we'll come back to it (OpenTofu).</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 14 : Terraform concepts ───────────────────────────── -->
|
||
<section class="s-tf">
|
||
<h2>HCL: HashiCorp Configuration Language</h2>
|
||
<p>Declarative, human-readable — pure JSON works too.</p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Declarative: you describe the desired end state, not the steps.</li>
|
||
<li>HCL is the common language across all HashiCorp tools.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 15 : Terraform workflow ───────────────────────────── -->
|
||
<section class="s-tf">
|
||
<h2>The Terraform workflow</h2>
|
||
<ul>
|
||
<li><code>terraform plan</code> — preview what will change</li>
|
||
<li><strong>Review</strong> — validate the plan before proceeding</li>
|
||
<li><code>terraform apply</code> — create or update resources</li>
|
||
<li><code>terraform destroy</code> — tear everything down</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Key safety feature: plan lets you preview before anything changes.</li>
|
||
<li>Always review the plan — this is what makes Terraform safe in prod.</li>
|
||
<li>apply is idempotent; destroy cleanly removes everything it created.</li>
|
||
</ul>
|
||
</aside>
|
||
<svg width="900" height="150" style="margin-top:0.5em;" viewBox="0 0 900 150" xmlns="http://www.w3.org/2000/svg">
|
||
<defs>
|
||
<marker id="wf-arrow" viewBox="0 0 10 7" refX="10" refY="3.5" markerWidth="10" markerHeight="7" orient="auto-start-auto">
|
||
<path d="M0,0 L10,3.5 L0,7z" fill="#7B42BC"/>
|
||
</marker>
|
||
</defs>
|
||
<!-- Box 1: HCL Code -->
|
||
<rect x="10" y="15" width="140" height="100" rx="12" fill="none" stroke="#7B42BC" stroke-width="2.2"/>
|
||
<g transform="translate(50,28)">
|
||
<path d="M0,4 L0,30 Q0,33 3,33 L27,33 Q30,33 30,30 L30,10 L20,0 L3,0 Q0,0 0,4z" fill="none" stroke="#7B42BC" stroke-width="1.6"/>
|
||
<path d="M20,0 L20,7 Q20,10 23,10 L30,10" fill="none" stroke="#7B42BC" stroke-width="1.4"/>
|
||
</g>
|
||
<text x="80" y="92" text-anchor="middle" font-family="system-ui,sans-serif" font-size="14" font-weight="600" fill="#00185e">HCL Code</text>
|
||
<!-- Arrow 1→2 -->
|
||
<line x1="158" y1="65" x2="195" y2="65" stroke="#7B42BC" stroke-width="2" marker-end="url(#wf-arrow)"/>
|
||
<!-- Box 2: plan -->
|
||
<rect x="203" y="15" width="140" height="100" rx="12" fill="none" stroke="#7B42BC" stroke-width="2.2"/>
|
||
<g transform="translate(248,30)">
|
||
<ellipse cx="15" cy="15" rx="18" ry="12" fill="none" stroke="#7B42BC" stroke-width="1.6"/>
|
||
<circle cx="15" cy="15" r="6" fill="none" stroke="#7B42BC" stroke-width="1.6"/>
|
||
<circle cx="15" cy="15" r="2.5" fill="#7B42BC"/>
|
||
</g>
|
||
<text x="273" y="92" text-anchor="middle" font-family="monospace" font-size="14" font-weight="600" fill="#00185e">plan</text>
|
||
<!-- Arrow 2→3 -->
|
||
<line x1="351" y1="65" x2="388" y2="65" stroke="#7B42BC" stroke-width="2" marker-end="url(#wf-arrow)"/>
|
||
<!-- Box 3: review (new) -->
|
||
<rect x="396" y="15" width="140" height="100" rx="12" fill="#f6f0ff" stroke="#7B42BC" stroke-width="2.2" stroke-dasharray="6,3"/>
|
||
<g transform="translate(446,28)">
|
||
<polyline points="3,18 10,25 25,8" fill="none" stroke="#7B42BC" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</g>
|
||
<text x="466" y="92" text-anchor="middle" font-family="system-ui,sans-serif" font-size="14" font-weight="600" fill="#00185e">review</text>
|
||
<!-- Arrow 3→4 -->
|
||
<line x1="544" y1="65" x2="581" y2="65" stroke="#7B42BC" stroke-width="2" marker-end="url(#wf-arrow)"/>
|
||
<!-- Box 4: apply -->
|
||
<rect x="589" y="15" width="140" height="100" rx="12" fill="none" stroke="#7B42BC" stroke-width="2.2"/>
|
||
<g transform="translate(634,30)">
|
||
<polygon points="5,2 30,15 5,28" fill="none" stroke="#7B42BC" stroke-width="1.8" stroke-linejoin="round"/>
|
||
</g>
|
||
<text x="659" y="92" text-anchor="middle" font-family="monospace" font-size="14" font-weight="600" fill="#00185e">apply</text>
|
||
<!-- Arrow 4→5 -->
|
||
<line x1="737" y1="65" x2="774" y2="65" stroke="#7B42BC" stroke-width="2" marker-end="url(#wf-arrow)"/>
|
||
<!-- Box 5: Resources -->
|
||
<rect x="782" y="15" width="108" height="100" rx="12" fill="none" stroke="#7B42BC" stroke-width="2.2"/>
|
||
<g transform="translate(810,28)">
|
||
<path d="M12,28 Q0,28 0,20 Q0,14 6,12 Q6,4 15,2 Q24,0 28,6 Q29,5 32,5 Q38,5 38,11 Q44,12 44,18 Q44,24 38,25 Q38,28 34,28z" fill="none" stroke="#7B42BC" stroke-width="1.6"/>
|
||
</g>
|
||
<text x="836" y="92" text-anchor="middle" font-family="system-ui,sans-serif" font-size="13" font-weight="600" fill="#00185e">Resources</text>
|
||
</svg>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 16 : Terraform state ──────────────────────────────── -->
|
||
<section class="s-tf">
|
||
<h2>Terraform remembers what it built.</h2>
|
||
<ul>
|
||
<li>The <code>.tfstate</code> file maps code to real-world resources</li>
|
||
<li>Store it <strong>remotely</strong> — never commit it to Git</li>
|
||
<li>May contain sensitive values: credentials, tokens, secrets</li>
|
||
</ul>
|
||
<p><em>Handle with care.</em></p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>State is how Terraform knows what it already built — maps code to real resources.</li>
|
||
<li>Two big gotchas: store it remotely (team access + locking), never commit it (secrets inside).</li>
|
||
<li>Lost or corrupt state = Terraform loses track of reality.</li>
|
||
</ul>
|
||
</aside>
|
||
<svg width="750" height="220" style="margin-top:0.5em;" viewBox="0 0 750 220" xmlns="http://www.w3.org/2000/svg">
|
||
<defs>
|
||
<marker id="st-arrow" viewBox="0 0 8 6" refX="8" refY="3" markerWidth="8" markerHeight="6" orient="auto-start-auto">
|
||
<path d="M0,0 L8,3 L0,6z" fill="#7B42BC"/>
|
||
</marker>
|
||
</defs>
|
||
<!-- Left: HCL files -->
|
||
<g transform="translate(30,18)">
|
||
<rect width="80" height="48" rx="6" fill="#f6f0ff" stroke="#7B42BC" stroke-width="1.5"/>
|
||
<text x="40" y="44" text-anchor="middle" font-family="monospace" font-size="9" fill="#4d5592">main.tf</text>
|
||
</g>
|
||
<g transform="translate(30,85)">
|
||
<rect width="80" height="48" rx="6" fill="#f6f0ff" stroke="#7B42BC" stroke-width="1.5"/>
|
||
<text x="40" y="44" text-anchor="middle" font-family="monospace" font-size="9" fill="#4d5592">network.tf</text>
|
||
</g>
|
||
<g transform="translate(30,152)">
|
||
<rect width="80" height="48" rx="6" fill="#f6f0ff" stroke="#7B42BC" stroke-width="1.5"/>
|
||
<text x="40" y="44" text-anchor="middle" font-family="monospace" font-size="9" fill="#4d5592">dns.tf</text>
|
||
</g>
|
||
<text x="70" y="215" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#666666">Configuration</text>
|
||
<!-- Lines to center -->
|
||
<line x1="115" y1="42" x2="280" y2="88" stroke="#7B42BC" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#st-arrow)"/>
|
||
<line x1="115" y1="109" x2="280" y2="108" stroke="#7B42BC" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#st-arrow)"/>
|
||
<line x1="115" y1="176" x2="280" y2="128" stroke="#7B42BC" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#st-arrow)"/>
|
||
<!-- Center: .tfstate cylinder -->
|
||
<g transform="translate(285,42)">
|
||
<rect y="18" width="180" height="100" fill="#f6f0ff" stroke="#7B42BC" stroke-width="2"/>
|
||
<ellipse cx="90" cy="18" rx="90" ry="18" fill="#f6f0ff" stroke="#7B42BC" stroke-width="2"/>
|
||
<path d="M0,118 Q0,136 90,136 Q180,136 180,118" fill="#f6f0ff" stroke="#7B42BC" stroke-width="2"/>
|
||
<rect x="1" y="100" width="178" height="19" fill="#f6f0ff" stroke="none"/>
|
||
<line x1="0" y1="18" x2="0" y2="118" stroke="#7B42BC" stroke-width="2"/>
|
||
<line x1="180" y1="18" x2="180" y2="118" stroke="#7B42BC" stroke-width="2"/>
|
||
<text x="90" y="78" text-anchor="middle" font-family="monospace" font-size="20" font-weight="700" fill="#00185e">.tfstate</text>
|
||
<text x="90" y="98" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#4d5592">state mapping</text>
|
||
<g transform="translate(72,-8)">
|
||
<rect x="8" y="12" width="20" height="15" rx="3" fill="#FFD54F" stroke="#E65100" stroke-width="1.3"/>
|
||
<path d="M12,12 L12,7 Q12,1 18,1 Q24,1 24,7 L24,12" fill="none" stroke="#E65100" stroke-width="1.5"/>
|
||
<circle cx="18" cy="19" r="2" fill="#E65100"/>
|
||
</g>
|
||
</g>
|
||
<text x="375" y="215" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#666666">Source of Truth</text>
|
||
<!-- Lines to right -->
|
||
<line x1="470" y1="88" x2="595" y2="42" stroke="#00a344" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#st-arrow)"/>
|
||
<line x1="470" y1="108" x2="595" y2="109" stroke="#00a344" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#st-arrow)"/>
|
||
<line x1="470" y1="128" x2="595" y2="176" stroke="#00a344" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#st-arrow)"/>
|
||
<!-- Right: cloud resources -->
|
||
<g transform="translate(600,18)">
|
||
<rect width="80" height="48" rx="6" fill="#e8f5e9" stroke="#00a344" stroke-width="1.5"/>
|
||
<text x="40" y="42" text-anchor="middle" font-family="system-ui,sans-serif" font-size="10" font-weight="600" fill="#00185e">VM</text>
|
||
</g>
|
||
<g transform="translate(600,85)">
|
||
<rect width="80" height="48" rx="6" fill="#e8f5e9" stroke="#00a344" stroke-width="1.5"/>
|
||
<text x="40" y="42" text-anchor="middle" font-family="system-ui,sans-serif" font-size="10" font-weight="600" fill="#00185e">VNet</text>
|
||
</g>
|
||
<g transform="translate(600,152)">
|
||
<rect width="80" height="48" rx="6" fill="#e8f5e9" stroke="#00a344" stroke-width="1.5"/>
|
||
<text x="40" y="42" text-anchor="middle" font-family="system-ui,sans-serif" font-size="10" font-weight="600" fill="#00185e">DNS Zone</text>
|
||
</g>
|
||
<text x="640" y="215" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#666666">Real Resources</text>
|
||
</svg>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 17 : Terraform providers ──────────────────────────── -->
|
||
<section class="s-tf">
|
||
<h2>One tool. Every API.</h2>
|
||
<ul>
|
||
<li><strong>1000+ providers</strong> — OVHcloud, Scaleway, Clever Cloud, AWS, Cloudflare, Kubernetes…</li>
|
||
<li>Not just cloud — DNS, monitoring, CI/CD, anything with an API</li>
|
||
</ul>
|
||
<p><em>If it has an API, there's a Terraform provider for it.</em></p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>The real power: one workflow for everything, not just one cloud.</li>
|
||
<li>Examples beyond cloud: DNS records, GitHub repos, monitoring dashboards.</li>
|
||
<li>Providers are the plugin ecosystem that makes Terraform universal.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 18 : 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 = "~> 2.0" }
|
||
}
|
||
}
|
||
|
||
resource "openstack_compute_instance_v2" "web" {
|
||
name = "finistdevs-web"
|
||
image_name = "Debian 13"
|
||
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>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Real OVHcloud example: spin up an instance, then point a DNS record at it.</li>
|
||
<li>Note the implicit dependency — the DNS record references the instance's IP, so Terraform orders them automatically.</li>
|
||
<li>Two different providers (OpenStack + OVH) working together in one file.</li>
|
||
<li>Version syntax: <code>~></code> is the pessimistic constraint operator. <code>~> 2.0</code> allows any 2.x (>= 2.0, < 3.0) — patch and minor updates, but never a breaking major bump. <code>~> 2.13.0</code> would pin tighter, allowing only 2.13.x.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 19 : Terraform CLI ────────────────────────────────── -->
|
||
<section class="s-tf">
|
||
<p class="filename">$ terminal</p>
|
||
<pre><code class="language-bash" data-trim data-noescape>
|
||
$ terraform init
|
||
Initializing provider plugins...
|
||
- Finding terraform-provider-openstack/openstack versions matching "~> 3.0"...
|
||
- Finding ovh/ovh versions matching "~> 2.0"...
|
||
- Installing terraform-provider-openstack/openstack v3.4.0...
|
||
- Installing ovh/ovh v2.13.1...
|
||
Terraform has been successfully initialized!
|
||
|
||
$ terraform plan
|
||
...
|
||
Plan: 2 to add, 0 to change, 0 to destroy.
|
||
|
||
$ terraform apply
|
||
...
|
||
openstack_compute_instance_v2.web: Creating...
|
||
openstack_compute_instance_v2.web: Creation complete after 45s [id=abc-123]
|
||
ovh_domain_zone_record.web: Creating...
|
||
ovh_domain_zone_record.web: Creation complete after 3s [id=456]
|
||
|
||
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
|
||
</code></pre>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Walk the lifecycle: init (download providers) → plan → apply.</li>
|
||
<li>"2 added, 0 changed, 0 destroyed" — exactly the diff you reviewed, nothing more.</li>
|
||
<li>Re-running apply with no changes does nothing — that's idempotence.</li>
|
||
<li>Each <code>...</code> on the slide hides real output — call it out so nobody thinks Terraform is this terse.</li>
|
||
<li>The <code>...</code> after <code>plan</code>: a full per-resource <code>+</code>/<code>-</code> diff of every attribute Terraform will set — this is what you actually review.</li>
|
||
<li>The <code>...</code> after <code>apply</code>: Terraform replays that same plan, then pauses for an interactive "yes" prompt — skip it with <code>-auto-approve</code> in CI.</li>
|
||
<li>I also trimmed init: real output adds backend init, "Installed … (signed by …)" lines, and the <code>.terraform.lock.hcl</code> being written.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 20 : OpenTofu ─────────────────────────────────────── -->
|
||
<section class="s-tf">
|
||
<h2>HashiCorp changed Terraform's license.</h2>
|
||
<ul>
|
||
<li><strong>BUSL 1.1</strong> instead of MPL — no longer truly open-source</li>
|
||
<li>The community responded: <strong>OpenTofu</strong>, now a CNCF project</li>
|
||
</ul>
|
||
<p><em>Drop-in for migration. Diverging features. Community-driven.</em></p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>2023: HashiCorp moved to BUSL 1.1 — restricts commercial competitors, not truly open-source anymore.</li>
|
||
<li>Community forked it: OpenTofu, accepted into the Linux Foundation in 2023, a CNCF project since 2025.</li>
|
||
<li>Practical takeaway: drop-in compatible, swap the binary. Worth knowing for licensing-sensitive orgs.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 21 : Terraform platforms ──────────────────────────── -->
|
||
<section class="s-tf">
|
||
<h2>Terraform at scale needs a platform.</h2>
|
||
<ul>
|
||
<li><strong>Terraform Enterprise / HCP Terraform</strong> — remote state, RBAC, audit logs</li>
|
||
<li><strong>Spacelift</strong> — GitOps-first CI/CD for Terraform and OpenTofu</li>
|
||
<li><strong>Atlantis</strong> — open-source, plan & apply from pull requests</li>
|
||
<li><strong>env0, Scalr</strong> — SaaS with policy & cost management</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Running Terraform from a laptop doesn't scale to a team.</li>
|
||
<li>You need: shared/locked state, RBAC, audit, plan-on-PR, policy & cost guardrails.</li>
|
||
<li>Range from SaaS (HCP, Spacelift, env0, Scalr) to self-hosted open-source (Atlantis).</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 22 : 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>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Second layer: configuration. The VMs exist — now install and set things up.</li>
|
||
<li>Transition: Terraform built the box, Ansible makes it useful.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
|
||
<!-- ─── SLIDE 23 : What is Ansible ──────────────────────────────── -->
|
||
<section class="s-ansible">
|
||
<h2>What is Ansible?</h2>
|
||
<ul>
|
||
<li><strong>Agentless automation</strong> tool for configuration and orchestration</li>
|
||
<li>Created by <strong>Michael DeHaan</strong> in 2012</li>
|
||
<li>Acquired by <strong>Red Hat</strong> in 2015</li>
|
||
<li>Written in <strong>Python</strong> — GPLv3 license</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Key differentiator: agentless — nothing to install on targets.</li>
|
||
<li>Red Hat backed, Python, genuinely open-source (GPLv3) — contrast with Terraform's BUSL.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 24 : Ansible concepts ─────────────────────────────── -->
|
||
<section class="s-ansible">
|
||
<h2>Push-based. Runs over SSH.</h2>
|
||
<ul>
|
||
<li>YAML playbooks run tasks in order, across any number of hosts</li>
|
||
<li>Nothing to install on target servers — just <strong>Python + SSH</strong></li>
|
||
<li>Idempotent modules — same playbook runs safely again and again</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Push model: control node connects out over SSH and runs tasks — point at the diagram.</li>
|
||
<li>"Agentless" = just needs Python + SSH on the target, no daemon.</li>
|
||
<li>Idempotent modules: re-running is safe, only changes what's needed.</li>
|
||
</ul>
|
||
</aside>
|
||
<svg width="750" height="250" viewBox="0 0 750 250" xmlns="http://www.w3.org/2000/svg" style="margin-top:0.5em;">
|
||
<defs>
|
||
<marker id="a-push" viewBox="0 0 10 7" refX="9" refY="3.5" markerWidth="9" markerHeight="7" orient="auto">
|
||
<polygon points="0 0,10 3.5,0 7" fill="#CC0000"/>
|
||
</marker>
|
||
</defs>
|
||
<!-- Laptop -->
|
||
<rect x="50" y="68" width="100" height="65" rx="6" fill="#2b2b3a"/>
|
||
<rect x="56" y="73" width="88" height="48" rx="3" fill="#14142a"/>
|
||
<text x="100" y="103" text-anchor="middle" fill="#5cf" font-family="monospace" font-size="13">>_</text>
|
||
<rect x="38" y="135" width="124" height="9" rx="4" fill="#444"/>
|
||
<text x="100" y="164" text-anchor="middle" fill="#4d5592" font-size="13" font-weight="bold" font-family="sans-serif">Control Node</text>
|
||
<!-- PUSH badge -->
|
||
<rect x="310" y="10" width="80" height="28" rx="14" fill="#CC0000"/>
|
||
<text x="350" y="30" text-anchor="middle" fill="#fff" font-size="14" font-weight="bold" font-family="sans-serif">PUSH</text>
|
||
<!-- Arrows -->
|
||
<line x1="170" y1="100" x2="548" y2="30" stroke="#CC0000" stroke-width="2" marker-end="url(#a-push)"/>
|
||
<line x1="170" y1="100" x2="548" y2="72" stroke="#CC0000" stroke-width="2" marker-end="url(#a-push)"/>
|
||
<line x1="170" y1="100" x2="548" y2="117" stroke="#CC0000" stroke-width="2" marker-end="url(#a-push)"/>
|
||
<line x1="170" y1="100" x2="548" y2="162" stroke="#CC0000" stroke-width="2" marker-end="url(#a-push)"/>
|
||
<line x1="170" y1="100" x2="548" y2="207" stroke="#CC0000" stroke-width="2" marker-end="url(#a-push)"/>
|
||
<!-- SSH label -->
|
||
<rect x="310" y="46" width="42" height="18" rx="3" fill="#fff" fill-opacity="0.85"/>
|
||
<text x="331" y="59" text-anchor="middle" fill="#CC0000" font-size="11" font-weight="bold" font-family="monospace">SSH</text>
|
||
<!-- Servers -->
|
||
<g fill="#f2f2f8" stroke="#aaa">
|
||
<g transform="translate(555,16)"><rect width="120" height="28" rx="4"/><circle cx="105" cy="19" r="3" fill="#5cf"/></g>
|
||
<g transform="translate(555,58)"><rect width="120" height="28" rx="4"/><circle cx="105" cy="19" r="3" fill="#5cf"/></g>
|
||
<g transform="translate(555,103)"><rect width="120" height="28" rx="4"/><circle cx="105" cy="19" r="3" fill="#5cf"/></g>
|
||
<g transform="translate(555,148)"><rect width="120" height="28" rx="4"/><circle cx="105" cy="19" r="3" fill="#5cf"/></g>
|
||
<g transform="translate(555,193)"><rect width="120" height="28" rx="4"/><circle cx="105" cy="19" r="3" fill="#5cf"/></g>
|
||
</g>
|
||
<text x="615" y="245" text-anchor="middle" fill="#4d5592" font-size="12" font-family="sans-serif">Managed Hosts</text>
|
||
<text x="615" y="12" text-anchor="middle" fill="#999" font-size="10" font-style="italic" font-family="sans-serif">no agent required</text>
|
||
</svg>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 25 : 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>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Plain YAML — tasks run top to bottom against the "webservers" group.</li>
|
||
<li>become: true = run as root (sudo).</li>
|
||
<li>Handlers are the neat bit: restart nginx only if the config actually changed.</li>
|
||
<li>The <code>.j2</code> in <code>nginx.conf.j2</code> = Jinja2, Ansible's templating engine. The template module renders it on the control node — <code>{{ variables }}</code>, <code>{% if %}</code>/<code>{% for %}</code> logic, filters like <code>{{ value | default(...) }}</code> — then ships the result to <code>dest</code>.</li>
|
||
<li>That's how one template serves many hosts: same file, per-host values from inventory/group_vars.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 26 : Ansible inventory ────────────────────────────── -->
|
||
<section class="s-ansible">
|
||
<h2>Who runs where? The inventory.</h2>
|
||
<ul>
|
||
<li><strong>Static</strong> — a hand-written INI/YAML file of hosts & groups. Simple, versioned, ideal for stable fleets.</li>
|
||
<li><strong>Dynamic</strong> — a plugin builds the host list at runtime. Best source here: <strong>the Terraform state we just wrote</strong> — Ansible configures exactly what Terraform provisioned.</li>
|
||
</ul>
|
||
<div style="display:flex; gap:1.5em; margin-top:0.3em; text-align:left;">
|
||
<div style="flex:1;">
|
||
<p class="filename"># inventory.yml — static</p>
|
||
<pre><code class="language-yaml" data-trim>
|
||
webservers:
|
||
hosts:
|
||
web-01:
|
||
ansible_host: 10.0.0.11
|
||
web-02:
|
||
ansible_host: 10.0.0.12
|
||
</code></pre>
|
||
</div>
|
||
<div style="flex:1;">
|
||
<p class="filename"># terraform.yml — dynamic (from TF state)</p>
|
||
<pre><code class="language-yaml" data-trim>
|
||
plugin: cloud.terraform.terraform_state
|
||
backend_type: local
|
||
backend_config:
|
||
path: ../terraform/terraform.tfstate
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>The <code>-i</code> flag on the next slide points here — Ansible needs to know which hosts to target.</li>
|
||
<li><strong>Static</strong>: explicit list checked into Git. Predictable, but you maintain it by hand.</li>
|
||
<li><strong>This is the Terraform → Ansible handoff</strong>: Terraform creates the instance and records it in <code>.tfstate</code> (remember slide 16); the <code>cloud.terraform.terraform_state</code> plugin reads that same state and turns each resource into an Ansible host. One source of truth — no second host list to keep in sync.</li>
|
||
<li>Other dynamic sources exist too — cloud plugins like <code>openstack.cloud.openstack</code> or AWS/OVHcloud query the provider API directly. <code>keyed_groups</code> build groups from tags/metadata.</li>
|
||
<li>Provision with Terraform, then immediately configure with Ansible against the freshly-created hosts — that's the combined workflow we land on at the end.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 27 : Ansible CLI ──────────────────────────────────── -->
|
||
<section class="s-ansible">
|
||
<p class="filename">$ terminal</p>
|
||
<pre><code class="language-bash" data-trim data-noescape>
|
||
$ ansible-playbook -i inventory playbook/webserver.yml
|
||
|
||
PLAY [Configure web server] ***************************************************
|
||
|
||
TASK [Gathering Facts] ********************************************************
|
||
ok: [finistdevs-web]
|
||
|
||
TASK [Install nginx] **********************************************************
|
||
changed: [finistdevs-web]
|
||
|
||
TASK [Deploy nginx config] ****************************************************
|
||
changed: [finistdevs-web]
|
||
|
||
RUNNING HANDLER [Restart nginx] ***********************************************
|
||
changed: [finistdevs-web]
|
||
|
||
PLAY RECAP ********************************************************************
|
||
finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
|
||
</code></pre>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>"ok" vs "changed" — ok means already in desired state, changed means it acted. That's idempotence visible in the output.</li>
|
||
<li>The handler only fired because the config task reported "changed".</li>
|
||
<li>Run it again and everything would be "ok", changed=0.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 28 : Ansible operations ───────────────────────────── -->
|
||
<section class="s-ansible">
|
||
<h2>Not just configuration. Operations.</h2>
|
||
<ul>
|
||
<li>Patch 200 servers tonight</li>
|
||
<li>Roll out a kernel upgrade with a canary strategy</li>
|
||
<li>Run a compliance audit across your whole fleet</li>
|
||
</ul>
|
||
<p><em>The go-to tool for one-off tasks and recurring operations.</em></p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Ansible isn't only "set up a server once" — it shines for ad-hoc operations.</li>
|
||
<li>Imperative/orchestration angle: patching, rolling upgrades, canary, audits across the fleet.</li>
|
||
<li>This is where it differs most from Puppet's "always converge" model.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 29 : Ansible Galaxy ───────────────────────────────── -->
|
||
<section class="s-ansible">
|
||
<h2>The community does the heavy lifting.</h2>
|
||
<ul>
|
||
<li><strong>Ansible Galaxy</strong> — 10,000+ ready-made roles and collections</li>
|
||
<li>Don't write a playbook to install Docker from scratch — someone already did</li>
|
||
</ul>
|
||
<pre><code class="language-bash" data-trim>
|
||
$ ansible-galaxy install <namespace>.<role>
|
||
</code></pre>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Galaxy = the package registry for reusable roles/collections.</li>
|
||
<li>Don't reinvent common setups — pull a battle-tested role (geerlingguy is the famous example).</li>
|
||
<li>Huge productivity multiplier — but it's a supply chain: roles run with privilege on your hosts.</li>
|
||
<li>Prefer trusted sources: Red Hat <strong>Certified</strong> collections and verified publishers (Automation Hub), or well-known community authors like geerlingguy. Be wary of unmaintained, low-download, single-author roles.</li>
|
||
<li>Pin versions in <code>requirements.yml</code> and skim the code before importing — treat it like any other dependency.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 30 : Ansible platforms ────────────────────────────── -->
|
||
<section class="s-ansible">
|
||
<h2>Ansible at scale: open-source vs enterprise.</h2>
|
||
<ul>
|
||
<li><strong>AWX</strong> — open-source web UI, API, and scheduler</li>
|
||
<li><strong>Ansible Automation Platform</strong> (Red Hat) — enterprise AWX with support</li>
|
||
<li><strong>Semaphore</strong> — lightweight open-source alternative</li>
|
||
</ul>
|
||
<p><em>Core engine remains GPLv3 — truly open-source.</em></p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>At scale you want a UI/scheduler/RBAC layer on top of the CLI.</li>
|
||
<li>AWX (free) → Ansible Automation Platform (Red Hat, supported) is the main path; Semaphore is a lighter option.</li>
|
||
<li>Reassure: the core stays GPLv3 — no license rug-pull like Terraform.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 31 : 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>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Third layer: enforcement. Configured once isn't enough — drift creeps back.</li>
|
||
<li>Puppet's job: keep state correct continuously, forever.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
|
||
<!-- ─── SLIDE 32 : What is Puppet ───────────────────────────────── -->
|
||
<section class="s-puppet">
|
||
<h2>What is Puppet?</h2>
|
||
<ul>
|
||
<li><strong>Configuration management</strong> tool for enforcing system state</li>
|
||
<li>Created by <strong>Luke Kanies</strong> in 2005</li>
|
||
<li>Puppet Inc. acquired by <strong>Perforce</strong> in 2022</li>
|
||
<li>Written in <strong>Ruby</strong> and <strong>Clojure</strong></li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>The oldest of the three (2005) — pioneered config management.</li>
|
||
<li>Now owned by Perforce. We'll touch on what that means for the community later.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 33 : Puppet concepts ──────────────────────────────── -->
|
||
<section class="s-puppet">
|
||
<h2>Pull, not push. Agents, not SSH.</h2>
|
||
<ul>
|
||
<li>Every 30 minutes, each <code>puppet-agent</code> polls the Puppet Server</li>
|
||
<li>Compiles a catalog and enforces it locally</li>
|
||
</ul>
|
||
<p><em>Drift is corrected automatically.</em></p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Opposite model to Ansible: pull, not push — agents reach out to the server.</li>
|
||
<li>Every ~30 min the agent fetches a catalog and converges the node — point at the diagram.</li>
|
||
<li>This is the key idea: enforcement runs on a loop, not just at deploy.</li>
|
||
</ul>
|
||
</aside>
|
||
<svg width="750" height="250" viewBox="0 0 750 250" xmlns="http://www.w3.org/2000/svg" style="margin-top:0.5em;">
|
||
<defs>
|
||
<marker id="a-pull" viewBox="0 0 10 7" refX="9" refY="3.5" markerWidth="9" markerHeight="7" orient="auto">
|
||
<polygon points="0 0,10 3.5,0 7" fill="#A06010"/>
|
||
</marker>
|
||
</defs>
|
||
<!-- Puppet Server -->
|
||
<rect x="290" y="28" width="170" height="60" rx="8" fill="#fdf4e8" stroke="#A06010" stroke-width="2"/>
|
||
<rect x="300" y="40" width="36" height="36" rx="4" fill="#f5e6d0" stroke="#A06010"/>
|
||
<line x1="306" y1="50" x2="324" y2="50" stroke="#c08030"/><line x1="306" y1="57" x2="324" y2="57" stroke="#c08030"/><line x1="306" y1="64" x2="324" y2="64" stroke="#c08030"/>
|
||
<rect x="346" y="42" width="24" height="30" rx="2" fill="#fff" stroke="#A06010"/>
|
||
<text x="380" y="53" fill="#A06010" font-size="9" font-family="sans-serif">catalog</text>
|
||
<text x="375" y="20" text-anchor="middle" fill="#4d5592" font-size="13" font-weight="bold" font-family="sans-serif">Puppet Server</text>
|
||
<!-- Clock -->
|
||
<circle cx="540" cy="40" r="18" fill="none" stroke="#A06010" stroke-width="1.5"/>
|
||
<line x1="540" y1="40" x2="540" y2="28" stroke="#A06010" stroke-width="1.5"/>
|
||
<line x1="540" y1="40" x2="550" y2="40" stroke="#A06010" stroke-width="1.5"/>
|
||
<text x="540" y="72" text-anchor="middle" fill="#A06010" font-size="10" font-family="sans-serif">every 30 min</text>
|
||
<!-- Pull arrows -->
|
||
<line x1="95" y1="160" x2="330" y2="92" stroke="#A06010" stroke-width="1.8" stroke-dasharray="6,4" marker-end="url(#a-pull)"/>
|
||
<line x1="225" y1="160" x2="350" y2="92" stroke="#A06010" stroke-width="1.8" stroke-dasharray="6,4" marker-end="url(#a-pull)"/>
|
||
<line x1="375" y1="160" x2="375" y2="92" stroke="#A06010" stroke-width="1.8" stroke-dasharray="6,4" marker-end="url(#a-pull)"/>
|
||
<line x1="525" y1="160" x2="400" y2="92" stroke="#A06010" stroke-width="1.8" stroke-dasharray="6,4" marker-end="url(#a-pull)"/>
|
||
<line x1="655" y1="160" x2="420" y2="92" stroke="#A06010" stroke-width="1.8" stroke-dasharray="6,4" marker-end="url(#a-pull)"/>
|
||
<rect x="420" y="112" width="38" height="17" rx="3" fill="#fff" fill-opacity="0.9"/>
|
||
<text x="439" y="124" text-anchor="middle" fill="#A06010" font-size="11" font-weight="bold" font-style="italic" font-family="sans-serif">pull</text>
|
||
<!-- Agent nodes -->
|
||
<g transform="translate(50,160)"><rect width="90" height="34" rx="4" fill="#f2f2f8" stroke="#aaa"/><circle cx="76" cy="22" r="3" fill="#5cf"/><rect x="26" y="36" width="38" height="14" rx="3" fill="#A06010"/><text x="45" y="47" text-anchor="middle" fill="#fff" font-size="8" font-family="sans-serif">agent</text></g>
|
||
<g transform="translate(180,160)"><rect width="90" height="34" rx="4" fill="#f2f2f8" stroke="#aaa"/><circle cx="76" cy="22" r="3" fill="#5cf"/><rect x="26" y="36" width="38" height="14" rx="3" fill="#A06010"/><text x="45" y="47" text-anchor="middle" fill="#fff" font-size="8" font-family="sans-serif">agent</text></g>
|
||
<g transform="translate(330,160)"><rect width="90" height="34" rx="4" fill="#f2f2f8" stroke="#aaa"/><circle cx="76" cy="22" r="3" fill="#5cf"/><rect x="26" y="36" width="38" height="14" rx="3" fill="#A06010"/><text x="45" y="47" text-anchor="middle" fill="#fff" font-size="8" font-family="sans-serif">agent</text></g>
|
||
<g transform="translate(480,160)"><rect width="90" height="34" rx="4" fill="#f2f2f8" stroke="#aaa"/><circle cx="76" cy="22" r="3" fill="#5cf"/><rect x="26" y="36" width="38" height="14" rx="3" fill="#A06010"/><text x="45" y="47" text-anchor="middle" fill="#fff" font-size="8" font-family="sans-serif">agent</text></g>
|
||
<g transform="translate(610,160)"><rect width="90" height="34" rx="4" fill="#f2f2f8" stroke="#aaa"/><circle cx="76" cy="22" r="3" fill="#5cf"/><rect x="26" y="36" width="38" height="14" rx="3" fill="#A06010"/><text x="45" y="47" text-anchor="middle" fill="#fff" font-size="8" font-family="sans-serif">agent</text></g>
|
||
<text x="375" y="240" text-anchor="middle" fill="#4d5592" font-size="12" font-family="sans-serif">Managed Nodes</text>
|
||
</svg>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 34 : 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>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Same nginx example as Ansible — compare the styles side by side.</li>
|
||
<li>Pure declarative: describe resources (package, file, service) and desired state, not steps.</li>
|
||
<li>notify chains the dependency: config change → restart service.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 35 : Puppet CLI ───────────────────────────────────── -->
|
||
<section class="s-puppet">
|
||
<p class="filename">$ terminal</p>
|
||
<pre><code class="language-bash" data-trim data-noescape>
|
||
$ puppet agent -t
|
||
Info: Using environment 'production'
|
||
Info: Retrieving pluginfacts
|
||
Info: Caching catalog for finistdevs-web.example.com
|
||
Info: Applying configuration version '1713052408'
|
||
|
||
Notice: /Stage[main]/Webserver/Package[nginx]/ensure: created
|
||
Notice: /Stage[main]/Webserver/File[/etc/nginx/nginx.conf]/content:
|
||
--- /etc/nginx/nginx.conf
|
||
+++ /tmp/puppet-file20260413
|
||
Notice: /Stage[main]/Webserver/Service[nginx]/ensure: started
|
||
|
||
Notice: Applied catalog in 12.34 seconds
|
||
</code></pre>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Agent run: fetch catalog → compare to actual → apply only the diffs.</li>
|
||
<li>This normally runs automatically every 30 min; -t is a manual trigger for demo.</li>
|
||
<li>Next run with no drift would report no changes.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 36 : Puppet drift detection ───────────────────────── -->
|
||
<section class="s-puppet">
|
||
<h2>Someone SSH'd in and changed something.</h2>
|
||
<ul>
|
||
<li>Puppet noticed. Puppet fixed it.</li>
|
||
<li>Continuous compliance — not just at deploy time. <strong>Every. 30. Minutes.</strong></li>
|
||
<li>No manual remediation</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>The killer feature: self-healing. Someone hand-edits a file → Puppet reverts it next run.</li>
|
||
<li>Walk the loop in the diagram: drift detected → agent applies → compliant → repeat.</li>
|
||
<li>This is what "continuous compliance" means in practice.</li>
|
||
</ul>
|
||
</aside>
|
||
<svg width="750" height="200" viewBox="0 0 750 200" xmlns="http://www.w3.org/2000/svg" style="margin-top:0.5em;">
|
||
<defs>
|
||
<marker id="a-cycle" viewBox="0 0 10 7" refX="9" refY="3.5" markerWidth="9" markerHeight="7" orient="auto">
|
||
<polygon points="0 0,10 3.5,0 7" fill="#4d5592"/>
|
||
</marker>
|
||
</defs>
|
||
<!-- 1: Drift detected -->
|
||
<rect x="30" y="50" width="190" height="80" rx="12" fill="#fde8ec" stroke="#e00034" stroke-width="1.5"/>
|
||
<polygon points="70,68 80,88 60,88" fill="none" stroke="#e00034" stroke-width="2" stroke-linejoin="round"/>
|
||
<text x="70" y="84" text-anchor="middle" fill="#e00034" font-size="11" font-weight="bold" font-family="sans-serif">!</text>
|
||
<text x="135" y="84" text-anchor="middle" fill="#e00034" font-size="13" font-weight="bold" font-family="sans-serif">Drift</text>
|
||
<text x="135" y="100" text-anchor="middle" fill="#e00034" font-size="13" font-weight="bold" font-family="sans-serif">detected</text>
|
||
<circle cx="42" cy="56" r="10" fill="#e00034"/><text x="42" y="60" text-anchor="middle" fill="#fff" font-size="10" font-weight="bold" font-family="sans-serif">1</text>
|
||
<!-- 2: Agent applies catalog -->
|
||
<rect x="520" y="50" width="200" height="80" rx="12" fill="#fdf4e8" stroke="#A06010" stroke-width="1.5"/>
|
||
<circle cx="565" cy="85" r="13" fill="none" stroke="#A06010" stroke-width="2"/>
|
||
<circle cx="565" cy="85" r="5" fill="#A06010"/>
|
||
<text x="632" y="84" text-anchor="middle" fill="#A06010" font-size="13" font-weight="bold" font-family="sans-serif">Agent</text>
|
||
<text x="632" y="100" text-anchor="middle" fill="#A06010" font-size="13" font-weight="bold" font-family="sans-serif">applies catalog</text>
|
||
<circle cx="532" cy="56" r="10" fill="#A06010"/><text x="532" y="60" text-anchor="middle" fill="#fff" font-size="10" font-weight="bold" font-family="sans-serif">2</text>
|
||
<!-- 3: Compliant -->
|
||
<rect x="275" y="140" width="200" height="50" rx="12" fill="#e6f7ee" stroke="#00a344" stroke-width="1.5"/>
|
||
<polyline points="310,164 318,174 332,156" fill="none" stroke="#00a344" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||
<text x="395" y="171" text-anchor="middle" fill="#00a344" font-size="15" font-weight="bold" font-family="sans-serif">Compliant</text>
|
||
<circle cx="287" cy="146" r="10" fill="#00a344"/><text x="287" y="150" text-anchor="middle" fill="#fff" font-size="10" font-weight="bold" font-family="sans-serif">3</text>
|
||
<!-- Curved arrows -->
|
||
<path d="M 220,72 C 330,20 420,20 518,72" fill="none" stroke="#4d5592" stroke-width="2" marker-end="url(#a-cycle)"/>
|
||
<path d="M 600,132 C 580,155 530,168 477,165" fill="none" stroke="#4d5592" stroke-width="2" marker-end="url(#a-cycle)"/>
|
||
<path d="M 275,165 C 210,168 140,155 120,132" fill="none" stroke="#4d5592" stroke-width="2" marker-end="url(#a-cycle)"/>
|
||
<text x="375" y="22" text-anchor="middle" fill="#4d5592" font-size="10" font-family="sans-serif">continuous enforcement loop</text>
|
||
</svg>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 37 : Puppet platforms ─────────────────────────────── -->
|
||
<section class="s-puppet">
|
||
<h2>Puppet: large fleets, zero drift.</h2>
|
||
<ul>
|
||
<li>Continuous compliance, auditability, and guaranteed state — at scale</li>
|
||
<li>Best suited for enterprises with hundreds or thousands of long-lived servers</li>
|
||
<li>Fewer SaaS options than Terraform or Ansible</li>
|
||
</ul>
|
||
<p><em>Puppet Enterprise and Foreman are self-hosted. No managed cloud offering.</em></p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Sweet spot: large fleets of long-lived servers where drift control matters.</li>
|
||
<li>Trade-off vs the others: fewer SaaS options, more setup — it's self-hosted.</li>
|
||
<li>Honest framing: overkill for a handful of ephemeral cloud VMs.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 38 : Puppet community ─────────────────────────────── -->
|
||
<section class="s-puppet">
|
||
<h2>The ecosystem outlives the company.</h2>
|
||
<ul>
|
||
<li><strong>Vox Pupuli</strong> — 100+ open-source Puppet modules, community-maintained</li>
|
||
<li><strong>OpenVox</strong> — an emerging open-source fork of the Puppet core</li>
|
||
</ul>
|
||
<p><em>The community is strong, with or without Puppet Inc.</em></p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Addresses the "is Puppet dying?" worry after the Perforce acquisition.</li>
|
||
<li>The Perforce shift: in Nov 2024 Perforce announced it would stop shipping public open-source Puppet binaries. From early 2025, official packages move to a private location — access needs a developer license (capped at 25 nodes) or a commercial license, and ongoing development moved to internal/private repos.</li>
|
||
<li>Vox Pupuli couldn't accept the Puppet Core Developer EULA — its restrictions block testing and redistribution of the community modules.</li>
|
||
<li>So they forked: OpenVox started as a community package mirror (Overlook InfraTech), and Vox Pupuli shipped the first release on Jan 21, 2025 — OpenVox 8.11 is functionally equivalent to Puppet 8.11, fully open, no EULA. (Same playbook as Terraform → OpenTofu.)</li>
|
||
<li>Reassurance: the open ecosystem outlives any single vendor.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 39 : They're complementary ────────────────────────── -->
|
||
<section>
|
||
<h2>They're not competing. They're complementary.</h2>
|
||
<p>Each solves a different layer of the same problem.</p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>The payoff slide — the "vs" framing was a trap. They stack.</li>
|
||
<li>Terraform provisions → Ansible configures → Puppet enforces. Three layers.</li>
|
||
<li>"Which should I use?" → depends which layer of the problem you have.</li>
|
||
</ul>
|
||
</aside>
|
||
<svg width="800" height="280" viewBox="0 0 800 280" xmlns="http://www.w3.org/2000/svg" style="margin-top:0.5em;">
|
||
<defs>
|
||
<marker id="a-down" viewBox="0 0 10 10" refX="5" refY="10" markerWidth="10" markerHeight="10" orient="auto">
|
||
<polygon points="0 0,10 0,5 10" fill="#4d5592"/>
|
||
</marker>
|
||
</defs>
|
||
<!-- Terraform layer -->
|
||
<rect x="120" y="10" width="560" height="64" rx="10" fill="#7B42BC" fill-opacity="0.12" stroke="#7B42BC" stroke-width="2"/>
|
||
<text x="220" y="38" fill="#7B42BC" font-size="18" font-weight="bold" font-family="sans-serif">Terraform</text>
|
||
<text x="338" y="38" fill="#5a2e8c" font-size="16" font-family="sans-serif">— Provision</text>
|
||
<text x="220" y="56" fill="#8855cc" font-size="11" font-style="italic" font-family="sans-serif">VMs, networks, cloud resources</text>
|
||
<!-- Arrow between Terraform and Ansible -->
|
||
<line x1="400" y1="78" x2="400" y2="100" stroke="#4d5592" stroke-width="2.5" marker-end="url(#a-down)"/>
|
||
<!-- Ansible layer -->
|
||
<rect x="120" y="104" width="560" height="64" rx="10" fill="#CC0000" fill-opacity="0.1" stroke="#CC0000" stroke-width="2"/>
|
||
<text x="220" y="132" fill="#CC0000" font-size="18" font-weight="bold" font-family="sans-serif">Ansible</text>
|
||
<text x="306" y="132" fill="#a00" font-size="16" font-family="sans-serif">— Configure</text>
|
||
<text x="220" y="150" fill="#cc3333" font-size="11" font-style="italic" font-family="sans-serif">packages, services, app deployment</text>
|
||
<!-- Arrow between Ansible and Puppet -->
|
||
<line x1="400" y1="172" x2="400" y2="194" stroke="#4d5592" stroke-width="2.5" marker-end="url(#a-down)"/>
|
||
<!-- Puppet layer -->
|
||
<rect x="120" y="198" width="560" height="64" rx="10" fill="#A06010" fill-opacity="0.1" stroke="#A06010" stroke-width="2"/>
|
||
<text x="220" y="228" fill="#A06010" font-size="18" font-weight="bold" font-family="sans-serif">Puppet</text>
|
||
<text x="298" y="228" fill="#8a5010" font-size="16" font-family="sans-serif">— Enforce</text>
|
||
<text x="220" y="246" fill="#b07020" font-size="11" font-style="italic" font-family="sans-serif">continuous compliance, drift correction</text>
|
||
</svg>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 40 : Real-world stack ─────────────────────────────── -->
|
||
<section>
|
||
<h2>A common production setup:</h2>
|
||
<ol>
|
||
<li><span class="tf-col">Terraform</span> provisions the VM</li>
|
||
<li><span class="ansible-col">Ansible</span> configures it and deploys the app</li>
|
||
<li><span class="puppet-col">Puppet</span> continuously enforces compliance</li>
|
||
</ol>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Concrete recap of how they fit together end to end.</li>
|
||
<li>You don't have to use all three — but they layer cleanly when you do.</li>
|
||
<li>Pick by your actual need: just provisioning? Terraform. Ad-hoc ops? Ansible. Drift control? Puppet.</li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
|
||
<!-- ─── SLIDE 41 : 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>
|
||
<p class="meta" style="margin-top:1.5em; font-size:0.45em; color:var(--ods-neutral-600);">🤖 Made with Claude & GitHub Copilot</p>
|
||
<aside class="notes">
|
||
<ul>
|
||
<li>Thank the audience, point to the slides URL.</li>
|
||
<li>Open the floor for questions.</li>
|
||
<li>Backup topics if quiet: Kubernetes operators, Pulumi, secrets management.</li>
|
||
</ul>
|
||
</aside>
|
||
</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>
|