$ cat ci-cd-anti-patterns.md

Τρία CI/CD anti-patterns που συναντάμε διαρκώς.

· 5 λεπτά · ci/cd

Καθένα από αυτά τα μοτίβα φαίνεται λογικό όταν το βλέπεις μόνο του — μερικές φορές και υπεύθυνο. Συνήθως τα προσθέτεις όταν κάτι έχει στραβώσει. Μετά γίνονται αυτά η αιτία για το επόμενο που θα στραβώσει.

1. Το long-lived feature branch με χειροκίνητο deploy

Φαίνεται στο repo πριν καν μπεις στο CI:

$ git branch -r --sort=-committerdate | head
  origin/feature/checkout-rewrite      # 47 μέρες, 312 commits μπροστά
  origin/feature/new-billing           # 23 μέρες, 89 commits μπροστά
  origin/main

Η ιστορία είναι πάντα η ίδια. Κάποιος καίγεται από κακό release, μπαίνει κανόνας: τίποτα δεν κάνει merge πριν περάσει QA. Τα branches μεγαλώνουν. Τα conflicts μεγαλώνουν. Το «QA pass» γίνεται πολυήμερη τελετουργία. Το να κάνεις merge το branch γίνεται πιο τρομακτικό από το αρχικό release που έβαλε τον κανόνα.

Η λύση δεν είναι θάρρος, είναι feature flags. Κάνε merge στο main καθημερινά, ακόμη και για ημιτελή δουλειά. Κρύψε ό,τι δεν είναι έτοιμο πίσω από feature flag — env variable, LaunchDarkly, OpenFeature, ό,τι σου ταιριάζει. Το production τρέχει τον νέο κώδικα, κανείς δεν το βλέπει μέχρι να γυρίσεις τον διακόπτη.

Αυτό λέγεται trunk-based development, και ο μόνος λόγος που οι περισσότερες ομάδες αντιστέκονται είναι ότι δεν εμπιστεύονται τα tests τους. Που είναι άλλο πρόβλημα — και δεν λύνεται κρυβόμενος σε branch.

2. Deploy-on-tag

Το pipeline τρέχει σε git tag:

on:
  push:
    tags:
      - 'v*'

Φαίνεται πειθαρχημένο. Είναι παγίδα.

Τα tags τα φτιάχνουν άνθρωποι — συνήθως ένας άνθρωπος, αργά Παρασκευή απόγευμα, που κάνει tag το v1.4.7, push, και βλέπει το deploy να σκάει κόκκινο. Το fix δεν είναι ακόμα στο main. Το χαλασμένο tag είναι πια ιστορία. Το να το ξε-tag-άρεις δεν αναιρεί το deploy. Το επόμενο tag πρέπει να γίνει v1.4.8, που είναι το τρίτο release της εβδομάδας, και οι version numbers χάνουν νόημα.

Χειρότερο: το artifact που κάνεις deploy είναι ό,τι παράγει το build job από το tagged commit. Αν το build δεν είναι deterministic — διαφορετικές versions dependencies, διαφορετικό base image — αυτό που τρέχει στο production δεν είναι αυτό που τεστάρισες.

Η λύση. Build μία φορά, σε κάθε commit του main. Πάρε αυτό το artifact και προώθησέ το στα environments. Το deployment γίνεται «δείξε αυτό το environment σε αυτό το image digest» — αλλαγή config, όχι build.

main commit  →  build  →  artifact (digest pinned)
                            ↓
                    deploy to staging  ←  αυτόματα
                            ↓
                    deploy to prod     ←  click, ή auto αν είναι πράσινο

Τα ίδια bytes που έτρεξαν στο staging τρέχουν στο prod. Τα tags γίνονται labels για ανθρώπους, όχι triggers για μηχανές.

3. Tests που εξαρτώνται μεταξύ τους

Τα συμπτώματα φαίνονται πριν δεις την αιτία:

Αιτία: shared state. Το ένα test δημιουργεί έναν user "[email protected]"· το άλλο ελέγχει ότι ο user υπάρχει· το τρίτο τον σβήνει. Τρέξ’ τα με άλλη σειρά και ο κόσμος καταρρέει.

Είναι το πιο ακριβό από τα τρία γιατί τρώει εμπιστοσύνη. Μόλις ένας dev μάθει ότι «το CI είναι κάποιες φορές flaky», σταματάει να κοιτάει τα κόκκινα builds. Τα πραγματικά failures γλιστράνε.

Η λύση. Κάθε test έχει το δικό του setup και teardown. Κάθε test δουλεύει με μοναδικά δεδομένα — UUIDs, factory patterns, transactional rollbacks. Αν εξαρτάται από κάτι, το δημιουργεί.

Μετά ενεργοποίησε parallel execution και shuffle order για να επιβληθεί:

# pytest
pytest -p random_order --random-order-seed=$RANDOM -n auto

# jest
jest --shuffle --maxWorkers=4

# go
go test -shuffle=on -parallel 4 ./...

Το πρώτο τρέξιμο θα είναι σφαγή. Αυτό είναι το νόημα.

Το μοτίβο πίσω από τα μοτίβα. Και τα τρία ξεκινούν ως αντίδραση σε φόβο — φόβο για κακό release, για χαλασμένο deploy, για flaky test. Η λύση δεν είναι ποτέ να προσθέσεις τελετουργία γύρω από τον φόβο. Είναι να τον αφαιρέσεις κάνοντας το επικίνδυνο πράγμα ασφαλές.

Αν το pipeline σου έχει κάποιο από αυτά, μάλλον το ξέρεις ήδη. Πες μας — ξεμπλέκουμε CI/CD setups καθημερινά.