Containerda çalışan uygulama loglarını ne yapsak?

Konuya girmeden önce, şu “containerized application” kavramını Türkçeleştirebilenler varsa, geliştiriciler olarak yardıma muhtaç durumdayız, duyurulur :)

Fotoğraf, Markus Spiske tarafından çekilmiş, Unsplash’den indirilmiştir.

Cloud dünyası aldı başını gidiyor, malumumuz. Geliştirdiğimiz uygulamalar da bu dünyanın doğasına uygun bir biçimde evrilmeye başladı. Geleneksel, her parçanın tek bir merkezden yürütüldüğü uygulamalar; yerlerini yavaş yavaş küçük parçalara bölünmüş ve her biri kendinden sorumlu mikro — hatta nano — uygulamalara bırakmaya başladı. Bununla birlikte de yazılım kültürü ve teknolojiler, jargonlar değişmeye başladı haliyle. Bu adaptasyon sürecinde bazı tekniklerimiz hızla kaybolup gözden uzaklaşırken (Memory cache kullanmayalı o kadar zaman oldu ki…), bazıları uygulamanın tipinden (monolitik/mikro) bağımsız olarak varlığını sürdürüyor. Bunlardan birincisi de — birincilik, tamamen kişisel gözlemim ve yorumum — Logging tekniklerimiz.

Ne yapıyoruz?

Önce bir log kütüphanesi seçiyoruz, .NET ekosisteminden bir iki örnek vermek gerekirse NLog, SeriLog, Log4Net, vb. Bu kütüphaneler için gerekli konfigürasyonları tanımlıyoruz, target olarak ifade edilen ve log’un nereye/nasıl yazılacağını belirttiğimiz şablonlar oluşturuyoruz. Sonrasında ise uygulamada log için gerekli olan akışı kodluyor ve gerek gördüğümüz yerlerde log sınıfı üzerinden bu akışı canlandırıyoruz.

Yani…

Logları Elasticsearch üzerinden tutmaya karar veriyoruz. Yanına bir Logstash kuruyoruz. NLog’a Network tipinde bir target tanımlıyoruz, bağlantı cümleciği olarak tcp:// ile başlayan ve Logstash’a referans eden bir url giriyoruz. Bu target’a da layout olarak bir JsonLayout tanımı giriyoruz (log içerisinde hangi alanlar yer alacak, bu alanların varsayılan değerleri nelerdir, hangi alanlar uygulama tarafında doldurulacak, vs…). Son olarak uygulamanın içerisinden _logger.LogInformation("Buraya log mesajı gelecek!") diyerek log atma eylemimizi tamamlıyoruz. Aşağıda örnek bir NLog konfigurasyonu görüyoruz:

Buraya kadar her şey normal, tam da olması gerektiği gibi aslında. Bu uygulamayı da dockerize ediyoruz ve bir orkestrasyon aracı üzerinde koşturuyoruz. Uygulamamız cloud-native hale geldi gibi görünüyor.

Gibi görünüyor, çünkü…

12Factor kavramı ile karşılaştıysanız muhtemelen siz de cloud-native uygulamalar geliştiriyorsunuz. Bu kavram altında bahsedilen 12 teori, bir uygulamanın cloud-native olarak tasarlanması için gerekliliklerden bahsediyor. Bu teorilerden biri de Logs.

Özetle diyor ki; uygulamalar, kendilerine ait logların nerede, hangi süreçlerden geçerek saklanması gerektiğiyle ilgili kaygılar taşımamalıdır. Çünkü uygulamanın sorumluluğu, “yorumlanabilir/değerlendirilebilir” kayıt üretmektir.

Bu teoriyi bir kenara bırakalım ve şu sorulara cevap verelim:

  1. Uygulamanızı en alt seviyede başka bir uygulamaya (log sistemine) bağımlı tutmak ne kadar sağlıklı?
  2. Günün birinde ilgili logları X noktasından Y noktasına geçirmek istediğinizde uygulamanızı değiştirmek zorunda mısınız?
  3. Log sistemine erişemediğiniz için uygulamanızın cevap verememesi (mimari bir hatadır, kabul edilemez) normal mi?
  4. Log’un akmadığını düşündüğünüz bir noktada konsantrasyonu nereye yönlendirirsiniz (uygulama mı logu iletemiyor, log sistemi mi uygulama loglarını alamıyor)?

Uygulamanızı mikro seviyede ve bağımlılıkları zayıf bir şekilde tasarlarken, farklı bir uygulamaya — ve mimarisine hakim olmadığınız — bu kadar bağımlı olmak bana kalırsa çok sağlıklı değil. Logların akış yönünü değiştirmek için uygulamayı değiştirmek ise zaman ve efor olarak oldukça maliyetli diye düşünüyorum.

Ne yapabiliriz?

12 faktörün de yönlendirdiği gibi, oldukça basit bir yordam: Log’u ekrana çıktı olarak iletin (stdout)!

Üstelik bu iş oldukça maliyetsiz. Yukarıdaki konfigürasyon dosyasında 1. satırı <target xsi:type="Console" name="NeIsimVerirsekO"> olarak değiştirmek yeterli!

Böylece uygulamanın tek sorumluluğu, şu bahsettiğimiz “yorumlanabilir/değerlendirilebilir” kayıtları ekrana çıktı olarak iletmek olacak. Ayrıca geliştirme aşamasında, logları daha hızlı bir şekilde görüp, daha hızlı aksiyon alabileceğiz.

Sonrası…

Tecrübe sahibi olduğunuz ve “log collect” eden ürünleri kurmak ve ekrana çıktı olarak basılan logları okuyup, yorumlayıp bir merkeze kaydedilmesini sağlamak. Örneğin;

Elastic’in FileBeat adlı ürünü, container loglarını yakalayabilme kapasitesine sahip bir ürün. Ayrıca yakaladığı logları diğer Elastic ürünlerine de direkt olarak iletebiliyor (Logstash’ iletmek ya da direkt olarak Elasticsearch’e yazmak gibi). Kibana ile de bu logları görselleştirebiliyorsanız, container olarak koşan her uygulama için ayrı bir log yapısı kurmaya gerek kalmadan bütün bu operasyonu tek bir merkezden yönetebilirsiniz.

Yukarıdaki örneği canlı canlı denemek isterseniz aşağıdaki Github reposundan kaynağı indirebilirsiniz:

Örneği çalıştırdığınızda ve bazı örnek loglar oluşturduğunuzda, çok kısa bir süre sonra FileBeat’in bu logları yakaladığını ve Elasticsearch’e yazdığını göreceksiniz. Kibana adresine gittiğinizde ise aşağıdaki gibi bir ekran sizi bekliyor olacak:

Sonuç

Örnekte yer alan Elasticsearch, Kibana, FileBeat ürünlerine ait konfigürasyonların “Production” ortamı için uygun olmadığını belirtmem gerek. Bir iki ufak ayar gerekli, ama genel davranış bu şekilde olacaktır. Ayrıca 12factor.net adresindeki diğer yaklaşımları da inceleyip, içselleştirmekte fayda var diye düşünüyorum. Bir sonraki yazıya dek, loglarınızın kara deliğe uğramaması dileğiyle :)