fixit
Blog

Bitbucket Pipelines για Magento: deploy χωρίς downtime, στην πράξη

Εισαγωγή

Κάθε eShop σε Magento φτάνει κάποια στιγμή στο ίδιο σημείο: η αλλαγή είναι έτοιμη, το ξέρεις ότι δουλεύει, αλλά κανείς δεν θέλει να την ανεβάσει. Γιατί «ανέβασμα» σημαίνει maintenance page, σημαίνει να κάθεσαι μπροστά στην οθόνη, και σημαίνει ότι αν κάτι πάει στραβά θα το μάθεις από τον πελάτη που τηλεφωνεί.

Αυτό δεν είναι πρόβλημα του Magento. Είναι πρόβλημα του τρόπου που κάνουμε deploy. Παρακάτω είναι η δομή που χρησιμοποιούμε με Bitbucket Pipelines, με πραγματικό bitbucket-pipelines.yml και — το πιο σημαντικό — με σχέδιο επιστροφής.

Το πρόβλημα δεν είναι το git pull

Ο κλασικός τρόπος είναι κάπως έτσι: μπαίνεις με SSH, κάνεις git pull, composer install, setup:upgrade, setup:di:compile, setup:static-content:deploy, καθαρίζεις cache. Το site είναι σε maintenance όσο τρέχουν όλα αυτά.

Το ζουμί είναι ότι τα τρία βαριά βήματα — composer, compile, static content — δεν έχουν καμία σχέση με τον live server σου. Παράγουν αρχεία. Τα ίδια αρχεία, από τα ίδια commits, όσες φορές κι αν τρέξουν. Το ότι τα τρέχεις πάνω στον κώδικα που εξυπηρετεί πελάτες είναι συνήθεια, όχι απαίτηση.

Και έχει και δεύτερο κόστος: όσο τρέχει το compile, ο server σου παλεύει με CPU που θα έπρεπε να εξυπηρετεί παραγγελίες. Την περίοδο αιχμής αυτό δεν είναι θεωρητικό — γι’ αυτό γράψαμε ότι ο Σεπτέμβρης είναι το deadline, όχι η Black Friday. Ένας deploy που δεν κόβει το site είναι ακριβώς αυτό που σου επιτρέπει να διορθώσεις κάτι μέσα στον Νοέμβρη χωρίς να το ζήσεις σαν επέμβαση.

Η αρχή: build στο pipeline, deploy στον server

Ο χωρισμός είναι απλός. Το Bitbucket φτιάχνει ένα artifact: έναν φάκελο με τα vendor, τα generated, τα static, όλα έτοιμα. Ο server δεν χτίζει τίποτα — παραλαμβάνει έτοιμο πακέτο και αλλάζει ένα symlink.

Η προϋπόθεση που ξεχνιέται: για να τρέξει το build χωρίς βάση δεδομένων, το app/etc/config.php πρέπει να είναι μέσα στο git. Εκεί μέσα ζει η λίστα των modules, τα themes και τα locales. Αν λείπει, το pipeline δεν ξέρει τι να χτίσει. Πρόσεξε όμως τι βάζεις εκεί: τιμές που έχουν γραφτεί με config:set --lock-config κλειδώνονται μόνιμα στο αρχείο και το admin παύει να τις ελέγχει — έχουμε δει ώρες να χάνονται σε ρύθμιση που «δεν αποθηκεύεται».

Ο φάκελος releases και το atomic switch

Στον server η δομή είναι τρία πράγματα:

releases/ — κάθε deploy γίνεται δικός του φάκελος, ονομασμένος με τον αριθμό build. Κρατάς τα τελευταία πέντε και σβήνεις τα παλιότερα.

shared/ — ό,τι ΔΕΝ πρέπει να αλλάζει ανά release: το app/etc/env.php, ο φάκελος pub/media, τα logs. Μπαίνουν με symlink μέσα στο νέο release.

current — ένα symlink που δείχνει στο ενεργό release. Το document root του nginx δείχνει σε αυτό.

Το «χωρίς downtime» κρύβεται σε αυτή τη γραμμή: το symlink αλλάζει ατομικά, σε ένα βήμα, όχι με rm και μετά ln. Αλλιώς υπάρχει ένα παράθυρο κλάσματος δευτερολέπτου όπου το current δεν υπάρχει και ο επισκέπτης βλέπει 500.

Το bitbucket-pipelines.yml στην πράξη

Ένα σκελετό που δουλεύει, με χειροκίνητο trigger για την παραγωγή:

image: your-registry/magento-build:php8.3

definitions:
  caches:
    composercache: /root/.composer/cache
  steps:
    - step: &build
        name: Build artifact
        caches:
          - composercache
        script:
          - composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader
          - bin/magento setup:di:compile
          - bin/magento setup:static-content:deploy el_GR en_US -f --jobs=4
          - tar -czf artifact.tar.gz --exclude=.git --exclude=var/cache .
        artifacts:
          - artifact.tar.gz

pipelines:
  branches:
    staging:
      - step: *build
      - step:
          name: Deploy to staging
          deployment: staging
          script:
            - ./ci/deploy.sh
    production:
      - step: *build
      - step:
          name: Deploy to production
          deployment: production
          trigger: manual
          script:
            - ./ci/deploy.sh

Τρία σημεία που αξίζουν προσοχή. Το image δεν μπορεί να είναι το σκέτο php:8.3-cli: του λείπουν τα extensions που απαιτεί το Magento (intl, soap, bcmath, xsl, sockets, gd, zip και τα υπόλοιπα). Φτιάξε ένα δικό σου image με Dockerfile μία φορά και ξέχνα το. Το -f στο static content χρειάζεται επειδή στο build δεν υπάρχει env.php, άρα το Magento δεν ξέρει ότι είναι σε production mode. Και τα credentials του repo.magento.com πάνε σε secured repository variable ως COMPOSER_AUTH, ποτέ σε αρχείο μέσα στο repo.

Το deployment: production με trigger: manual σου δίνει κάτι που δεν φαίνεται στο yml: ιστορικό. Ποιος πάτησε deploy, πότε, με ποιο commit — και ένα κουμπί «Redeploy» σε παλιότερο build.

Πού κρύβεται το πραγματικό downtime

Αν το switch είναι ένα symlink, γιατί μιλάμε ακόμα για maintenance; Επειδή δύο πράγματα δεν είναι ατομικά.

Το schema. Αν ένα module άλλαξε πίνακα, το setup:upgrade πρέπει να τρέξει και όσο τρέχει το site κοιτάει μισή βάση. Ο έλεγχος είναι bin/magento setup:db:status: αν γυρίσει μηδέν, δεν υπάρχει καμία αλλαγή σχήματος και δεν χρειάζεσαι maintenance καθόλου. Στην πράξη τα περισσότερα deploys — CSS, template, ρύθμιση, μια διόρθωση σε controller — είναι ακριβώς αυτή η περίπτωση. Άρα το script σου δεν πρέπει να μπαίνει σε maintenance «για σιγουριά», αλλά μόνο όταν το status το ζητήσει.

Το OPcache. Άλλαξες symlink, αλλά η PHP κρατάει στη μνήμη τα παλιά αρχεία με τα παλιά paths. Χωρίς reload της php-fpm (ή opcache reset) το site συνεχίζει να σερβίρει τον προηγούμενο κώδικα — ή, χειρότερα, μισό-μισό. Αυτή είναι η νούμερο ένα αιτία του «ανέβηκε αλλά δεν άλλαξε τίποτα», μαζί με το full page cache που δεν καθαρίστηκε.

Η σειρά στο ci/deploy.sh, λοιπόν, είναι: ανέβασμα artifact σε νέο release → symlinks προς shared → app:config:import → έλεγχος setup:db:status → (μόνο αν χρειάζεται) maintenance + setup:upgrade --keep-generated → atomic αλλαγή του current → reload php-fpm → cache:flush → καθάρισμα παλιών releases.

Rollback: ο κώδικας γυρίζει, η βάση όχι

Εδώ είναι που οι περισσότεροι οδηγοί σταματάνε, και είναι το πιο σημαντικό κομμάτι.

Ο κώδικας γυρίζει πίσω σε δευτερόλεπτα: γυρνάς το symlink στο προηγούμενο release, reload php-fpm, cache flush. Γι’ αυτό κρατάμε τα προηγούμενα releases στον δίσκο — το rollback δεν είναι νέο build, είναι αλλαγή δείκτη.

Η βάση όμως δεν γυρίζει. Το declarative schema του Magento δεν έχει «κάτω» βήμα, και ένα data patch που έτρεξε, έτρεξε. Αν το προηγούμενο release περιμένει στήλη που μόλις μετονομάστηκε, το rollback σε αφήνει με σπασμένο site από την άλλη πλευρά.

Οι δύο κανόνες που το λύνουν: dump της βάσης πριν από κάθε setup:upgrade που όντως αλλάζει σχήμα (όχι πριν από κάθε deploy — δεν χρειάζεται), και αλλαγές σχήματος σε δύο βήματα: πρώτα προσθέτεις τη νέα στήλη και γράφεις και στις δύο, και μόνο σε επόμενο deploy, όταν όλα δουλεύουν, αφαιρείς την παλιά. Έτσι κάθε ενδιάμεση κατάσταση είναι συμβατή και προς τις δύο κατευθύνσεις.

Και ένα πρακτικό: δοκίμασε το rollback σε staging πριν το χρειαστείς. Ένα σχέδιο επιστροφής που δεν έχει εκτελεστεί ποτέ δεν είναι σχέδιο.

Τι να κάνεις αυτή την εβδομάδα

Δεν χρειάζεται να τα στήσεις όλα μαζί. Με τη σειρά: βάλε το config.php στο git· φτιάξε το build image και δες το pipeline να περνάει χωρίς να ανεβάζει τίποτα· στήσε τη δομή releases/shared/current σε staging και κάνε τρεις deploys εκεί· βάλε τον έλεγχο setup:db:status ώστε το maintenance να μπαίνει μόνο όταν πρέπει· και τέλος κάνε ένα rollback σε staging με χρονόμετρο, για να ξέρεις τον πραγματικό αριθμό.

Αν το κατάστημά σου τρέχει ήδη σε Magento και ο deploy είναι ακόμα χειροκίνητος, αυτό είναι από τα λίγα έργα υποδομής που αποδίδουν από την πρώτη εβδομάδα — γίνεται μία φορά και μετά απλά υπάρχει. Εμείς το στήνουμε ως κομμάτι της υποστήριξης Magento που παρέχουμε, μαζί με το staging περιβάλλον που το συνοδεύει.

Θέλεις να το δούμε στο δικό σου setup — τι τρέχει, πού φιλοξενείται, πόσο κρατάει σήμερα ένα ανέβασμα; Πες μας τι τρέχεις και σου λέμε τι χρειάζεται.

Προηγούμενη ανάρτηση
Ο Σεπτέμβρης είναι το deadline, όχι η Black Friday

Πρόσφατα Άρθρα