Docker & DevOps

    Uygulama Sağlık Kontrolü ve Otomatik Yeniden Başlatma

    Liveness ve readiness ayrımı, Docker healthcheck ve systemd ile otomatik kurtarma kurulumu.

    10 dk okuma Güncellendi: 25 Ağustos 2026

    Sunucu ayakta, süreç listesinde uygulaman görünüyor, port dinleniyor — ama site hâlâ hata veriyor. Bu, gece yarısı en çok can sıkan senaryodur, çünkü klasik izleme araçlarına göre her şey yolundadır. Uygulama sağlık kontrolü tam olarak bu boşluğu kapatmak için vardır: "süreç var mı" sorusunu değil, "uygulama gerçekten iş yapabiliyor mu" sorusunu sorar ve cevap hayırsa müdahaleyi otomatik başlatır.

    Bu rehberde işe yarar bir sağlık kontrolünün nasıl tasarlandığını anlatacağım. Liveness ile readiness arasındaki farkı, sağlık ucunun neyi kontrol etmesi ve neyi kesinlikle kontrol etmemesi gerektiğini, Docker HEALTHCHECK ile systemd Restart yapılandırmasının nasıl kurulduğunu ve ikisinin nerede birleştiğini göreceğiz. Sonda da en pahalı tuzakları — özellikle yeniden başlatma döngüsüne giren ve gerçek arızayı maskeleyen kurulumları — tek tek konuşacağız.

    İyi Bir Sağlık Kontrolü Neyi Ölçer#

    Sağlık ucu yazarken düşülen ilk hata, onu bir izleme paneline çevirmektir. /health ucun veritabanına, Redis'e, ödeme sağlayıcısına ve S3'e tek tek bağlanıp hepsinin durumunu döndürüyorsa, bu bir sağlık kontrolü değil, bir arıza çoğaltıcıdır: ödeme sağlayıcısının API'si bir dakikalığına yavaşladığında senin uygulaman "sağlıksız" damgası yer ve yeniden başlatılır. Oysa uygulamanda hiçbir sorun yoktur.

    Doğru yaklaşım iki farklı soruyu iki farklı uca ayırmaktır:

    • Liveness (canlılık): "Bu süreç kurtarılabilir durumda mı, yoksa öldürülmesi mi gerekiyor?" Yanıtı yalnızca sürecin kendi iç durumuna bakar. Olay döngüsü kilitlendiyse, bellek onarılamaz şekilde şiştiyse, iş parçacıkları kilitlendiyse hayır der. Başarısız olduğunda yeniden başlatma tetiklenir.
    • Readiness (hazır olma): "Bu örneğe şu an trafik gönderebilir miyim?" Yanıtı bağımlılıklara bakar: veritabanı bağlantı havuzu açıldı mı, önbellek ısındı mı, göç (migration) bitti mi. Başarısız olduğunda trafik kesilir ama süreç öldürülmez.

    Ayrım kritiktir: veritabanı bir dakika erişilemez olduğunda uygulamanı yeniden başlatmak hiçbir şeyi düzeltmez, sadece açık bağlantıları da kaybettirir. Doğru davranış trafiği kesip beklemek, veritabanı dönünce yeniden trafiğe açılmaktır.

    Neyi kontrol ederBaşarısızlıkta ne olurTipik süre
    /healthz (liveness)Süreç yanıt veriyor muSüreç yeniden başlatılır1 sn altı
    /readyz (readiness)Bağımlılıklar hazır mıTrafik kesilir1–3 sn
    /startupİlk açılış tamamlandı mıBekleme süresi uzatılır10–60 sn

    Küçük ama önemli bir tasarım kuralı: liveness ucu kimlik doğrulama istememeli, veritabanına gitmemeli ve kendi içinde ağır hesap yapmamalıdır. Sadece "ben buradayım ve istek işleyebiliyorum" demelidir.

    # Liveness: hiçbir dış bağımlılığa dokunmaz
    @app.get("/healthz")
    def healthz():
        return {"status": "ok"}, 200
    
    # Readiness: bağımlılıkları kısa zaman aşımıyla yoklar
    @app.get("/readyz")
    def readyz():
        try:
            db.execute("SELECT 1", timeout=2)
            cache.ping(timeout=1)
            return {"status": "ready"}, 200
        except Exception as e:
            # 503 döndür ki yük dengeleyici bu örneği havuzdan çıkarsın
            return {"status": "degraded", "detail": str(e)}, 503
    

    Docker HEALTHCHECK ile Konteyner Sağlığı#

    Docker, konteynerin içinde periyodik bir komut çalıştırıp çıkış koduna bakar: 0 sağlıklı, 1 sağlıksız. Bunu Dockerfile içine gömebilirsin:

    FROM node:20-alpine
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci --omit=dev
    COPY . .
    EXPOSE 3000
    
    # Sağlık kontrolü: 30 sn'de bir dene, 3 sn'de cevap gelmezse başarısız say,
    # ilk 20 sn'yi açılış payı olarak geç, üst üste 3 başarısızlıkta 'unhealthy' işaretle
    HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
      CMD wget -q -O /dev/null http://127.0.0.1:3000/healthz || exit 1
    
    CMD ["node", "server.js"]
    

    Compose tarafında aynı ayarı servis bazında da tanımlayabilir, üstelik servisler arası bağımlılığa bağlayabilirsin:

    services:
      api:
        build: .
        healthcheck:
          test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:3000/healthz"]
          interval: 30s
          timeout: 3s
          retries: 3
          start_period: 20s
        depends_on:
          db:
            condition: service_healthy   # db sağlıklı olmadan api başlamasın
        restart: unless-stopped
    
      db:
        image: postgres:16-alpine
        environment:
          POSTGRES_PASSWORD: gizli
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U postgres"]
          interval: 10s
          timeout: 5s
          retries: 5
        restart: unless-stopped
    

    Burada iki ayrı mekanizma var ve karıştırılmamalı. healthcheck konteynerin durumunu etiketler; restart politikası ise konteyner çıktığında ne olacağını belirler. Docker tek başına, sağlıksız işaretlenen bir konteyneri otomatik olarak yeniden başlatmaz. Durumu görmek için:

    # Sağlık durumunu ve son kontrol çıktısını gör
    docker inspect --format='{{.State.Health.Status}}' api
    docker inspect --format='{{json .State.Health.Log}}' api | head -c 500
    
    # Sağlıksız konteynerleri listele
    docker ps --filter health=unhealthy
    

    Restart politikalarının davranışı da birbirinden farklıdır ve seçim üretim davranışını doğrudan etkiler:

    PolitikaNe zaman yeniden başlatırSunucu yeniden başlayınca
    noHiçBaşlamaz
    on-failure[:n]Sıfırdan farklı çıkış kodundaBaşlamaz
    alwaysHer çıkıştaBaşlar
    unless-stoppedElle durdurulmadıysa her çıkıştaElle durdurulmadıysa başlar

    Üretimde çoğu servis için doğru seçim unless-stopped'tır: bakım için elle durdurduğun bir konteyner, sunucu yeniden başladığında beklenmedik biçimde geri gelmez. Konteyner sağlıksızken sürekli döngüye giriyorsa asıl sorunu bulmak için konteyner sürekli yeniden başlıyor yazısındaki teşhis adımlarını izle; günlükleri okumakta zorlanıyorsan konteyner loglarını okuma ve exec yazısı komutları veriyor.

    systemd ile Otomatik Kurtarma#

    Uygulaman konteynerde değil de doğrudan sunucuda çalışıyorsa, kurtarma işini systemd üstlenir ve bunu Docker'dan daha ince ayarlarla yapar. Temel şablon şudur:

    [Unit]
    Description=Siparis API
    After=network-online.target postgresql.service
    Wants=network-online.target
    
    [Service]
    Type=simple
    User=uygulama
    WorkingDirectory=/opt/siparis-api
    ExecStart=/usr/bin/node /opt/siparis-api/server.js
    
    # --- Otomatik kurtarma ---
    Restart=always
    RestartSec=5s
    # 5 dakikalık pencerede en fazla 5 başlatma denemesi; aşarsa servis 'failed' kalır
    StartLimitIntervalSec=300
    StartLimitBurst=5
    
    [Install]
    WantedBy=multi-user.target
    

    StartLimitIntervalSec ve StartLimitBurst çifti, sonsuz yeniden başlatma döngüsünü engelleyen frendir. Bu ikisi olmadan Restart=always yazarsan, açılışta hemen çöken bir uygulama saniyede birkaç kez yeniden başlar; CPU'yu yer, logu şişirir ve asıl hatayı gürültünün altına gömer. Sınırı aşan servis failed durumunda kalır ve bunu görmek istersin — sessizce dönmesinden iyidir.

    Uygulaman kendi sağlık ucunu sunuyorsa, systemd'nin bunu düzenli yoklamasını da sağlayabilirsin. Bunun en temiz yolu bir timer ile eşleşen küçük bir kontrol servisidir:

    # /etc/systemd/system/siparis-saglik.service
    [Unit]
    Description=Siparis API saglik kontrolu
    
    [Service]
    Type=oneshot
    # 5 sn icinde 200 donmezse servisi yeniden baslat
    ExecStart=/bin/sh -c 'curl -fsS --max-time 5 http://127.0.0.1:3000/healthz >/dev/null || systemctl restart siparis-api.service'
    
    # /etc/systemd/system/siparis-saglik.timer
    [Unit]
    Description=Siparis API saglik kontrolunu her dakika calistir
    
    [Timer]
    OnBootSec=2min
    OnUnitActiveSec=1min
    AccuracySec=10s
    
    [Install]
    WantedBy=timers.target
    

    Devreye almak:

    sudo systemctl daemon-reload
    sudo systemctl enable --now siparis-api.service
    sudo systemctl enable --now siparis-saglik.timer
    
    # Durum ve son kurtarma denemelerini gör
    systemctl status siparis-api.service
    journalctl -u siparis-api.service --since "1 hour ago" | grep -i restart
    

    Unit dosyalarındaki Type, User, ExecStart gibi alanların her birini ayrıntılı öğrenmek istersen systemd servis dosyası yazma rehberi bu dosyanın tamamını satır satır açıklıyor.

    Yeniden Başlatma Bir Çözüm Değil, Zaman Kazandırıcıdır#

    Otomatik yeniden başlatmanın en tehlikeli yanı işe yaramasıdır. Bellek sızıntısı olan bir uygulama her altı saatte bir yeniden başlatılırsa kullanıcı hiçbir şey fark etmez, sen de aylarca sızıntıyı fark etmezsin. Bir gün trafik iki katına çıkar, sızıntı üç saatte doluma ulaşır ve yeniden başlatmalar kullanıcıya değmeye başlar. O gün elinde yalnızca "servis sık sık yeniden başlıyor" bilgisi olur, sebep hakkında hiçbir kayıt olmaz.

    Bu yüzden kurtarma mekanizmasının yanına mutlaka görünürlük koymalısın. En basit hâliyle, yeniden başlatma sayısının kendisi bir metrik olmalıdır:

    # Son 24 saatte servis kaç kez başladı? (kabaca)
    journalctl -u siparis-api.service --since "24 hours ago" \
      | grep -c "Started Siparis API"
    
    # Docker tarafında konteynerin toplam yeniden başlatma sayısı
    docker inspect --format='{{.RestartCount}}' api
    

    Bu sayı sıfırdan farklıysa ve düzenli artıyorsa, elinde çözülmemiş bir sorun var demektir. Sağlıklı bir kurulumda yeniden başlatmalar istisnadır, günlük rutin değil. Servislerinin dışarıdan erişilebilirliğini de takip etmek ve hedeflerini sayısallaştırmak istersen uptime ve SLA hesaplayıcı aracıyla kabul edilebilir kesinti bütçeni hesaplayabilirsin.

    Bir başka önemli nokta, yeniden başlatmanın veri güvenliğiyle ilişkisidir. Uygulaman yarım kalmış bir işlemin ortasında öldürülürse ne olacağını bilmelisin: veritabanı işlemi geri alınır mı, yazılmakta olan dosya bozulur mu, kuyruktan çekilen mesaj geri döner mi? Zarif kapanma (graceful shutdown) desteği olan bir uygulama SIGTERM sinyalini yakalar, yeni istek almayı keser, elindeki işleri bitirir ve sonra kapanır. systemd tarafında bunun için pay tanımlarsın:

    [Service]
    KillSignal=SIGTERM
    TimeoutStopSec=30s   # 30 sn içinde kapanmazsa SIGKILL
    

    Sık Yapılan Hatalar ve Tuzaklar#

    Sağlık ucunu ağırlaştırmak. /healthz içinde tam bir veritabanı sorgusu, dış API çağrısı ve disk kontrolü yapmak, kontrolün kendisini sistemin en kırılgan parçası hâline getirir. Liveness ucu hiçbir dış kaynağa dokunmamalı; bağımlılık kontrolü readiness'a aittir.

    start_period vermemek. Uygulaman açılırken 20 saniye göç çalıştırıyorsa ve sağlık kontrolü ilk saniyeden itibaren başlıyorsa, konteyner daha ilk kez hazır olmadan unhealthy damgası yer. Docker'da --start-period, Compose'da start_period tam olarak bu açılış payını verir.

    Sağlık kontrolünü konteynerin dışından yapmak. HEALTHCHECK komutu konteynerin içinde çalışır; localhost derken konteynerin kendi ağ alanını kastedersin. Konteyner içinde curl ya da wget kurulu değilse komut her seferinde başarısız olur ve "uygulama bozuk" sanırsın. Alpine tabanlı imajlarda wget genelde vardır, curl çoğunlukla yoktur.

    Yeniden başlatma sınırı koymamak. Restart=always tek başına bir tuzaktır; StartLimitBurst olmadan çöken bir uygulama makineyi meşgul eder. Docker tarafında da restart: always ile sonsuz döngüye girmiş bir konteyner aynı sonucu verir.

    Sağlıksız durumu kimseye bildirmemek. Docker sağlıksız işaretlediği konteyneri kendiliğinden yeniden başlatmaz; bunun için bir dış izleyici gerekir. Sağlıksız konteyner listesini periyodik kontrol edip haber veren küçük bir görev, pahalı bir izleme aracından önce atılacak en ucuz adımdır.

    Aynı anda iki kurtarma mekanizması çalıştırmak. Konteyner hem Docker restart politikasıyla hem de dışarıdan bir betikle yeniden başlatılıyorsa, ikisi birbirinin ayağına basar ve davranış öngörülemez hâle gelir. Kurtarmadan tek bir katman sorumlu olmalıdır.

    Sıkça Sorulan Sorular#

    Liveness ve readiness arasındaki fark nedir#

    Liveness, sürecin kurtarılabilir durumda olup olmadığını sorar ve başarısız olduğunda süreç yeniden başlatılır; yalnızca uygulamanın kendi iç durumuna bakmalıdır. Readiness ise o örneğe trafik gönderilip gönderilemeyeceğini sorar ve veritabanı, önbellek gibi bağımlılıkları yoklar; başarısız olduğunda süreç öldürülmez, sadece yük dengeleyici havuzdan çıkarılır. İkisini tek uçta birleştirmek, dış bir servisin yavaşlamasında uygulamanı gereksiz yere yeniden başlatır.

    Docker sağlıksız konteyneri otomatik yeniden başlatır mı#

    Hayır. Docker HEALTHCHECK sonucuna göre konteyneri yalnızca unhealthy olarak etiketler; yeniden başlatma yapmaz. Restart politikaları ise konteyner çıktığında devreye girer, sağlıksız işaretlendiğinde değil. Sağlıksız bir konteynerin yeniden başlatılmasını istiyorsan bunu dışarıdan bir izleyici ya da orkestratör katmanıyla yapman gerekir.

    Sağlık kontrolü aralığı ne olmalı#

    Çoğu web servisi için 15–30 saniyelik aralık, 2–5 saniyelik zaman aşımı ve 3 başarısızlık eşiği dengeli bir başlangıçtır. Çok kısa aralıklar gereksiz yük yaratır ve geçici bir yavaşlamayı arıza gibi gösterir; çok uzun aralıklar ise gerçek arızayı geç fark ettirir. Açılışı yavaş uygulamalarda mutlaka bir başlangıç payı tanımlamalı, ilk kontrolün açılış bitmeden yapılmasını engellemelisin.

    Uygulamam sürekli yeniden başlıyor, nasıl teşhis ederim#

    Önce yeniden başlatma sayısını ve zaman aralığını ölç; docker inspect --format='{{.RestartCount}}' ya da journalctl -u <servis> çıktısı bunu verir. Ardından kurtarmayı geçici olarak kapat ve uygulamanın çıkış kodu ile son log satırlarını doğrudan gör; sürekli yeniden başlatma en çok gerçek hata mesajını gizlediği için can sıkar. Bellek sınırı aşımı, eksik ortam değişkeni ve açılışta erişilemeyen bağımlılık en sık üç sebeptir.

    Sağlık ucunu dışarıya açmalı mıyım#

    Genel kural, sağlık uçlarını internete kapatmaktır. Bu uçlar sürüm bilgisi, bağımlılık adları ve hata detayı sızdırabilir; readiness ucu özellikle ayrıntılı yanıt verdiği için risklidir. En temiz çözüm bunları yalnızca yerel arayüzden ya da özel ağdan erişilebilir kılmak, gerekiyorsa dışarıya yalnızca gövdesiz ve detaysız bir durum kodu döndüren ayrı bir uç sunmaktır.

    Zarif kapanma (graceful shutdown) neden önemli#

    Yeniden başlatma sırasında uygulaman istek işliyorsa, sinyali yakalamayan bir süreç bu isteği yarıda keser ve kullanıcı hata görür. Zarif kapanma desteği olan uygulama SIGTERM aldığında yeni istek almayı bırakır, devam edenleri bitirir ve sonra çıkar. systemd tarafında TimeoutStopSec, Docker tarafında --stop-timeout bu iş için tanınan süreyi belirler; süre dolduğunda süreç zorla sonlandırılır.

    Kapanış#

    Sağlık kontrolü ve otomatik kurtarma, doğru kurulduğunda gecelerini geri verir; yanlış kurulduğunda ise gerçek arızayı gizler. Aklında tutman gereken dört alışkanlık şu: liveness ile readiness'ı ayır ve liveness ucunu hiçbir dış bağımlılığa bağlama, açılış payını mutlaka tanımla, yeniden başlatmaya sınır koy ve o sınırın aşıldığını gör, yeniden başlatma sayısını bir metrik olarak izle. Kurtarma bir çözüm değildir; sana sorunu düzeltmek için zaman kazandıran bir yastıktır.

    Bu tür bir kurulumun gerçekten işe yaraması için altyapının da öngörülebilir olması gerekir. Kaynak sınırlarını kendin belirlemek ve servisleri istediğin gibi yapılandırmak için VDS ve bulut sunucu paketlerimize bakabilir, izleme ve kurtarma kurulumunu üzerinize almak istemiyorsanız sunucu yönetimi hizmetimizden yararlanabilirsiniz. Kritik verilerinizin kurtarma senaryosunu tamamlamak içinse yedekleme çözümlerimize göz atın.

    Health CheckDockersystemd

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.