Compare commits

...

18 Commits

Author SHA1 Message Date
Arnaud Prémel-Cabic
e4e42f3e69 docs: slide 33 — add speaker notes on Puppet's CA/certificate auth model
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 14:59:09 +02:00
Arnaud Prémel-Cabic
e763f16a07 docs: slide 14 — add a small HCL example illustrating block anatomy
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 14:50:12 +02:00
Arnaud Prémel-Cabic
9b33886075 feat: vendor tool icons and "this is fine" GIF locally
Download the Terraform/Ansible/Puppet simpleicons SVGs and the giphy
GIF into vendor/ and point all references at local paths, so the deck
runs fully offline with no runtime CDN dependencies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 14:41:55 +02:00
Arnaud Prémel-Cabic
b93bf81151 docs: slide 41 — drop Copilot credit, reword to "with the help of Claude"
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:57:45 +02:00
Arnaud Prémel-Cabic
0e1f2d896b docs: slides 39/40 — note Ansible for on-demand one-off patching
Highlight Ansible's punctual/push ops (single patch on demand) alongside
config/deploy, contrasted with Puppet's continuous enforcement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:55:48 +02:00
Arnaud Prémel-Cabic
b53bb67791 fix: slide 39 SVG — correct a-down arrow marker orientation
Marker polygon pointed down while orient=auto expects a right-pointing
arrow, so the heads rendered sideways. Use a right-pointing triangle
with proper refX/refY so orient=auto rotates it to point down.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:53:33 +02:00
Arnaud Prémel-Cabic
6e32edfe28 docs: slide 38 — add Perforce/OpenVox context in speaker notes
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>
2026-06-08 11:50:38 +02:00
Arnaud Prémel-Cabic
c80049b0ce fix: slide 36 SVG — move loop label above the curved arrow
"continuous enforcement loop" overlapped the top arc; raise it
above the curve (y 42 -> 22).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:46:45 +02:00
Arnaud Prémel-Cabic
a208ff4986 fix: slide 33 SVG — move Puppet Server label above box
Label was behind the pull arrows; move it on top of the box, shift
the box/icons down to make room, and re-point arrows to the new edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:42:37 +02:00
Arnaud Prémel-Cabic
4d22fc0f2a docs: fix slide 30 Ansible core license Apache 2.0 -> GPLv3
Same correction as slide 23 — ansible-core is GPLv3, not Apache 2.0
(slide line + speaker note)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:36:50 +02:00
Arnaud Prémel-Cabic
f8db0fda48 docs: slide 29 Galaxy — minimal install example, trusted-source guidance in notes
- replace specific geerlingguy example with generic
  ansible-galaxy install <namespace>.<role>
- move trusted/official/certified supply-chain guidance into speaker notes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:35:36 +02:00
Arnaud Prémel-Cabic
99cc2de41e docs: link slide 26 dynamic inventory to Terraform state
- dynamic example now uses cloud.terraform.terraform_state plugin
  reading the .tfstate from the earlier Terraform slides
- notes explain the Terraform -> Ansible handoff (single source of truth)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:29:38 +02:00
Arnaud Prémel-Cabic
d085952a69 style: set global header/bullet sizing, drop inline overrides
- .reveal h2 -> 1.05em; section ul -> 0.9em globally
- keep Terraform/Ansible/Puppet divider titles large (2em on colored span)
- remove per-slide inline font-size overrides on slides 13/16/20/21/26

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:26:11 +02:00
Arnaud Prémel-Cabic
e893e39871 docs: add Ansible inventory slide (static vs dynamic) + Jinja2 notes
- new slide 26: inventory construction — static YAML vs dynamic
  OpenStack plugin, with side-by-side examples and notes
- renumber subsequent slide markers 26-40 -> 27-41 (now 41 slides)
- slide 25: add Jinja2 templating speaker notes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:22:25 +02:00
Arnaud Prémel-Cabic
b1dab6fde4 docs: fix Ansible license + bullet sizing + slide 21 notes
- slide 23: Apache 2.0 -> GPLv3 (bullet + note), BSL -> BUSL in note
- slide 21: add Scalr to SaaS list in notes
- slides 13/16/20/21: shrink bullet lists to 0.9em

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:05:03 +02:00
Arnaud Prémel-Cabic
58ae6e890d docs: fix slide 20 OpenTofu facts + slide 21 heading size
- slide 20: BSL -> BUSL 1.1; "OpenTF Foundation" -> CNCF project;
  soften "fully compatible" to "diverging features"; note CNCF since 2025
- slide 21: smaller heading to match slide 20

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:58:33 +02:00
Arnaud Prémel-Cabic
5b11b26642 docs: refine Terraform slides (providers, versions, copy)
- slide 7: "Snowflake Hell" -> "configuration drift"
- slide 8: drop "Prod breaks on a Tuesday" bullet
- slide 13: BSL -> BUSL 1.1
- slide 17: lead with FR/EU providers (OVHcloud, Scaleway, Clever Cloud), drop GCP/Azure
- slide 18: Debian 13, OVH provider ~> 2.0, add version-syntax note
- slide 19: bump versions (openstack v3.4.0, ovh v2.13.1), minimal "..." trim markers + notes
- slide 20: smaller heading

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:54:09 +02:00
Arnaud Prémel-Cabic
b7b7998f22 docs: switch README to FR title + bilingual abstract
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:18:35 +02:00
6 changed files with 156 additions and 75 deletions

View File

@@ -1,16 +1,18 @@
# Configuration as Code: Terraform, Ansible & Puppet — The Three Musketeers of Infrastructure # Terraform, Ansible & Puppet — Les Trois Mousquetaires de la Configuration as Code
Presentation for the **Finistère Dev Meetup**. Présentation pour le **Finistère Dev Meetup**.
## 📝 Abstract ## 📝 Abstract (FR)
> "It works on my server." One machine, hand-configured, impossible to reproduce — and the problem only gets worse as you scale to ten, a hundred, a thousand servers. Configuration drift creeps in silently, and nobody remembers exactly how anything was set up. > Gérer un serveur à la main, ça se fait. En gérer 10, c'est encore faisable. Mais provisionner, configurer et maintenir 100, 1000+ serveurs, c'est une autre histoire.
> >
> What if your infrastructure was just… code? > Et si votre infrastructure n'était que du code ? Venez découvrir comment provisionner, configurer et maintenir facilement une flotte de serveurs avec les Trois Mousquetaires de la Configuration as Code : Terraform, Ansible et Puppet. Que sont ces outils, à quoi servent-ils, et comment sont-ils complémentaires ?
## 📝 Abstract (EN)
> Managing one server by hand? Doable. Managing 10 by hand? Still manageable. But provisioning, configuring and maintaining 100, 1000+ servers — that's another story.
> >
> This talk introduces **Configuration as Code** through its three best-known tools — **Terraform**, **Ansible**, and **Puppet**. We'll see what each one actually does: how Terraform provisions infrastructure declaratively and tracks its state, how Ansible configures servers and runs operations over SSH, and how Puppet's agents continuously enforce compliance and heal drift at scale. Along the way we'll touch on the real-world questions that matter — licensing changes, open-source forks, enterprise vs community editions, and tooling at scale. > What if your infrastructure was just code? Come discover how to easily provision, configure and maintain a fleet of servers with the Three Musketeers of Configuration as Code: Terraform, Ansible and Puppet. What are these tools, what do they do, and how are they complementary?
>
> The punchline: these tools aren't competitors. They're complementary, each solving a different layer of the same problem. By the end you'll understand where each one fits — and how a typical production setup uses all three together: Terraform to provision the VM, Ansible to configure it and deploy the app, and Puppet to keep it compliant, forever.
## 🌐 View the slides ## 🌐 View the slides

View File

@@ -54,7 +54,15 @@
.reveal h1, .reveal h2 { text-align: left; } .reveal h1, .reveal h2 { text-align: left; }
.reveal h1 { font-size: 1.8em; } .reveal h1 { font-size: 1.8em; }
.reveal h2 { font-size: 1.3em; } .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, .title-slide h1 { text-align: center !important; }
.title-slide .subtitle { color: var(--ods-text); } .title-slide .subtitle { color: var(--ods-text); }
@@ -124,9 +132,9 @@
<section class="title-slide"> <section class="title-slide">
<h1>Configuration as Code</h1> <h1>Configuration as Code</h1>
<p class="subtitle"> <p class="subtitle">
<img src="https://cdn.simpleicons.org/puppet/C17F00" alt="Puppet" style="height:0.9em; vertical-align:middle;"> Puppet · <img src="vendor/icons/puppet.svg" 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="vendor/icons/ansible.svg" 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 <img src="vendor/icons/terraform.svg" alt="Terraform" style="height:0.9em; vertical-align:middle;"> Terraform
<br>— What's the difference and when to use what? —</p> <br>— What's the difference and when to use what? —</p>
<p class="meta">FinistDevs · 2026</p> <p class="meta">FinistDevs · 2026</p>
<aside class="notes"> <aside class="notes">
@@ -442,7 +450,7 @@
<!-- ─── SLIDE 7 ─────────────────────────────────────────────────── --> <!-- ─── SLIDE 7 ─────────────────────────────────────────────────── -->
<section> <section>
<h2>Unique. Unreproducible. Undocumented.</h2> <h2>Unique. Unreproducible. Undocumented.</h2>
<p><em>Welcome to Snowflake Hell.</em></p> <p><em>Welcome to configuration drift.</em></p>
<aside class="notes"> <aside class="notes">
<ul> <ul>
<li>"Snowflake servers" — unique, fragile, impossible to recreate.</li> <li>"Snowflake servers" — unique, fragile, impossible to recreate.</li>
@@ -450,7 +458,7 @@
<li>Lighten the mood, then pivot to the consequences.</li> <li>Lighten the mood, then pivot to the consequences.</li>
</ul> </ul>
</aside> </aside>
<img src="https://media.giphy.com/media/QMHoU66sBXqqLqYvGO/giphy.gif" <img src="vendor/img/this-is-fine.gif"
alt="This is fine" alt="This is fine"
style="height:220px; margin-top:0.5em; border-radius:6px;"> style="height:220px; margin-top:0.5em; border-radius:6px;">
</section> </section>
@@ -459,7 +467,6 @@
<section> <section>
<h2>Configuration drift is silent…</h2> <h2>Configuration drift is silent…</h2>
<ul> <ul>
<li>Prod breaks on a Tuesday</li>
<li>Can't reproduce the bug locally</li> <li>Can't reproduce the bug locally</li>
<li>Can't scale reliably</li> <li>Can't scale reliably</li>
<li>Can't onboard a new server without fear</li> <li>Can't onboard a new server without fear</li>
@@ -598,9 +605,9 @@
<section style="text-align:center;"> <section style="text-align:center;">
<h2 style="text-align:center;">Meet the three musketeers of infrastructure.</h2> <h2 style="text-align:center;">Meet the three musketeers of infrastructure.</h2>
<p style="font-size:1.2em; margin-top:1em;"> <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> &nbsp;·&nbsp; <img src="vendor/icons/puppet.svg" alt="Puppet" style="height:0.9em; vertical-align:middle;"> <span class="puppet-col">Puppet</span> &nbsp;·&nbsp;
<img src="https://cdn.simpleicons.org/ansible" alt="Ansible" style="height:0.9em; vertical-align:middle;"> <span class="ansible-col">Ansible</span> &nbsp;·&nbsp; <img src="vendor/icons/ansible.svg" alt="Ansible" style="height:0.9em; vertical-align:middle;"> <span class="ansible-col">Ansible</span> &nbsp;·&nbsp;
<img src="https://cdn.simpleicons.org/terraform" alt="Terraform" style="height:0.9em; vertical-align:middle;"> <span class="tf-col">Terraform</span> <img src="vendor/icons/terraform.svg" alt="Terraform" style="height:0.9em; vertical-align:middle;"> <span class="tf-col">Terraform</span>
</p> </p>
<p><em>Each solves a different problem.</em></p> <p><em>Each solves a different problem.</em></p>
<aside class="notes"> <aside class="notes">
@@ -632,7 +639,7 @@
<li><strong>Infrastructure as Code</strong> tool for provisioning cloud resources</li> <li><strong>Infrastructure as Code</strong> tool for provisioning cloud resources</li>
<li>Created by <strong>HashiCorp</strong> in 2014</li> <li>Created by <strong>HashiCorp</strong> in 2014</li>
<li>Written in <strong>Go</strong></li> <li>Written in <strong>Go</strong></li>
<li>BSL license since 2023 (was MPL)</li> <li>BUSL 1.1 license since 2023 (was MPL)</li>
</ul> </ul>
<aside class="notes"> <aside class="notes">
<ul> <ul>
@@ -646,10 +653,18 @@
<section class="s-tf"> <section class="s-tf">
<h2>HCL: HashiCorp Configuration Language</h2> <h2>HCL: HashiCorp Configuration Language</h2>
<p>Declarative, human-readable — pure JSON works too.</p> <p>Declarative, human-readable — pure JSON works too.</p>
<pre><code class="language-hcl" data-trim>
resource "openstack_compute_instance_v2" "web" {
name = "finistdevs-web"
flavor_name = "b3-8"
tags = ["web", "demo"]
}
</code></pre>
<aside class="notes"> <aside class="notes">
<ul> <ul>
<li>Declarative: you describe the desired end state, not the steps.</li> <li>Declarative: you describe the desired end state, not the steps.</li>
<li>HCL is the common language across all HashiCorp tools.</li> <li>HCL is the common language across all HashiCorp tools.</li>
<li>Block anatomy: a block type (<code>resource</code>), two labels (provider type + local name), then <code>key = value</code> arguments — strings, lists, etc.</li>
</ul> </ul>
</aside> </aside>
</section> </section>
@@ -802,7 +817,7 @@
<section class="s-tf"> <section class="s-tf">
<h2>One tool. Every API.</h2> <h2>One tool. Every API.</h2>
<ul> <ul>
<li><strong>1000+ providers</strong>AWS, GCP, Azure, Cloudflare, GitHub, Kubernetes…</li> <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> <li>Not just cloud — DNS, monitoring, CI/CD, anything with an API</li>
</ul> </ul>
<p><em>If it has an API, there's a Terraform provider for it.</em></p> <p><em>If it has an API, there's a Terraform provider for it.</em></p>
@@ -822,13 +837,13 @@
terraform { terraform {
required_providers { required_providers {
openstack = { source = "terraform-provider-openstack/openstack", version = "~> 3.0" } openstack = { source = "terraform-provider-openstack/openstack", version = "~> 3.0" }
ovh = { source = "ovh/ovh", version = "~> 1.0" } ovh = { source = "ovh/ovh", version = "~> 2.0" }
} }
} }
resource "openstack_compute_instance_v2" "web" { resource "openstack_compute_instance_v2" "web" {
name = "finistdevs-web" name = "finistdevs-web"
image_name = "Debian 12" image_name = "Debian 13"
flavor_name = "b3-8" flavor_name = "b3-8"
network { name = "Ext-Net" } network { name = "Ext-Net" }
} }
@@ -845,6 +860,7 @@ resource "ovh_domain_zone_record" "web" {
<li>Real OVHcloud example: spin up an instance, then point a DNS record at it.</li> <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>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>Two different providers (OpenStack + OVH) working together in one file.</li>
<li>Version syntax: <code>~&gt;</code> is the pessimistic constraint operator. <code>~&gt; 2.0</code> allows any 2.x (&gt;= 2.0, &lt; 3.0) — patch and minor updates, but never a breaking major bump. <code>~&gt; 2.13.0</code> would pin tighter, allowing only 2.13.x.</li>
</ul> </ul>
</aside> </aside>
</section> </section>
@@ -856,16 +872,17 @@ resource "ovh_domain_zone_record" "web" {
$ terraform init $ terraform init
Initializing provider plugins... Initializing provider plugins...
- Finding terraform-provider-openstack/openstack versions matching "~> 3.0"... - Finding terraform-provider-openstack/openstack versions matching "~> 3.0"...
- Finding ovh/ovh versions matching "~> 1.0"... - Finding ovh/ovh versions matching "~> 2.0"...
- Installing terraform-provider-openstack/openstack v3.0.0... - Installing terraform-provider-openstack/openstack v3.4.0...
- Installing ovh/ovh v1.3.0... - Installing ovh/ovh v2.13.1...
Terraform has been successfully initialized! Terraform has been successfully initialized!
$ terraform plan $ terraform plan
openstack_compute_instance_v2.web: Refreshing state... ...
Plan: 2 to add, 0 to change, 0 to destroy. Plan: 2 to add, 0 to change, 0 to destroy.
$ terraform apply $ terraform apply
...
openstack_compute_instance_v2.web: Creating... openstack_compute_instance_v2.web: Creating...
openstack_compute_instance_v2.web: Creation complete after 45s [id=abc-123] openstack_compute_instance_v2.web: Creation complete after 45s [id=abc-123]
ovh_domain_zone_record.web: Creating... ovh_domain_zone_record.web: Creating...
@@ -878,6 +895,10 @@ Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
<li>Walk the lifecycle: init (download providers) → plan → apply.</li> <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>"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>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> </ul>
</aside> </aside>
</section> </section>
@@ -886,14 +907,14 @@ Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
<section class="s-tf"> <section class="s-tf">
<h2>HashiCorp changed Terraform's license.</h2> <h2>HashiCorp changed Terraform's license.</h2>
<ul> <ul>
<li><strong>BSL</strong> instead of MPL — no longer truly open-source</li> <li><strong>BUSL 1.1</strong> instead of MPL — no longer truly open-source</li>
<li>The community responded: <strong>OpenTofu</strong>, by the OpenTF Foundation</li> <li>The community responded: <strong>OpenTofu</strong>, now a CNCF project</li>
</ul> </ul>
<p><em>Drop-in replacement. Fully compatible. Community-driven.</em></p> <p><em>Drop-in for migration. Diverging features. Community-driven.</em></p>
<aside class="notes"> <aside class="notes">
<ul> <ul>
<li>2023: HashiCorp moved to BSL — restricts commercial competitors, not truly open-source anymore.</li> <li>2023: HashiCorp moved to BUSL 1.1 — restricts commercial competitors, not truly open-source anymore.</li>
<li>Community forked it: OpenTofu, now under the Linux Foundation.</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> <li>Practical takeaway: drop-in compatible, swap the binary. Worth knowing for licensing-sensitive orgs.</li>
</ul> </ul>
</aside> </aside>
@@ -912,7 +933,7 @@ Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
<ul> <ul>
<li>Running Terraform from a laptop doesn't scale to a team.</li> <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>You need: shared/locked state, RBAC, audit, plan-on-PR, policy & cost guardrails.</li>
<li>Range from SaaS (HCP, Spacelift, env0) to self-hosted open-source (Atlantis).</li> <li>Range from SaaS (HCP, Spacelift, env0, Scalr) to self-hosted open-source (Atlantis).</li>
</ul> </ul>
</aside> </aside>
</section> </section>
@@ -937,12 +958,12 @@ Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
<li><strong>Agentless automation</strong> tool for configuration and orchestration</li> <li><strong>Agentless automation</strong> tool for configuration and orchestration</li>
<li>Created by <strong>Michael DeHaan</strong> in 2012</li> <li>Created by <strong>Michael DeHaan</strong> in 2012</li>
<li>Acquired by <strong>Red Hat</strong> in 2015</li> <li>Acquired by <strong>Red Hat</strong> in 2015</li>
<li>Written in <strong>Python</strong>Apache 2.0 license</li> <li>Written in <strong>Python</strong>GPLv3 license</li>
</ul> </ul>
<aside class="notes"> <aside class="notes">
<ul> <ul>
<li>Key differentiator: agentless — nothing to install on targets.</li> <li>Key differentiator: agentless — nothing to install on targets.</li>
<li>Red Hat backed, Python, genuinely open-source (Apache 2.0) — contrast with Terraform's BSL.</li> <li>Red Hat backed, Python, genuinely open-source (GPLv3) — contrast with Terraform's BUSL.</li>
</ul> </ul>
</aside> </aside>
</section> </section>
@@ -1030,11 +1051,53 @@ Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
<li>Plain YAML — tasks run top to bottom against the "webservers" group.</li> <li>Plain YAML — tasks run top to bottom against the "webservers" group.</li>
<li>become: true = run as root (sudo).</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>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> </ul>
</aside> </aside>
</section> </section>
<!-- ─── SLIDE 26 : Ansible CLI ──────────────────────────────────── --> <!-- ─── 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 &amp; 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"> <section class="s-ansible">
<p class="filename">$ terminal</p> <p class="filename">$ terminal</p>
<pre><code class="language-bash" data-trim data-noescape> <pre><code class="language-bash" data-trim data-noescape>
@@ -1066,7 +1129,7 @@ finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
</aside> </aside>
</section> </section>
<!-- ─── SLIDE 27 : Ansible operations ───────────────────────────── --> <!-- ─── SLIDE 28 : Ansible operations ───────────────────────────── -->
<section class="s-ansible"> <section class="s-ansible">
<h2>Not just configuration. Operations.</h2> <h2>Not just configuration. Operations.</h2>
<ul> <ul>
@@ -1084,24 +1147,28 @@ finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
</aside> </aside>
</section> </section>
<!-- ─── SLIDE 28 : Ansible Galaxy ───────────────────────────────── --> <!-- ─── SLIDE 29 : Ansible Galaxy ───────────────────────────────── -->
<section class="s-ansible"> <section class="s-ansible">
<h2>The community does the heavy lifting.</h2> <h2>The community does the heavy lifting.</h2>
<ul> <ul>
<li><strong>Ansible Galaxy</strong> — 10,000+ ready-made roles and collections</li> <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> <li>Don't write a playbook to install Docker from scratch — someone already did</li>
</ul> </ul>
<p><em>Just <code>ansible-galaxy install geerlingguy.docker</code>.</em></p> <pre><code class="language-bash" data-trim>
$ ansible-galaxy install &lt;namespace&gt;.&lt;role&gt;
</code></pre>
<aside class="notes"> <aside class="notes">
<ul> <ul>
<li>Galaxy = the package registry for reusable roles/collections.</li> <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>Don't reinvent common setups — pull a battle-tested role (geerlingguy is the famous example).</li>
<li>Huge productivity multiplier; just vet what you import.</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> </ul>
</aside> </aside>
</section> </section>
<!-- ─── SLIDE 29 : Ansible platforms ────────────────────────────── --> <!-- ─── SLIDE 30 : Ansible platforms ────────────────────────────── -->
<section class="s-ansible"> <section class="s-ansible">
<h2>Ansible at scale: open-source vs enterprise.</h2> <h2>Ansible at scale: open-source vs enterprise.</h2>
<ul> <ul>
@@ -1109,17 +1176,17 @@ finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
<li><strong>Ansible Automation Platform</strong> (Red Hat) — enterprise AWX with support</li> <li><strong>Ansible Automation Platform</strong> (Red Hat) — enterprise AWX with support</li>
<li><strong>Semaphore</strong> — lightweight open-source alternative</li> <li><strong>Semaphore</strong> — lightweight open-source alternative</li>
</ul> </ul>
<p><em>Core engine remains Apache 2.0 — truly open-source.</em></p> <p><em>Core engine remains GPLv3 — truly open-source.</em></p>
<aside class="notes"> <aside class="notes">
<ul> <ul>
<li>At scale you want a UI/scheduler/RBAC layer on top of the CLI.</li> <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>AWX (free) → Ansible Automation Platform (Red Hat, supported) is the main path; Semaphore is a lighter option.</li>
<li>Reassure: the core stays Apache 2.0 — no license rug-pull like Terraform.</li> <li>Reassure: the core stays GPLv3 — no license rug-pull like Terraform.</li>
</ul> </ul>
</aside> </aside>
</section> </section>
<!-- ─── SLIDE 30 : Puppet intro ─────────────────────────────────── --> <!-- ─── SLIDE 31 : Puppet intro ─────────────────────────────────── -->
<section class="s-puppet"> <section class="s-puppet">
<h2><span class="puppet-col">Puppet</span></h2> <h2><span class="puppet-col">Puppet</span></h2>
<p>Your servers are configured. Now keep them that way.</p> <p>Your servers are configured. Now keep them that way.</p>
@@ -1132,7 +1199,7 @@ finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
</section> </section>
<!-- ─── SLIDE 31 : What is Puppet ───────────────────────────────── --> <!-- ─── SLIDE 32 : What is Puppet ───────────────────────────────── -->
<section class="s-puppet"> <section class="s-puppet">
<h2>What is Puppet?</h2> <h2>What is Puppet?</h2>
<ul> <ul>
@@ -1149,7 +1216,7 @@ finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
</aside> </aside>
</section> </section>
<!-- ─── SLIDE 32 : Puppet concepts ──────────────────────────────── --> <!-- ─── SLIDE 33 : Puppet concepts ──────────────────────────────── -->
<section class="s-puppet"> <section class="s-puppet">
<h2>Pull, not push. Agents, not SSH.</h2> <h2>Pull, not push. Agents, not SSH.</h2>
<ul> <ul>
@@ -1162,6 +1229,11 @@ finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
<li>Opposite model to Ansible: pull, not push — agents reach out to the server.</li> <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>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> <li>This is the key idea: enforcement runs on a loop, not just at deploy.</li>
<li><strong>Authentication: mutual TLS, not SSH keys or passwords.</strong> The Puppet Server runs its own built-in Certificate Authority (CA). Every agent and the server are identified by an X.509 certificate the CA signs.</li>
<li>First contact: a new agent generates a keypair and sends a Certificate Signing Request (CSR) to the CA, keyed on its <code>certname</code> (usually the FQDN). The cert is <em>pending</em> until signed.</li>
<li>Signing gate: an admin runs <code>puppetserver ca sign --certname &lt;node&gt;</code> (or autosign for trusted ranges/policies). Until signed, the agent gets no catalog — so signing is the enrollment/trust decision.</li>
<li>Steady state: every run is mutual TLS — the agent verifies the server's cert, the server verifies the agent's. Same CA signs both sides, so identity is cryptographic, not network-trust.</li>
<li>Lifecycle: revoke a decommissioned node with <code>puppetserver ca revoke</code> (lands in the CRL); <code>ca clean</code> removes the cert so a rebuilt host can re-enroll. Watch for clock skew and cert expiry — classic agent-checkin failures.</li>
</ul> </ul>
</aside> </aside>
<svg width="750" height="250" viewBox="0 0 750 250" xmlns="http://www.w3.org/2000/svg" style="margin-top:0.5em;"> <svg width="750" height="250" viewBox="0 0 750 250" xmlns="http://www.w3.org/2000/svg" style="margin-top:0.5em;">
@@ -1171,23 +1243,23 @@ finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
</marker> </marker>
</defs> </defs>
<!-- Puppet Server --> <!-- Puppet Server -->
<rect x="290" y="10" width="170" height="60" rx="8" fill="#fdf4e8" stroke="#A06010" stroke-width="2"/> <rect x="290" y="28" width="170" height="60" rx="8" fill="#fdf4e8" stroke="#A06010" stroke-width="2"/>
<rect x="300" y="22" width="36" height="36" rx="4" fill="#f5e6d0" stroke="#A06010"/> <rect x="300" y="40" width="36" height="36" rx="4" fill="#f5e6d0" stroke="#A06010"/>
<line x1="306" y1="32" x2="324" y2="32" stroke="#c08030"/><line x1="306" y1="39" x2="324" y2="39" stroke="#c08030"/><line x1="306" y1="46" x2="324" y2="46" stroke="#c08030"/> <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="24" width="24" height="30" rx="2" fill="#fff" stroke="#A06010"/> <rect x="346" y="42" width="24" height="30" rx="2" fill="#fff" stroke="#A06010"/>
<text x="380" y="35" fill="#A06010" font-size="9" font-family="sans-serif">catalog</text> <text x="380" y="53" fill="#A06010" font-size="9" font-family="sans-serif">catalog</text>
<text x="375" y="88" text-anchor="middle" fill="#4d5592" font-size="13" font-weight="bold" font-family="sans-serif">Puppet Server</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 --> <!-- Clock -->
<circle cx="540" cy="40" r="18" fill="none" stroke="#A06010" stroke-width="1.5"/> <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="540" y2="28" stroke="#A06010" stroke-width="1.5"/>
<line x1="540" y1="40" x2="550" y2="40" 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> <text x="540" y="72" text-anchor="middle" fill="#A06010" font-size="10" font-family="sans-serif">every 30 min</text>
<!-- Pull arrows --> <!-- Pull arrows -->
<line x1="95" y1="160" x2="330" y2="74" stroke="#A06010" stroke-width="1.8" stroke-dasharray="6,4" marker-end="url(#a-pull)"/> <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="74" 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="74" 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="74" 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="74" 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"/> <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> <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 --> <!-- Agent nodes -->
@@ -1200,7 +1272,7 @@ finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
</svg> </svg>
</section> </section>
<!-- ─── SLIDE 33 : Puppet code ──────────────────────────────────── --> <!-- ─── SLIDE 34 : Puppet code ──────────────────────────────────── -->
<section class="s-puppet"> <section class="s-puppet">
<p class="filename"># manifests/webserver.pp</p> <p class="filename"># manifests/webserver.pp</p>
<pre><code class="language-puppet" data-trim> <pre><code class="language-puppet" data-trim>
@@ -1230,7 +1302,7 @@ class webserver {
</aside> </aside>
</section> </section>
<!-- ─── SLIDE 34 : Puppet CLI ───────────────────────────────────── --> <!-- ─── SLIDE 35 : Puppet CLI ───────────────────────────────────── -->
<section class="s-puppet"> <section class="s-puppet">
<p class="filename">$ terminal</p> <p class="filename">$ terminal</p>
<pre><code class="language-bash" data-trim data-noescape> <pre><code class="language-bash" data-trim data-noescape>
@@ -1257,7 +1329,7 @@ Notice: Applied catalog in 12.34 seconds
</aside> </aside>
</section> </section>
<!-- ─── SLIDE 35 : Puppet drift detection ───────────────────────── --> <!-- ─── SLIDE 36 : Puppet drift detection ───────────────────────── -->
<section class="s-puppet"> <section class="s-puppet">
<h2>Someone SSH'd in and changed something.</h2> <h2>Someone SSH'd in and changed something.</h2>
<ul> <ul>
@@ -1301,11 +1373,11 @@ Notice: Applied catalog in 12.34 seconds
<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 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 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)"/> <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="42" text-anchor="middle" fill="#4d5592" font-size="10" font-family="sans-serif">continuous enforcement loop</text> <text x="375" y="22" text-anchor="middle" fill="#4d5592" font-size="10" font-family="sans-serif">continuous enforcement loop</text>
</svg> </svg>
</section> </section>
<!-- ─── SLIDE 36 : Puppet platforms ─────────────────────────────── --> <!-- ─── SLIDE 37 : Puppet platforms ─────────────────────────────── -->
<section class="s-puppet"> <section class="s-puppet">
<h2>Puppet: large fleets, zero drift.</h2> <h2>Puppet: large fleets, zero drift.</h2>
<ul> <ul>
@@ -1323,7 +1395,7 @@ Notice: Applied catalog in 12.34 seconds
</aside> </aside>
</section> </section>
<!-- ─── SLIDE 37 : Puppet community ─────────────────────────────── --> <!-- ─── SLIDE 38 : Puppet community ─────────────────────────────── -->
<section class="s-puppet"> <section class="s-puppet">
<h2>The ecosystem outlives the company.</h2> <h2>The ecosystem outlives the company.</h2>
<ul> <ul>
@@ -1334,13 +1406,15 @@ Notice: Applied catalog in 12.34 seconds
<aside class="notes"> <aside class="notes">
<ul> <ul>
<li>Addresses the "is Puppet dying?" worry after the Perforce acquisition.</li> <li>Addresses the "is Puppet dying?" worry after the Perforce acquisition.</li>
<li>Vox Pupuli keeps the modules alive; OpenVox forks the core (echoes the OpenTofu story).</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> <li>Reassurance: the open ecosystem outlives any single vendor.</li>
</ul> </ul>
</aside> </aside>
</section> </section>
<!-- ─── SLIDE 38 : They're complementary ────────────────────────── --> <!-- ─── SLIDE 39 : They're complementary ────────────────────────── -->
<section> <section>
<h2>They're not competing. They're complementary.</h2> <h2>They're not competing. They're complementary.</h2>
<p>Each solves a different layer of the same problem.</p> <p>Each solves a different layer of the same problem.</p>
@@ -1348,13 +1422,14 @@ Notice: Applied catalog in 12.34 seconds
<ul> <ul>
<li>The payoff slide — the "vs" framing was a trap. They stack.</li> <li>The payoff slide — the "vs" framing was a trap. They stack.</li>
<li>Terraform provisions → Ansible configures → Puppet enforces. Three layers.</li> <li>Terraform provisions → Ansible configures → Puppet enforces. Three layers.</li>
<li>Ansible also covers one-off ops — push a single patch across the fleet on demand (the push model). Puppet handles the continuous side; Ansible the punctual side.</li>
<li>"Which should I use?" → depends which layer of the problem you have.</li> <li>"Which should I use?" → depends which layer of the problem you have.</li>
</ul> </ul>
</aside> </aside>
<svg width="800" height="280" viewBox="0 0 800 280" xmlns="http://www.w3.org/2000/svg" style="margin-top:0.5em;"> <svg width="800" height="280" viewBox="0 0 800 280" xmlns="http://www.w3.org/2000/svg" style="margin-top:0.5em;">
<defs> <defs>
<marker id="a-down" viewBox="0 0 10 10" refX="5" refY="10" markerWidth="10" markerHeight="10" orient="auto"> <marker id="a-down" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="9" markerHeight="9" orient="auto">
<polygon points="0 0,10 0,5 10" fill="#4d5592"/> <polygon points="0 0,10 5,0 10" fill="#4d5592"/>
</marker> </marker>
</defs> </defs>
<!-- Terraform layer --> <!-- Terraform layer -->
@@ -1368,7 +1443,7 @@ Notice: Applied catalog in 12.34 seconds
<rect x="120" y="104" width="560" height="64" rx="10" fill="#CC0000" fill-opacity="0.1" stroke="#CC0000" stroke-width="2"/> <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="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="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> <text x="220" y="150" fill="#cc3333" font-size="11" font-style="italic" font-family="sans-serif">packages, services, deploys, on-demand patching</text>
<!-- Arrow between Ansible and Puppet --> <!-- 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)"/> <line x1="400" y1="172" x2="400" y2="194" stroke="#4d5592" stroke-width="2.5" marker-end="url(#a-down)"/>
<!-- Puppet layer --> <!-- Puppet layer -->
@@ -1379,24 +1454,25 @@ Notice: Applied catalog in 12.34 seconds
</svg> </svg>
</section> </section>
<!-- ─── SLIDE 39 : Real-world stack ─────────────────────────────── --> <!-- ─── SLIDE 40 : Real-world stack ─────────────────────────────── -->
<section> <section>
<h2>A common production setup:</h2> <h2>A common production setup:</h2>
<ol> <ol>
<li><span class="tf-col">Terraform</span> provisions the VM</li> <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="ansible-col">Ansible</span> configures it, deploys the app, and pushes one-off patches</li>
<li><span class="puppet-col">Puppet</span> continuously enforces compliance</li> <li><span class="puppet-col">Puppet</span> continuously enforces compliance</li>
</ol> </ol>
<aside class="notes"> <aside class="notes">
<ul> <ul>
<li>Concrete recap of how they fit together end to end.</li> <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>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> <li>Ansible's role isn't only first-time setup — it's also the tool for punctual ops, like pushing a single patch across the fleet on demand (slide on Operations). Puppet then keeps that state from drifting.</li>
<li>Pick by your actual need: just provisioning? Terraform. Ad-hoc ops / one-off patch? Ansible. Drift control? Puppet.</li>
</ul> </ul>
</aside> </aside>
</section> </section>
<!-- ─── SLIDE 40 : Closing ──────────────────────────────────────── --> <!-- ─── SLIDE 41 : Closing ──────────────────────────────────────── -->
<section class="title-slide"> <section class="title-slide">
<h1>Questions?</h1> <h1>Questions?</h1>
<p class="subtitle">Thank you!</p> <p class="subtitle">Thank you!</p>
@@ -1404,7 +1480,7 @@ Notice: Applied catalog in 12.34 seconds
Arnaud Prémel-Cabic &nbsp;·&nbsp; arnaud.premel-cabic@ovhcloud.com<br> 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> 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>
<p class="meta" style="margin-top:1.5em; font-size:0.45em; color:var(--ods-neutral-600);">🤖 Made with Claude &amp; GitHub Copilot</p> <p class="meta" style="margin-top:1.5em; font-size:0.45em; color:var(--ods-neutral-600);">🤖 Made with the help of Claude</p>
<aside class="notes"> <aside class="notes">
<ul> <ul>
<li>Thank the audience, point to the slides URL.</li> <li>Thank the audience, point to the slides URL.</li>
@@ -1423,9 +1499,9 @@ Notice: Applied catalog in 12.34 seconds
<script src="vendor/reveal.js/plugin/highlight/highlight.js"></script> <script src="vendor/reveal.js/plugin/highlight/highlight.js"></script>
<script> <script>
const toolLogos = { const toolLogos = {
's-tf': 'https://cdn.simpleicons.org/terraform', 's-tf': 'vendor/icons/terraform.svg',
's-ansible': 'https://cdn.simpleicons.org/ansible', 's-ansible': 'vendor/icons/ansible.svg',
's-puppet': 'https://cdn.simpleicons.org/puppet/C17F00' 's-puppet': 'vendor/icons/puppet.svg'
}; };
const altNames = {'s-tf': 'Terraform', 's-ansible': 'Ansible', 's-puppet': 'Puppet'}; const altNames = {'s-tf': 'Terraform', 's-ansible': 'Ansible', 's-puppet': 'Puppet'};

1
vendor/icons/ansible.svg vendored Normal file
View File

@@ -0,0 +1 @@
<svg fill="#EE0000" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Ansible</title><path d="M10.617 11.473l4.686 3.695-3.102-7.662zM12 0C5.371 0 0 5.371 0 12s5.371 12 12 12 12-5.371 12-12S18.629 0 12 0zm5.797 17.305c-.011.471-.403.842-.875.83-.236 0-.416-.09-.664-.293l-6.19-5-2.079 5.203H6.191L11.438 5.44c.124-.314.427-.52.764-.506.326-.014.63.189.742.506l4.774 11.494c.045.111.08.234.08.348-.001.009-.001.009-.001.023z"/></svg>

After

Width:  |  Height:  |  Size: 455 B

1
vendor/icons/puppet.svg vendored Normal file
View File

@@ -0,0 +1 @@
<svg fill="#C17F00" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Puppet</title><path d="M8.984 21.611H6.595v-2.388h2.39zM6.595 2.39h2.39v2.388h-2.39zm13.198 6.028h-5.48l.001-.002-2.941-2.941V0H4.207v7.166h5.48l2.938 2.938.002-.001v3.794l-.003-.003-2.94 2.94H4.207V24h7.166v-5.477l2.94-2.94h5.48V8.417"/></svg>

After

Width:  |  Height:  |  Size: 337 B

1
vendor/icons/terraform.svg vendored Normal file
View File

@@ -0,0 +1 @@
<svg fill="#844FBA" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Terraform</title><path d="M1.44 0v7.575l6.561 3.79V3.787zm21.12 4.227l-6.561 3.791v7.574l6.56-3.787zM8.72 4.23v7.575l6.561 3.787V8.018zm0 8.405v7.575L15.28 24v-7.578z"/></svg>

After

Width:  |  Height:  |  Size: 268 B

BIN
vendor/img/this-is-fine.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB