Compare commits
18 Commits
f8dc80027f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4e42f3e69 | ||
|
|
e763f16a07 | ||
|
|
9b33886075 | ||
|
|
b93bf81151 | ||
|
|
0e1f2d896b | ||
|
|
b53bb67791 | ||
|
|
6e32edfe28 | ||
|
|
c80049b0ce | ||
|
|
a208ff4986 | ||
|
|
4d22fc0f2a | ||
|
|
f8db0fda48 | ||
|
|
99cc2de41e | ||
|
|
d085952a69 | ||
|
|
e893e39871 | ||
|
|
b1dab6fde4 | ||
|
|
58ae6e890d | ||
|
|
5b11b26642 | ||
|
|
b7b7998f22 |
18
README.md
18
README.md
@@ -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.
|
||||
>
|
||||
> 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.
|
||||
> 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?
|
||||
|
||||
## 🌐 View the slides
|
||||
|
||||
|
||||
210
index.html
210
index.html
@@ -54,7 +54,15 @@
|
||||
|
||||
.reveal h1, .reveal h2 { text-align: left; }
|
||||
.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 .subtitle { color: var(--ods-text); }
|
||||
@@ -124,9 +132,9 @@
|
||||
<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
|
||||
<img src="vendor/icons/puppet.svg" alt="Puppet" style="height:0.9em; vertical-align:middle;"> Puppet ·
|
||||
<img src="vendor/icons/ansible.svg" alt="Ansible" style="height:0.9em; vertical-align:middle;"> Ansible ·
|
||||
<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>
|
||||
<p class="meta">FinistDevs · 2026</p>
|
||||
<aside class="notes">
|
||||
@@ -442,7 +450,7 @@
|
||||
<!-- ─── SLIDE 7 ─────────────────────────────────────────────────── -->
|
||||
<section>
|
||||
<h2>Unique. Unreproducible. Undocumented.</h2>
|
||||
<p><em>Welcome to Snowflake Hell.</em></p>
|
||||
<p><em>Welcome to configuration drift.</em></p>
|
||||
<aside class="notes">
|
||||
<ul>
|
||||
<li>"Snowflake servers" — unique, fragile, impossible to recreate.</li>
|
||||
@@ -450,7 +458,7 @@
|
||||
<li>Lighten the mood, then pivot to the consequences.</li>
|
||||
</ul>
|
||||
</aside>
|
||||
<img src="https://media.giphy.com/media/QMHoU66sBXqqLqYvGO/giphy.gif"
|
||||
<img src="vendor/img/this-is-fine.gif"
|
||||
alt="This is fine"
|
||||
style="height:220px; margin-top:0.5em; border-radius:6px;">
|
||||
</section>
|
||||
@@ -459,7 +467,6 @@
|
||||
<section>
|
||||
<h2>Configuration drift is silent…</h2>
|
||||
<ul>
|
||||
<li>Prod breaks on a Tuesday</li>
|
||||
<li>Can't reproduce the bug locally</li>
|
||||
<li>Can't scale reliably</li>
|
||||
<li>Can't onboard a new server without fear</li>
|
||||
@@ -598,9 +605,9 @@
|
||||
<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>
|
||||
<img src="vendor/icons/puppet.svg" alt="Puppet" style="height:0.9em; vertical-align:middle;"> <span class="puppet-col">Puppet</span> ·
|
||||
<img src="vendor/icons/ansible.svg" alt="Ansible" style="height:0.9em; vertical-align:middle;"> <span class="ansible-col">Ansible</span> ·
|
||||
<img src="vendor/icons/terraform.svg" 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">
|
||||
@@ -632,7 +639,7 @@
|
||||
<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>BSL license since 2023 (was MPL)</li>
|
||||
<li>BUSL 1.1 license since 2023 (was MPL)</li>
|
||||
</ul>
|
||||
<aside class="notes">
|
||||
<ul>
|
||||
@@ -646,10 +653,18 @@
|
||||
<section class="s-tf">
|
||||
<h2>HCL: HashiCorp Configuration Language</h2>
|
||||
<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">
|
||||
<ul>
|
||||
<li>Declarative: you describe the desired end state, not the steps.</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>
|
||||
</aside>
|
||||
</section>
|
||||
@@ -802,7 +817,7 @@
|
||||
<section class="s-tf">
|
||||
<h2>One tool. Every API.</h2>
|
||||
<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>
|
||||
</ul>
|
||||
<p><em>If it has an API, there's a Terraform provider for it.</em></p>
|
||||
@@ -822,13 +837,13 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
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" {
|
||||
name = "finistdevs-web"
|
||||
image_name = "Debian 12"
|
||||
image_name = "Debian 13"
|
||||
flavor_name = "b3-8"
|
||||
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>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>
|
||||
@@ -856,16 +872,17 @@ resource "ovh_domain_zone_record" "web" {
|
||||
$ terraform init
|
||||
Initializing provider plugins...
|
||||
- Finding terraform-provider-openstack/openstack versions matching "~> 3.0"...
|
||||
- Finding ovh/ovh versions matching "~> 1.0"...
|
||||
- Installing terraform-provider-openstack/openstack v3.0.0...
|
||||
- Installing ovh/ovh v1.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
|
||||
openstack_compute_instance_v2.web: Refreshing state...
|
||||
...
|
||||
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...
|
||||
@@ -878,6 +895,10 @@ Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
|
||||
<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>
|
||||
@@ -886,14 +907,14 @@ Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
|
||||
<section class="s-tf">
|
||||
<h2>HashiCorp changed Terraform's license.</h2>
|
||||
<ul>
|
||||
<li><strong>BSL</strong> instead of MPL — no longer truly open-source</li>
|
||||
<li>The community responded: <strong>OpenTofu</strong>, by the OpenTF Foundation</li>
|
||||
<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 replacement. Fully compatible. Community-driven.</em></p>
|
||||
<p><em>Drop-in for migration. Diverging features. Community-driven.</em></p>
|
||||
<aside class="notes">
|
||||
<ul>
|
||||
<li>2023: HashiCorp moved to BSL — restricts commercial competitors, not truly open-source anymore.</li>
|
||||
<li>Community forked it: OpenTofu, now under the Linux Foundation.</li>
|
||||
<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>
|
||||
@@ -912,7 +933,7 @@ Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
|
||||
<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) to self-hosted open-source (Atlantis).</li>
|
||||
<li>Range from SaaS (HCP, Spacelift, env0, Scalr) to self-hosted open-source (Atlantis).</li>
|
||||
</ul>
|
||||
</aside>
|
||||
</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>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> — Apache 2.0 license</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 (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>
|
||||
</aside>
|
||||
</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>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 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 & 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>
|
||||
@@ -1066,7 +1129,7 @@ finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ─── SLIDE 27 : Ansible operations ───────────────────────────── -->
|
||||
<!-- ─── SLIDE 28 : Ansible operations ───────────────────────────── -->
|
||||
<section class="s-ansible">
|
||||
<h2>Not just configuration. Operations.</h2>
|
||||
<ul>
|
||||
@@ -1084,24 +1147,28 @@ finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ─── SLIDE 28 : Ansible Galaxy ───────────────────────────────── -->
|
||||
<!-- ─── 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>
|
||||
<p><em>Just <code>ansible-galaxy install geerlingguy.docker</code>.</em></p>
|
||||
<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; 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>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ─── SLIDE 29 : Ansible platforms ────────────────────────────── -->
|
||||
<!-- ─── SLIDE 30 : Ansible platforms ────────────────────────────── -->
|
||||
<section class="s-ansible">
|
||||
<h2>Ansible at scale: open-source vs enterprise.</h2>
|
||||
<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>Semaphore</strong> — lightweight open-source alternative</li>
|
||||
</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">
|
||||
<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 Apache 2.0 — no license rug-pull like Terraform.</li>
|
||||
<li>Reassure: the core stays GPLv3 — no license rug-pull like Terraform.</li>
|
||||
</ul>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ─── SLIDE 30 : Puppet intro ─────────────────────────────────── -->
|
||||
<!-- ─── 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>
|
||||
@@ -1132,7 +1199,7 @@ finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ─── SLIDE 31 : What is Puppet ───────────────────────────────── -->
|
||||
<!-- ─── SLIDE 32 : What is Puppet ───────────────────────────────── -->
|
||||
<section class="s-puppet">
|
||||
<h2>What is Puppet?</h2>
|
||||
<ul>
|
||||
@@ -1149,7 +1216,7 @@ finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ─── SLIDE 32 : Puppet concepts ──────────────────────────────── -->
|
||||
<!-- ─── SLIDE 33 : Puppet concepts ──────────────────────────────── -->
|
||||
<section class="s-puppet">
|
||||
<h2>Pull, not push. Agents, not SSH.</h2>
|
||||
<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>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><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 <node></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>
|
||||
</aside>
|
||||
<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>
|
||||
</defs>
|
||||
<!-- Puppet Server -->
|
||||
<rect x="290" y="10" 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"/>
|
||||
<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"/>
|
||||
<rect x="346" y="24" 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="375" y="88" text-anchor="middle" fill="#4d5592" font-size="13" font-weight="bold" font-family="sans-serif">Puppet Server</text>
|
||||
<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="74" 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="375" y1="160" x2="375" 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="74" 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="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 -->
|
||||
@@ -1200,7 +1272,7 @@ finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
|
||||
</svg>
|
||||
</section>
|
||||
|
||||
<!-- ─── SLIDE 33 : Puppet code ──────────────────────────────────── -->
|
||||
<!-- ─── SLIDE 34 : Puppet code ──────────────────────────────────── -->
|
||||
<section class="s-puppet">
|
||||
<p class="filename"># manifests/webserver.pp</p>
|
||||
<pre><code class="language-puppet" data-trim>
|
||||
@@ -1230,7 +1302,7 @@ class webserver {
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ─── SLIDE 34 : Puppet CLI ───────────────────────────────────── -->
|
||||
<!-- ─── SLIDE 35 : Puppet CLI ───────────────────────────────────── -->
|
||||
<section class="s-puppet">
|
||||
<p class="filename">$ terminal</p>
|
||||
<pre><code class="language-bash" data-trim data-noescape>
|
||||
@@ -1257,7 +1329,7 @@ Notice: Applied catalog in 12.34 seconds
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ─── SLIDE 35 : Puppet drift detection ───────────────────────── -->
|
||||
<!-- ─── SLIDE 36 : Puppet drift detection ───────────────────────── -->
|
||||
<section class="s-puppet">
|
||||
<h2>Someone SSH'd in and changed something.</h2>
|
||||
<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 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="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>
|
||||
</section>
|
||||
|
||||
<!-- ─── SLIDE 36 : Puppet platforms ─────────────────────────────── -->
|
||||
<!-- ─── SLIDE 37 : Puppet platforms ─────────────────────────────── -->
|
||||
<section class="s-puppet">
|
||||
<h2>Puppet: large fleets, zero drift.</h2>
|
||||
<ul>
|
||||
@@ -1323,7 +1395,7 @@ Notice: Applied catalog in 12.34 seconds
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ─── SLIDE 37 : Puppet community ─────────────────────────────── -->
|
||||
<!-- ─── SLIDE 38 : Puppet community ─────────────────────────────── -->
|
||||
<section class="s-puppet">
|
||||
<h2>The ecosystem outlives the company.</h2>
|
||||
<ul>
|
||||
@@ -1334,13 +1406,15 @@ Notice: Applied catalog in 12.34 seconds
|
||||
<aside class="notes">
|
||||
<ul>
|
||||
<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>
|
||||
</ul>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ─── SLIDE 38 : They're complementary ────────────────────────── -->
|
||||
<!-- ─── 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>
|
||||
@@ -1348,13 +1422,14 @@ Notice: Applied catalog in 12.34 seconds
|
||||
<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>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>
|
||||
</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 id="a-down" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="9" markerHeight="9" orient="auto">
|
||||
<polygon points="0 0,10 5,0 10" fill="#4d5592"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<!-- 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"/>
|
||||
<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>
|
||||
<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 -->
|
||||
<line x1="400" y1="172" x2="400" y2="194" stroke="#4d5592" stroke-width="2.5" marker-end="url(#a-down)"/>
|
||||
<!-- Puppet layer -->
|
||||
@@ -1379,24 +1454,25 @@ Notice: Applied catalog in 12.34 seconds
|
||||
</svg>
|
||||
</section>
|
||||
|
||||
<!-- ─── SLIDE 39 : Real-world stack ─────────────────────────────── -->
|
||||
<!-- ─── 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="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>
|
||||
</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>
|
||||
<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>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ─── SLIDE 40 : Closing ──────────────────────────────────────── -->
|
||||
<!-- ─── SLIDE 41 : Closing ──────────────────────────────────────── -->
|
||||
<section class="title-slide">
|
||||
<h1>Questions?</h1>
|
||||
<p class="subtitle">Thank you!</p>
|
||||
@@ -1404,7 +1480,7 @@ Notice: Applied catalog in 12.34 seconds
|
||||
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>
|
||||
<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">
|
||||
<ul>
|
||||
<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>
|
||||
const toolLogos = {
|
||||
's-tf': 'https://cdn.simpleicons.org/terraform',
|
||||
's-ansible': 'https://cdn.simpleicons.org/ansible',
|
||||
's-puppet': 'https://cdn.simpleicons.org/puppet/C17F00'
|
||||
's-tf': 'vendor/icons/terraform.svg',
|
||||
's-ansible': 'vendor/icons/ansible.svg',
|
||||
's-puppet': 'vendor/icons/puppet.svg'
|
||||
};
|
||||
const altNames = {'s-tf': 'Terraform', 's-ansible': 'Ansible', 's-puppet': 'Puppet'};
|
||||
|
||||
|
||||
1
vendor/icons/ansible.svg
vendored
Normal file
1
vendor/icons/ansible.svg
vendored
Normal 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
1
vendor/icons/puppet.svg
vendored
Normal 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
1
vendor/icons/terraform.svg
vendored
Normal 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
BIN
vendor/img/this-is-fine.gif
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
Reference in New Issue
Block a user