Docker container loglarının diski doldurmasını önlemenin yolu, log sürücüsüne bir boyut sınırı vermektir. Varsayılan json-file sürücüsü hiçbir rotasyon yapmaz; çok çıktı üreten bir container zamanla gigabaytlarca log biriktirebilir. Çözüm ya /etc/docker/daemon.json dosyasında json-file için max-size ve max-file seçeneklerini tanımlamak ya da Docker belgelerinin önerdiği gibi varsayılan sürücüyü baştan rotasyon yapan local sürücüsüne çevirmektir. Ayar daemon yeniden başlatıldıktan sonra yalnızca yeni oluşturulan container'lara uygulanır.

Bu yazıda Docker'ın log sürücüsü (logging driver) kavramını, json-file ile local arasındaki farkı, seçeneklerin daemon, container ve Compose düzeyinde nasıl verildiğini, journald sürücüsünü, uzak log sürücüleriyle birlikte çalışan dual logging önbelleğini ve log teslim modlarını anlatıyoruz. Örnekler kendi VPS'inde Docker çalıştıran okur düşünülerek hazırlandı.

Docker logları nereden gelir, nereye yazılır?

Docker logları nereden gelir, nereye yazılır?

docker logs komutu bir container'ın ana sürecinin standart çıktısını (STDOUT) ve standart hata akışını (STDERR) gösterir. Yani Docker'ın "log" dediği şey, uygulamanın terminale yazacağı satırlardır. Container içinde bir dosyaya yazılan log satırları bu akışa girmez ve docker logs ile görünmez. Belgeler bu durumu iki örnekle açıklar: resmî nginx imajı /var/log/nginx/access.log dosyasını /dev/stdout'a, error.log dosyasını /dev/stderr'e sembolik bağla yönlendirir; resmî httpd imajı ise Apache'nin yapılandırmasını değiştirip çıktıyı doğrudan /proc/self/fd/1 ve /proc/self/fd/2 yollarına yazdırır. Kendi imajınızda da logları dosya yerine standart akışlara göndermek, aşağıdaki tüm sürücü ayarlarının işe yaraması için ön koşuldur.

Bu akışları yakalayıp bir yere kaydeden bileşene log sürücüsü denir. Her Docker daemon'ın bir varsayılan sürücüsü vardır ve aksini belirtmediğiniz her container onu kullanır. Varsayılan sürücü json-file'dır: her satırı kaynağıyla (stdout veya stderr) ve zaman damgasıyla birlikte JSON olarak yazar, her container için ayrı dosya tutar. Belgelerdeki örnek kayıt şöyledir:

json
{
  "log": "Log line is here\n",
  "stream": "stdout",
  "time": "2019-01-01T11:11:11.111111111Z"
}

Disk neden doluyor: varsayılan ayarda rotasyon yok

json-file sürücüsünün max-size seçeneğinin varsayılan değeri -1, yani sınırsızdır. Bir dosya hiçbir zaman döndürülmez ve container yaşadığı sürece büyümeye devam eder. Belgeler Docker'ın bu davranışı neden koruduğunu da açıklar: eski Docker sürümleriyle geriye dönük uyumluluk ve Docker'ın Kubernetes için çalışma zamanı olarak kullanıldığı durumlar. Bunların dışındaki kullanımlar için önerilen sürücü local'dir, çünkü varsayılan olarak rotasyon yapar ve daha verimli bir dosya biçimi kullanır.

Log dosyalarının ne kadar büyüyebileceğini sınırlayan tek şey, Docker veri dizininin bulunduğu diskin kapasitesidir. Sürekli istek alan bir web uygulaması, ayrıntılı hata ayıklama çıktısı basan bir arka plan işçisi veya döngüye girip aynı hatayı saniyede yüzlerce kez yazan bir servis, sınırsız log ayarıyla sunucunun diskini fark edilmeden doldurabilir. Belgeler bu durumun disk alanının tükenmesine yol açabileceğini açıkça yazar. Bu yüzden log sınırı, bir sunucuya Docker kurduktan sonra yapılacak ilk ayarlardan biridir. Kullanılmayan imaj ve container'ların kapladığı alanı temizlemek ayrı bir konudur; onu Docker prune ile disk temizleme yazısında ele alıyoruz.

daemon.json ile varsayılan log sürücüsünü ayarlama

Sunucudaki tüm yeni container'lar için geçerli bir varsayılan belirlemek isterseniz daemon.json dosyasında log-driver anahtarını kullanırsınız. Belgelerin önerdiği yol varsayılanı local yapmaktır:

json
{
  "log-driver": "local"
}

json-file biçiminde kalmak istiyorsanız (örneğin JSON satırlarını bekleyen bir araç kullanıyorsanız) sürücüyü değiştirmeden log-opts nesnesiyle rotasyonu açabilirsiniz. Aşağıdaki ayarda her dosya en fazla 10 MB olur ve en fazla 3 dosya tutulur; böylece container başına log alanı yaklaşık 30 MB ile sınırlanır:

json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Burada iki ayrıntı sık gözden kaçar. Birincisi, log-opts içindeki tüm değerler metin olarak yazılmalıdır; max-file gibi sayısal ya da compress gibi mantıksal değerler de tırnak içine alınır. İkincisi, json-file için max-file yalnızca max-size da tanımlıysa etkilidir; tek başına max-file yazmak rotasyonu açmaz. Dosyayı kaydettikten sonra systemd kullanan bir Linux sunucuda daemon'ı yeniden başlatırsınız:

bash
sudo systemctl restart docker

Geçerli varsayılan sürücüyü görmek için belgelerdeki şu komut kullanılır; sürücü değiştirilmemiş bir kurulumda çıktı json-file olur:

bash
docker info --format '{{.LoggingDriver}}'

json-file

daemon.json dosyasının genel yapısı, diğer anahtarları ve dosya bozuk olduğunda daemon'ın açılmaması gibi konular daemon.json yapılandırma yazısında anlatılıyor.

json-file ve local karşılaştırması

json-file ve local karşılaştırması: max-size varsayılanı, max-file varsayılanı, Sıkıştırma (compress), Dosya biçimi

İki sürücü de logları yerel diskte tutar ve ikisi de docker logs ile okunabilir. Fark varsayılanlarda ve dosya biçimindedir. local sürücüsü container başına varsayılan olarak 100 MB log saklar: bu değer 20 MB'lık dosya boyutu ile 5 dosya sayısının çarpımından gelir ve döndürülen dosyalar otomatik sıkıştırılır.

Özellikjson-filelocal
max-size varsayılanı-1 (sınırsız)20m
max-file varsayılanı1 (yalnızca max-size ile etkili)5
Sıkıştırma (compress)Varsayılan kapalı (false)Varsayılan açık
Dosya biçimiHer satır bir JSON nesnesiDüşük ek yük için özel biçim
Ek alan seçeneklerilabels, labels-regex, env, env-regexEngine 29.5.0 ile label, label-regex, env, env-regex ve tag eklendi
Belgelerin önerisiGeriye dönük uyumluluk ve KubernetesDiğer tüm kullanımlar

Engine 29 sürüm notları, 29.5.0 ile local sürücüsünün özel öznitelikleri desteklemeye başladığını ve label, label-regex, env, env-regex ve tag seçeneklerinin eklendiğini yazar. Daha eski bir Engine kullanıyorsanız bu seçenekleri local ile kullanmadan önce sürümünüzü kontrol edin.

Sıkıştırmanın bir bedeli vardır: belgelerin sınırlamalar bölümüne göre döndürülmüş dosyalardaki eski kayıtları okumak için Docker'ın bu dosyaları açması gerekir. Bu, okuma sürerken geçici olarak ek disk kullanımına ve daha yüksek işlemci kullanımına yol açar. Disk kazancı çoğu sunucu için bu maliyetten daha önemlidir, ancak çok eski logları sık sık okuyorsanız bu davranışı bilmekte yarar var.

Tek bir container için sürücü ve seçenek verme

Varsayılanı değiştirmeden belirli bir container'a farklı ayar verebilirsiniz. docker run ve docker container create komutları --log-driver bayrağını ve tekrar edilebilen --log-opt AD=DEĞER bayrağını kabul eder. Container varsayılan sürücüyü kullansa bile kendi seçeneklerini taşıyabilir. Aşağıdaki iki komut belgelerden uyarlanmıştır: ilki local sürücüsünü 10 MB'lık 3 dosyayla, ikincisi varsayılan sürücüyü yalnızca seçeneklerle sınırlar:

bash
docker run -it --log-driver local --log-opt max-size=10m --log-opt max-file=3 alpine ash

docker run -it --log-opt max-size=10m --log-opt max-file=3 alpine ash

Hiç log tutulmasını istemediğiniz kısa ömürlü işler için none sürücüsü vardır; bu durumda docker logs hiçbir çıktı döndürmez. Çalışan bir container'ın hangi sürücüyle oluşturulduğunu görmek için belgelerdeki inspect şablonu kullanılır:

bash
docker run -it --log-driver none alpine ash

docker inspect -f '{{.HostConfig.LogConfig.Type}}' <CONTAINER>

json-file

Compose dosyasında logging anahtarı

Compose ile çalışıyorsanız aynı ayarları servis düzeyindeki logging anahtarıyla verirsiniz: driver sürücünün adını, options ise sürücüye özel anahtar-değer çiftlerini alır. Compose referansı varsayılan ve kullanılabilir değerlerin platforma bağlı olduğunu belirtir. Aşağıdaki dosyada web servisi local sürücüsünü, işçi servisi ise sıkıştırma açık json-file sürücüsünü kullanıyor:

yaml
services:
  web:
    image: nginx:alpine
    ports:
      - "127.0.0.1:8080:80"
    logging:
      driver: local
      options:
        max-size: "10m"
        max-file: "3"

  worker:
    image: alpine
    command: ["ping", "127.0.0.1"]
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"

Değerleri burada da tırnak içinde yazmak, daemon.json için belgelerin istediği metin biçimiyle aynı alışkanlığı korur. Sunucudaki tüm projeler için ortak bir sınır istiyorsanız her Compose dosyasına tek tek yazmak yerine daemon varsayılanını değiştirmek daha az hata bırakır; servis düzeyindeki logging ise belirli bir servise daha geniş ya da daha dar sınır vermek için uygundur. Compose'un temel kullanımı için Docker Compose rehberi yazısına bakabilirsiniz.

journald sürücüsü: logları systemd günlüğüne göndermek

Sunucunuzda zaten systemd günlüğünü (journal) merkezi log kaynağı olarak kullanıyorsanız journald sürücüsü container loglarını oraya yazar. Kayıtlar journalctl komutuyla, journal API'siyle ya da docker logs ile okunabilir. Sürücü her mesajla birlikte şu meta verileri saklar: 12 karaktere kısaltılmış CONTAINER_ID, 64 karakterlik CONTAINER_ID_FULL, CONTAINER_NAME, CONTAINER_TAG ve SYSLOG_IDENTIFIER, uzun satırlar için CONTAINER_PARTIAL_MESSAGE ve IMAGE_NAME.

Belirli bir container'ın kayıtlarını süzmek için alan adını filtre olarak verirsiniz. -b son açılıştan beri olan kayıtları, -o json JSON çıktısını getirir. TTY açık bir container'da çıktıda [10B blob data] görürseniz sebebi satır sonlarına eklenen \r karakteridir; --all eklediğinizde journalctl bu satırları da gösterir:

bash
sudo journalctl CONTAINER_NAME=webserver
sudo journalctl -b CONTAINER_NAME=webserver
sudo journalctl -o json CONTAINER_NAME=webserver
sudo journalctl -b CONTAINER_NAME=webserver --all

Compose'da tag, labels ve env seçenekleri birlikte kullanılabilir. tag değeri CONTAINER_TAG ve SYSLOG_IDENTIFIER alanlarını belirler ve {{.Name}}, {{.ImageName}}, {{.ID}} gibi şablon işaretlerini kabul eder. labels ve env ise container'da tanımlıysa mesaja eklenecek etiket ve ortam değişkeni adlarını listeler; ikisi aynı anahtarı içerirse env değeri kazanır:

yaml
services:
  api:
    image: example/api
    environment:
      TEST: "false"
    labels:
      location: west
    logging:
      driver: journald
      options:
        tag: "{{.ImageName}}/{{.Name}}"
        labels: location
        env: TEST

Bir ayrıntıya dikkat edin: CONTAINER_NAME alanı ve {{.Name}} şablonu container başlatıldığı andaki adı kullanır. Container'ı sonradan docker rename ile yeniden adlandırırsanız journal kayıtları eski adla gelmeye devam eder. Rotasyon ve saklama süresi bu durumda Docker'ın değil journald'ın kendi ayarlarına bağlıdır. journalctl kullanımının genel ayrıntıları için Linux loglarını okuma yazısına bakabilirsiniz.

Uzak log sürücüleri ve dual logging önbelleği

Uzak log sürücüleri ve dual logging önbelleği: cache-disabled, cache-max-size, cache-max-file, cache-compress

Docker yerel sürücülerin yanında logları başka sistemlere gönderen sürücülerle gelir: syslog, gelf (Graylog veya Logstash gibi uç noktalar), fluentd, awslogs, splunk, gcplogs ve yalnızca Windows'ta çalışan etwlogs. syslog, journald ve fluentd için ilgili servisin sunucuda çalışıyor olması gerekir. Ayrıca log sürücüsü eklentileri (plugin) de kullanılabilir.

Logları okuma desteği olmayan bir sürücü seçtiğinizde Docker Engine, docker logs komutunun yine çalışabilmesi için local sürücüsünü önbellek olarak kullanır. Buna dual logging denir ve bu durumda otomatik açılır. Önbellek varsayılan olarak container başına sıkıştırma öncesi 20 MB'lık en fazla 5 dosya tutar. Ayarları local sürücüsünün seçenekleriyle aynıdır, yalnızca başlarına cache- eklenir:

SeçenekVarsayılanAnlamı
cache-disabled"false"Yerel önbelleği kapatır
cache-max-size"20m"Önbellek dosyası döndürülmeden önceki en büyük boyut
cache-max-file"5"Tutulacak en fazla önbellek dosyası
cache-compress"true"Döndürülen dosyaların sıkıştırılması

Logları yalnızca uzak sistemden okuyorsanız ve hata ayıklama için docker logs komutuna ihtiyacınız yoksa cache-disabled ile disk alanı kazanabilirsiniz. Önbellek kapalıyken docker logs çalıştırırsanız daemon "configured logging driver does not support reading" hatası döndürür. local, json-file ve journald gibi okumayı destekleyen sürücülerde dual logging devreye girmez, dolayısıyla bu seçenek onlarda etkisizdir. Belgeler üç sınırlama da sayar: uzak sürücü ağ sorunu yaşarsa önbelleğe yazma yapılmaz; önbelleğe yazma herhangi bir sebeple başarısız olursa (disk dolu, yazma izni kaldırılmış) bu durum daemon loguna düşer ve yeniden denenmez; varsayılan ayarda container'ın çıktısını yavaş dosya yazımı yüzünden bloke etmemek için halka tampon kullanıldığından bazı kayıtlar önbellekten kaybolabilir.

Teslim modu: blocking ve non-blocking

Docker, mesajları container'dan sürücüye iki şekilde iletir. Varsayılan blocking modda teslim doğrudandır ve sürücü yetişemezse uygulamanın yazma işlemi bekler. non-blocking modda mesajlar container başına bir ara tampona alınır ve sürücü oradan tüketir. Belgeler STDOUT veya STDERR akışı bloke olduğunda uygulamaların beklenmedik şekillerde çökebileceğini, bu yüzden non-blocking modun uygulamayı log baskısından koruduğunu anlatır. Bedeli şudur: tampon dolduğunda yeni mesajlar kuyruğa alınmaz, yani kaybolur. Belgelere göre mesaj kaybetmek çoğu zaman uygulamanın log yazarken bloke olmasına tercih edilir.

Tampon boyutunu max-buffer-size belirler, varsayılanı 1m yani 1 milyon bayttır. Aşağıdaki örnek non-blocking modu 4 MB tamponla açar:

bash
docker run -it --log-opt mode=non-blocking --log-opt max-buffer-size=4m alpine ping 127.0.0.1

Sık yapılan hatalar

  • Sayıları tırnaksız yazmak: "max-file": 3 yazmak yerine "max-file": "3" yazın. log-opts değerleri metin olmalıdır.
  • Yalnızca max-file vermek: json-file sürücüsünde max-file, max-size olmadan etkisizdir.
  • Eski container'ların değişmesini beklemek: Yeni varsayılan yalnızca daemon yeniden başlatıldıktan sonra oluşturulan container'lara uygulanır; eskiler yeniden oluşturulmalıdır.
  • Log dosyalarını elle silmek: Belgeler bu dosyalara harici araçlarla dokunmamanızı söyler; sınırı sürücü seçenekleriyle koyun.
  • Uygulamanın dosyaya log yazması: Container içinde dosyaya yazılan satırlar docker logs ile görünmez ve log sürücüsü sınırlarına da tabi değildir; çıktıyı STDOUT ve STDERR'e yönlendirin.
  • cache-disabled ile docker logs beklemek: Uzak sürücüde önbelleği kapattıysanız docker logs hata verir.

Sık Sorulan Sorular

max-size ve max-file için hangi değerleri seçmeliyim?

Belgeler tek bir doğru değer vermez; örneklerinde max-size için 10m, max-file için 3 kullanır. Container başına toplam alan yaklaşık bu iki değerin çarpımıdır. Sunucudaki container sayısını bu değerle çarparak logların en kötü durumda ne kadar yer kaplayacağını hesaplayabilir, diskinize göre ayarlayabilirsiniz. local sürücüsünün varsayılanı 20 MB x 5 dosyadır.

Sürücüyü değiştirince eski loglar ne olur?

Mevcut container'lar oluşturuldukları sürücüyle çalışmaya devam eder. Belgelere göre bir container'ın log ayarını güncellemenin tek yolu onu istenen seçeneklerle yeniden oluşturmaktır; yeni container'ın çıktısı yeni sürücüye gider. Eski container'dan ihtiyaç duyduğunuz kayıtları, yeniden oluşturmadan önce docker logs ile okuyup bir yere kaydedin.

Docker'ın kendi daemon logları da bu ayardan etkilenir mi?

Hayır. Log sürücüsü container'ların çıktısıyla ilgilidir. Daemon'ın kendi loglarını okumak ve daemon hatalarını çözmek Docker daemon bağlantı hatası yazısının konusudur.

Swarm servislerinin loglarını nasıl görürüm?

Belgelere göre docker service logs komutu bir servise ait tüm container'ların loglarını gösterir. Servisler Swarm modunda çalışır; kurulum için Docker Swarm kurulumu yazısına bakabilirsiniz.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Docker öğrenme rehberi · daemon.json yapılandırma · Docker prune ile disk temizleme · Docker daemon hataları · Linux loglarını okuma · Docker Compose rehberi