Κάθε λίγους μήνες μπαίνουμε σε καινούρια συνεργασία και βρίσκουμε ένα Deployment που μοιάζει κάπως έτσι:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: api
image: myorg/api:latest
imagePullPolicy: Always
Την Τρίτη δουλεύει. Το Σάββατο δεν δουλεύει, και κανείς δεν μπορεί να εξηγήσει γιατί.
Τι σημαίνει στ’ αλήθεια το :latest
Τίποτα. Το :latest είναι απλώς ένα string — μια ετικέτα που το registry τυχαίνει να δείχνει στο τελευταίο image που έγινε push. Το Kubernetes το βλέπει σαν αδιαφανές. Δεν έχει ιδέα ότι το «latest σήμερα» και το «latest χθες» είναι διαφορετικά bytes στον δίσκο.
Αυτό σπάει τρία πράγματα πάνω στα οποία στηρίζεσαι:
- Rollback. Το
kubectl rollout undoγυρίζει πίσω το spec, αλλά το spec λέει ακόμαmyorg/api:latest. Το προηγούμενο image είναι ό,τι θεωρεί «latest» το registry τώρα — δηλαδή το χαλασμένο που μόλις ανέβασες. - Συνέπεια μεταξύ replicas. Όταν ένας node κάνει restart και ξανατραβάει image, παίρνει το σημερινό latest. Το cluster σου τρέχει πλέον δύο εκδόσεις του ίδιου Deployment.
- Audit trail. Έξι μήνες μετά, όταν προσπαθείς να ανασυνθέσεις τι έτρεχε στο production την ώρα του incident, το Deployment YAML δεν σου λέει τίποτα.
Tα tags δεν αρκούν. Pin σε digests.
Το να πας από :latest σε :v1.4.2 είναι καλύτερο, αλλά τα tags παραμένουν mutable στα περισσότερα registries. Όποιος έχει push access μπορεί να ξανα-tag-άρει το v1.4.2 να δείχνει σε άλλα bytes. Το μόνο που το registry εγγυάται ως αμετάβλητο είναι το digest του image:
image: myorg/api@sha256:3f7e1b9c5d2a8f4e6b1d0c9a8f7e6d5c4b3a2918f7e6d5c4b3a291807f6e5d4c
Το digest είναι content-addressed. Θα τραβήξει τα ίδια bytes σήμερα, του χρόνου, και αφότου το storage backend του registry έχει μετακομίσει δύο φορές. Το kubectl rollout undo πλέον όντως γυρίζει στην προηγούμενη έκδοση. Τα crashloops δεν διορθώνονται μυστηριωδώς με ένα restart.
Πώς το αυτοματοποιείς
Κανείς δεν θα αντιγράφει SHA256 strings στο χέρι. Άστο στο pipeline.
Αν είσαι σε GitHub Actions, το build step σου επιστρέφει ήδη το digest:
- id: build
uses: docker/build-push-action@v5
with:
push: true
tags: myorg/api:${{ github.sha }}
- name: Update manifest with digest
run: |
yq -i '.spec.template.spec.containers[0].image = "myorg/api@${{ steps.build.outputs.digest }}"' \
k8s/deployment.yaml
Κάνε commit το ενημερωμένο manifest πίσω στο repo. Αν τρέχεις Argo CD ή Flux, αυτό το commit είναι το deployment.
Δεν έχεις ακόμα GitOps; Το Kustomize μπορεί να επιλύει digests κατά το apply:
# kustomization.yaml
images:
- name: myorg/api
newTag: v1.4.2
digest: sha256:3f7e1b9c5d2a8f4e6b1d0c9a8f7e6d5c4b3a2918f7e6d5c4b3a291807f6e5d4c
Κλείδωσε και το registry
Το να κάνεις pin digest στα manifests δεν εμποδίζει κάποιον να ξανα-tag-άρει από τη μεριά του registry. Αν ελέγχεις το registry, ενεργοποίησε tag immutability:
- ECR: βάλε
imageTagMutability: IMMUTABLEστο repository. - Artifact Registry (GCP): ενεργοποίησε Immutable image tags στο repo.
- GHCR / Docker Hub: δεν υπάρχει native immutability — ενίσχυσέ το στο CI αρνούμενος push αν το tag υπάρχει ήδη.
kubectl describe pod σου δείχνει digest που μπορείς να γκρεπάρεις στα CI logs, μπορείς να debug-άρεις ένα incident σε λεπτά. Αν δείχνει :latest, δεν μπορείς.
Σειρά μετάβασης
- Ενεργοποίησε tag immutability στο registry. Τα νέα pushes γίνονται ασφαλή.
- Άλλαξε το CI ώστε να γράφει digests στα manifests. Τα νέα deployments γίνονται αναπαραγώγιμα.
- Re-deploy τα υπάρχοντα services ώστε το ζωντανό spec τους να περιέχει digest. Τώρα το rollback δουλεύει.
Συνολικός χρόνος: ένα απόγευμα για τις περισσότερες ομάδες. Συνολικό όφελος: κάθε επόμενο incident γίνεται πιο σύντομο.
Θες βοήθεια να το συνδέσεις με το pipeline σου; Κάνουμε ακριβώς αυτή τη δουλειά για ομάδες σε Ελλάδα και Ευρώπη — πες μας.