İki Dockerfile, tek uygulama, aralarında tek kelimelik fark var. CMD python app.py ve CMD ["python", "app.py"] aynı görünüyor, container da siz durdurmaya çalışana kadar aynı çalışıyor. Birincisini durdurmak 10.30 saniye sürdü ve uygulamanın kapanış kodu hiç çalışmadı. İkincisi 0.27 saniyede durdu. Fark buradan çıkıyor: container içinde hangi süreç PID 1 oluyor ve durdurma sinyali uygulamanıza mı, yoksa onu yok sayan bir shell'e mi ulaşıyor. Bu yazıda önce bunu ölçtüm, sonra aynısını ENTRYPOINT ile CMD argümanları için, bir de ADD ile COPY için yaptım.
CMD ve ENTRYPOINT arasındaki fark nedir?
İkisi de container başladığında ne çalışacağını belirler ve her biri iki şekilde yazılabilir:
- Exec form (
["python", "app.py"]): Docker programı doğrudan çalıştırır. Programınız PID 1 olur ve sinyalleri alır. - Shell form (
python app.py): Docker/bin/sh -c "python app.py"çalıştırır. PID 1 artık shell, programınız onun çocuğu.
ENTRYPOINT her zaman çalışan komuttur. CMD ise varsayılan argümanları verir, docker run sırasında bunların üzerine yazabilirsiniz. İkisini birlikte kullanınca ENTRYPOINT ["python", "app.py"] + CMD ["--port", "8080"] şu demek: uygulama her zaman çalışır, --port 8080 ise değiştirebileceğiniz bir varsayılan.
Bunu doğru yapmak hangi sorunu çözer?
- Yavaş ve temiz olmayan kapanışlar. Shell form uygulamanızı
/bin/sharkasına saklıyor, bu yüzden SIGTERM ona hiç ulaşmıyor. Runtime bütün grace süresini bekliyor (docker stopile 10 sn, Kubernetes'te varsayılan 30 sn), sonra SIGKILL gönderiyor. Açık istekler düşüyor, bellekteki veri diske yazılmıyor. - Kaybolan kapanış mantığı.
finallybloğu, bağlantıların boşaltılması, "kapanıyorum" log satırı hiç çalışmıyor. - Sandığınız işi yapmayan argümanlar. Yalnızca
CMDvarkendocker run img somecmdbütün komutun üzerine yazıyor ve uygulamanız hiç başlamıyor.ENTRYPOINTvarken fazladan argümanlar sona ekleniyor. - ADD sürprizleri.
ADDtar arşivlerini sessizce açıyor ve URL indirebiliyor. Bu da imajı şişiriyor,COPY'nin asla bozmadığı şekilde build cache'ini bozuyor.
Shell form ve exec form: ne kazanırsınız, ne ödersiniz
Shell form (CMD python app.py) |
Exec form (CMD ["python","app.py"]) |
|
|---|---|---|
| İçeride PID 1 | /bin/sh |
uygulamanız |
| SIGTERM'i alır | hayır (shell alır) | evet |
docker stop süresi |
10.30 sn (grace, sonra SIGKILL) | 0.27 sn |
| Kapanış kodunu çalıştırır | hayır | evet |
Değişken genişletme ($PORT) |
evet | hayır (sh -c ile sarmazsanız) |
| Zombie reaping / PID 1 görevleri | shell bir kısmını yapar | --init/tini gerekir |
Nasıl çalışır: sinyal nereye gidiyor

Lab
Uygulama, SIGTERM yakalayınca got SIGTERM, draining yazıp çıkan, sekiz satırlık bir Python. İki imaj da python:3.13-slim; tek fark CMD satırı. Aşağıdaki her sayı bu koşudan.


- Shell form ve exec form. Her container'ın içinde
cat /proc/1/commçalıştırdım: shell formsh, exec formpythongösteriyor. Shell-form container'dadocker stop10.30 sn sürdü ve logunda yalnızcaapp started, pid 7var, SIGTERM satırı yok: shell sinyali iletmedi, Docker 10 saniyelik grace'i bekleyip SIGKILL gönderdi. Exec-form container 0.27 sn'de durdu, logundaapp started, pid 1ve ardındangot SIGTERM, drainingvar. - Beklenen şey uygulama değil, grace süresi. Shell-form container'da
docker stop -t 22.29 sn sürdü: içeride dinleyen bir şey olmadığı için süre, verdiğiniz grace neyse o. - ENTRYPOINT + CMD argümanları.
ENTRYPOINT ["python", "argv.py"]veCMD ["--port", "8080"]iledocker run img,argv.py --port 8080çalıştırdı;docker run img --port 9090iseargv.py --port 9090çalıştırdı. ENTRYPOINT kaldı, CMD değişti. - Tek başına CMD kırılgan. Yalnızca
CMD ["python", "argv.py", "--port", "8080"]varkendocker run img echo hi,hiyazdırdı ve uygulama hiç başlamadı: bir argüman bütün komutun üzerine yazdı. - ADD ve COPY.
COPY payload.tar.gz /dest/tek dosya, yani tarball'ı bıraktı.ADD payload.tar.gz /dest/üç dosya açtı, tarball'ı bırakmadı ve bu dosyaların sahibi uid 501'di: imajda benim atamadığım, arşivde saklı bir id. - Kubernetes'te aynı şey. İki imajı da kind üzerinde, varsayılan
terminationGracePeriodSeconds: 30ile pod olarak çalıştırdım. Shell-form pod'u silmek 30.8 sn sürdü; exec-form pod 1.4 sn'de gitti. Kubernetes, Docker'ın 10 saniyesini değil 30 saniyeyi bekliyor, yani shell-form container her rollout'ta daha da pahalıya patlıyor. execshell form'u kurtarıyor.CMD ["sh", "-c", "python app.py"]hâlâ PID 1'deshbıraktı ve 10.27 sn'de durdu.CMD ["sh", "-c", "exec python app.py"]isepython'ı PID 1'e geri koydu ve 0.27 sn'de durdu:exec, shell'i kendi sürecinizle değiştiriyor, böylece sinyal ona ulaşıyor. Gerçekten değişken genişletme için bir shell'e ihtiyacınız olduğunda kullanacağınız kalıp bu.

Nerede patladı
1. Shell-form container docker stop -t'yi yok saydı
Daha kısa grace vermek shell-form container'ı temiz kapatmadı; yalnızca SIGKILL'den önceki beklemeyi kısalttı. Tek çözüm, uygulamayı PID 1 yapmak.
2. ADD benim atamadığım bir uid ile dosya bıraktı
Tar, uid 501 taşıyordu ve ADD bunu olduğu gibi korudu, böylece /dest, kimsenin atamadığı sayısal bir id'ye ait oldu. COPY olsaydı build'in kendi sahipliğini kullanırdı.
3. Exec form değişkenleri genişletmiyor
CMD ["python", "app.py", "--port", "$PORT"], $PORT metnini birebir geçti, çünkü genişletecek bir shell yok. Çözüm CMD ["sh", "-c", "exec python app.py --port $PORT"]: shell değişkeni genişletiyor, exec ise PID 1'i python'a geri veriyor, böylece sinyaller çalışmaya devam ediyor.
ADD ve COPY: hangisi, ne zaman
COPY |
ADD |
|
|---|---|---|
| Yerel dosya/dizin | evet | evet |
| Yerel tar'ı otomatik açar | hayır | evet |
| Uzak URL indirir | hayır | evet (açmaz) |
| Önerilen kullanım | tüm yerel dosyalar | yalnızca açmak istediğiniz yerel tar |
Kural: yerel dosyalar için COPY kullanın. ADD'i yalnızca yerel bir tarball'ı otomatik açmak için kullanın. URL için RUN curl -fsSL … && … && rm kullanın, böylece tek katmanda hem doğrular hem temizlersiniz.
Ne zaman hangisi
- Uygulamanız PID 1 olsun ve sinyalleri alsın diye varsayılan olarak exec-form
CMDkullanın. - Program sabitse ve yalnızca argümanlar değişiyorsa
ENTRYPOINT(exec form) +CMDkullanın. - Süreciniz çocuk süreçleri reap etmiyorsa
--init(ya da tini) ekleyin. - Shell form'u yalnızca gerçekten shell özelliğine ihtiyacınız varsa (değişken genişletme, pipe) kullanın; o zaman da gerçek bir init çalıştırın ya da uygulamanızı
execile PID 1 yapın. - Varsayılan olarak
COPY;ADDyalnızca yerel tar açmak için.
Özet
- Shell form PID 1'e
/bin/shkoyar; SIGTERM uygulamanıza hiç ulaşmaz;docker stop10.30 sn bekler, sonra SIGKILL. - Exec form uygulamanızı PID 1 yapar; 0.27 sn'de durur ve kapanış kodunu çalıştırır.
ENTRYPOINTkomutu sabitler,CMDüzerine yazılabilen varsayılan argümanları verir.- Yerel dosyalar için
COPY; yalnızca yerel tar içinADD; URL içinRUN curl.
Seride sırada: kind üzerinde Prometheus ve Grafana, bir cluster'ın ham metriklerini bir servisin ne zaman gerçekten sıkıntıda olduğunu gösteren panoya çeviriyorum.