Aller au contenu
English
Conteneurs & Orchestration medium

StatefulSets Kubernetes : identité stable et stockage persistant

70 min de lecture

logo kubernetes

Un StatefulSet gère des Pods avec une identité stable, un stockage persistant dédié et un ordre de déploiement garanti. Contrairement à un Deployment où les Pods sont interchangeables, chaque Pod d'un StatefulSet conserve son nom, son réseau et son stockage même après un redémarrage.

Ce guide couvre la création, le headless Service, les volumeClaimTemplates, les stratégies de mise à jour et le scaling, tout ce qu'il faut pour la certification CKAD.

  • Créer un StatefulSet avec un headless Service
  • Configurer le stockage persistant avec volumeClaimTemplates
  • Comprendre le DNS stable des Pods StatefulSet
  • Mettre à jour un StatefulSet avec RollingUpdate et partitions
  • Choisir entre Deployment, StatefulSet et DaemonSet

Un StatefulSet est conçu pour les applications qui nécessitent :

BesoinExemple
Identité réseau stableChaque Pod garde son nom (app-0, app-1)
Stockage persistant dédiéChaque Pod a son propre PVC
Ordre de déploiementapp-0 démarre avant app-1
Scaling ordonnéScale down en ordre inverse

Le point commun de ces quatre familles est qu'un Pod n'y est pas interchangeable avec un autre : une réplique de base de données détient des données que ses pairs n'ont pas, un nœud etcd participe à une élection de leader qui suppose une identité fixe. C'est précisément ce que le Deployment ne sait pas garantir, et ce qui justifie le StatefulSet. Si votre application ne figure pas dans ce tableau et que ses instances sont parfaitement équivalentes, le comparatif en fin de guide vous orientera vers un Deployment, plus simple.

ApplicationPourquoi StatefulSet
Bases de donnéesPostgreSQL, MySQL, MongoDB, chaque instance a ses données
Clusters distribuésetcd, Zookeeper, Kafka, identité stable pour l'élection
Caches répliquésRedis Cluster, nœuds avec rôles spécifiques
Queues de messagesRabbitMQ, identité stable pour le clustering

Cette leçon s'appuie sur deux modules déjà parcourus. Côté réseau, elle suppose acquis le fonctionnement des Services et la résolution de noms par CoreDNS. Côté stockage, elle réclame les PersistentVolumeClaims et le provisionnement dynamique par StorageClass. Si l'un des deux vous semble flou, revenez-y d'abord : un StatefulSet ne fait que combiner ces briques.

Un StatefulSet nécessite un headless Service pour fournir le DNS stable de chaque Pod.

Un headless Service se distingue d'un Service classique par clusterIP: None. Au lieu de load-balancer vers les Pods, il crée une entrée DNS pour chaque Pod.

headless-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
clusterIP: None # Headless Service
selector:
app: nginx
ports:
- port: 80
name: web

Appliquer :

Fenêtre de terminal
kubectl apply -f headless-service.yaml

Chaque Pod d'un StatefulSet obtient un nom DNS prévisible :

<nom-pod>.<service-headless>.<namespace>.svc.cluster.local

Exemple avec un StatefulSet web et un Service nginx dans le namespace default :

PodDNS complet
web-0web-0.nginx.default.svc.cluster.local
web-1web-1.nginx.default.svc.cluster.local
web-2web-2.nginx.default.svc.cluster.local

Ce DNS stable permet aux applications distribuées de joindre un pair précis, même après un redémarrage du Pod.

Voici un StatefulSet simple avec 3 réplicas Nginx et un volume persistant de 1 Go par Pod :

statefulset-nginx.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: nginx # Référence au headless Service
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.30@sha256:d5792f71a9496b833bc08ea834a758c46e2b6a6306c10f4be926f38a656cdc1c
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi

Points clés :

ChampRôle
serviceName: nginxLie le StatefulSet au headless Service
replicas: 3Crée 3 Pods : web-0, web-1, web-2
volumeClaimTemplatesCrée un PVC dédié par Pod (www-web-0, www-web-1, etc.)

Appliquer :

Fenêtre de terminal
kubectl apply -f statefulset-nginx.yaml

Observer le déploiement ordonné :

Fenêtre de terminal
kubectl get pods -l app=nginx -w
# NAME READY STATUS RESTARTS AGE
# web-0 0/1 Pending 0 0s
# web-0 1/1 Running 0 3s
# web-1 0/1 Pending 0 0s # Démarre après web-0 Ready
# web-1 1/1 Running 0 3s
# web-2 0/1 Pending 0 0s # Démarre après web-1 Ready
# web-2 1/1 Running 0 3s

Vérifier les PVC créés :

Fenêtre de terminal
kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES
# www-web-0 Bound pvc-xxx 1Gi RWO
# www-web-1 Bound pvc-yyy 1Gi RWO
# www-web-2 Bound pvc-zzz 1Gi RWO

C'est la différence la plus visible avec un Deployment, qui démarrerait ses trois Pods en même temps. Ici, web-1 n'est même pas créé tant que web-0 n'est pas prêt.

Fenêtre de terminal
kubectl get pods -l app=nginx -w
Sortie, relevée seconde par seconde
t+2s web-0 Pending
t+6s web-0 ContainerCreating
t+8s web-0 Running web-1 Pending
t+10s web-0 Running web-1 Running web-2 Pending
t+14s web-0 Running web-1 Running web-2 Running

Chaque Pod obtient au passage son volume, nommé d'après le volumeClaimTemplates et l'ordinal du Pod :

Fenêtre de terminal
kubectl get pvc
Sortie
NAME STATUS VOLUME CAPACITY STORAGECLASS
donnees-web-0 Bound pvc-5fcd8b67-ea3c-4df8-88b4-0b1abc6277a0 1Gi standard
donnees-web-1 Bound pvc-f8e36aa0-2ef6-40cd-bf14-7cbc08bd1da9 1Gi standard
donnees-web-2 Bound pvc-f3b52c18-eae6-41a8-a9ae-fc67306aed0e 1Gi standard

La règle de nommage est <nom-du-template>-<nom-du-statefulset>-<ordinal>, et c'est elle qui permet au même Pod de retrouver le même volume.

Les valeurs par défaut, relevées sur l'objet créé

Section intitulée « Les valeurs par défaut, relevées sur l'objet créé »
ChampDéfaut
updateStrategy.typeRollingUpdate
updateStrategy.rollingUpdate.partition0
podManagementPolicyOrderedReady
persistentVolumeClaimRetentionPolicy.whenDeletedRetain
persistentVolumeClaimRetentionPolicy.whenScaledRetain
revisionHistoryLimit10

Les deux Retain sont la raison pour laquelle vos volumes survivent à tout, y compris à ce que vous ne vouliez pas conserver.

Observer un StatefulSet demande de regarder trois objets, pas un. Le StatefulSet lui-même dit combien de répliques sont prêtes ; les Pods portent les identités numérotées et disent laquelle bloque ; les PVC révèlent l'état du stockage, qui est la cause la plus fréquente d'un Pod qui ne démarre pas. Les commandes ci-dessous suivent cet ordre, et elles sont aussi celles attendues à l'examen CKAD.

Ces commandes se lisent du survol au détail. Retenez l'abréviation sts, le nom court de statefulset accepté partout, qui fait gagner un temps précieux à l'examen. La commande la plus révélatrice est kubectl rollout status sts/web : elle bloque jusqu'à la fin d'une mise à jour et affiche sa progression Pod par Pod, ce qui permet de savoir si un déploiement avance ou reste coincé sur un ordinal donné.

Fenêtre de terminal
# Vue d'ensemble des StatefulSets
kubectl get sts
# Détails complets (événements, stratégie, réplicas)
kubectl describe sts web
# Pods du StatefulSet avec leurs nœuds
kubectl get pods -l app=nginx -o wide
# PVC associés
kubectl get pvc -l app=nginx
# État du rollout en cours
kubectl rollout status sts/web
# Historique des révisions
kubectl rollout history sts/web

Vérifier le DNS stable, et le piège du nom court

Section intitulée « Vérifier le DNS stable, et le piège du nom court »

Depuis un Pod du cluster, interrogez le nom pleinement qualifié. C'est une précision qui a l'air pédante et qui ne l'est pas du tout, comme la suite le montre :

Fenêtre de terminal
kubectl run dns-test --rm -it \
--image=busybox:1.37@sha256:9db7b59979c38555a39def84a31fb98b5296952f9e3afd4f6f11f05b07adfab0 \
-- nslookup web-0.nginx.default.svc.cluster.local
Sortie
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: web-0.nginx.default.svc.cluster.local
Address: 10.244.1.199

nslookup échoue sur le nom court, et pourtant l'application se connecte

Section intitulée « nslookup échoue sur le nom court, et pourtant l'application se connecte »

Tentez la même chose sur web-0.nginx, sans le suffixe, et vous n'obtiendrez aucune adresse. La conclusion naturelle, « mon DNS de StatefulSet est cassé », est fausse : au même instant, depuis le même conteneur, curl http://web-0.nginx répond HTTP 200.

La raison tient en une phrase. nslookup et dig interrogent le nom tel quel, alors que la liste search de /etc/resolv.conf, celle qui complète web-0.nginx en web-0.nginx.<namespace>.svc.cluster.local, est appliquée par le résolveur de la libc, donc par vos applications et pas par ces deux outils. Mesuré :

CommandeRésultat
dig +short web-0.nginxrien
dig +search +short web-0.nginx10.244.1.199
getent hosts web-0.nginx10.244.1.199
curl http://web-0.nginxHTTP 200

Diagnostiquez donc avec le FQDN, ou avec getent hosts, qui passe par la libc comme votre application.

Un dernier piège si votre Service s'appelle web : le nom court ne rend pas « rien » mais 127.0.53.53, l'adresse réservée aux collisions de noms, parce que .web est un vrai domaine de premier niveau et que la requête part donc vers l'extérieur. Une réponse fausse est plus dangereuse qu'une absence de réponse.

L'affirmation « le Pod retrouve son nom et son volume » se vérifie en trois commandes : on écrit une marque dans le volume, on supprime le Pod, on relit.

Fenêtre de terminal
kubectl exec web-1 -- sh -c 'echo marque-web-1 > /usr/share/nginx/html/index.html'
kubectl delete pod web-1
kubectl exec web-1 -- cat /usr/share/nginx/html/index.html
Sortie
marque-web-1

Le Pod revient sous le même nom, avec un UID différent (c'est bien un nouveau Pod), et remonte le même PVC, donnees-web-1. Un Deployment aurait rendu un nom aléatoire et, sans volume nommé, une page vide.

Kubernetes supporte deux stratégies de mise à jour pour les StatefulSets.

C'est la stratégie par défaut. Kubernetes met à jour les Pods un par un, en ordre inverse (du plus grand ordinal vers le plus petit).

spec:
updateStrategy:
type: RollingUpdate

Comportement :

  1. web-2 est mis à jour et doit devenir Ready
  2. Puis web-1 est mis à jour
  3. Puis web-0

Appliquer une mise à jour (par exemple changer l'image) :

Fenêtre de terminal
kubectl set image sts/web nginx=nginx:1.28

Suivre la progression :

Fenêtre de terminal
kubectl rollout status sts/web
# Waiting for partitioned roll out to finish: 0 out of 3 new pods have been updated...
# statefulset rolling update complete 3 pods...

Vous pouvez limiter la mise à jour aux Pods d'ordinal supérieur ou égal à une valeur avec partition. C'est utile pour les déploiements progressifs.

spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # Seul web-2 sera mis à jour

Exemple : avec partition: 2 et 3 réplicas :

  • web-2 : reçoit la nouvelle spec
  • web-1 : garde l'ancienne spec
  • web-0 : garde l'ancienne spec

C'est une forme de canary deployment pour StatefulSet.

Avec OnDelete, Kubernetes ne met pas à jour automatiquement les Pods. Vous devez les supprimer manuellement pour qu'ils soient recréés avec la nouvelle spec.

spec:
updateStrategy:
type: OnDelete

Workflow :

Fenêtre de terminal
# 1. Modifier la spec du StatefulSet
kubectl apply -f statefulset-updated.yaml
# 2. Les Pods existants ne changent pas
# 3. Supprimer manuellement un Pod pour déclencher sa recréation
kubectl delete pod web-0
# 4. Le nouveau Pod est créé avec la nouvelle spec

Si une nouvelle version se comporte mal, kubectl rollout undo ramène le StatefulSet à la révision précédente enregistrée dans son historique. Le retour suit la même mécanique ordonnée qu'une mise à jour normale : les Pods reviennent à l'ancienne spec un par un, du plus grand ordinal vers le plus petit. Une limite à connaître pour la CKAD : le rollback restaure la spec des Pods, il ne restaure pas les données déjà écrites sur les volumes persistants, qui survivent au changement de version.

Fenêtre de terminal
kubectl rollout undo sts/web

Par défaut, les Pods sont créés et supprimés de manière ordonnée. Vous pouvez changer ce comportement avec podManagementPolicy.

Les Pods sont créés un par un, chaque Pod devant être Ready avant de créer le suivant. Lors du scale down, les Pods sont supprimés en ordre inverse.

spec:
podManagementPolicy: OrderedReady # Par défaut

Cas d'usage : applications où l'ordre de démarrage est critique (bases de données distribuées, clusters avec élection de leader).

Les Pods peuvent être créés ou supprimés simultanément. Kubernetes n'attend pas que chaque Pod soit Ready avant de passer au suivant.

spec:
podManagementPolicy: Parallel

Cas d'usage : applications tolérantes au démarrage simultané, où la vitesse de scaling est prioritaire sur l'ordre.

Le scaling d'un StatefulSet n'a rien de symétrique, et c'est ce qu'il faut retenir. La montée crée les Pods dans l'ordre croissant, la descente les supprime dans l'ordre décroissant, du plus grand indice au plus petit. Un web-0 n'est donc jamais supprimé tant qu'il reste un web-1, ce qui protège le Pod qui joue souvent le rôle de référence dans les applications à réplication.

Deuxième asymétrie, moins visible : les PVC ne suivent pas les Pods. Réduire à une réplique supprime deux Pods mais laisse deux PVC en place, sujet traité juste après.

Fenêtre de terminal
kubectl scale sts web --replicas=5

Avec OrderedReady, les nouveaux Pods sont créés dans l'ordre : web-3, puis web-4.

Fenêtre de terminal
kubectl scale sts web --replicas=2

Les Pods sont supprimés en ordre inverse : web-4, web-3, web-2.

Le comportement par défaut est de tout conserver, et il se vérifie sur le StatefulSet lui-même :

Fenêtre de terminal
kubectl get statefulset web -o jsonpath='{.spec.persistentVolumeClaimRetentionPolicy}'
Sortie
{"whenDeleted":"Retain","whenScaled":"Retain"}

Concrètement, réduire de trois répliques à une supprime web-1 et web-2 mais laisse donnees-web-1 et donnees-web-2 en état Bound. Une remontée en charge ultérieure les retrouve intacts, et c'est exactement l'intention. La contrepartie est double : un coût de stockage qui ne baisse pas quand vous réduisez, et des volumes oubliés qui s'accumulent.

Supprimer un StatefulSet pose une question que les Deployments ne posent pas : que faire des données. Trois portées sont possibles et se combinent mal si on ne les distingue pas. Vous pouvez supprimer le contrôleur en laissant les Pods vivre, supprimer contrôleur et Pods en laissant les PVC, ce qui est le comportement par défaut, ou tout supprimer, ce qui exige un geste explicite et reste irréversible.

Pour supprimer le StatefulSet sans toucher aux Pods :

Fenêtre de terminal
kubectl delete sts web --cascade=orphan

Les Pods continuent de tourner, mais ne sont plus gérés par un contrôleur. Utile pour recréer un StatefulSet avec une configuration différente.

Fenêtre de terminal
kubectl delete sts web

Cela supprime le StatefulSet et ses Pods, mais pas les PVC.

Fenêtre de terminal
kubectl delete sts web
kubectl delete pvc -l app=nginx

Le champ persistentVolumeClaimRetentionPolicy configure le sort des PVC à la suppression du StatefulSet et au scale down. Il est stable depuis Kubernetes 1.32 et ne demande donc aucun réglage préalable :

spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Retain # Garde les PVC quand le StatefulSet est supprimé
whenScaled: Delete # Supprime les PVC quand le Pod est supprimé par scaling
ValeurComportement
RetainLes PVC sont conservés (comportement par défaut actuel)
DeleteLes PVC sont supprimés avec le Pod/StatefulSet

Un StatefulSet en panne se diagnostique différemment d'un Deployment, pour une raison unique : l'ordre est bloquant. Si web-1 ne démarre pas, web-2 ne sera même pas tenté, et vous verrez un StatefulSet arrêté à deux répliques sans aucun événement sur le troisième Pod, qui n'existe pas encore. Cherchez donc toujours le Pod au plus petit indice non prêt : c'est lui qui bloque toute la série, les autres ne sont que les conséquences.

La particularité du dépannage d'un StatefulSet tient à son ordre strict : un Pod n'est créé que si son prédécesseur est Ready. La deuxième ligne du tableau en découle directement, et c'est la cause la plus déroutante pour un débutant. Si web-1 reste bloqué, la vraie panne est souvent sur web-0. Le second grand foyer de problèmes est le stockage : un Pod ou un PVC coincé en Pending pointe presque toujours vers une StorageClass absente ou un provisioner qui ne répond pas, à confirmer avec kubectl describe pvc.

SymptômeCause probableSolution
Pod PendingPas de PV disponible ou StorageClass manquantekubectl describe pod + vérifier StorageClass
Pod ne démarre pasPod précédent pas ReadyVérifier l'état du Pod d'ordinal inférieur
PVC PendingProvisioner défaillant ou quota atteintkubectl describe pvc
DNS non résoluHeadless Service manquant ou mal configuréVérifier clusterIP: None
Mise à jour bloquéePod en erreurkubectl describe pod + logs

Suivez ces commandes dans l'ordre du plus large au plus ciblé. Commencez toujours par la section Events de kubectl describe sts web, car le contrôleur y consigne pourquoi il n'avance pas (attente d'un PVC, échec de programmation). Descendez ensuite au Pod bloquant, dont l'ordinal est le plus petit parmi ceux qui ne sont pas Ready, puis à son PVC. Le --timeout=60s sur rollout status évite d'attendre indéfiniment un déploiement qui ne se terminera jamais.

Fenêtre de terminal
# Voir les événements du StatefulSet
kubectl describe sts web
# Vérifier un Pod spécifique
kubectl describe pod web-0
kubectl logs web-0
# Vérifier les PVC
kubectl describe pvc www-web-0
# État du rollout
kubectl rollout status sts/web --timeout=60s

Comparatif : Deployment vs StatefulSet vs DaemonSet

Section intitulée « Comparatif : Deployment vs StatefulSet vs DaemonSet »

Choisir la bonne ressource est essentiel pour la CKAD.

CritèreDeploymentStatefulSetDaemonSet
Identité des PodsAnonymeStable (app-0, app-1)Anonyme
NommageHash aléatoireOrdinal incrémentalHash aléatoire
StockagePVC partagé ou sans étatPVC dédié par PodGénéralement hostPath
Ordre de déploiementNon garantiOrdonné (par défaut)Non garanti
DNS stableNonOui (headless Service)Non
ScalingHorizontal libreOrdonnéSuit les nœuds
Cas d'usageAPIs, web statelessBases de données, clustersAgents système

Cet arbre reprend le tableau sous forme de questions à se poser dans l'ordre. La première tranche le cas le plus net : une charge qui doit tourner sur chaque nœud (agent de logs, exportateur de métriques) relève du DaemonSet, sans discussion. Ne descendez vers le StatefulSet que si vous répondez oui à la combinaison identité stable ET stockage dédié par instance : l'un sans l'autre ne le justifie pas, et un Deployment avec un volume partagé suffit souvent.

Arbre de décision : si l'application doit tourner sur chaque nœud éligible, choisir un DaemonSet ; sinon, si elle réclame une identité stable et un stockage persistant par instance, choisir un StatefulSet ; dans tous les autres cas, choisir un Deployment

Voici un exemple plus complet avec anti-affinité pour répartir les Pods sur différents nœuds :

statefulset-distributed.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: distributed-app
spec:
serviceName: distributed-app
replicas: 3
podManagementPolicy: OrderedReady
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 0
selector:
matchLabels:
app: distributed-app
template:
metadata:
labels:
app: distributed-app
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: distributed-app
topologyKey: kubernetes.io/hostname
terminationGracePeriodSeconds: 30
containers:
- name: app
image: busybox:1.37@sha256:9db7b59979c38555a39def84a31fb98b5296952f9e3afd4f6f11f05b07adfab0
command: ["sh", "-c", "echo 'Pod $(hostname) started'; sleep infinity"]
ports:
- containerPort: 8080
name: http
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
livenessProbe:
exec:
command: ["cat", "/tmp/healthy"]
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
exec:
command: ["cat", "/tmp/ready"]
initialDelaySeconds: 5
periodSeconds: 5
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi

Points clés :

  • podAntiAffinity : empêche deux Pods d'être sur le même nœud
  • terminationGracePeriodSeconds: 30 : laisse le temps à l'application de se terminer proprement
  • livenessProbe et readinessProbe : essentiels pour les rolling updates ordonnés
  1. Un StatefulSet fournit une identité stable (app-0, app-1) et un stockage persistant dédié par Pod
  2. Il nécessite un headless Service (clusterIP: None) pour le DNS stable
  3. La stratégie de mise à jour par défaut est RollingUpdate, pas OnDelete
  4. volumeClaimTemplates crée automatiquement un PVC par Pod
  5. Les mises à jour se font en ordre inverse (du plus grand ordinal vers le plus petit)
  6. podManagementPolicy: Parallel accélère le scaling mais pas les updates
  7. Les PVC ne sont pas supprimés automatiquement lors d'un scale down ou d'une suppression du StatefulSet (sauf avec persistentVolumeClaimRetentionPolicy)
  8. Le DNS d'un Pod est <pod>.<service>.<namespace>.svc.cluster.local

Sept questions sur ce qui distingue vraiment un StatefulSet d'un Deployment : l'ordre, l'identité stable, et le sort des volumes quand on réduit le nombre de répliques.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

7 questions
5 min.
80% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  • Les Opérateurs : Ce qui prend le relais quand un StatefulSet ne suffit plus à piloter une base de données.
  • Opérateurs et CRD : Étendre l'API avec ses propres objets, la suite naturelle du modèle StatefulSet.
  • Scheduling avancé : La répartition des réplicas sur des nœuds et des zones distincts.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn