Zero downtime deployment, yani benim çevirimle "projede hiç problem yaşamadan yeni sürümü yayına almak" çokça kullanılan ve çoğu kişinin kavramsal ve teknik olarak kaçındığı bir konu. Bunun temelinde, bu kavramın sadece belli başlı araçlar ile sağlandığının düşünülmesi yatıyor. Çoğu kişi bunun için uygulamanın bulutta olması gerektiğini veya öğrenmek için bilgi ve vakit gerektiren bir aracın kullanılması gerektiğini düşünüyor.
Başlamadan önce
Sizleri yıllar önce yazılmış ve benim bu konuyla ilgili hemen her sunumumda referans verdiğim bir yazıya götürmek istiyorum. FileZilla ile Continuous Integration: https://www.sonsuzdongu.com/blog/filezilla-ile-continuous-integration
Açık konuşmam gerekirse, benim de bilgisayarıma ilk yüklediğim araçlardan birtanesi FileZilla oluyor.
Şaka bir yana
Zero-downtime deployment, çeşitli yöntemlerle uygulanabiliyor – FileZilla ile uygulanmadığında mutabık kalmışızdır. Biz Moneo olarak, her projede o projeye uygun bir yöntem belirleyerek veya varolan yöntemi uyarlayarak/geliştirerek kullanıyoruz. Projenin büyüklüğü farketmeksizin tüm projelerimizde bu yöntemi kullanıyoruz.
Bunun için elbette hayatı kolaylaştıran çokça araç var. Sıklıkla kullandığımız araçların başında Deployer ve SaaS servis olarak Envoyer.io geliyor. Veya doğrudan Gitlab CI süreçlerine bağlayabiliyoruz. AWS tarafında ise CodeBuild‘i aktif olarak kullanıyoruz. Bunlara daha sonra değiniriz. Bu yazıda biraz daha kavramsal olarak ilerleyeceğiz.
Deploy esnasında hata oluşturabilecek noktalar
Dosya senkronizasyonu
Deploylarda yaşanan en büyük problem çoğunlukla dosyaların senkron biçimde yüklenmemesinden kaynaklanıyor. Ftp veya rsync ile dosyaları uzak sunuculara yüklediğimizi varsayalım. Bu senaryoda birbirine bağımlılık içeren iki dosyadan birisi henüz yüklenmemiş olabilir! Bu noktada sistem hata verecektir ve deploy tamamlanana kadar projenin çalışması aksayabilir.
Tabi git pull
gibi bir yöntem kullandığımızda bu durumla pek karşılaşılmıyor çünkü tüm dosyalar bir paket olarak indiriliyor ve açılıyor (yine de çok kısa downtime yaşanabilir).
3. Parti kütüphaneler
Bu problemi 3.parti kütüphaneler takip ediyor. PHP üzerinden örnekleyecek olursak, projeye eklenen yeni paketler ya da güncellenen paketler yüklenene kadar projenin çalışması aksayabilir. Bir önceki maddeyle aslında benzer bir konu bu.
git pull
composer install --prefer-dist --no-dev --optimize-autoloader
Veritabanı değişiklikleri
Veritabanına yeni eklenen bir kolon, henüz oluşturulmadan kullanılmak istendiğinde takdir edersiniz ki hata verecektir. Bu sefer dosya bağımlılığımız yok ama veritabanına bir bağımlılığımız var.
git pull
composer install --prefer-dist --no-dev --optimize-autoloader
php artisan migrate --force
Migration işlemini git pull öncesinde yapamıyoruz. Çünkü yeni değişiklikler ile birlikte geliyor. Sonrasında migration yaptığımızda da downtime yaşayabiliyoruz. Bunun çözümü için yeni özelliği 2 parça halinde deploy edebilirsiniz.
# repoya sadece migration dosyası gönderili.
git pull
composer install --prefer-dist --no-dev --optimize-autoloader
php artisan migrate --force
# repoya kodsal değişiklikler de gönderilir
git pull
composer install --prefer-dist --no-dev --optimize-autoloader
Takdir edersiniz ki bu oldukça yorucu ve zahmetli bir iş. Yaptığınız geliştirmeyi 2 seferde deploy etmek için 2 ayrı PR açıp, ilkine sadece migration dosyalarını koymak ikincisinde ise kodları koymak gerçekten uğraştırıcı. Migration işlemini reponuz içerisinden yapmıyorsanız biraz daha kolay olabilir. Örneğin biz bir projemizde FlywayDB kullanıyoruz. Tabi bunun takibi için de farklı bir repo kullanıyoruz ve DB değişikliklerini bu repoya pushluyoruz. Bir kodsal değişiklik için Pre-MR ve Post-MR migrationlar tanımlayabiliyoruz. Ama tabi bunun yönetimini de doğru yapmak gerekiyor ve açıkçası bazen konforlu olmayabiliyor.
Compile edilecek konular
Aslında bu konu 3.parti kütüphaneler altında da işlenebilirdi. Ama ayrı bir paragrafı hakediyor. Şu ana kadar yaptığımız yayına alma işlemlerinde en çok süreyi npm paketlerinin yüklenmesi ve derlenmesi alıyor. Elbette bu süreler kullanıma göre azalabilir. Ama genel olarak npm install
ve MARKDOWN_HASH3d5b25a38b4cc172dbcdb8c6ceecc0ddMARKDOWNHASH
(elbette bu komutlar projeye göre değişkenlik gösterirler)_ işlemlerinde uzunca vakit geçiyor.
Bu yüzden bu kodların "önceden derlenmesi" ve yayına alınması gerekiyor. Bunun için dist
(derlenmiş hali) klasörünü git
içerisine mi atmalıyız? Tabi ki hayır!
Aslında zero-downtime deployment’ın öne çıktığı yer de burası oluyor.
Zero downtime proje derleme
Çok basitçe, projenin başka bir ortamda/klasörde derlenmesi ve derlenmiş halinin yayına alınması olarak düşünebiliriz. Diyelim ki projeniz /var/www/html
klasörü içerisinde yayınlanıyor. Biz yayına alacağımız versiyonu başka bir klasör içerisine çekip tüm işlemleri yapıp bittiğinde yayına alacağız.
cd /var/www/build
git checkout your-repository-url
composer install --prefer-dist --no-dev --optimize-autoloader
php artisan migrate --force
npm install
npm build
Yukarıdaki satırları çalıştırdığımızda, projenin son halinin build
klasöründe durduğunu görebiliyoruz. Artık tek yapmamız gereken bu değişiklikleri /var/www/html
altına almak.
Bunun için basitçe cp -R /var/www/build /var/www/html
komutunu kullanabilirsiniz. Ama bu ilk bahsettiğimiz dosya senkronizasyonuna yol açabilir. Benzer şekilde rsync
kullanabilirsiniz fakat bu da aynı kapıya çıkar. Bunun için güzel bir çözümümüz var.
Symlink
Linux’ta sembolik link olarak geçen symlink
bu tarz deploylarda en çok kullanılan yöntemlerden birisi olarak geçiyor. Bunun için yapmamız gereken bir ön konu var. Her build için bir build numarası (numara olmak zorunda değil, ayrıştırıcı olabilir) eklememiz gerekiyor. Yani /var/www/build
klasörü içerisinde değil de, her bir deployu farklı bir klasörde yapacağız.
Örneğin /var/www/build-2022-03-19-09-25
gibi bir klasör oluşturabiliriz. Zero-downtime konseptinde bu klasörlere "releases" adı veriliyor.
Bu klasörü oluşturduktan sonra yapmanız gereken çok basit:
ln -s /var/www/build-2022-03-19-09-25 /var/www/html
. Bu komutla birlikte Linux sembolik linki yenileyecek ve artık yayındaki klasörünüz istediğiniz klasör altından çalışmaya başlayacak.
Rollback – Geçmişe dönüş
releases
klasörleriniz sayesinde, artık sürümler arası geçiş yapabilir duruma geleceksiniz. Yani herhangi bir anda istediğiniz release klasörünü yayındaki klasörünüz ile linkleyebilirsiniz. Böylece yayına aldıktan sonra bir hatayla karşılaşmanız durumunda çok çok hızlı bir biçimde geri dönebilirsiniz. Zero downtime deployment konusundaki en önemli noktalardan birisi bence bu. Zero downtime rollback!
Elbette yazılan kodun veya veritabanı değişikliklerinin de buna uyumlu olması gerekiyor. Örneğin bir kolonun ismini a
yerine b
yaptığınızda, artık eski release klasörleri kullanılamaz hale gelecek. Çünkü bu klasörlerdeki sürümlerde a
kolonu bir bağımlılık. Bu noktada rollback migration çalıştırabilirsiniz veya daha "safe" migrationlar yazabilirsiniz.
Sonuç
Zero downtime deployment için hayatınızı kolaylaştıracak ve hem deploy hem de rollback işlerini yapacak onlarca araç var. Ama bunları kullansanız dahi, öncesinde konsept olarak anlamanız faydalı olacaktır. Bir müşterimizin projesinde çok basit bir bash script
kullanarak zero-downtime deployment yapıyoruz. Hem de 12 farklı app sunucusuna.
Edge case ya da problem mutlaka yaşanacaktır. Bunlar da zamanla tecrübe ile çözülecektir ve hem kodu yazarken, hem 3.parti paket eklerken, hem bağımlılıkları kurgularken deployment düşünerek geliştirme yapacaksınız. Küçük büyük demeden tüm projelerinizde uygulamaya başlayın!
Extra Not
Bu yazı içerisinde sizlerle bir sunucu içerisinde klasör bazlı zero-downtime deployment nasıl olur göstermek istedim. Çok farklı yöntemler olduğunu bilmenizi isterim. Örneğin her bir deployment için bir sunucu ayağa kaldırabilir, sunucular arası geçiş dahi yapabilirsiniz. Önemli olan bu işin mantığını kavramak!