Jul 252015
 

Oldukça uzun bir süredir neredeyse antik sayılabilecek kod konuşlandırma/yayınlama (deployment) yöntemlerimizi daha pratik ve kontrollü bir hale getirip modernize etmeyi düşünüyordum. Sonunda birkaç ay önce uygun bir boşluk bulup bir pilot proje üzerinde denemeler yaparak bir düzen oluşturdum ve o günden bu yana da yönettiğim hemen her projeyi yavaş yavaş bu yeni düzene taşıdım. Bugün de yaptıklarım üzerine bir kaç şey karalamak istedim.

Öncelikle eskiden kullanılan yöntem(ler)i anlatayım:

  1. Eskiden uygulanan konuşlandırma yöntemleri zaman içerisinde evrim geçirmişler. Yıllar önceden bugüne kullanılan farklı yöntemleri sıralayayım:
    Kod Subversion deposuna gönderildikten sonra geliştirme makinesinde test edilir. Test edilen kod kendi içinde rsync kullanan bir bash betiği ile canlı ortama alınır.
  2. Kod Subversion deposuna gönderildikten sonra yine geliştirme makinesinde test edilir. Testler sonrasında bir sorun yoksa kod Subversion’da canlı ortamdaki stabil kodun bulunduğu dal (branch) ile birleştirilir (svn merge). Bir bash betiği sayesinde önce yeni bir tag oluşturularak kod etiketlenir, sonra canlı ortam ile birebir aynı konfigürasyona sahip bir ‘stage’ sunucusunda test edilir. Her şey yolundaysa kod yine başka bir bash betiği ile rsync kullanılarak canlı ortam sunucularına gönderilir.
  3. Uygulamanın çalıştığı dizin aslında sembolik bağ ile en son stabil kodun bulunduğu Subversion etiketi dizinine bağldır. 2. yöntemin bir değişik hali olarak her tag ayrı ayrı canlı ortam sunucularına gönderilir. Fakat bash betiği her çalıştığında uygulamanın sembolik bağı yeni Subversion etiketine bağlanır. Olur da yeni kodda bir sorun olursa, sembolik link hemen bir önceki stabil olan tage bağlanıyor.

Bu yöntemlerin farklı varyasyonları zamanla denendi, her biri, bir önceki yöntemdeki boşlukları giderse ve yaşanan sorunları çözse de yeterli değildi.

Projeler yoğun olduğunda yıllardır süre gelen ve alışılmış ve kabul edilmiş bir konuşlandırma yöntemini geliştirip daha iyi bir hale getirmek de oldukça zor ve sıkıntılı oluyor. Bu nedenle de eski yöntemler ‘bozuk değilse tamir etme’ mantığıyla değişmeden kullanılıyor.

Yeni özellik/kod konuşlandırma sürecinde genel olarak şu sıkıntıları yaşıyorduk:

  1. Kod gözden geçirmesi (code review) tamamen yeni kodu yazan programcının ekip liderlerine ‘şu şu şu dosyaları değiştirdim, canlı ortama almak istiyorum, kontrol eder misin?’ şeklinde iletişime geçmesi, ekip liderinin son değişiklikleri öncekilerle (svn diff) karşılaştırıp, ‘tamamdır’ şeklinde cevap vermesinden ibaretti. Gözden geçirilmemiş kodun canlı ortama alınmasını engelleyen hiçbir mekanizma yoktu. Commit hooklar ile commit engelleme yapmak mümkün olsa da, bu test sürecini etkiliyordu. Eğer bir geliştirici Subversion deposuna commit yapamıyorsa, test sunucusuna kodu elle taşıması gerekir ki, bu daha beter. Post commit hook ile svn diff alınıp e-posta ile bildirmek uzun bir süre denendi. Ama gün içerisinde onlarca ve belki de yüzlerce commit yapıldığı durumlarda baş ağrısından başka bir işe yaramadı.
  2. Subversion ile branch kullanımının ne kadar sıkıntılı olduğu malum.
  3. Her ne kadar prosedür gereği her kodun bir testten geçmesi gerekiyor olsa da, birim testini zorlayan herhangi bir mekanizma yoktu.
  4. Değiştirilen kod dosyalarının canlı ortama tek tek alınması birçok kez bazı dosyaların unutulması nedeniyle canlı ortamda sorunlara neden oluyordu.

Daha şimdi aklıma gelmeyen bir sürü problem.

Çözüm ve iyileştirme ilk olarak herkesi Subversion’dan Git’e geçmeye ikna ederek başladı. Git’e geçişi yaparken hem Git depolarını rahatlıkla yönetebileceğim, hem de kod gözden geçirmeyi kolaylaştıracak bir araç arayışına girdim. Her ne kadar Atlassian’ın Stash adlı aracı oldukça işe yarar olsa da, yönetime ekstra maliyeti pek açıklayamayacağımdan ücretsiz ve açık kaynak kodlu bir araç olan Phabricator‘de karar kıldım.

Phabricator, Facebook tarafından geliştirilmiş dahili bir araçken (aslında araçlar topluluğu) kamu kullanımına da açılmış. Subversion, Mercurial ve Git depolarını yönetebiliyor ve kod gözden geçirme için web arayüzü sunuyor.

Subversion’da tutulan depoları önce yerel Git depolarına çevirdim. Daha sonra da yerel depoları, Phabricator altındaki Diffusion ile oluşturduğum merkezi Git depolarına aktardım.

Phabricator’da kod gözden geçirme işlemi için Differential adı verilen bir araç kullanılıyor. Geliştiriciler Differential üzerinden 2 şekilde kod gözden geçirmesi talebinde bulunabiliyor:

  1. Değişen kodu bir yama olarak kaydedip, web arayüzü üzerinden göndermek.
  2. Arcanist adı verilen araç ile komut satırından talepte bulunmak.

Web arayüzüyle yama göndermek oldukça kolay olsa da komut satırından Arcanist kullanımının aşağıdaki gibi birçok avantajı var:

  • Arcanist ile basit bir komut vererek yama otomatik olarak gönderilebiliyor ve gönderilen yama bilgisi daha detaylı olduğu için değişikliği inceleyecek olan kişi veya kişiler değişen dosyayı bir bütün olarak da görebiliyor. Fakat web arayüzüyle gönderildiğinde bu mümkün değil.
  • Arcanist’in birim testlerini çalıştırıp, sonuçları değişiklik talebiyle birlikte göndermesi gibi bir özelliği var. Böylelikle incelemeye gönderilen her kod mutlaka birim testinden geçmiş oluyor.
  • Kod değişikliği inceleyen kişi tarafından onaylandıktan sonra Arcanist değişikliğin yapıldığı branch’i asıl branch ile (Git’te master) birleştiriyor ve eski branch’i siliyor.

Phabricator’u ayarlayıp, kod depolarını aktardıktan sonra iş akışımız şu şekilde değişti:

  1. Geliştirici her özellik, hata giderme, değişiklik vs. için (basitçe JIRA’da açılan her iş için) bir branch oluşuturuyor.
  2. Geliştirici ilgili branch’te çalışıp, yerel deposuna kaydediyor.
  3. Kodu test ettikten sonra ‘arc diff’ komutunu vererek değişiklik inceleme talebini gönderiyor.
  4. Arcanist önce linter ile kod hatalarını inceliyor, sorun yoksa birim testlerini çalıştırıyor.
  5. Kod birim testlerinden geçtikten sonra geliştiriciye konsolda bir form sunup, değişiklik ile ilgili bilgi girmesini istiyor (ne değişti, neden değişti?). Aynı formda bir de kodu kimin inceleyeceği bilgisi de soruluyor. Eğer herhangi birisinin ismi girilmezse, Phabricator’de önceden tanımlanmış bir kişi inceleyecek kişi olarak atanmış oluyor.
  6. İnceleyecek kişi, kod değişikliği ile ilgili bir e-posta alıyor. Differential (web arayüzü) her bir değişikliği kendi içinde ayrı bir ‘revision’ olarak saklıyor. E-postadaki revision linkine tıklayıp Differential’dan değişiklikler incelenebiliyor.
  7. Kodu inceleyen kişi, dilerse Differential’da kod satırlarının arasına notlar alabilir, değişikliği kabul edebilir veya reddedebilir.
  8. Eğer değişiklik reddedildiyse, ilgili değişiklikleri yapması için geliştirici bir e-posta alıyor. Değişiklikler yapıldıktan sonra yine ‘arc diff’ komutuyla inceleyen kişiye gönderiliyor.
  9. Kod değişiklikleri kabul edildiyse, geliştirici yine bir e-posta alıyor. Bu durumda ilgili branch’te ‘arc land’ komutu verilip kodun master branch’i ile birleşimi ve eksi branch’in silinmesi sağlanıyor.
  10. Son olarak master branch’i merkezi depoya gönderiliyor ve iş bitmiş oluyor.

Buraya kadar her şey güzel, ama kodu canlı ortama gönderme sürecinde bir değişiklik olmadı. Başlarda post-receive hook ile kodun farklı bir branch veya depoya gönderilmesiyle canlı ortama konuşlandırma işlemini otomatik bir hale getirmeyi düşünmüştüm. Oldukça kolay bir şekilde incelenen ve onaylanan kod canlı ortama alınmış olacaktı. Fakat, canlı ortama alma işlemini hem takip etmek, hem de kaydını tutmak istiyordum. Daha bir çok şeyi otomatikleştirebileceğim Jenkins’i denemeye karar verdim.

Projelerimizin çoğu PHP ile geliştirildiği için Jenkins’i de ona göre ayarlamam gerekiyordu. Bunun için http://jenkins-php.org adresindeki yönergeleri izleyerek bir kurulum gerçekleştirdim. Her proje için bir Ant betiği hazırladım. Bu sayede kod canlı ortama alınmadan önce linter, birim testi, kopyala yapıştır kontrolleri, vs. gibi birçok kontrolden geçiyor ve her biri için ayrı rapor hazırlanıyor.

Her bir proje için 4 tane Jenkins işi oluşturdum:

  1. Beta ortamına konuşlandırma
  2. Stage ortamına konuşlandırma
  3. Canlı ortama konuşlandırma
  4. Geri alma (rollback)

Beta ortamına konuşlandırma işlemi tamamen otomatik. Jenkins’in Git deposundaki master branch’ini takip edip, gönderilen her değişiklik için yeni bir ‘build’ hazırlamasını sağladım. Her build işlemi Ant ile şunları yapıyor:

  1. Linter kontrolleri
  2. phploc ile kod satırı istatistikleri
  3. phpmd ile kötü kod istatistikleri
  4. phpcpd ile birbirinin aynısı olan kodları arama (kopyala yapıştır programcılığını engellemek için)
  5. phpunit ile birim testleri ve test kapsamı raporları
  6. phpdox ile kod referans dökümanı hazırlama

Eğer kod yukarıdaki kontrollerden geçerse beta ortamına aktarılmış oluyor.

Stage ortamına konuşlandırma işi ise elle çalıştırılıyor. Basit olarak master’daki son kod canlı ortamın bire bir aynısı olan ama kullanıcılara açık olmayan bir sunucuya gidiyor. Burada Ant çalıştırmaya gereksinim duymadım. Çünkü zaten ilgili kontroller beta’ya giderken otomatik olarak yapılıyor.

Canlı ortama konuşlandırma işi de elle çalıştırılıyor. Fakat sonunda aşağıda bahsedeceğim bazı ek işlemler yapılıyor.

Geri alma işini de, olur da canlı ortama alınmış bir kodda sorun çıkarsa ve önceki bir revizyona dönüş gerekirse diye oluşturdum. Bu iş parametre olarak Jenkins BUILD_NUMBER’ı alıyor ve o build’daki versiyonu canlı ortama tekrar gönderiyor. Böylelikle projenin büyüklüğüne göre birkaç saniye içerisinde eski stabil koda geri dönüş sağlanabiliyor.

Jenkins sayesinde Phabricator ile yapılan kod inceleme işleminden geçmiş kodun canlı ortama alınış süreci şöyle şekillenmiş oldu:

  1. Merkezi depodaki master branch’e gönderilen yeni kod Jenkins tarafından otomatik olarak alınıp birçok kontrole tabi tutulup, raporlar hazırlanarak beta ortamına gönderiliyor.
  2. Eğer kontrollerden herhangi birisinde sorun çıkarsa Jenkins otomatik olarak geliştirme ekibine bir e-posta göndererek durumu bildiriyor.
  3. Beta ortamına alma işleminin sonucu (başarılı veya başarısız), Jenkins tarafından ilgili JIRA işlerine yorum olarak ekleniyor. Böylelikle işi açan, hatayı bildiren, işi yapan, işi takip eden herkes Jenkins’in kodu beta ortamına aldığını veya bir sorun nedeniyle alamadığını öğrenebiliyor.
  4. Beta’da kullanıcı testinden geçtikten sonra stage ortamına alınıp gerçek canlı ortamda doğabilecek sorunları gidermek amacıyla bir QA testi yapılıyor.
  5. QA ekibi stage ortamındaki koda onay verdikten sonra kod Jenkins ile canlı ortama aktarılıyor.
  6. Jenkins kodu canlı ortama aktardıktan sonra yine ilgili JIRA işlerini güncelleyerek kodun canlı ortama alındığını bildiriyor.
  7. Jenkins build numarasını kullanarak canlı ortama alınmış kodu Git deposunda etiketliyor (örn: production-3125).
  8. Son olarak tüm Git commit mesajlarını bir değişiklik kaydı (Change log) şeklinde kaydediyor.

Doctrine kullanılan projelerde her kod değişikliğinde /tmp dizinindeki proxy dosyalarının yeniden oluşturulması gerekebilir. Bazı projelerde de arka planda çalışan süreçlerin (mesela RabbitMQ kullanan işçi – worker süreçleri) durdurulup tekrar çalıştırılması gerekebilir. Bu gibi işleri yapmak için bir bash betiği oluşturdum. Sonra da Jenkins’e kodu canlı ortama aldıktan sonra ilgili sunuculara SSH ile bağlanıp bu betikleri çalışmasını belirtim.

Son birkaç aydır hemen herşeyi Phabricator ve Jenkins’e yüklemiş  ve rahata ermiş durumdayım. Eğer fırsat bulur ve üşenmezsem de uzun bir how-to yazmayı düşünüyorum.