Uygulaman geliştirici makinesinde çalışıyor ama sunucuda çalışmıyor. Yapılandırma dosyası ortama göre elle düzenleniyor, yüklenen dosyalar konteynerin içine yazıldığı için her yayında kayboluyor ve ikinci bir kopya çalıştırdığında oturumlar bozuluyor. Bu sorunların hiçbiri tesadüf değil; hepsi uygulamanın çalıştığı ortama sıkı sıkıya bağlı olmasından kaynaklanıyor. Twelve-Factor App, tam olarak bu bağımlılığı koparmak için ortaya çıkmış on iki maddelik pratik bir kural setidir.
Bu rehberde twelve-factor app metodolojisinin ne olduğunu, on iki faktörün her birinin günlük işteki somut karşılığını, Docker ve konteyner dünyasında bunların nasıl uygulandığını, metodolojinin bugün hangi noktalarda eleştirildiğini ve sahada en sık ihlal edilen maddeleri anlatacağım. Amacım, listeyi ezberlemen değil; kendi uygulamana bakıp hangi maddeyi ihlal ettiğini ve bunun sana neye mal olduğunu görebilmen.
Twelve-Factor App Nedir ve Neden Hâlâ Geçerli#
Twelve-Factor App, bir platform ekibinin binlerce uygulamayı çalıştırırken gözlemlediği ortak sorunlardan çıkarılmış bir ilkeler listesidir. Çıkış noktası şudur: bir uygulama, altında çalıştığı işletim sistemi, dosya sistemi ve komşu servisler hakkında ne kadar az varsayımda bulunursa o kadar kolay taşınır, ölçeklenir ve otomatikleştirilir.
Liste kısaca şöyledir:
| # | Faktör | Özü |
|---|---|---|
| 1 | Kod tabanı | Tek depo, çok yayın |
| 2 | Bağımlılıklar | Açıkça bildir, sistemden bekleme |
| 3 | Yapılandırma | Ortamda tut, kodda değil |
| 4 | Destek servisleri | Veritabanı, kuyruk gibi kaynaklar takılıp çıkarılabilir olsun |
| 5 | Yapı, yayın, çalıştırma | Üç aşamayı kesin biçimde ayır |
| 6 | Süreçler | Durumsuz ve paylaşımsız çalış |
| 7 | Port bağlama | Kendi portunu dinle, hazır sunucuya gömülme |
| 8 | Eşzamanlılık | Süreç sayısını artırarak ölçekle |
| 9 | Tek kullanımlık olma | Hızlı başla, zarifçe kapan |
| 10 | Ortam eşliği | Geliştirme ile üretim birbirine benzesin |
| 11 | Loglar | Olay akışı olarak yaz, dosya yönetme |
| 12 | Yönetim görevleri | Tek seferlik işleri aynı ortamda çalıştır |
Bu maddeler bugün de geçerlidir çünkü konteynerleştirme ve orkestrasyon araçları tam olarak bu varsayımlar üzerine kurulmuştur. Bir uygulamayı Docker'a taşırken yaşadığın zorlukların büyük bölümü, aslında ihlal edilmiş bir faktörün faturasıdır.
Kod Tabanı, Bağımlılıklar ve Yapılandırma#
Kod tabanı maddesi tek bir şey söyler: bir uygulama, bir depo. Aynı uygulamanın üretim ve test sürümlerinin ayrı depolarda tutulması, ikisinin sessizce farklılaşmasına yol açar. Aynı kod tabanından farklı yayınlar üretilir; fark koda değil yapılandırmaya yazılır.
Bağımlılıklar maddesi, uygulamanın ihtiyaç duyduğu her şeyi açıkça bildirmesini ve sistemde bir şeyin kurulu olduğunu varsaymamasını ister. package-lock.json, requirements.txt ya da composer.lock gibi kilit dosyaları bu maddenin doğrudan karşılığıdır. Kilit dosyası varsa kurulum komutunu da ona göre seç:
# Kilit dosyasıyla birebir kurulum; uyuşmazlık varsa HATA verir
npm ci
# Yanlış: yeni sürümlere sessizce yükseltebilir
npm install
Yapılandırma maddesi en sık ihlal edilenidir ve testi basittir: kodunu şu anda herkese açık hale getirsen, gizli hiçbir bilgi sızar mıydı? Cevap "evet" ise yapılandırma koddadır. Doğru yer ortam değişkenleridir; çünkü dile ve işletim sistemine bağımsızdırlar ve yanlışlıkla depoya girmezler.
# Ortama göre değişen her şey burada, kodda değil
DATABASE_URL=postgres://uygulama:[email protected]:5432/uretim
REDIS_URL=redis://cache.firmaniz.com:6379/0
LOG_LEVEL=info
SMTP_HOST=smtp.firmaniz.com
Dikkat edilmesi gereken bir ayrım var: ortamdan gelmesi gereken şey ortama göre değişen şeydir. Ürün adı ya da yönlendirme tablosu gibi her ortamda aynı olan değerleri ortam değişkenine taşımak, gereksiz karmaşıklık üretir.
Destek Servisleri, Yapı-Yayın-Çalıştırma ve Süreçler#
Destek servisleri maddesi, veritabanı, önbellek, kuyruk ve e-posta sağlayıcısı gibi bileşenleri "takılıp çıkarılabilir kaynaklar" olarak görmeyi söyler. Uygulaman, veritabanının aynı makinede mi yoksa başka bir sunucuda mı olduğunu bilmemeli; tek bildiği bir bağlantı adresi olmalı. Bu sayede yerelde konteynerde çalışan Postgres ile üretimdeki yönetilen veritabanı arasında kod değişikliği gerekmez.
Yapı, yayın, çalıştırma maddesi üç aşamayı kesin biçimde ayırır. Yapı aşaması kodu çalıştırılabilir bir artefakta dönüştürür; yayın aşaması bu artefaktı belirli bir ortamın yapılandırmasıyla birleştirir; çalıştırma aşaması ise onu başlatır. Kritik kural şudur: çalışan bir yayının kodu değiştirilemez. Sunucuya bağlanıp dosyayı elle düzeltirsen, o düzeltme bir sonraki yayında kaybolur ve kimse ne olduğunu bilemez.
# Yapı: değişmez bir artefakt üret
docker build -t registry.firmaniz.com/api:1.4.2 .
# Yayın: artefakt + ortam yapılandırması
docker run -d --env-file /etc/uygulama/uretim.env \
--name api registry.firmaniz.com/api:1.4.2
# Çalıştırma sırasında kod DEĞİŞTİRİLMEZ
Süreçler maddesi durumsuzluğu şart koşar. Uygulama süreci bellekte ya da yerel diskte kalıcı bir şey tutmamalıdır; tutması gereken her şey veritabanına, önbelleğe ya da nesne depolamaya gitmelidir. Bu maddenin ihlali en çok iki yerde acıtır: oturum bilgisi süreç belleğinde tutulduğunda her yayında kullanıcılar çıkış yapar, kullanıcı yüklemeleri yerel diske yazıldığında ise ikinci kopya o dosyaları göremez. Konteynerde kalıcı veriyi doğru yönetmenin yollarını Docker volume ve veri yönetimi yazısında anlattım.
Port Bağlama, Eşzamanlılık ve Tek Kullanımlık Olma#
Port bağlama maddesi, uygulamanın kendi HTTP sunucusunu içermesini ve bir portu dinlemesini söyler. Uygulamanın hazır bir web sunucusunun içine modül olarak gömülmesi yerine, kendi başına çalışabilen bir süreç olması onu taşınabilir kılar. Dinlenecek port da yapılandırmadan gelmelidir:
# Uygulama portu ortamdan okur; sabit kodlanmaz
PORT=8080
Önünde bir ters vekil sunucu bulunur ve TLS sonlandırma, sıkıştırma, önbellekleme gibi işleri o üstlenir. Bu ayrımın Nginx ve Apache tarafındaki karşılıklarını merak ediyorsan Apache ve Nginx karşılaştırması yazısı iyi bir başlangıç.
Eşzamanlılık maddesi, yükü karşılamak için tek süreci büyütmek yerine süreç sayısını artırmayı önerir. Bu, yatay ölçeklenmenin ta kendisidir ve altıncı maddenin (durumsuzluk) doğrudan sonucudur: süreçler durum tutmuyorsa istediğin kadarını çalıştırabilirsin. İş türlerini ayrı süreç tipleri olarak düşünmek de bu maddenin parçasıdır: web istekleri bir tip, arka plan işleri başka bir tip, zamanlanmış görevler üçüncü bir tip.
services:
web:
image: registry.firmaniz.com/api:1.4.2
command: ["node", "server.js"]
deploy:
replicas: 4 # yükü süreç sayısıyla karşıla
worker:
image: registry.firmaniz.com/api:1.4.2 # aynı imaj, farklı komut
command: ["node", "worker.js"]
deploy:
replicas: 2
Tek kullanımlık olma maddesi, süreçlerin hızlı başlaması ve SIGTERM sinyalini alınca zarifçe kapanmasını ister. Bu, kesintisiz yayının ön koşuludur: kapanan süreç elindeki isteği bitirmeden ölürse her yayında bir avuç istek düşer. Zarif kapanışın nasıl kurulacağını adım adım sıfır kesintili deploy yazısında anlattım.
Ortam Eşliği, Loglar ve Yönetim Görevleri#
Ortam eşliği, geliştirme ile üretim arasındaki üç boşluğu kapatmayı hedefler: zaman boşluğu (kodun yazılmasıyla yayınlanması arasındaki süre), personel boşluğu (yazan ile çalıştıran kişinin farklı olması) ve araç boşluğu (yerelde SQLite, üretimde PostgreSQL kullanmak gibi). Üçüncüsü en sinsi olanıdır; yerelde çalışan bir sorgunun üretimde farklı davranması buradan gelir. Konteyner bu boşluğu kapatmanın en pratik yoludur, çünkü aynı imaj her ortamda çalışır.
Loglar maddesi belki de en kolay uygulanan ama en çok ihlal edilenidir: uygulama log dosyası yönetmemeli, döndürme yapmamalı, dosya yolu bilmemelidir. Tek yapması gereken, olayları standart çıktıya yazmaktır. Toplama, döndürme ve saklama işi çalışma ortamının sorumluluğudur.
# Doğru: uygulama stdout'a yazar, ortam toplar
docker logs --since 30m --tail 200 api
# Yapılandırılmış log yazıyorsan sorgulanabilir olur
docker logs api 2>&1 | grep '"level":"error"' | tail -20
Log dosyalarını konteyner içine yazmak, diskin sessizce dolmasının en yaygın sebebidir; belirtilerini ve çözümünü Docker diski doldurdu yazısında ele aldım. Log okuma ve hata ayıklama pratiği için konteyner loglarını okuma yazısına bakabilirsin.
Yönetim görevleri maddesi, veritabanı göçü ya da tek seferlik veri düzeltmesi gibi işlerin uygulamayla aynı ortamda ve aynı sürümle çalıştırılmasını söyler. Geliştiricinin kendi makinesinden üretim veritabanına bağlanıp betik çalıştırması bu maddenin klasik ihlalidir; kullanılan kütüphane sürümleri farklı olduğu için sonuç öngörülemez.
# Doğru: aynı imaj, aynı yapılandırma, tek seferlik komut
docker run --rm --env-file /etc/uygulama/uretim.env \
registry.firmaniz.com/api:1.4.2 npm run migrate
Docker ile Twelve-Factor'ı Uygulamak#
Konteynerler bu ilkeleri uygulamanın en doğal yolu haline geldi ama otomatik olarak uygulamıyorlar; yanlış yazılmış bir Dockerfile birçok faktörü tek başına ihlal eder. En sık karşılaştığım eşleşmeler şöyle:
- Yapılandırmayı imaja gömme.
.envdosyasınıCOPYile imaja koyarsan aynı imaj farklı ortamlarda kullanılamaz ve gizli bilgi imajın içinde dolaşır. Yapılandırma çalıştırma anında verilmelidir. - Sinyalin uygulamaya ulaşmasını sağla.
CMDkomutunu kabuk biçiminde yazarsanSIGTERMkabuğa gider, uygulamana ulaşmaz. Exec biçimini kullan:CMD ["node", "server.js"]. - Log dosyası değil, standart çıktı. Uygulamayı log dosyasına yazacak biçimde yapılandırma;
stdoutvestderryeterlidir. - Aynı imaj, farklı komut. Web ve işçi süreçleri için ayrı imaj üretme; tek imajı farklı komutlarla çalıştır.
- Kalıcı veriyi birime taşı. Yükleme dizinleri ve veritabanı dosyaları konteyner katmanında değil, birimde durmalıdır.
Bu maddelerin ayrıntılı Dockerfile karşılıkları için Dockerfile en iyi pratikler yazısına, çok servisli kurulumun yapılandırılması için Docker Compose kullanımı yazısına bakabilirsin.
Sık Yapılan Hatalar ve Modern Eleştiriler#
Metodoloji yaklaşık on beş yıl önce yazıldı ve bazı maddeleri bugün tartışılıyor. En yaygın eleştiri, destek servislerinin fazla basitleştirilmesidir: "veritabanını takılıp çıkarılabilir bir kaynak gibi düşün" demek kulağa hoş gelir ama gerçekte veritabanı geçişi kolay bir işlem değildir ve bu ilke bazı ekipleri gereksiz soyutlama katmanları yazmaya itmiştir. Benzer biçimde, üçüncü madde katı yorumlandığında yüzlerce ortam değişkeni ortaya çıkar; bunları bir yapılandırma dosyasında toplayıp yalnızca gizli olanları ortamdan almak çoğu ekip için daha yönetilebilirdir.
Sahada en sık gördüğüm gerçek ihlaller ise şunlar. Oturumu süreç belleğinde tutmak: tek kopya çalışırken sorun çıkmaz, ikinci kopyayı eklediğin gün kullanıcılar rastgele çıkış yapmaya başlar. Yüklenen dosyaları yerel diske yazmak: yayın yapılana kadar her şey normal görünür, sonra dosyalar kaybolur. Yapılandırmayı ortama göre kodda dallandırmak: if (ortam === "uretim") yazan her satır, ortam eşliği maddesinin ihlalidir ve yalnızca üretimde ortaya çıkan hataların kaynağıdır. Göçü uygulama açılışında çalıştırmak: tek kopyada işe yarar, birden fazla kopya aynı anda açıldığında aynı göçü paralel çalıştırmaya kalkarlar.
Metodolojiyi bir sertifikasyon listesi gibi değil, bir tanı aracı gibi kullan. Uygulaman ölçeklenmiyorsa, taşınamıyorsa ya da her yayında bir şeyler kayboluyorsa, sebebin ihlal edilmiş bir maddede olma ihtimali yüksektir.
Sıkça Sorulan Sorular#
Twelve-Factor App hâlâ geçerli mi#
Evet, özellikle konteynerleştirilmiş uygulamalar için. Docker ve orkestrasyon araçları tam olarak bu varsayımlar üzerine kuruludur: durumsuz süreç, ortamdan gelen yapılandırma, standart çıktıya yazılan log. Bazı maddeler bugün daha esnek yorumlanıyor ama temel fikir — uygulamanın çalıştığı ortam hakkında az varsayımda bulunması — hiç eskimedi.
Monolit uygulamalar için de uygun mu#
Kesinlikle. Metodoloji mikroservis mimarisi gerektirmez; tek parça bir uygulama da durumsuz olabilir, yapılandırmasını ortamdan alabilir ve loglarını standart çıktıya yazabilir. Aslında bu maddeleri uygulayan iyi yapılandırılmış bir monolit, ihlal eden bir mikroservis kümesinden çok daha kolay işletilir.
Tüm yapılandırmayı ortam değişkenine mi taşımalıyım#
Ortama göre değişen her şeyi ortamdan al, özellikle de gizli bilgileri. Her ortamda aynı olan sabitleri ise kodda ya da yapılandırma dosyasında tutmakta sakınca yoktur. Değişken sayısı yönetilemez hale geliyorsa, gizli olmayanları bir yapılandırma dosyasında toplayıp yalnızca parola ve anahtarları ortam değişkeni olarak vermek pratik bir dengedir.
Durumsuz uygulama nedir, oturumları nerede tutmalıyım#
Durumsuz uygulama, iki isteğin farklı kopyalara düşmesi durumunda da aynı sonucu üreten uygulamadır; yani süreç belleğinde ya da yerel diskte kalıcı bilgi tutmaz. Oturumları paylaşılan bir depoda (Redis ya da veritabanı) veya sunucuda saklanmayan imzalı çerezlerde tut. Bu değişiklik, yatay ölçeklenmenin ve kesintisiz yayının ön koşuludur.
Log dosyası yazmak gerçekten yanlış mı#
Uygulamanın kendi log dosyasını yönetmesi, döndürmesi ve temizlemesi yanlıştır; çünkü bu iş ortamın sorumluluğudur ve uygulama ortamı hakkında varsayım yapmış olur. Uygulama olayları standart çıktıya yazsın; toplama, döndürme ve saklama işini konteyner çalışma zamanı ya da bir log toplayıcı üstlensin. Bu ayrım, konteynerde diskin sessizce dolmasını da önler.
Veritabanı göçünü nasıl çalıştırmalıyım#
Uygulamanın açılış yolundan çıkar ve tek seferlik bir yönetim görevi olarak, uygulamayla aynı imaj ve aynı yapılandırma kullanılarak çalıştır. Birden fazla kopya çalışan bir sistemde göçü açılışta yaptırırsan, kopyalar aynı göçü paralel çalıştırmaya kalkar. Yayın hattında ayrı bir adım olarak tanımlamak hem güvenli hem izlenebilirdir.
Kapanış#
Twelve-Factor'ı bir listeyi tik atarak geçme egzersizi olarak değil, uygulamanı ortamdan koparma rehberi olarak düşün. Aklında dört madde kalsın: yapılandırmayı ortamdan al, süreçlerini durumsuz tut, logları standart çıktıya yaz ve kapanış sinyaline zarifçe cevap ver. Bu dördü sağlandığında uygulaman hem yatay ölçeklenir hem de kesintisiz yayınlanabilir hale gelir; kalan sekiz madde çoğunlukla bunların doğal sonucudur.
Bu ilkeleri uygulayacağın esnek bir ortama ihtiyacın olursa tam root erişimli VDS ve bulut sunucu paketlerimizde kendi konteyner yığınını kurabilirsin. Uygulaman paylaşımlı bir ortamda çalışıyorsa web hosting paketlerimiz de ortam değişkeni ve zamanlanmış görev yönetimini destekler; kurulum ve bakım yükünü devretmek istersen sunucu yönetimi hizmetimize göz atabilirsin.