$ cat stop-shipping-latest-tag-kubernetes.md

Μη κάνεις deploy το :latest σε production Kubernetes.

· 4 λεπτά · kubernetes

Τα mutable image tags σπάνε τα rollbacks, ακυρώνουν το cache και μετατρέπουν ένα incident σε αρχαιολογική ανασκαφή. Pin σε digests — ο μελλοντικός εαυτός σου στις 03:00 θα σ’ ευγνωμονεί.

Κάθε λίγους μήνες μπαίνουμε σε καινούρια συνεργασία και βρίσκουμε ένα 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 στον δίσκο.

Αυτό σπάει τρία πράγματα πάνω στα οποία στηρίζεσαι:

  1. Rollback. Το kubectl rollout undo γυρίζει πίσω το spec, αλλά το spec λέει ακόμα myorg/api:latest. Το προηγούμενο image είναι ό,τι θεωρεί «latest» το registry τώρα — δηλαδή το χαλασμένο που μόλις ανέβασες.
  2. Συνέπεια μεταξύ replicas. Όταν ένας node κάνει restart και ξανατραβάει image, παίρνει το σημερινό latest. Το cluster σου τρέχει πλέον δύο εκδόσεις του ίδιου Deployment.
  3. 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:

Πρακτικός κανόνας. Αν το kubectl describe pod σου δείχνει digest που μπορείς να γκρεπάρεις στα CI logs, μπορείς να debug-άρεις ένα incident σε λεπτά. Αν δείχνει :latest, δεν μπορείς.

Σειρά μετάβασης

  1. Ενεργοποίησε tag immutability στο registry. Τα νέα pushes γίνονται ασφαλή.
  2. Άλλαξε το CI ώστε να γράφει digests στα manifests. Τα νέα deployments γίνονται αναπαραγώγιμα.
  3. Re-deploy τα υπάρχοντα services ώστε το ζωντανό spec τους να περιέχει digest. Τώρα το rollback δουλεύει.

Συνολικός χρόνος: ένα απόγευμα για τις περισσότερες ομάδες. Συνολικό όφελος: κάθε επόμενο incident γίνεται πιο σύντομο.


Θες βοήθεια να το συνδέσεις με το pipeline σου; Κάνουμε ακριβώς αυτή τη δουλειά για ομάδες σε Ελλάδα και Ευρώπη — πες μας.