12faktör, Heroku’nun kurucu ortaklarından Adam Wiggins’ın kendi deneyimlerinden yola çıkarak, DevOps, teknoloji, programlama dili, mimari, vb. konuları kapsayacak şekilde oluşturulmuş olduğu on iki maddelik bir manifestodur. Bu manifesto, bulut odaklı uygulama geliştirilirken uygulanması gereken best practices’lere değinen bir kılavuz niteliği taşımaktadır.
12 Faktör kısaca aşağıdaki başlıklardan oluşmaktadır.
Codebase, dependencies, config, backing services, build-release-run, processes, port binding, concurrency, disposability, dev/prod parity, logs, admin processes
Kimler İçin
Yazılım geliştiriciler, yazılım mimarları ve DevOps mühendisleri bu yazının hedef kitlesidir. Adam Wiggins’e göre ise; servis odaklı uygulama geliştiren, dağıtan veya yöneten herkes bu manifestonun muhattabıdır.
1. Codebase
Kaynak kodlar versiyon kontrol sistemi (Git, SVN, TFS, vb.) üzerinde tutulmalıdır. Versiyon kontrol araçları sayesinde; geliştirme, review, versiyonlama ve dağıtma süreçleri kolaylıkla uygulanabilir olmaktadır.
Versiyon kontrol sistemi üzerinde one-to-one ilişki kurulmalıdır.
One App — One Code Repo ve One Code Base — Multi Platform Deployment
Bu maddeye göre her “uygulama” ayrı kod repolarda saklanmalıdır ve her uygulamanın tek bir code base’i olmalıdır. Git versiyonlama aracını kullanıyorsanız ve birinci ilkeye uygun branch stratejinisini nasıl kuracağınızı merak ediyorsanız git flow’lara göz atabilirsiniz (örneğin:Github flow, Microsoft Flow, vb.).
Neden?
Yazılım projelerinde Dev, Test ve Release gibi farklı kod repoları görülebiliyor ve kodlar repolar arasında taşınıyor. Bu yaklaşım size farklı avantajlar sağlayabilir fakat 12 faktör’e göre bu yanlıştır. Bu yaklaşımda, her ortam için oluşan çıktılar (so, jar, dll, vb.) farklıdır. İstenilen şey ise, tek bir kod reposu olması ve tüm ürün ortamlarına aynı repodan alınan çıktıların atılmasıdır. Bu yaklaşım ile birlikte ortamlar arası run-time farklılıkları en aza indirilebilir.
Birden fazla uygulamayı tek kod reposuna koymak ise 12 faktörün ilk ilkesine ters düşmektedir. Uygulamalar ayrılmalı ve ayrı kod repolarında tutulmalıdır, bu sayede uygulamaları versiyonlamak, versiyonlar arası geçiş yapmak, kod kalitesi ölçümlemek, uygulamalar arası bağımlıkları koparmak ve belkide en önemlisi CI/CD süreçleri daha kolay bir şekilde uygulayabilme avantajına sahip olunur.
2. Dependencies
Bu ilkeye göre projedeki her şey (kod, paket, direktif dosyaları, scriptler, vb.) paketlenmelidir. Ortak kullanım için geliştirilen helper, common, shared kernel, vb. her şey geliştirme yaptığınız programlama dilinin paket yöneticisi kullanılarak paketlenmelidir. Projeler arası kod, dll, so, jar, vb. taşımak yerine kullanılacak şeyler versiyonlanmalı ve paketlenmelidir. Paket yöneticilerine örnek olarak Python için pip, .net için nuget, java için maven, java script için npm gösterilebilir. Bağımlılıklar referans olarak verilirken kesinlikle versiyon belirtilmelidir, csproj içerisindeki bir efcore kütüphanesi, pom.xml içerisindeki bir spring paketi versiyon numarası belirtilmeden tanımlanmamalıdır. Bu ilkeyi esnetebilecek tek durum referans verilen paketlerin yine sizin tarafınızdan geliştiriliyor olması ve her zaman nihayi versiyonun gerekli olduğu durumdur.
Bağımlılıklar kod veya proje bağımlılığı olarak düşünülmemelidir. Örneğin bir .net uygulaması için geliştirme esnasında eklenecek bir IIS gereksinimi veya bir Java uygulaması için Tomcat de bir bağımlılıktır. Ortam bağımlılıklarıda bu başlık altında ele alınabilirler, örneğin geliştirme ortamında kurulması gereken bir uygulama bile bu kapsamında değerlendirilmektedir. Bu bağımlılıklar uygulamadan izole edilmeli ve yönetilebilir olmalıdır. Tüm bağımlılıklar açık bir şekilde belirtilmelidir.
Neden?
Bu kriter için bir çok neden sayabiliriz. Öncelikli olarak tüm referansların tek bir noktadan belirtilmemesi (package file, pom file, vb.) hem geliştirme hemde yaygınlaştırma sürecini zorlaştıracaktır. Farklı ortamlara yaygınlaştırma yapılması durumunda gerekli bağımlılıklar manuel olarak taşınmak durumunda kalınacaktır. Bağımlılıkların versiyonlanmaması durumunda ise yapılan bir geliştirme paketinin geri alınması senaryosunu zora sokacaktır.
Proje bağımlılıkların açık bir şekilde belirtilmediği ve izole edilmediği bir durumunda, projeye yeni başlayan bir geliştirici bu bağımlıkları; derleme hataları, log takibi gibi zaman kaybettirecek şeylerle bulmak zorunda kalacaktır.
3. Config
Uygulamada ortamlar arası (dev, test, release, vb.) değişen her şey konfigürasyondur. Uygulamanın ihtiyaç duyduğu tüm konfigürasyonlar (kullanıcı adı, dosya dizini, ip adresi, veritabanı adı, vb.), uygulama içerisinde sabit olarak tanımlanmamalıdır. Konfigürasyonlar ile kod kesinlikle ayrılmalı ve bir arada olmamalıdır. Konfigürasyonlar uygulama dışında bir servis olarak sunulmalıdır. Konfigürasyonlar ve credentials birbirinden ayrı tutulmalıdır. Spring config server, vault, etcd, consul, vb. teknolojiler kullanılabileceği gibi ortam değişkenleride konfigürasyonları yönetmek için kullanılabilir.
Neden?
Konfigürasyonları uygulamadan ayırmadan kullanılması senaryosunda, konfigürasyonların değiştirilmesinin istendiği bir durumda uygulamanın güncellenip tekrar deploy edilmesi gerekecektir. Konfigürasyonların servis olarak sunulması ve uygulamaların bu servisi consume etmesi durumunda tüm uygulamaların konfigürasyonları merkezi bir noktadan gözlemlenebilir, yönetilebilir ve kolay bir şekilde çalışma zamanında değiştirilebilir olacaktır.
4. Backing Services
Veritabanı, API servisleri, Cloud (S3, DB instance, vb.), sosyal ağlar, mail sunucuları hatta dosya sistemi gibi her şey 12 faktörün dördüncü ilkesine göre uygulama için destek servisidir ve bu ilkeye göre tüm servisler uygulama kapanmadan veya restart edilmeden çalışma zamanında attach ve detach edilebilmelidir. Kullanılan destek servislerinin tüm parametreleri konfigürasyon servisi ile yönetilebilir olmalıdır.
Neden?
Ölçeklenebilir bir uygulama geliştirmek istediğinizde, uygulamanın kullandığı tüm servislerinde bu ölçeklemeye izin vermesi gerekir. Bu ilkeye uygun olarak destek servislerini istenilen zamanda takıp çıkartılabilir şekilde tasarlarsanız uygulamanızı ölçeklemek başka bir ortama taşımak veya destek servisini başka bir servisle değiştirmek oldukça kolay olacaktır.
5. Build-Release-Run
12 faktörün beşinci ilkesine göre; projenin yaşam döngüsü içerisindeki üç süreci (build, release, run) kesin çizgilerle bir birinden ayırmak gerekmektedir. Bu ilke, CI/CD süreçlerini düzgün olarak uygulamaya zorlamaktadır, bu aşamada da DevOps sürecini oturtup kendinize uygun bir derleme dağıtım aracı (Azure DevOps, AWS Pipeline, Travis, Jenkins, Gitlab CI/CD, vb. ) seçmeniz gerekecektir. Bu ilkeye göre;
Build Süreci:
– İstenildiği kadar kompleks süreçlere sahip olabilir.
– Süreç deterministik olmalı.
– Build makinesi geliştiricinin çalışma ortamından ayrı olmalı.
– Bu aşamada konfigürasyonlar yapılmamalı, kaynak kod build makinesinde geliştiricide olduğu gibi derlenebilir olmalı.
Release Süreci:
– Kaynak kodun versiyonlanması bu aşamada olmalıdır.
– Bu aşamada konfigürasyon yapılabilir.
Run Süreci:
– Süreç oldukça basit olmalıdır, proje çok kolay bir şekilde çalışabilir olmalı.
– Herhangi bir konfigürasyon gereksinimi olmadan release alınan kod çalışabilir olmalı.
– Bu aşamada, proje üzerinde bug fix işlemleri yapılmamalı. Bug fix gereksinimi varsa süreç tekrardan başlatılmalıdır.
Neden?
Geliştiricinin tüm bug fix işlemlerinin runtime esnasında giderdiği bir ortamda uygulamanın versiyonlanması, versiyonlar arasında geçiş yapılması, uygulamanın ve uygulama paketlerin arşivlenmesi çok mümkün olmayacaktır. Bu üç sürecin bir birinden ayrıldığı durumda, release kodun her an çalışmaya hazır olduğunu bilmek istenildiği anda deployment yapılabilmesini sağlayacaktır.
6.Processes
Altınca ilke derki; süreçlerinizi stateless (durumsuz) olarak tasarlayın. Stateless bir uygulama, ihtiyaç duyduğu state’i in-memory saklamak yerine bunları kalıcı olabilecek bir yerde (örneğin veritabanı) depolar. Sate’lere ihtiyaç duyulduğu her durumda, veriler destek servislerinden getirilmelidir.
Neden?
Teknolojik yapınız (örneğin Kubernetes) ölçeklemeye izin versede geliştirdiğiniz uygulamalarında ölçeklenebilmesi gerekir. Stateless olarak tasarlanan süreçler mikroservis mimarideki uygulamarın yatay ölçeklenebilmesini sağlar. Tüm state’lerin in-memory olarak tutulduğu bir uygulama birden fazla pod üzerine dağıtıldığı zaman state’lerin bütünlüğü korumak mümkün değildir. Bu sebeple ilgili state’leri harici bir destek servisi üzerinde tutmak daha mantıklı olacaktır.
7.Port Binding
Servisler portlar üzerinden dışarıya açılmalıdır, dışa açılırken file path kullanılmamalıdır. IIS üzerine deploy edilen .net veya Glassfish üzerine deploy edilen her java uygulamasını http://hostname/myapp1, http://hostname/myapp2, http://hostname/myapp3 şeklinde açmak yerine her bir uygulamayı bir birinden farklı http://hostname:5000, http://hostname:5001 gibi portlar üzerinden açılmalıdır.
Neden?
Her servisi port üzerinden dışarıya açmak bu servislerin deployment süreçlerinde DevOps ekibinin işini kolaylaştıracaktır. Uygulamalarınızı dockerize etmek istediğinizde path binding yapmak her zaman kolay olmayabilir. Mikroservis mimarisine geçmek istediğinizde servisler arası haberleşme ihtiyacı doğacaktır, bu durumda kullanacağınız service discovery araçları ise dışarıya açtığınız portları arayacaktır. Tekbir uygulama sunucusu üzerinden açtığınızda bu uygulamaları parçalamak mümkün olmayacaktır.
Bu sayede API Geteway araçlarının nimetlerinden (limitleme, servis mapping, vb.) de yararlanabilirsiniz.
8.Concurrency
Process ilkesi altında bir sürecin nasıl olduğundan bahsetmiştik. Sekizinci ilkeye göre de bir uygulama içerisinde ki işler (kullanıcı arayüzleri, destek servisleri, vb.) ayrılmalı ve farklı süreçler olarak ele alınmalıdır. Örneğin; javascript ile geliştirilmiş içerisinde chartlar, vs. bulunan bir kullanıcı arayüzü ile bu uygulama içerisinde bulunan kullanıcı authentication backend servisi biribirinden ayrılmalıdır.
Neden?
Uygulamlarının ölçeklenebilir olmasını kim istemezki. Sekizinci ilkeye uymadığımızı ve UI ile bu UI’a hizmet eden, çok fazla sistem kaynağı tükecek işleri gerçekleştiren bir backend servisinin aynı jar veya dll içerisinde olduğunu varsayalım. Kullanıcı sayısındaki artış sebebiyle bu sistemi ölçeklemek istediğinizi varsayalım, yapacağınız şey dikey ölçekleme olarak adlandırılan sunucu kaynaklarını arttırmak olacaktır veya aynı uygulamayı kopyalayıp başka bir makinede dikey olarak ölçekleyin. Bu durumda nereye kadar ölçekleyebilirsiniz ki? Dikey ölçeklemede elbet fiziksel kaynak sınırlarına ulaşacaksınız. Yatay ölçekleme yaptığınız da ise her kopyaladığınız jar veya dll içerisinde hem kaynak tüketimini arttıran ölçeklemeye neden olacak backend servisiniz hemde çok az kaynak tüketen ölçekleme gereksinimi olmayan UI projeniz var. Bu iki süreci bir birinden ayırsaydınız sadece kaynak gereksinimi olan işleri ölçekleyebilirdiniz.
9. Disposability
Dokuzuncu ilke diyorki; uygulama her an çalışmaya ve kapanmaya hazır olmalıdır. Uygulamaya, anlık olarak bir çok kullanıcı isteği gelebilir bu durumda uygulama ölçeklenmeli ve yeni instance oluşturulmalıdır. Ölçeklenen uygulama çok hızlı bir şekilde ayağa kalkarak taleplerin bir kısmını işlemeye başlamalıdır.
Kullanıcı talebi azaldığında ise instance, gracefully shutdown yani hali hazırdaki talepleri kaybetmeden kapanış senaryosunu işleterek kapanabilmelidir. Kapanmak üzere olan bir instance gönderilen kullanıcı talepleri, tasarladığınız kapanma senaryosuna göre; yeni talep almadan eldeki talepler işlenip veya işlenmek üzere olan tüm talepler dahil tekrardan işlem kuyruğuna atılmak suretiyle instance kapatılabilmelidir.
Neden?
Nedenini anlamak için bu duruma gerçek hayattan bir örnek verelim. Bildiğiniz üzere ÖSYM sonuç sayfası sadece sınav zamanlarına yoğun kullanıcı isteğine maruz kalmaktadır. Sınav sonucu açıklandığında veritabanından sonucu alıp kullanıcıya döndürecek olan backend servisi trafik arttığı anda yeni instance’lara dağıtılmalı, uygulama çok hızlı bir şekilde yeni taleplere cevap vermeye başlamalıdır. Ölçeklemenin ardından uygulamanın 10dk da ayağa kakması sadece bir saat çalışacak bir instance için kabul edilebilecek bir süre değildir.
Kullanıcı trafiği azaldığında hali hazırdaki tüm instance’lar üzerinde işlenmeyi bekleyen talepler olacaktır. Trafiğin azalması ile bu instance’lara gerek yok diye kapatabilirsiniz fakat kullanıcı girişini yapmış sonuç bekleyen bir kullanıcı bu instance üzerine ise bu kullanıcı doğrudan hata alacak ve tekrardan giriş yapması istenecektir. Bu durumun önüne geçebilmek için gracefully shutdown senaryosu eklenmeli ve sınav sonucu istekleri açık instance üzerine yönlendirilmelidir, böylece kullanıcı hiçbir şeyin farkına bile varmadan sonucunu görecektir.
10. Dev/Prod Parity
Onuncu ilke der ki; projenin geliştirme ve çalışma ortamındaki farklılıklar ortadan kaldırılmalıdır, dev ve prod ortamı “aynı” olmalıdır. On iki faktöre göre sürekli dağıtım yapmayı engelleyecek üç farklılık faktörü vardır. Bu farklılıklar; zaman, personel ve kullanılan araç farklılığı. Bu farklılıklar ne kadar az olursa sürekli dağıtım ve bug fix o kadar kolay olacaktır.
Zaman: Geliştirme süreci ile projenin canlı ortama alınması arasındaki süre zaman gap’i olarak adlandırılır ve bu süre haftalar aylar yerine günlük belkide saatlik seviyesine indirgenmelidir.
Personel: Gelenekselsel yaklaşımda projeyi geliştiren, deploy eden ve izleyenler bir birinden farklı kişilerdir ve genellikle sadece ilgili süreç kapsamında muhattap olurlar. On iki faktöre ise diyorki; uygulamayı geliştiren deploy eden ve izleyenler aynı kişiler olmalıdır.
Oluşturulan feature ekibi, uygulamayı develop, deploy ve run edebilecek yeteneklerine sahip olmalıdır.
Araç: Geliştiricinin development ortamındaki araçları ile uygulamanın production ortamında kullandığı araçlar olabildiğince aynı olmalıdır. Geliştirici Linux üzerinde geliştirme ve test yaparken production ortamında Windows Server üzerinde çalışacak olması önerilmeyen bir durumdur. Aynı şekilde geliştirme ortamındaki Java veya .Net versiyonu ile yaygınlaştırmanın yapılacağı ortamdaki versiyonun farklı olması da araç farklılığı olarak değerlendirilir ve bu farklılıklar olabildiğince minimize edilmelidirler.
Neden?
Zaman gap’inin fazla olması durumunda yani geliştiricinin yazdığı kod aylar sonra prod ortamına alınırsa bu süreçte oluşacak bug’ları fix etmek zor olabilir çünkü üzerinden çok fazla zaman geçmiş ve yapılan geliştirmeler unutulmuş olabilir.
İşi yapan personellerin farklı olması durumunda kodu yazan, yaygınlaştıran ve monitor edenler farklı ise oluşacak bir hata durumunda muhtemelen bir birlerini suçlayacaktır. Bu senaryoda projeyi kimse sahiplenmeyecek geliştirici geliştirme sürecinden sonra top benden çıktı moduna girebilecektir fakat bu süreçler tek bir takım içerisinde yapılırsa o ürün o takım tarafından sahiplenilecek ve takım ürüne tamamen hakim olacaktır.
Ortamlar arası farklılıklar olması durumunda dev ve testte çalışan bir ürünün prod ortamında çalışacağının garantisi hiçbir zaman verilemez.
11. Log
On iki faktöre göre, loglar bir akıştır ve akış olarak değerlendirilmelidir asla dosya olarak bakılmamalıdır. Uygulama logların depolanması veya işlenmesi gibi süreçlerle ilgilenmemelidir. Uygulama logları system out olarak yazmalı ve bırakmalıdır. Logların toplanması ve işlenmesi ise başka bir araç tarafından gerçekleştirilmelidir. Logların kendi içerisinde bütünlüğü olmalıdır ölçeklenmiş ve paralel çalışan uygulamalarda da bu bütünlük sağlanmalıdır. Loglar zaman damgalı olmalıdır ve bu zaman damgasına göre saklanmalıdır.
Neden?
Uygulamalara , log saklama, işleme, vb. gibi görevleri vermek uygulamaya ekstra yüklemek anlamına gelir. Ölçeklenmiş birden fazla instance üzerinde çalışan bir uygulama dosyalara log saklamakla uğraştığında instance’lar kapanırken bunları senkronize etmek gerekecektir çünkü o dosyalar instance ile birlikte silinecektir. Dosya sistemi yerine bir backend servisine yazmak ise her log oluştuğunda uygulamaya gereğinden fazla yük yüklemek anlamına gelecektir.
12. Admin Process
On iki faktör diyor ki; uygulama dağıtım döngüsünün ilk evresinde tek seferlik bazı işlemler yapmanız gerekebilir (örneğin veritabanı migration script’ çalıştırılması, sistem değişkeni tanımlanması, vb. gibi bash işlemleri) bu gibi durumda standartları koruyun bu işlemleri yaparken aynı kod base’i kullanın ve mümkün olduğunca süreci otomatize edin. On iki faktöre göre mümkün olduğunca REPL destekleyen programlama dilleri (Python, Ruby, *Java, Go, Rust, vb.) kullanılmalı ve yapılması gereken tek seferlik bu işler programlama dili tarafından sağlanan bu shell üzerinden yapılmalı.
Neden?
Tek seferlik işleri otomatize etmek uygulamanın taşınmasını, devrini veya başka birileri tarafından kolaylıkla bakımının yapılmasını sağlar. Tek seferlik işler tek seferlik olduğu için genellikle bir defa yapılır, unutulur ve bu işi yapan kişiler sürekli olarak aynı kalmayabilir. REPL destekleyen dillerin kullanılması durumunda tüm instance’lara tek bir araç ile bağlanıp doğrudan shell üzerinde tek seferlik işlemler çok kolay bir şekilde güvenli olarak gerçekleştirilebilir.
Kaynaklar
https://12factor.net
https://roots.io/twelve-factor-wordpress/
Beyond the Twelve-Factor App