Add Makefile, package.json, and initial configuration for VitePress and Slidev integration
- Introduced a Makefile for streamlined commands related to building, running, and managing presentations. - Created package.json to define project metadata, dependencies, and scripts for development and testing. - Added .gitignore entries to exclude build artifacts and temporary files from version control. - Included playwright.config.mjs for end-to-end testing setup with Playwright. - Added README.md for project documentation, outlining setup instructions and development guidelines.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* Pied de page fixe pour tous les decks Slidev : retour portail + lien Specificat.
|
||||
* Lien « Accueil » en relatif ../../../ pour fonctionner avec --base …/slides/<slug>/
|
||||
* (ex. /slides/foo/12 → racine du site).
|
||||
*
|
||||
* Ne pas cibler .slidev-page en CSS global (padding, box-sizing, etc.) : Slidev centre
|
||||
* la planche avec transform + dimensions ; modifier cette boîte casse le centrage.
|
||||
*/
|
||||
const specificat = 'https://specificat.io/'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="deck-footer" aria-label="Navigation du portail">
|
||||
<a class="deck-footer__link" href="../../../">Accueil</a>
|
||||
<span class="deck-footer__sep" aria-hidden="true">·</span>
|
||||
<a
|
||||
class="deck-footer__link"
|
||||
:href="specificat"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>Specificat.io</a>
|
||||
<span class="deck-footer__sep" aria-hidden="true">·</span>
|
||||
<span class="deck-footer__rights">All rights reserved</span>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.deck-footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/*
|
||||
* Slidev place la webcam sur .z-camera (= z-index 15, voir @slidev/client/uno.config.ts).
|
||||
* Un footer plus haut (ex. 30) passait au-dessus et masquait la miniature.
|
||||
*/
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.35em 0.5em;
|
||||
padding: 0.35rem 2.5rem 0.55rem;
|
||||
font-size: clamp(10px, 1.5vw, 12px);
|
||||
line-height: 1.3;
|
||||
color: rgba(245, 245, 245, 0.75);
|
||||
pointer-events: none;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.07);
|
||||
background: linear-gradient(to top, rgba(5, 7, 9, 0.72), transparent);
|
||||
}
|
||||
|
||||
.deck-footer__link {
|
||||
pointer-events: auto;
|
||||
color: #2bc9c6;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.deck-footer__link:hover,
|
||||
.deck-footer__link:focus-visible {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.deck-footer__sep {
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.deck-footer__rights {
|
||||
opacity: 0.85;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Reveal : center: true — le thème Slidev « default » aligne le bloc en haut-gauche.
|
||||
* Si une planche reste en layout default (cache, ancien bundle, etc.), on centre
|
||||
* le bloc comme le layout intégré « center ». Ne cible pas .slidev-layout.center.
|
||||
*/
|
||||
.slidev-layout.default {
|
||||
min-height: 100%;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import QRCode from 'qrcode'
|
||||
|
||||
const REPO = 'https://git.specificat.io/arnault/Talks'
|
||||
|
||||
const canvasRepo = ref<HTMLCanvasElement | null>(null)
|
||||
const canvasHome = ref<HTMLCanvasElement | null>(null)
|
||||
|
||||
/** Accueil portail : même logique que l’ancien Reveal (parent depuis /slides/<slug>/<n>). */
|
||||
function portalHomeHref(): string {
|
||||
if (typeof window === 'undefined') return 'https://specificat.io/'
|
||||
try {
|
||||
const u = new URL(window.location.href)
|
||||
if (u.pathname.includes('/slides/')) {
|
||||
u.pathname = '/'
|
||||
u.hash = ''
|
||||
return u.href
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return `${window.location.origin}/`
|
||||
}
|
||||
|
||||
const opts = {
|
||||
width: 150,
|
||||
margin: 1,
|
||||
color: { dark: '#0d1115ff', light: '#ffffffff' },
|
||||
}
|
||||
|
||||
async function draw() {
|
||||
if (canvasRepo.value)
|
||||
await QRCode.toCanvas(canvasRepo.value, REPO, opts)
|
||||
if (canvasHome.value)
|
||||
await QRCode.toCanvas(canvasHome.value, portalHomeHref(), opts)
|
||||
}
|
||||
|
||||
onMounted(draw)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="closing-qr" aria-label="QR codes dépôt et accueil">
|
||||
<figure class="closing-qr__block">
|
||||
<figcaption class="closing-qr__cap">Dépôt</figcaption>
|
||||
<div class="closing-qr__wrap">
|
||||
<canvas
|
||||
ref="canvasRepo"
|
||||
role="img"
|
||||
width="150"
|
||||
height="150"
|
||||
aria-label="QR code vers le dépôt Git"
|
||||
/>
|
||||
</div>
|
||||
</figure>
|
||||
<figure class="closing-qr__block">
|
||||
<figcaption class="closing-qr__cap">Slides — accueil</figcaption>
|
||||
<div class="closing-qr__wrap">
|
||||
<canvas
|
||||
ref="canvasHome"
|
||||
role="img"
|
||||
width="150"
|
||||
height="150"
|
||||
aria-label="QR code vers l’accueil du portail"
|
||||
/>
|
||||
</div>
|
||||
</figure>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.closing-qr {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
gap: 1.75rem;
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
.closing-qr__block {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.closing-qr__cap {
|
||||
margin: 0 0 0.35rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: rgba(245, 245, 245, 0.85);
|
||||
}
|
||||
|
||||
.closing-qr__wrap {
|
||||
display: inline-block;
|
||||
padding: 0.2em;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
line-height: 0;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.closing-qr__wrap canvas {
|
||||
display: block;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import DeckFooter from '../_shared/DeckFooter.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DeckFooter />
|
||||
</template>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"slug": "kubernetes-operators",
|
||||
"title": "Kubernetes Operators — De l’enfer au paradis",
|
||||
"description": "Mental model (API, etcd, boucle), controllers et opérateurs, exemples concrets (Reflector, Reloader, cert-manager, Postgres) et pièges à éviter.",
|
||||
"tags": ["kubernetes", "operators", "devops", "controllers"],
|
||||
"order": 1,
|
||||
"featured": true
|
||||
}
|
||||
@@ -0,0 +1,577 @@
|
||||
---
|
||||
theme: default
|
||||
title: Kubernetes Operators — De l'enfer au paradis
|
||||
info: |
|
||||
Mental model Kubernetes, boucles de contrôle, opérateurs, exemples et pièges.
|
||||
highlighter: shiki
|
||||
# Comme Reveal « center: true » : planche centrée (layout intégré Slidev).
|
||||
defaults:
|
||||
layout: center
|
||||
---
|
||||
|
||||
# Kubernetes Operators — De l'enfer… au paradis
|
||||
|
||||
> Kubernetes n'est **pas** compliqué.
|
||||
> Mais **mal compris**… c'est un enfer.
|
||||
|
||||
<!--
|
||||
On parle surtout de pourquoi les opérateurs changent la manière de travailler — puissants mais dangereux si le mental model est faux.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Kubernetes : délire d'Ops ?
|
||||
|
||||
> On nous l'a surtout vendu comme un truc d'infra.
|
||||
> *Moi aussi, je suis passé par ce réflexe.*
|
||||
|
||||
- « Trop complexe »
|
||||
- Réservé aux spécialistes plateforme
|
||||
- Magie noire / pas « mon » métier
|
||||
|
||||
<!--
|
||||
Souvent présenté comme réservé aux spécialistes — en réalité proche de ce qu'on fait déjà en dev.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Kubernetes c'est un délire de dev
|
||||
|
||||
> Kubernetes c'est un délire de dev
|
||||
|
||||
<!--
|
||||
Transition : contraste avec la slide précédente.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Kubernetes = API + état + boucle
|
||||
|
||||
- API REST
|
||||
- Base d'état (etcd)
|
||||
- Boucle de réconciliation → events
|
||||
- Pas de mystère → mécanismes explicites (controllers, kubelet, containerd, etc.)
|
||||
|
||||
<!--
|
||||
Pas de magie — des patterns qu'on retrouve ailleurs en dev.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## De vos outils au noyau Linux
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
B[kube-api]
|
||||
subgraph Clients["Clients"]
|
||||
A01[kubectl]
|
||||
A02[Helm]
|
||||
A03[Kustomize]
|
||||
end
|
||||
A01 --> B
|
||||
A02 --> B
|
||||
A03 --> B
|
||||
B --> C[(etcd)]
|
||||
B --> D[Controllers]
|
||||
D --> B
|
||||
D --> E[kubelet]
|
||||
E --> F[containerd]
|
||||
F --> G[Kernel Linux]
|
||||
```
|
||||
|
||||
- **kube-api** — point d'entrée
|
||||
- **etcd** — source de vérité
|
||||
- **kubelet** — agent sur chaque nœud
|
||||
- **container runtime** (ex. containerd) — exécution des conteneurs
|
||||
|
||||
<!--
|
||||
Helm, kubectl, Flux… appellent l'API. L'API parle à etcd, les controllers poussent vers les kubelets, le runtime lance les conteneurs.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Kubernetes ne fait pas tourner vos apps : la boucle de contrôle
|
||||
|
||||
Il **orchestre** · il **délègue** · il **observe**
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
DS["Spec · désir"] --> AP[kube-apiserver]
|
||||
AP <-->|objets cluster| ET[(etcd)]
|
||||
AP --> EV["Watch · événements"]
|
||||
EV --> CTRL[Controllers]
|
||||
CTRL -->|réconciliation · requêtes API| AP
|
||||
subgraph NODE["Nœud"]
|
||||
K[kubelet] -->|CRI| CD[containerd]
|
||||
end
|
||||
AP -->|spec Pods · synchro| K
|
||||
K -->|status| AP
|
||||
```
|
||||
|
||||
**↻** Les changements d'objets vivent dans l'API ; le **kubelet** **synchronise** depuis l'API, traduit en **CRI** vers **containerd**. Le **status** remonte par le kubelet.
|
||||
|
||||
<!--
|
||||
Aligné sur la doc Kubernetes : observer, rapprocher le courant du spec ; informers / watches → files d'événements. etcd derrière l'API.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Sous containerd : cgroups et namespaces
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
F[containerd] --> K[Linux Kernel]
|
||||
```
|
||||
|
||||
- **cgroups**
|
||||
- **namespaces**
|
||||
|
||||
<!--
|
||||
Derrière containerd, le noyau — vous ne voulez pas manipuler les fichiers à la main au quotidien.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Kubernetes vous évite de vivre dans le noyau
|
||||
|
||||
- Manipuler les **cgroups** à la main
|
||||
- Gérer **isolation**, **CPU**, **mémoire** sur vos nœuds
|
||||
|
||||
> À côté de ça, un cluster qui râle, c'est presque du repos.
|
||||
|
||||
---
|
||||
|
||||
## CNI, CSI, CRI : de l'infra branchable
|
||||
|
||||
- **CNI** → réseau (ex. Istio, Cilium, Calico, Flannel…)
|
||||
- **CSI** → stockage (ex. Ceph, Longhorn, Amazon…)
|
||||
- **CRI** → runtime (ex. containerd, CRI-O…)
|
||||
- Ici : brancher l'infra — pas encoder la logique métier (*≠* opérateur)
|
||||
|
||||
---
|
||||
|
||||
## Brancher l'infra ou coder le métier ?
|
||||
|
||||
| Type | Rôle |
|
||||
|------|------|
|
||||
| **CNI / CSI / CRI** | Infrastructure |
|
||||
| **Opérateur** | Logique métier |
|
||||
|
||||
<!--
|
||||
D'un côté briques branchables ; de l'autre du code qui encode le comportement dans le cluster. Icônes : icônes K8s officielles, CNCF Landscape, Simple Icons.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Qui écrit la logique du cluster ?
|
||||
|
||||
<!--
|
||||
Qui décide quand l'état réel diverge ? (pause)
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## La logique vit dans les controllers
|
||||
|
||||
*Ils ferment la boucle : observation, décision, action.*
|
||||
|
||||
<!--
|
||||
L'intelligence du cluster est dans les controllers.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Les opérateurs : des controllers spécialisés
|
||||
|
||||
<!--
|
||||
Même mécanisme, focalisé sur un domaine métier ou un logiciel (base, queue…).
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Sans opérateur : scripts, crons, humains dans la boucle
|
||||
|
||||
- Scripts bash
|
||||
- Cron jobs
|
||||
- Interventions humaines
|
||||
- État incohérent
|
||||
|
||||
---
|
||||
|
||||
## Automatisation éclatée : la logique reste dans les têtes
|
||||
|
||||
- Runbooks Confluence
|
||||
- Scripts bash « magiques »
|
||||
- Cron jobs oubliés
|
||||
- Interventions humaines
|
||||
|
||||
**La logique est dans la tête des gens, pas dans le système.**
|
||||
|
||||
---
|
||||
|
||||
## Drift, pas de reproductibilité, 3 h du matin
|
||||
|
||||
- Drift de configuration
|
||||
- Actions non reproductibles
|
||||
- Dépendance aux humains
|
||||
- Incidents à 3 h du matin
|
||||
|
||||
- Pas **reproductible** / pas **observable** cluster / pas **fiable** dans le temps
|
||||
- Impossible de **raisonner globalement** sur l'état
|
||||
|
||||
---
|
||||
|
||||
## Vous déclarez ; le système réconcilie
|
||||
|
||||
```yaml
|
||||
kind: Database
|
||||
spec:
|
||||
size: 3
|
||||
```
|
||||
|
||||
<!--
|
||||
État voulu → le contrôleur fait les transitions répétitives.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Spec, opérateur, cluster : ça tourne en boucle
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[Spec] --> B[Operator]
|
||||
B --> C[Cluster]
|
||||
C --> B
|
||||
```
|
||||
|
||||
*Dès que le réel diverge, la réconciliation repart.*
|
||||
|
||||
<!--
|
||||
Exemples : annotations sur ressources natives, puis vrais CRD ; projet maison pour ancrer le discours.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Reflector & Reloader : la puissance des annotations
|
||||
|
||||
| Outil | Idée | Déclencheur typique |
|
||||
|-------|------|---------------------|
|
||||
| **Reflector** (EmberStack) | Copie / réplique **Secrets** et **ConfigMaps** (autres ns, miroirs) | Annotations sur la ressource source ou la copie |
|
||||
| **Reloader** (Stakater) | **Redémarre** les Pods si Secret / ConfigMap référencé change | Annotation sur **Deployment** / **StatefulSet** / etc. |
|
||||
|
||||
- Reflector : `reflector.v1.k8s.emberstack.com/*`
|
||||
- Reloader : `reloader.stakater.com/auto: "true"`
|
||||
|
||||
<!--
|
||||
Pas toujours un gros opérateur CRD — parfois contrôleur sur objets standards + annotations. Liens : kubernetes-reflector, Reloader sur GitHub.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Exemple Reflector : secret DB → namespace applicatif
|
||||
|
||||
*Le secret « source de vérité » vit avec la base ; Reflector recrée un miroir là où l'app tourne.*
|
||||
|
||||
```yaml
|
||||
# Namespace database — secret maître
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: db-credentials
|
||||
namespace: database
|
||||
annotations:
|
||||
reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
|
||||
reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"
|
||||
reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "app-shop"
|
||||
stringData:
|
||||
DATABASE_URL: "postgres://…"
|
||||
```
|
||||
|
||||
- Reflector maintient un **Secret du même nom** dans `app-shop`, à jour quand la source change.
|
||||
- Alternative : `reflector.v1.k8s.emberstack.com/reflects: "database/db-credentials"`
|
||||
|
||||
---
|
||||
|
||||
## Exemple Reloader : rollout quand le secret miroir change
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: shop-api
|
||||
namespace: app-shop
|
||||
annotations:
|
||||
reloader.stakater.com/auto: "true"
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: api
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: db-credentials
|
||||
```
|
||||
|
||||
- Rotation dans `database/db-credentials` → Reflector met à jour `app-shop/db-credentials` → Reloader déclenche un **rollout**.
|
||||
- Sans Reloader, les Pods gardent l'ancienne valeur en mémoire.
|
||||
|
||||
---
|
||||
|
||||
## Cert-manager, Postgres, Keycloak : la spec devient une entité
|
||||
|
||||
| Opérateur | CRD (exemples) | Ce que ça matérialise |
|
||||
|-----------|----------------|----------------------|
|
||||
| **cert-manager** | `Certificate`, `Issuer`, `ClusterIssuer` | Secrets TLS, ACME / PKI |
|
||||
| **Postgres Operator** (Zalando) | `postgresql` (`acid.zalan.do`) | Cluster PG (Patroni, volumes, users…) |
|
||||
| **Keycloak Operator** | `Keycloak`, `KeycloakRealm`, `KeycloakClient`… | Instances, royaumes, clients |
|
||||
|
||||
La spec décrit l'état métier ; le contrôleur crée / met à jour Deployments, Services, Secrets…
|
||||
|
||||
<!--
|
||||
Trois domaines, même mécanisme CRD + réconciliation. Liens cert-manager.io, Zalando postgres-operator, Keycloak operator.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Un certificat déclaratif, tout le cycle TLS en dessous
|
||||
|
||||
```yaml
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: api-example
|
||||
spec:
|
||||
dnsNames:
|
||||
- api.example.com
|
||||
```
|
||||
|
||||
Suite dérivée automatiquement : `CertificateRequest`, challenge ACME, Secret TLS.
|
||||
|
||||
---
|
||||
|
||||
## Certificats : du Certbot au renouvellement automatique
|
||||
|
||||
*Avant — sans opérateur*
|
||||
|
||||
- Certbot manuel
|
||||
- Scripts cron
|
||||
- Cert expiré = prod down
|
||||
|
||||
*Avec cert-manager*
|
||||
|
||||
- Déclaratif
|
||||
- Auto-renouvellement
|
||||
- Observabilité native (`Certificate`, conditions, events)
|
||||
|
||||
<!--
|
||||
Complexité énorme encapsulée — pertinent quand le domaine est clair, répétitif et critique.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Postgres répliqué sans bricoler le failover à la main
|
||||
|
||||
```yaml
|
||||
apiVersion: acid.zalan.do/v1
|
||||
kind: postgresql
|
||||
metadata:
|
||||
name: my-cluster
|
||||
spec:
|
||||
numberOfInstances: 3
|
||||
```
|
||||
|
||||
Derrière : réplication, failover, backup, users / rôles.
|
||||
|
||||
---
|
||||
|
||||
## Quelle techno pour développer votre opérateur ?
|
||||
|
||||
| Piste | Outils typiques | À retenir |
|
||||
|-------|-----------------|-----------|
|
||||
| **Go** (référence) | **Kubebuilder**, `controller-runtime`, client-go | Même patterns upstream ; sous charge : goroutines, files, back-off — peu de RAM en plus. |
|
||||
| **Operator SDK** | CLI Red Hat · Go · Ansible / Helm | Go solide ; Ansible/Helm = wrapper — court si la logique métier épaissit. |
|
||||
| **Autres** | Python **Kopf**, Rust **kube-rs**, Java… | Selon l'équipe ; moins d'exemples core. |
|
||||
|
||||
**Go + controller-runtime** pour un opérateur maison sérieux ; sinon celui que l'équipe **maintient** le mieux.
|
||||
|
||||
*Charge ↑ : un runtime qui gonfle la RAM par événement mange votre quota.*
|
||||
|
||||
---
|
||||
|
||||
## Quand un opérateur est légitime
|
||||
|
||||
- Système **complexe**
|
||||
- **Règles** claires et stables
|
||||
- **État** critique pour l'activité
|
||||
- Opérations **répétitives**
|
||||
|
||||
<!--
|
||||
Si tu ne peux pas formaliser « si X alors Y » proprement — très mauvaise idée en opérateur.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Keycloak Credential Manager — le même schéma, chez moi
|
||||
|
||||
- Objectif : **réconcilier** des **credentials Keycloak** avec ce qui est **déclaré dans le cluster** (CRD, Secrets annotés…).
|
||||
- Montrer le **même schéma** : *observe → compare → corrige* sur **ton** domaine.
|
||||
- **Incident / compromission** : reload Keycloak → événements côté IdP ; l'opérateur **interroge** (ex. toutes les minutes), repère l'écart et **propage** la correction.
|
||||
|
||||
<!--
|
||||
Pas pour vendre un produit — prouver que la logique est la même : API K8s, état désiré, code à la place du runbook.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Exemple — `KeycloakClientCredential`
|
||||
|
||||
*CRD* `secrets.carbogame.com/v1alpha1` — `spec.source` (realm, client) → `spec.target` (Secret, rafraîchissement, mapping).
|
||||
|
||||
```yaml
|
||||
apiVersion: secrets.carbogame.com/v1alpha1
|
||||
kind: KeycloakClientCredential
|
||||
metadata:
|
||||
name: gitea-creds
|
||||
namespace: keycloak
|
||||
spec:
|
||||
source:
|
||||
keycloakServer: prod-keycloak
|
||||
realm: operator
|
||||
clientId: gitea
|
||||
target:
|
||||
secretName: gitea-secret-creds
|
||||
namespace:
|
||||
- gitea
|
||||
refreshPeriod: 10m
|
||||
fields:
|
||||
clientId: key
|
||||
clientSecret: secret
|
||||
```
|
||||
|
||||
*En chart Helm, `namespace` et `keycloakServer` sont souvent injectés via templates.*
|
||||
|
||||
---
|
||||
|
||||
## Développer son opérateur : réutiliser les autres
|
||||
|
||||
- **Ne pas tout recoder** : s'appuyer sur des contrôleurs existants pour ce qu'ils font déjà bien.
|
||||
- Ex. **Secrets / ConfigMaps** vers d'autres namespaces : brancher **Reflector** plutôt que dupliquer la logique — une politique de réflexion, **un RBAC** à raisonner.
|
||||
- Votre code se concentre sur **le métier**.
|
||||
|
||||
---
|
||||
|
||||
## Ce qu'un opérateur bien choisi vous apporte
|
||||
|
||||
- Automatiser du **répétitif**
|
||||
- **Encapsuler** de la complexité
|
||||
- **Stabiliser** un système
|
||||
|
||||
… mais les opérateurs peuvent aussi devenir un enfer.
|
||||
|
||||
---
|
||||
|
||||
## Quand ne pas écrire d'opérateur
|
||||
|
||||
- Logique **floue** ou en perpétuelle négociation métier
|
||||
- Processus **instables**
|
||||
- Dépendances **externes** imprévisibles
|
||||
- **One-shot** sans cycle de vie
|
||||
|
||||
---
|
||||
|
||||
## Cinq signaux qu'un opérateur part en vrille
|
||||
|
||||
- Spec **incompréhensible** sans lire le code
|
||||
- Effets **implicites** (créations ailleurs invisibles)
|
||||
- **Couplage** externe fort
|
||||
- **Debug** impossible sans les mainteneurs
|
||||
- **État** non observable (pas de conditions / status utiles)
|
||||
|
||||
> Un opérateur mal conçu, c'est un microservice dont plus personne ne tient les règles du jeu.
|
||||
|
||||
---
|
||||
|
||||
## Débugger un opérateur : describe, events, status, logs
|
||||
|
||||
- `kubectl describe` (resource + events)
|
||||
- **Events** du namespace
|
||||
- **Status / conditions** sur la CRD
|
||||
- **Logs** du contrôleur
|
||||
|
||||
---
|
||||
|
||||
## Un opérateur, c'est un produit logiciel — pas un script
|
||||
|
||||
- **Code** et revues
|
||||
- **Tests** (unitaires, intégration, e2e si possible)
|
||||
- **Maintenance** dans la durée
|
||||
- **Compatibilité** avec les versions Kubernetes
|
||||
- **Migrations** de CRD / spec
|
||||
|
||||
---
|
||||
|
||||
## Quand l'opérateur devient l'enfer
|
||||
|
||||
- Logique **implicite**, **effets de bord**, **debugging** difficile, **couplage** fort
|
||||
|
||||
En pratique :
|
||||
|
||||
- Tout mettre en opérateur
|
||||
- Logique métier **floue**
|
||||
- Effets **cachés**
|
||||
|
||||
---
|
||||
|
||||
## Kubernetes, plateforme de programmation
|
||||
|
||||
- Kubernetes évite **l'enfer de l'infrastructure** bas niveau.
|
||||
- Les **bons** opérateurs évitent **l'enfer du manuel** — les **mauvais** y renvoient autrement.
|
||||
|
||||
<!--
|
||||
Fil : pas magique → API + boucle → controllers / opérateurs → avant/après → exemples → bon usage / pièges → conclusion.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Avant d'écrire un opérateur : la checklist
|
||||
|
||||
- Répétitif
|
||||
- Règles claires
|
||||
- Déclaratif possible
|
||||
- Idempotent
|
||||
- Observable
|
||||
|
||||
Si une case importante manque : **reculez**.
|
||||
|
||||
---
|
||||
|
||||
## Votre logique, exécutée par le cluster au lieu d'un humain
|
||||
|
||||
- « OK, je comprends enfin le cadre Kubernetes. »
|
||||
- « Les opérateurs : puissants, à manier avec précision. »
|
||||
|
||||
Un opérateur, c'est votre logique métier — **lisible**, **testable**, **bornée**.
|
||||
|
||||
---
|
||||
|
||||
## Kubernetes simplifie l'infra — les opérateurs, ça peut simplifier ou empirer
|
||||
|
||||
Les **bons** **encapsulent** une complexité maîtrisée ; les **mauvais** recréent un enfer **plus sophistiqué**.
|
||||
|
||||
---
|
||||
|
||||
## Merci
|
||||
|
||||
Merci pour votre attention.
|
||||
|
||||
**Des questions ?**
|
||||
|
||||
<ClosingQr />
|
||||
|
||||
- Dépôt : [git.specificat.io/arnault/Talks](https://git.specificat.io/arnault/Talks)
|
||||
- Site : [specificat.io](https://specificat.io/)
|
||||
- Index des slides : portail talks (page d'accueil des présentations)
|
||||
|
||||
<!--
|
||||
Laisser le temps pour les questions ; rappeler l'URL du dépôt à l'oral.
|
||||
-->
|
||||
@@ -0,0 +1 @@
|
||||
@import "../_shared/center-default-like-reveal.css";
|
||||
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import DeckFooter from '../_shared/DeckFooter.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DeckFooter />
|
||||
</template>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"slug": "spring-ai",
|
||||
"title": "Spring AI",
|
||||
"description": "Intégrer des modèles LLM dans l’écosystème Spring avec des API cohérentes.",
|
||||
"tags": ["java", "spring", "llm", "ai"],
|
||||
"order": 2,
|
||||
"featured": true
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
theme: default
|
||||
title: Spring AI
|
||||
info: |
|
||||
Démo technique — portail talks
|
||||
highlighter: shiki
|
||||
defaults:
|
||||
layout: center
|
||||
---
|
||||
|
||||
# Spring AI
|
||||
|
||||
Intégration **LLM** dans une stack Java familière.
|
||||
|
||||
---
|
||||
|
||||
## Objectifs
|
||||
|
||||
- Abstractions stables (prompts, chat, embeddings)
|
||||
- Choix de backends (OpenAI, Ollama, etc.)
|
||||
- Testabilité et observabilité
|
||||
|
||||
---
|
||||
|
||||
## Exemple de flux
|
||||
|
||||
```text
|
||||
Controller → ChatClient → Model (provider)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bonnes pratiques
|
||||
|
||||
- Isoler les prompts et versions
|
||||
- Mesurer latence et coûts
|
||||
- Prévoir reprise et timeouts
|
||||
|
||||
---
|
||||
|
||||
## Merci
|
||||
|
||||
[Documentation Spring AI](https://docs.spring.io/spring-ai/reference/)
|
||||
@@ -0,0 +1 @@
|
||||
@import "../_shared/center-default-like-reveal.css";
|
||||
Reference in New Issue
Block a user