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.

 

Feb 242015
 

MySQL’in eski şifre kaydetme tekniğini bırakmasının üzerinden uzun zaman geçti. Ancak birçok kurumda halen geriye dönük uyumluluk nedeniyle yeni şifre kaydetme tekniği kullanılamıyor. Bu nedenle de bazı MySQL sunucuları halen old_passwords=1 çalıştırılıyor.

Eğer geliştirme yaptığınız bilgisayarınız bir Mac ise aşağıdaki gibi bir hata almanız çok normal:
Warning: mysql_connect(): The server requested authentication method unknown to the client [mysql_old_password] in /Users/yasar/test.php on line 3

Warning: mysql_connect(): The server requested authentication method unknown to the client in /Users/yasar/test.php on line 3

Bunun nedeni OS X ile birlikte gelen PHP’nin eski şifre kaydetme tekniğini desteklemeyen bir MySQL sürücüsünü kullanıyor olması.

Her ne kadar OS X birçok Unix aracıyla birlikte geliyor olsa da eğer benim gibi Linux veya FreeBSD’den geçiş yaptıysanız zaten ya MacPorts ya da Homebrew kurmuşsunuzdur. İstediğimde rahatlıkla yeni sürüm paketleri alabildiğim ve ihtiyacım olan özellikleri ekleyebildiğim için zaten önceden OS X ile gelen PHP sürümü yerine MacPorts paket deposundaki PHP’yi kurmuştum. Tabi MacPorts depolarındaki PHP 5.6 sürümü de aynı OS X ile birlikte gelen PHP 5.5 gibi MySQL’in eski yetkilendirme yöntemini desteklemiyor.

Bu sorunu çözmek için aşağıdaki komutu vermek yeterli:
sudo port install php56-mysql -mysqlnd +mysql55

Bu sayede php56-mysql paketini kurarken mysqlnd sürücüsünü değil eski şifreleme yöntemini destekleyen mysql55 sürücüsünü yükleyebilirsiniz.

Eğer MacPorts ile PHP’yi kurmak istiyorsanız MacPorts’un sitesinden paket yöneticisini indirip kurduktan sonra şu komutu verin:
sudo port search php56

Bu komut, paket depolarında bulunan PHP 5.6 paketlerini listeler. Daha sonra da bunlardan istediklerinizi şöyle kurabilirsiniz:
sudo port install paket1 paket2 paket3 paketN

Örnek:
sudo port install php56 php56-xsl php56-xmlrpc

Dec 282012
 

Kısa bir hikaye bu. Mutlu sonla da bitmiyor. Belki limoni, ekşi bir son diyebiliriz.

Hikayemizin kahramanı Yaşar PHP ile yazılmış bir kodun, yapması gereken işi neden tam olarak yapmadığını anlamaya çalışmaktadır. Kapkaranlık terminal penceresinden akıp giden logları satır satır okumakta, her log satırını kod ile karşılaştırıp, programın akışını takip etmektedir. Yaptığı her testte, çalışması gereken kod satırı bir türlü çalışmamakta, PHP inatla o kod satırını es geçmektedir.

Söz konusu kod şuna benziyordu:

....
....
....
// 3. Parti API'den gelen cevabı loga yaz
// 3. Parti API'den gelen cevabı parse et, bu cevaptaki X değerini $x değişkenine Ata
....
....
....
if ($x > 0)
{
    BirSeyYap();
}
else
{
    BaskaBirSeyYap();
}

Logda 3. Partiden gelen cevap yazılıdır ve o logda X’in değeri 2’dir. Ama sanki $x değişkeni hiç bu değeri almıyormuş gibi kod sürekli BaskaBirSeyYap() fonksiyonunu çağırmaktadır. Yaşar en az 10 test yapmıştır, artık gözleri kararmaktadır. En sonunda değiştirmek istemediği kodda, if deyiminin hemen bir satır üstüne Log::writeLog(“X’in değeri bu: $x”); satırını ekler ki logda $x değişkeninin değeri görünsün. Bunun dışında hiçbir değişiklik yapmaz, “yemişim commiti” deyip, kodu Subversion deposuna göndermeden doğrudan production’a alır.

Aynı testi tekrarlar.

3. Parti API daha önceki 10 testte verdiği cevap ile aynı cevabı verir. X değeri 2’dir. Yaşar logları okumaya devam eder ve yeni eklediği log satırını görür. Şöyle yazmaktadır:
X’in değeri bu: 2

ve hemen altında BirSeyYap() fonksiyonun çağrıldığını gösteren log satırını görür. Kod doğru çalışmıştır. Halbuki $x değişkenini loga yazmak haricinde hiçbir değişiklik yapmamıştır. Gözlerini oluşturur. Testi yineler. Sonuç aynıdır. $x 2 değerini alıyordur ve kod doğru çalışıyordur.

Yaşar, “hayır, bu kadar aptalca bir şey olamaz” der ve 3. Parti API’den aldığı önceki cevaplarla, $x’ i loglara yazmasından sonraki cevapları karakter karakter karşılaştırır. Birebir aynıdırlar. Hatta bir Hex editörü ile log dosyasını açıp karşılaştırma yapacak kadar ileri gider. API’ nin döndürdüğü sonuç her durumda aynıdır ve koddaki tek değişiklik $x değişkeninin değerinin loga yazılmasıdır.

Hemen son yaptığı değişikliği geri alır ve kodu production’ a alp tekrar test eder. API aynı cevabı döndürür, $x artık logda görünmemektedir, fakat BirSeyYap() fonksiyonu çalışmaya devam etmektedir. Yaşar ne yaparsa yapsın, aynı durumu tekrar edemez. Halbuki o log satırını yazmadan önce en az 10 kez test etmiş, hepsinde de BaskaBirSeyYap() fonksiyonu çalışmıştır. Log satırını yazınca herşey düzelmiştir. Olayı etrafındakilere anlatır, ama tekrar edemediği için logları göstermesine rağmen kimseyi inandıramaz.

Sonuçta PHP’ ye bol miktarda güzel iltifatlar ederek işine devam eder.

Oct 182009
 

Evet, lutfen, cik hayatimdan. Seninle uzun zamandir suren bir birlikteligimiz vardi. Seni hep sevdim ve ozellikle web programciliginda hep tercih ettim, hep savundum. Hala daha seni web programciliginda savunabilirim. Ama sen de cok iyi biliyorsun ki 4 yildir web programciligi yerine, “back-end” diye tabir edilen tarafta calisiyorum ve mecburen seni kullaniyorum. Ve sen, back-end’de ayni sabahlari daha ayilamamis kaprisli bir kadin gibisin. Hircin, cirkin, suratsiz ve kaprisli. Seni calismasi uzun surecek bir betik icin kullandigimda sacmaliyorsun. Karsilastirma bildirimlerin bazen dogru calismayabiliyor ve dengesizligin beni cildirtacak noktalara kadar varabiliyor. O kadar oluyor ki, seni tum sunucularin disklerinden silmek geliyor icimden. Ama yapamiyorum.

Python ile ne guzel hayallerimiz vardi. Yeni sistemi sadece Python kullanarak gelistirecektik ve seninle sadece eski platformda calisan servislerin bakimi veya guncellemeleri icin resmi bir iliski kuracaktim. Python ile de tutkulu ve heyecanli bir beraberligimiz olacakti. Ama olmadi. Super is arkadaslarimin yeni bir dil ogrenmek ve kullanmak konusundaki tembellikleri yuzunden, gelistirmekte oldugumuz yeni platformda senden kurtulma planlarim alt-ust oldu. Onlar sayesinde en azindan bir 2 yil daha seni kullanmam gerekecek back-end’de. Ustelik gecenlerde senin yaraticin Rasmus Lerdorf’un Brooklyn’deki PHPTalk’ta soyledigi “PHP web icin tasarlandi, arkaplan icin degil” sozlerine ragmen…

Daha once seninle yazdigim bazi betikleri, sirf uyku tutmadigi icin sabahin korunde bir de Python ile yazmayi denedim. Nerede senin kaprislerin, nerede Python’un uysalligi?…

Web’de yine gorusebiliriz ama arkaplan uygulamalari icin soruyorum: Ne zaman cikacaksin hayatimdan?

Jul 202007
 

?irkette metin dosyalar? üzerinde çal???rken kullanmak üzere birkaç kabuk beti?i yazd?m. Tabi bunlar? PHP ile yazmak daha kolay geldi.

ayir.php: Bir metin dosyas?ndaki verilen kritere sahip sat?rlar? ay?r?r.

iki_dosya_farki.php: Verilen iki metin dosyas?n? kar??la?t?rarak farkl? sat?rlar? ayr? bir dosyaya yazar.

satirkirp.php: Bir metin dosyas?ndaki sat?rlar? belirtilen sat?r uzunlu?una göre k?rpar.