Files
finistdev-configuration-as-…/index.html
ministicraft c01de466df fix: consistent tool logo placement across slides
- Use px instead of em so position doesn't shift with font size
- Explicitly set position:relative on sections for reliable anchoring

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-14 01:00:54 +02:00

452 lines
19 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);
}
.reveal .slides section {
text-align: left;
border-top: 6px solid var(--strip-color);
position: relative;
}
.reveal h1, .reveal h2 {
text-align: left;
}
.reveal h1 { font-size: 1.8em; }
.reveal h2 { font-size: 1.4em; line-height: 1.2; }
.reveal p, .reveal li { font-size: 0.85em; }
.reveal code { font-size: 0.7em; }
.reveal pre { width: 100%; }
.title-slide { text-align: center !important; }
.title-slide h1 { text-align: center; font-size: 2em; margin-bottom: 0.2em; }
.title-slide .subtitle { font-size: 1em; color: var(--ods-text); }
.title-slide .meta { font-size: 0.7em; 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;
}
.slide-logo {
position: absolute;
bottom: 12px;
right: 16px;
width: 40px;
height: 40px;
opacity: 0.8;
}
.ovh-logo {
height: 36px;
width: auto;
margin-top: 1.2em;
display: block;
margin-left: auto;
margin-right: auto;
}
.ovh-logo-global {
position: fixed;
bottom: 1em;
left: 1em;
height: 28px;
width: auto;
opacity: 0.7;
z-index: 50;
pointer-events: none;
}
</style>
</head>
<body>
<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">Puppet · Ansible · Terraform<br>— What's the difference and when to use what? —</p>
<p class="meta">FinistDevs · 2026</p>
<img src="vendor/ovhcloud-logo.svg" alt="OVHcloud" class="ovh-logo">
</section>
<!-- ─── SLIDE 2 : Speaker intro ────────────────────────────────── -->
<section>
<h2>Arnaud Prémel-Cabic</h2>
<p>Tech Lead @ OVHCloud</p>
<p style="color:var(--ods-text); font-size:0.75em;">arnaud.premel-cabic@ovhcloud.com</p>
<img src="vendor/ovhcloud-logo.svg" alt="OVHcloud" style="height:32px; width:auto; margin-top:1em;">
</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>
</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>
</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>
</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 style="margin-top:0.8em; color:var(--ods-neutral-600);">Reproducible &nbsp;·&nbsp; Versionable &nbsp;·&nbsp; Auditable</p>
</section>
<!-- ─── SLIDE 11 ──────────────────────────────────────────────────── -->
<section>
<h2>Meet the three musketeers of infrastructure.</h2>
<p><span class="puppet-col">Puppet</span> &nbsp;·&nbsp; <span class="ansible-col">Ansible</span> &nbsp;·&nbsp; <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 &nbsp;·&nbsp; <code>terraform apply</code> creates &nbsp;·&nbsp; <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 {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" { region = "eu-west-3" }
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = { Name = "finistdevs-web" }
}
output "public_ip" {
value = aws_instance.web.public_ip
}
</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 &amp; apply triggered by pull requests<br>
<strong>env0, Scalr</strong> — SaaS alternatives with policy &amp; 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 &amp; 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 &nbsp;·&nbsp; 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'};
Object.entries(toolLogos).forEach(([cls, src]) => {
document.querySelectorAll('section.' + cls).forEach(s => {
const img = document.createElement('img');
img.src = src;
img.alt = altNames[cls];
img.className = 'slide-logo';
s.appendChild(img);
});
});
Reveal.initialize({
width: 1280,
height: 720,
margin: 0.08,
minScale: 0.2,
maxScale: 2.0,
hash: true,
slideNumber: 'c/t',
transition: 'slide',
backgroundTransition: 'fade',
controls: true,
progress: true,
center: false,
plugins: [ RevealNotes, RevealHighlight ]
});
</script>
</body>
</html>