Kubernetes Deployment: mi történik az állapot mögött igazán
Egy ideje már nem írtam cikket a Kubernetes-ről, pedig mint tudjátok szeretem ezt a témát. Amikor a Kubernetes API-ról írtam, sokan kérdezték, hogy jó, de mi történik egy deployment esetén valójában, amikor kiadunk egy kubectl apply parancsot. Most erről szeretnék írni nektek: mi zajlik a színfalak mögött, amikor egy Deployment-et létrehozunk, frissítünk, vagy éppen visszaállítunk egy korábbi verzióra.
Mi történik, amikor létrehozunk egy Deployment-et
A Deployment egy YAML vagy JSON specifikációból épül fel. Amint bekerül a clusterbe, a controller automatikusan létrehoz egy ReplicaSet-et, az pedig legenerálja a Podokat. Ha egy egyszerű nginx web szervert szeretnénk futtatni, a parancs ennyi:
kubectl create deployment dev-web --image=nginx:1.31
A rendszer válasza egyszerű visszaigazolás, hogy a deployment létrejött. Ha meg akarjuk nézni, mi került létre a háttérben, a kubectl get deployments,rs,pods -o yaml paranccsal kérhetjük le a teljes objektumfát. Én gyakran ezzel kezdek, amikor egy hibát próbálok visszakövetni, mert így látom, hogy a Deployment, a ReplicaSet és a Pod pontosan hogyan kapcsolódik egymáshoz.

Hogyan is néz ez akkor ki? Mik a fő összetevői?
A metadata blokk – mit érdemes tudni róla
Minden objektumnak van egy metadata szekciója, amiben nem a működést, hanem az azonosítást és a nyomon követést segítő adatok vannak. Itt találjuk a labeleket, az annotációkat, a létrehozás időpontját (creationTimestamp), valamint egy uid mezőt, amely az objektum egész életciklusa alatt egyedi marad. A generation mező azt mutatja, hányszor módosították az objektumot – ez praktikus, ha egy rollout történetét szeretnénk visszakövetni.
Fontos különbség, hogy az annotációk nem használhatók objektumok kiválasztására kubectl paranccsal, ellentétben a labelekkel. Ezt sokan összekeverik kezdőként, pedig a különbség lényegi: az annotáció információt hordoz, a label viszont szelekciós eszköz.
A spec – itt dől el, hogyan frissül a rendszer
A Deployment specifikációjában két külön spec blokk van: az egyik a ReplicaSet-et, a másik a Podot konfigurálja. A ReplicaSet szintű spec tartalmazza a replikák számát (replicas), a selector.matchLabels mezőt, amely meghatározza, mely Podokat tekinti a rendszer a sajátjának, valamint a frissítési stratégiát.
A strategy mező alapértelmezetten RollingUpdate, ami azt jelenti, hogy a frissítés fokozatosan történik: a maxSurge határozza meg, hány új Pod jöhet létre a régiek törlése előtt, a maxUnavailable pedig azt, hány Pod lehet átmenetileg nem elérhető. Ha ezt Recreate-re állítjuk, a rendszer előbb töröl minden meglévő Podot, és csak azután hozza létre az újakat – ez egyszerűbb, de rövid kiesést okoz.
A Pod template – a konténer tényleges beállításai
A Deployment egyik legfontosabb része a Pod template, mert ez határozza meg, milyen konténer fut valójában. Itt szerepel az image neve és verziója, az imagePullPolicy, amely azt szabályozza, mikor töltse le újra a rendszer az image-et, valamint a restartPolicy, amely biztosítja, hogy egy leállt konténer automatikusan újrainduljon.
Érdemes megjegyezni, hogy a resources mező alapértelmezetten üres. Ez azt jelenti, hogy ha nem állítunk be CPU vagy memória limitet, a konténer korlátlanul foglalhatja ezeket az erőforrásokat. Ez az egyik leggyakoribb kezdő hiba, amit én is sokszor láttam: valaki elindít egy tesztelési célú Podot limit nélkül, és napok múlva veszi észre, hogy az felzabálja a node erőforrásait.
A status – amit a rendszer visszamond
A status blokk nem konfiguráció, hanem visszajelzés. Az availableReplicas és a readyReplicas összevetésével látjuk, hogy a kívánt állapot valóban létrejött-e. A conditions mezőben szöveges üzenetek is megjelennek, például hogy a Deployment elérte a minimális elérhetőséget, vagy hogy egy új ReplicaSet sikeresen kigördült. Az observedGeneration pedig azt mutatja, hányszor követte a rendszer a konfiguráció változását – ez jó kiindulópont, ha egy rollout állapotát szeretnénk diagnosztizálni.
És egy példa a cluster-ről:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
argocd.argoproj.io/tracking-id: n8n-automation-app:apps/Deployment:n8n-prod/n8n-automation-app
deployment.kubernetes.io/revision: "2"
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"argocd.argoproj.io/tracking-id":"n8n-automation-app:apps/Deployment:n8n-prod/n8n-automation-app"},"labels":{"app":"n8n-automation","component":"n8n","environment":"production","managed-by":"argocd"},"name":"n8n-automation-app","namespace":"n8n-prod"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"n8n-automation","component":"n8n"}},"template":{"metadata":{"labels":{"app":"n8n-automation","component":"n8n"}},"spec":{"containers":[{"env":[{"name":"DB_TYPE","value":"postgresdb"},{"name":"DB_POSTGRESDB_HOST","value":"postgres-rw.n8n-prod.svc.cluster.local"},{"name":"DB_POSTGRESDB_PORT","value":"5432"},{"name":"DB_POSTGRESDB_DATABASE","value":"n8n-prod"},{"name":"DB_POSTGRESDB_USER","value":"pguser"},{"name":"DB_POSTGRESDB_PASSWORD","valueFrom":{"secretKeyRef":{"key":"DB_POSTGRESDB_PASSWORD","name":"n8n-prod-secrets"}}},{"name":"N8N_ENCRYPTION_KEY","valueFrom":{"secretKeyRef":{"key":"N8N_ENCRYPTION_KEY","name":"n8n-prod-secrets"}}},{"name":"N8N_RUNNERS_ENABLED","value":"true"},{"name":"N8N_BLOCK_ENV_ACCESS_IN_NODE","value":"false"},{"name":"N8N_GIT_NODE_DISABLE_BARE_REPOS","value":"true"},{"name":"WEBHOOK_URL","value":"https://n8n.tools.cloud-mentor.hu"},{"name":"N8N_EDITOR_BASE_URL","value":"https://n8n.tools.cloud-mentor.hu"}],"image":"docker.n8n.io/n8nio/n8n:2.20.9","livenessProbe":{"httpGet":{"path":"/healthz","port":5678},"initialDelaySeconds":60,"periodSeconds":30},"name":"n8n","ports":[{"containerPort":5678}],"readinessProbe":{"httpGet":{"path":"/healthz","port":5678},"initialDelaySeconds":30,"periodSeconds":10},"resources":{"limits":{"cpu":"500m","memory":"768Mi"},"requests":{"cpu":"250m","memory":"512Mi"}}}]}}}}
creationTimestamp: "2026-05-15T22:21:16Z"
generation: 4
labels:
app: n8n-automation
component: n8n
environment: production
managed-by: argocd
name: n8n-automation-app
namespace: n8n-prod
resourceVersion: "59814215"
uid: 89a42a18-df5a-42c8-991a-b9d4dda73dbb
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: n8n-automation
component: n8n
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: n8n-automation
component: n8n
spec:
containers:
- env:
- name: DB_TYPE
value: postgresdb
- name: DB_POSTGRESDB_HOST
value: postgres-rw.n8n-prod.svc.cluster.local
- name: DB_POSTGRESDB_PORT
value: "5432"
- name: DB_POSTGRESDB_DATABASE
value: n8n-prod
- name: DB_POSTGRESDB_USER
value: pguser
- name: DB_POSTGRESDB_PASSWORD
valueFrom:
secretKeyRef:
key: DB_POSTGRESDB_PASSWORD
name: n8n-prod-secrets
- name: N8N_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
key: N8N_ENCRYPTION_KEY
name: n8n-prod-secrets
- name: N8N_RUNNERS_ENABLED
value: "true"
- name: N8N_BLOCK_ENV_ACCESS_IN_NODE
value: "false"
- name: N8N_GIT_NODE_DISABLE_BARE_REPOS
value: "true"
- name: WEBHOOK_URL
value: https://n8n-prod.cloud-mentor.hu
- name: N8N_EDITOR_BASE_URL
value: https://n8n-prod.cloud-mentor.hu
image: docker.n8n.io/n8nio/n8n:2.20.9
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 5678
scheme: HTTP
initialDelaySeconds: 60
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 1
name: n8n
ports:
- containerPort: 5678
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 5678
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources:
limits:
cpu: 500m
memory: 768Mi
requests:
cpu: 250m
memory: 512Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status:
availableReplicas: 1
conditions:
- lastTransitionTime: "2026-05-15T22:21:16Z"
lastUpdateTime: "2026-05-29T19:36:13Z"
message: ReplicaSet "n8n-automation-app-7cc5b5bbf6" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
- lastTransitionTime: "2026-07-01T19:47:22Z"
lastUpdateTime: "2026-07-01T19:47:22Z"
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
observedGeneration: 4
readyReplicas: 1
replicas: 1
terminatingReplicas: 0
updatedReplicas: 1
Skálázás és rolling update a gyakorlatban
A replikák száma egy egyszerű paranccsal módosítható:
kubectl scale deploy/dev-web --replicas=4
Ha a replikák számát nullára állítjuk, a konténerek megszűnnek, de a Deployment és a ReplicaSet objektum megmarad – ez pontosan az, ami a háttérben történik, amikor egy Deployment-et törlünk.
Nem minden mező módosítható szabadon, egyes értékek immutable-ek, ez verziófüggő. Az image verziójának módosítása viszont mindig lehetséges, és kubectl edit deployment paranccsal élőben is elvégezhető. Egy ilyen módosítás automatikusan elindítja a rolling update-et.
Ha valami elromlik – rollback
Az egyik legmegnyugtatóbb tulajdonsága a Deployment-eknek, hogy a korábbi ReplicaSet verziók megmaradnak, így vissza lehet állítani egy működő állapotot. Ha egy image frissítés hibás verzióra mutat, és a Podok ImagePullBackOff állapotba kerülnek, a kubectl rollout undo deployment/ghost paranccsal egyszerűen visszaléphetünk. Konkrét revízióra is visszaállhatunk a --to-revision opcióval. A kubectl rollout pause és resume parancsok pedig lehetővé teszik, hogy egy rollout közepén megállítsuk, majd később folytassuk a folyamatot.
DaemonSet – amikor minden node-on kell egy Pod
A DaemonSet más logikát követ, mint a Deployment: biztosítja, hogy minden node-on pontosan egy Pod fusson ugyanazzal az image-dzsel. Ha új node kerül a clusterbe, a DaemonSet automatikusan odatelepíti a Podot, ha egy node kikerül, a Pod is törlődik vele. Ez tipikusan logolási vagy metrika-gyűjtő ügynökök esetén hasznos, ahol fontos, hogy minden node-on futnia kelljen az adott komponensnek, anélkül, hogy erről manuálisan kellene gondoskodni.
Labelek – a cluster rendezésének kulcsa
A labelek nem API objektumok, mégis a Kubernetes-alapú üzemeltetés egyik legfontosabb eszközei. Bármilyen objektumhoz rendelhetünk kulcs-érték párokat, és ezek alapján szűrhetünk, csoportosíthatunk. A kubectl get pods -l run=ghost parancs pontosan ezt teszi lehetővé. Fontos, hogy egy Deployment label selectora apps/v1 API verziótól kezdve immutable, tehát létrehozás után már nem módosítható.
A labelek gyakorlati haszna a nodeSelector alkalmazásánál a legszemléletesebb: ha egy node-hoz disktype: ssd labelt rendelünk, egy Pod specifikációjában megadhatjuk, hogy csak ilyen node-ra kerüljön ki. Ez egyszerű, de sokat elárul arról, hogy a Kubernetes tervezésekor mennyire tudatosan gondolták végig a rugalmas, ám átlátható erőforrás-kezelést.
És mi a lényeg?
A Deployment látszólag egyszerű objektum, de a mögötte húzódó réteges struktúra – metadata, spec, template, status – valójában egy jól megtervezett állapotgép. Aki most kezd Kubernetes-szel dolgozni, annak érdemes megszokni, hogy minden változás egy új ReplicaSet-et generál, és hogy a resources mező üresen hagyása előbb-utóbb kellemetlen meglepetést hoz. Ez az egyik legjellemzőbb kezdő csapda, amivel én is sokszor szembesültem: a rendszer működik, csak épp senki nem korlátozta, mennyi erőforrást használhat.
Ha valaki mélyebben szeretné megérteni ezt a réteges logikát, a managed Kubernetes szolgáltatások, mint az Azure AKS, az AWS EKS vagy a Google Cloud GKE, jó kiindulópontot adnak, mert ugyanezt az alapstruktúrát használják, csak a klaszter üzemeltetését veszik le a fejlesztő kezéből.
