GitFlow Kullanarak Jenkins ile CI/CD

Samet Emek
inventiv
Published in
9 min readNov 16, 2020

İnventiv olarak; yazılım işçiliğine, ortaya çıkan ürünün fonksiyonalitesi kadar önem verme hedefindeyiz. Bunu yaparken, bir yandan da son teknolojiyi takip etmeye ve trendleri kaçırmamaya gayret ediyoruz. Bugünlerde uzun süredir yenilemekte olduğumuz teknolojik altyapımızda nispeten geride kaldığını düşündüğümüz DevOps kısmına eğilmeye karar verdik. Bu yazımda bu problemi aşmak için benimsediğimiz çözümden bahsedeceğim.

Öncelikle CI (Continuous Integration) ve CD (Continuous Delivery) kavramlarından kısaca açıklamak istiyorum.

Continuous Integration (Sürekli entegrasyon): Geliştiricilerin kod değişikliklerini düzenli olarak belli bir kaynakta birleştirdiği ve ardından derlemelerin otomatik olarak yapıldığı ve testlerin çalıştırıldığı bir DevOps yazılım geliştirme metodolojisidir. Hataları daha hızlı bulmayı ve gidermeyi, yazılım kalitesini iyileştirmeyi, yeni yazılım güncellemelerini doğrulamayı ve yayınlamak için gereken süreyi azaltmayı hedefler.

Continuous Delivery (Sürekli teslimat): Bir yazılım kodunda değişiklik yapıldığında, çeşitli adımların üzerinden geçerek doğrudan kullanıcının kullanımına açılacak şekilde canlı ortamına deploy edilmesidir. Bu adımlar aşağıdaki diyagramda gösterilmiştir.

Bu süreçte adımlar aşağıdaki gibi işletilmektedir.

  • Repo’dan değişikliklerini al(GitHub, TVFC)
  • Kodu derle (Build)
  • Testleri koş ve coverage ölçümü yap(Unit Test, Coverage)
  • Kodu publish et (Artifacts)
  • Dev, Test ortamına deploy et ve otomasyon ve kullanıcı testlerini koş (QA)
  • Staging ortamına deploy et (Staging Deploy)
  • Pre-prod ortamına deploy et. (Pre-Prod Deploy)
  • Prod ortamına deploy et. (Prod Deploy)

Kaynak Kodu Versiyon Kontrolü aracı olarak GitHub’ı, CI/CD tool olarak ise halihazırda on premise çalışabilen ve açık kaynak kodlu tek alternatif olan Jenkins’i kullanıyoruz.

Core uygulamalarımız “Single Codebase” mantığıyla geliştirildiği için, branch yapımız da bu mantığı destekleyecek şekilde kurgulanmalıydı. Bu kavramı biraz açmak gerekirse, bu türdeki projelerimiz aynı repository altında birden fazla solution barındırıyor. Bu sayede birbirinden farklı uygulamalar, temel bazı modülleri ortaklaşa kullanabiliyor. Bunun bir sonucu olarak bu tarz projelerde bir değişiklik yapıldığında, etkilenen tüm uygulamalar için CI/CD kurgusunu işletmemiz gerekiyor.

Jenkins’te oluşturduğumuz 2 ayrı build job’ı bu bahsettiğim kurgu için tüm ortamlarımıza yeterli geliyor.

  1. Github’da açılmış, Dev ortamına özel, bir pull request’in build edilmesini sağlayan ve artifact oluşturan job (MULTITRAVEL-DEV-BUILD)
  2. Kalan tüm ortamlar (Test, Staging, Pre-Prod ve Prod) için artifact oluşturan job (MULTITRAVEL-BUILD)
Build Jobları

Jenkins’te build-deploy job’larının nasıl oluştuğu makalemizin konusu dışında kaldığı için bu yazımda bahsetmeyeceğim. Bunun için linki takip edebilirsiniz.

Dev ortamı için neden ayrı bir job hazırladığımızı merak etmiş olabilirsiniz. GitFlow akışında bildiğiniz gibi feature branch’ler işleri tamamlanınca develop branch’e merge edilir. Ancak birbirine yakın zamanlarda merge edilen iki ayrı feature’un develop branch’ine merge edildiğinde birbirlerini etkilemediğinden de emin olmamız gerekir.

İşte bu problemi aşmak adına, DEV-BUILD job’ı feature branch’i, develop branch e build anında (on-the-fly) birleştirip, buradan bir artifact oluşturuyor. Birleştirmek için açılan pull-request ise ancak bu artifact testlerden başarılı bir şekilde çıkabilirse onaylanıyor.

Şimdi biraz bu kurgunun detaylarına bakalım.

Jenkins Pull Request Build

Önceki paragrafta bahsettiğim gibi GitHub üzerinde açılmış bir PR’ın develop branch’ine merge edilmeden test edilmesi gerekmektedir. Bunun için Jenkins’te DEV-BUILD job’ı çalıştırılırken PR seçimi yaptırıp, build işlemini buna göre gerçekleştiriyoruz. Burada önemli olan nokta, seçilen PR’ın develop branch’ine sanal olarak merge edilmiş halinin build edilmesidir. Yani seçilen PR, gerçekte develop branch’ine merge edilmeden, develop branch’ine birleştirilmiş gibi build işlemi yapılmaktadır.

DEV-BUILD job’ı için bahsettiğim bu işlemler aşağıdaki adımlardan oluşuyor.

  • Jenkins ile GitHub arasında bağlantı kurulması
  • Github’dan ilgili PR’ların çekilip listelenmesi
  • İlgili PR’ın develop branch’i ile merge edilmiş halinin derlenmesi
  • Testlerin koşulması
  • Test Coverage çalıştırılması
  • Artifact’in oluşturulması

GitHub bağlantısını yapabilmek için Git plugin’i kullanıyoruz. Bu bağlantı işlemini GitHub profilinden elde ettiğimiz SSH’i ile sağlıyoruz. Github’da SSH key oluşturma işleminin detayları için burayı takip edebilirsiniz. Aşağıda görüldüğü gibi Git plugin’i kullanılarak GitHub bağlantısını gerçekleştirdik.

Jenkins joblarına Git Parameter pluginini kullanarak branchleri, tagleri ya da pull requestleri parametre olarak verebiliyoruz. Ancak bizim ihtiyacımız biraz daha spesifik olduğu için, kendimize has bir scriptle bunu elde etmemiz gerekti. Bu noktada yardımımıza Groovy yetişti.

Groovy, Java platformu (JVM) üzerinde çalışan nesne yönelimli bir programlama dilidir. Geliştiricileri James Strachan ve Bob McWhirter, Ruby gibi esnek, dinamik dillerden ilham almışlar. En öne çıkan özelliği ise kolay entegre edilebilir bir dil olmasıdır. Bir Groovy scripti içerisinden herhangi bir Java nesnesini bir Groovy nesnesi gibi çağırabilirsiniz. Halihazırda Java ile ilgili biraz bilginiz varsa, kolay bir şekilde Groovy kodlamaya başlayabilirsiniz.

Yaptığımız otomasyonda bizi Groovy kullanmaya iten sebep de bu kolay entegre olabilme özelliği oldu. Tetiklemek istediğimiz GitHub API’ları Groovy sayesinde çok hızlı bir şekilde Jenkins’e entegre edebildik.

Temel olarak yukarıda bahsedilen fonksiyonaliteyi gerçekleştirmek için GitHub’ın Branch Listeleme, PullRequest Listeleme, Branch Oluşturma, Branch Silme, Label Ekleme ve son olarak Release Oluşturma endpoint’lerine ihtiyaç duyduk.

Github api ile ilgili detaylara bu linkten ulaşabilirsiniz.

Şimdi biraz DEV-BUILD job’ının detaylarını inceleyelim. Yukarıda bahsettiğim PR listesini Jenkins’de “Extended Choice Parameter” parametresini kullanarak listeledik. Liste içeriğini ise yazdığımız bir groovy script’i ile besledik.

Görselde gördüğünüz GetPulls.gy script’inin içeriğini aşağıda paylaşıyorum.

GitHub’tan PullRequest listesinin çekilmesi

Bu script ile belirtilen organizasyon ve repo ya ait PullRequest listesini çekebilirsiniz.

Not : Token parametresi için gereken değeri, GitHub hesabınızda Settings/Developer Settings ekranında “Personal access tokens” menüsünden “Generate new token” butonuna tıklayarak elde edebilirsiniz.

Token parametresinin elde edilmesi

Yazdığımız script’leri job içerisine gömmek yerine, dosya olarak Jenkins makinesinde tutuyoruz. Bu şekilde yeniden kullanılabilir ve kolay ulaşılabilir oluyor.

Jenkins job’ınız içerisinde bir Groovy script’i ilk kez çalıştırmak istediğinizde karşınıza bir güvenlik uyarısı gelecektir. Jenkins’in güvenlik plugin’i, kötü niyetli bir script’in sinsice sunucularına yerleşip bir zafiyet oluşturmaması için sizi uyaracaktır. Bu nedenle ilgili script’i “In-process Script Approval” sayfası üzerinde onaylamanız gerekmektedir. Script üzerinde değişiklik yaptığınızda, Jenkins bunu algılayacak ve aşağıdaki uyarıyı çıkarıp, onay isteyecektir.

Onaylama yapıldıktan sonra, aşağıda görüldüğü gibi PR’lar GitHub’dan çekilerek listelenmektedir. Listeden seçilen PR’ın tekil numarası “PR” değişkeninde tutulmaktadır.

Son olarak Git plugin’inde aşağıdaki değişiklikleri yaptığımızda, build job’ımız hazır hale geliyor.

Refsec: +refs/pull/${PR}/*:refs/remotes/origin/pr/${PR}/*
Branch Specifier (blank for ‘any’): origin/pr/${PR}/*

Bizim kurgumuzda, ayarlarını yaptığımız bu Build işlemleri tamamlandıktan sonra testler çalışmaktadır. Testler başarıyla tamamlandıktan ve coverage standartlarına uygunluğu ölçüldükten sonra ise artifact oluşturularak deploy edilmeye hazır duruma getirilir.

Deployment işleminin tamamlanmasının ardından, test otomasyonu ve kullanıcı testlerinden başarıyla geçen PR, develop ortamına merge edilir.

Jenkins Branch Build

Kurgumuz gereği, PR develop brachine merge edilmesinin hemen ardından, develop branch’inden yeni bir branch (release branch) oluşturuyoruz. Bu branch’i oluştururken dikkat edilmesi gereken nokta, develop branch’inin son commit’i değil, ilgili PR’ın sahip olduğu son commit’ten referans almaktayız. Çünkü o PR’dan hemen sonra merge edilmiş bir başka PR yapılan tüm testleri boşa çıkarabilir.

Bu yeni branch oluşturma işlemi, normal şartlar altında GitHub WebHooks kullanarak yapılabilir. Detaylarına bu linkten erişebilirsiniz.

Ancak security departmanımızın güvenlik gereksinimleri nedeniyle, bu işlemi WebHook kullanmadan yapmamız gerekti. Bu nedenle, bu işlemi Jenkins üzerinde periyodik olarak çalışan bir job ile gerçekleştirdik.

Daha önce uygulamalarımızın bir bölümünün “Single Code Base” mantığıyla geliştirildiğinden bahsetmiştik. Bunun bir sonucu olarak bu tarz projelerde bir değişiklik yapıldığında, etkilenen tüm uygulamalar için CI/CD kurgusunu işletmemiz gerekiyor. Bunu yapabilmek için ise merge edilen PR’ın hangi uygulamaların etkilediğini bilmemiz gerekiyor.

Bunu belirtebilmek için GitHub Labels’tan yararlandık. GitHub üzerinde her bir uygulamamız için ayrı ayrı etiketler oluşturduk.

Oluşturduğumuz bu etiketleri açılan tüm PR’lara etkilenen uygulamaları belirtmek için aşağıdaki gibi ekliyoruz.

Yukarıda bahsettiğim Jenkins üzerinde periyodik olarak çalışan job, görseldeki PR için 2 tane release branch oluşturacaktır. Bu yeni oluşturulan release branch’leri aşağıdaki pattern ile oluşturuyoruz.

release/{app name}/{pr merge date}-{pr number}Örnek:
release/es-acq/20201113–1222
release/walvent/20201113–1222

Oluşturulan bu release branch’ler, Jenkins üzerinde Dev ortamı dışında tüm ortamlar için kullandığımız build job’ında listeleniyor .

Oluşturulan release branch lerin listelenmesi

Daha önce bahsettiğim gibi Git Parameter plugin’i kullanılarak branch’ler çekilebilmektedir. Ancak ilgili branch seçilerek, build işlemi başlatıldığında, Jenkins varsayılan olarak GitHub ortamındaki bu kodları her seferinde çekmektedir. Aslında Jenkins job içerisinde “Delete workspace before build starts” isminde bir seçenek var. Fakat bu yöntemin sağlıklı çalışmadığı tecrübeler yaşadığımız için, epey süredir bu checkbox bizde işaretliydi. Yani build işlemine başlamadan önce, geçmiş tamamen temizleniyordu.

Bu durum özellikle dosya boyutu büyük projelerde can sıkıcı olabiliyordu. Bundan dolayı Git pluginini kullanmama kararı aldık. Bunun yerine, kodları yazılım geliştirirken kullandığımız komutlarla (fetch, checkout, pull gibi) çekme yoluna gittik. Bu yöntem bize hem zaman, hem de fonskiyonaliteyi özelleştirebilme imkanı sağladı.

Ancak bu yöntemi kullanıyor olmamızın bir dezavantajı var. Git Parameter plugin’i, branch isimlerini listelemek için Git plugin’ini kullanıyor. Bu nedenle yine bir Groovy script’i kullanarak, branch listesini GitHub üzerinden sağlamak durumunda kaldık.

Yukarıda görüldüğü gibi “Extended Choice Parameter” parametresini,“GetBranches.groovy” scriptini kullanarak branch isimleri ile dolduruyoruz.

“GetBranches.groovy” dosyasının içeriği ise aşağıdaki gibi:

İlgili branch seçildikten sonra değeri “BRANCH” jenkins değişkeni içerisine set ediliyor. Ardından, aşağıdaki komutları “Jenkins Build Step” de çalıştırarak git checkout işlemini tamamlanıyor.

start-ssh-agent.cmdgit fetch
git checkout — .
git checkout $env:BRANCH
git fetch
git pull
git branch

PowerShell scriptinin ilk satırında gördüğünüz gibi, Github ve Jenkins makinesi arasında güvenli bağlantıyı, ssh-agent çalıştırarak sağlamaktayız. İlgili ayarlar için burayı takip edebilirsiniz.

Jenkins Branch Deploy

Build işlemlerini tamamlandıktan sonra deployment işlemine geçiyoruz. Bu kısımda her ortam için ayrı ayrı deploy job’ları oluşturduk.

Deploy job’larınında build esnasında oluşturduğumuz artifact’i parametre olarak seçerek deployment işlemini tamamlamaktayız. Bu kısımda artifact’leri direk dizinden okuyoruz. Bu işlem için “File system objects list Parameter” tipindeki parametre kullanılıyor.

Rutin deployment kurgusunun dışında değinilmesi gereken bir nokta daha var. Bildiğiniz gibi bir .Net projesi publish edilirken (eğer gerekli ayarlamalar yapılmışsa) seçilen build config’e göre web.config, site.map gibi dosyalar transform edilir. Bu sayede farklı ortamlar için farklı web.config dosyaları oluşturulması sağlanır.

Ancak bizim kurgumuzda, deployment yapılan tüm ortamlar için sadece bir kez build etmek (Build once, deploy to multiple environments) gibi bir ihtiyacımız vardı. Web.config gibi ortam bazında değişen ayarlar saklayan dosyaları deployment sırasında generate ederek zaman kazanmak istiyorduk.

Konfigürasyonların Deployment Sırasında Generate Edilmesi

Bu noktada Microsoft’un sunduğu araçlar yeterli gelmediği için alternatif çözümlere yöneldik ve imdadımıza “WebConfigTransformRunner” isimli nuget paketi yetişti.

Bu pakette bizim için önemli olan, paketi projenize ekledikten sonra ilgili nuget dizininin altında oluşan “Microsoft.Web.XmlTransform.dll” ve “WebConfigTransformRunner.exe” dosyalarıdır. Bu dosyaları aldıktan sonra nuget paketinin bizim için ekstra bir işlevi kalmıyor.

Elde ettiğimiz bu iki dosyayı dilediğimiz yere taşıyarak, Visual Studio’da publish sırasında gerçekleştirilen transform olayını gerçekleştirme imkanımız var.

Çalıştırılabilir dosyaya aşağıdaki parametreleri yollayarak işlemi gerçekleştirebiliriz.

WebConfigTransformationRunner.exe WebConfigFilename TransformFilename OutputFilename

Paketle ilgili daha detaylı bilgili buraya tıklayarak edinebilirsiniz.

Bu iki dosyayı kullanarak web.config generate eden bir PowerShell scriptini Jenkins joblarımızda tetikleyerek amacımıza ulaştık.

Poweshell scriptinin çalışan bir örneğini buraya tıklayarak inceleyebilirsiniz.

WebConfigTransformation’ın Jenkins’ten Çağırılması

Bütün bu aşamaların neticesi olarak deploy job’ı, ilgili artifact seçilerek işleme başlanacak şekilde hazır hale gelir.

Bütün bu bilgilerin ışığında, yazılım mimarisi oluşturulurken, ileride dah büyük eforlar sarfetmemek adına üretkenlik (developer productivity), esneklik (flexibility), ölçeklenebilirlik (scalability), güvenilirlik (reliability) gibi kavramların yanında, hedeflediğimiz sistemin büyüklüğünden bağımsız olarak, DevOps süreçlerini de hesaba katmak yerinde olur kanaatindeyim.

Makaleyi yazarken katkı veren, değerli mesai arkadaşım melih orhan’a teşekkürlerimi sunuyorum.

Referanslar

https://tr.wikipedia.org/wiki/Groovy
https://github.com/erichexter/WebConfigTransformRunner
https://neoteric.eu/blog/make-jenkins-speak-git-flow/
https://www.bmc.com/blogs/continuous-delivery-continuous-deployment-continuous-integration-whats-difference/
https://aws.amazon.com/tr/devops/continuous-integration/

--

--