İçeriğe geç
Muhammet Şafak
en
Soran: Tuğçe Cevaplandı:

Container'daki Go worker'ım SIGTERM almıyor ve graceful kapanmıyor, bu bir PID 1 sorunu mu?


Soru

Go ile yazdığım bir queue worker'ını Docker container'ında çalıştırıyorum ve Kubernetes üzerinde deploy ediyorum. Kodda `os.Interrupt` dinleyip kanal geldiğinde context'i iptal eden bir graceful shutdown mantığım var. Ama her deploy'da worker grace period boyunca hiçbir şey yapmıyor ve sonunda SIGKILL yiyor. Loglara bakınca SIGTERM'ü hiç görmediğini fark ettim; sanki sinyal worker'a hiç ulaşmıyor. Bu bir PID 1 sorunu mu, yoksa kodumda mı bir şey eksik?

Cevap

Kısa cevap: Büyük olasılıkla evet, bu bir PID 1 sorunu.

Kısa cevap

Ama iki ayrı hata aynı anda var: sinyal büyük ihtimalle shell-form CMD yüzünden worker’a hiç ulaşmıyor, ayrıca kodunuz os.Interrupt (SIGINT) dinleyip SIGTERM’ü kaçırıyor.

Neden

  1. Shell form sizi /bin/sh -c’nin çocuğu yapar. CMD ./worker (shell form) worker’ı sh’nin altında çalıştırır; PID 1 olan sh ise SIGTERM’ü çocuğuna iletmez. Bu, sinyalin worker’a hiç ulaşmamasının bir numaralı sebebidir. Çözüm exec form: CMD ["./worker"] ya da ENTRYPOINT ["/app/worker"].

    # SIGTERM'ü doğrudan binary'ye iletir
    ENTRYPOINT ["/app/worker"]
  2. PID 1’in varsayılan sinyal davranışı yoktur. Doğrudan PID 1 olsanız bile kernel, PID 1 için varsayılan sinyal dispozisyonlarını uygulamaz; handler’ı açıkça kurmak zorundasınız. Sadece os.Interrupt dinliyorsanız SIGTERM’ü kaçırırsınız — Kubernetes’in deploy’da gönderdiği sinyal tam olarak SIGTERM’dür.

    ctx, stop := signal.NotifyContext(context.Background(),
        syscall.SIGTERM, syscall.SIGINT)
    defer stop()
    // ctx'i worker döngüsüne geçirin; iptal gelince yeni iş çekmeyi durdurun,
    // devam eden işi bitirin, çıkın.
  3. entrypoint script’i de bunu bozar. Kabuk bir entrypoint kullanıyorsanız son satır mutlaka exec "$@" olmalı. exec olmadan "$@" çalıştırırsanız kabuk PID 1 olarak kalır ve sinyali yine yutar.

Ne yapmalı

  1. Sinyal → context iptal → drain zincirini kurun. SIGTERM’de worker context’ini iptal edin, yeni job çekmeyi bırakın, in-flight işi bitirin ve çıkın. Bu, terminationGracePeriodSeconds (varsayılan 30s) içinde tamamlanmalı; aşarsanız SIGKILL yersiniz. Uzun işler varsa grace period’u buna göre ayarlayın.

  2. Çocuk process açıyorsanız zombie reaping gerekir. Worker başka process’ler fork ediyorsa PID 1 olarak onları reap etmeniz gerekir; bu durumda tini’yi image’a kendiniz eklemeniz gerekir (Docker’da bunu docker run --init sağlar, Kubernetes’in buna denk yerleşik bir bayrağı yoktur). Tek bir Go binary için genellikle exec form + doğru sinyal yakalama yeter, tini’ye ihtiyaç olmaz.

  3. Önce teşhisi doğrulayın, tahmin etmeyin. Container içinde PID 1’in gerçekten binary’niz mi yoksa sh mi olduğunu görmek beş saniyelik iştir: docker exec <container> ps -o pid,comm. PID 1’de sh görüyorsanız teşhis kesindir. Ayrıca image’ın SIGTERM beklediğinden emin olun; STOPSIGNAL SIGTERM zaten varsayılandır ama farklı bir şeye ayarlanmışsa worker’ınız hiç beklemediği bir sinyal alıyor olabilir.

  4. Kubernetes’te trafiği önce kesin. Graceful shutdown sadece sinyal yakalamak değildir. Pod sonlandırılırken readiness probe’u düşürüp bir preStop hook (kısa bir sleep) koyarak, SIGTERM gelmeden önce pod’un Service endpoint’lerinden çıkarılmasını sağlayın. Aksi halde drain ederken bile yeni istek/job almaya devam edebilirsiniz.

Sonuç: Ben olsam önce docker exec ... ps ile PID 1’i doğrular, Dockerfile’ı exec form’a çevirir, kodda os.Interrupt yerine signal.NotifyContext(..., syscall.SIGTERM, syscall.SIGINT) kullanır ve grace period içinde drain edildiğini bir deploy ile doğrulardım. tini’yi yalnızca worker gerçekten çocuk process fork ediyorsa eklerdim; aksi halde gereksiz katman olur. Sıralama: önce sinyal ulaşsın, sonra kod onu yakalasın, en son da trafiği zamanında kesin.

Etiketler: #Docker#Go
Paylaş:

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

Diğer Sorular

Tüm sorular

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi